keungliang commited on
Commit
0067eb3
·
verified ·
1 Parent(s): f39674e

Upload 5 files

Browse files
Files changed (5) hide show
  1. Dockerfile +13 -0
  2. README.md +3 -3
  3. index.js +545 -0
  4. logger.js +66 -0
  5. package.json +20 -0
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:lts-alpine
2
+
3
+ WORKDIR /app
4
+
5
+ COPY package*.json ./
6
+
7
+ RUN npm install
8
+
9
+ COPY . .
10
+ ENV PORT=7860
11
+ EXPOSE 7860
12
+
13
+ CMD ["npm", "start"]
README.md CHANGED
@@ -1,7 +1,7 @@
1
  ---
2
- title: XAIGrok
3
- emoji: 👀
4
- colorFrom: yellow
5
  colorTo: gray
6
  sdk: docker
7
  pinned: false
 
1
  ---
2
+ title: XgrokService
3
+ emoji: 🏆
4
+ colorFrom: blue
5
  colorTo: gray
6
  sdk: docker
7
  pinned: false
index.js ADDED
@@ -0,0 +1,545 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from 'express';
2
+ import FormData from 'form-data';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ import fetch from 'node-fetch';
5
+ import cors from 'cors';
6
+ import Logger from './logger.js';
7
+ import dotenv from 'dotenv';
8
+
9
+ // 初始化环境变量
10
+ dotenv.config();
11
+
12
+ // 配置管理
13
+ const CONFIG = {
14
+ SERVER: {
15
+ PORT: process.env.PORT || 25526,
16
+ BODY_LIMIT: '5mb',
17
+ CORS_OPTIONS: {
18
+ origin: '*',
19
+ methods: ['GET', 'POST', 'OPTIONS'],
20
+ allowedHeaders: ['Content-Type', 'Authorization'],
21
+ credentials: true
22
+ }
23
+ },
24
+ API: {
25
+ API_KEY: process.env.API_KEY || "sk-123456",
26
+ AUTH_TOKEN: process.env.AUTH_TOKEN,
27
+ CT0: process.env.CT0,
28
+ ENDPOINTS: {
29
+ CHAT: 'https://grok.x.com/2/grok/add_response.json',
30
+ CREATE_CONVERSATION: 'https://x.com/i/api/graphql/vvC5uy7pWWHXS2aDi1FZeA/CreateGrokConversation',
31
+ DELETE_CONVERSATION: 'https://x.com/i/api/graphql/TlKHSWVMVeaa-i7dqQqFQA/ConversationItem_DeleteConversationMutation',
32
+ UPLOAD_IMAGE: 'https://x.com/i/api/2/grok/attachment.json'
33
+ }
34
+ },
35
+ MODELS: {
36
+ "grok-3": "grok-3",
37
+ "grok-3-deepsearch": "grok-3",
38
+ "grok-3-reasoning": "grok-3",
39
+ },
40
+ IS_IMG_GEN: false,
41
+ IS_THINKING: false
42
+ };
43
+
44
+ // HTTP 请求头配置
45
+ const DEFAULT_HEADERS = {
46
+ 'authorization': 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA',
47
+ 'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
48
+ 'sec-ch-ua-mobile': '?0',
49
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
50
+ 'accept': '*/*',
51
+ 'content-type': 'text/plain;charset=UTF-8',
52
+ 'origin': 'https://x.com',
53
+ 'sec-fetch-site': 'same-site',
54
+ 'sec-fetch-mode': 'cors',
55
+ 'accept-encoding': 'gzip, deflate, br, zstd',
56
+ 'accept-language': 'zh-CN,zh;q=0.9',
57
+ 'priority': 'u=1, i'
58
+ };
59
+
60
+ // 工具类
61
+ class Utils {
62
+ static generateRandomString(length, charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') {
63
+ return Array(length).fill(null)
64
+ .map(() => charset[Math.floor(Math.random() * charset.length)])
65
+ .join('');
66
+ }
67
+
68
+ static createAuthHeaders() {
69
+ return {
70
+ ...DEFAULT_HEADERS,
71
+ 'x-csrf-token': CONFIG.API.CT0,
72
+ 'cookie': `auth_token=${CONFIG.API.AUTH_TOKEN};ct0=${CONFIG.API.CT0}`
73
+ };
74
+ }
75
+
76
+ static getImageMimeType(base64String) {
77
+ const matches = base64String.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,/);
78
+ return matches ? matches[1] : 'image/jpeg';
79
+ }
80
+
81
+ static async handleApiResponse(response, errorMessage) {
82
+ if (!response.ok) {
83
+ throw new Error(`${errorMessage} Status: ${response.status}`);
84
+ }
85
+ return await response.json();
86
+ }
87
+ }
88
+
89
+ // 会话管理类
90
+ class ConversationManager {
91
+ static async generateNewId() {
92
+ const response = await fetch(CONFIG.API.ENDPOINTS.CREATE_CONVERSATION, {
93
+ method: 'POST',
94
+ headers: Utils.createAuthHeaders(),
95
+ body: JSON.stringify({
96
+ variables: {},
97
+ queryId: "vvC5uy7pWWHXS2aDi1FZeA"
98
+ })
99
+ });
100
+
101
+ const data = await Utils.handleApiResponse(response, '创建会话失败!');
102
+ return data.data.create_grok_conversation.conversation_id;
103
+ }
104
+
105
+ static async deleteConversation(conversationId) {
106
+ if (!conversationId) return;
107
+
108
+ await fetch(CONFIG.API.ENDPOINTS.DELETE_CONVERSATION, {
109
+ method: 'POST',
110
+ headers: Utils.createAuthHeaders(),
111
+ body: JSON.stringify({
112
+ variables: { conversationId },
113
+ queryId: "TlKHSWVMVeaa-i7dqQqFQA"
114
+ })
115
+ });
116
+ }
117
+ }
118
+
119
+ // 消息处理类
120
+ class MessageProcessor {
121
+ static createChatResponse(message, model, isStream = false) {
122
+ const baseResponse = {
123
+ id: `chatcmpl-${uuidv4()}`,
124
+ created: Math.floor(Date.now() / 1000),
125
+ model: model
126
+ };
127
+
128
+ if (isStream) {
129
+ return {
130
+ ...baseResponse,
131
+ object: 'chat.completion.chunk',
132
+ choices: [{
133
+ index: 0,
134
+ delta: { content: message }
135
+ }]
136
+ };
137
+ }
138
+
139
+ return {
140
+ ...baseResponse,
141
+ object: 'chat.completion',
142
+ choices: [{
143
+ index: 0,
144
+ message: {
145
+ role: 'assistant',
146
+ content: message
147
+ },
148
+ finish_reason: 'stop'
149
+ }],
150
+ usage: null
151
+ };
152
+ }
153
+
154
+ static processMessageContent(content) {
155
+ if (typeof content === 'string') return content;
156
+ if (Array.isArray(content)) {
157
+ if (content.some(item => item.type === 'image_url')) return null;
158
+ return content
159
+ .filter(item => item.type === 'text')
160
+ .map(item => item.text)
161
+ .join('\n');
162
+ }
163
+ if (typeof content === 'object') return content.text || null;
164
+ return null;
165
+ }
166
+ }
167
+
168
+ // Grok API 客户端类
169
+ class TwitterGrokApiClient {
170
+ constructor(modelId) {
171
+ if (!CONFIG.MODELS[modelId]) {
172
+ throw new Error(`不支持的模型: ${modelId}`);
173
+ }
174
+ this.modelId = CONFIG.MODELS[modelId];
175
+ this.modelType = {
176
+ isDeepSearch: modelId === 'grok-3-deepsearch',
177
+ isReasoning: modelId === 'grok-3-reasoning'
178
+ };
179
+ }
180
+
181
+ async uploadImage(imageData) {
182
+ const formData = new FormData();
183
+ const imageBuffer = Buffer.from(imageData.split(',')[1], 'base64');
184
+ const mimeType = Utils.getImageMimeType(imageData);
185
+
186
+ formData.append('photo', imageBuffer, {
187
+ filename: 'image.png',
188
+ contentType: mimeType
189
+ });
190
+
191
+ const response = await fetch(CONFIG.API.ENDPOINTS.UPLOAD_IMAGE, {
192
+ method: 'POST',
193
+ headers: {
194
+ ...Utils.createAuthHeaders(),
195
+ ...formData.getHeaders()
196
+ },
197
+ body: formData
198
+ });
199
+
200
+ return await Utils.handleApiResponse(response, '图片上传失败');
201
+ }
202
+
203
+ async transformMessages(messages) {
204
+ const processedMessages = [];
205
+ for (let i = 0; i < messages.length; i++) {
206
+ const isLastTwoMessages = i >= messages.length - 2;
207
+ const content = await this.processMessageContent(messages[i], isLastTwoMessages);
208
+ if (content) {
209
+ processedMessages.push(content);
210
+ }
211
+ }
212
+ return processedMessages;
213
+ }
214
+
215
+ async processMessageContent(msg, isLastTwoMessages) {
216
+ const { role, content } = msg;
217
+ let message = '';
218
+ let fileAttachments = [];
219
+
220
+ if (typeof content === 'string') {
221
+ message = content;
222
+ } else if (Array.isArray(content) || typeof content === 'object') {
223
+ const { text, imageAttachments } = await this.processComplexContent(content, isLastTwoMessages);
224
+ message = text;
225
+ fileAttachments = imageAttachments;
226
+ }
227
+
228
+ return {
229
+ message,
230
+ sender: role === 'user' ? 1 : 2,
231
+ ...(role === 'user' && { fileAttachments })
232
+ };
233
+ }
234
+
235
+ async processComplexContent(content, isLastTwoMessages) {
236
+ let text = '';
237
+ let imageAttachments = [];
238
+
239
+ const processItem = async (item) => {
240
+ if (item.type === 'text') {
241
+ text += item.text;
242
+ } else if (item.type === 'image_url' && item.image_url.url.includes('data:image')) {
243
+ if (isLastTwoMessages) {
244
+ const uploadResult = await this.uploadImage(item.image_url.url);
245
+ if (Array.isArray(uploadResult)) {
246
+ imageAttachments.push(...uploadResult);
247
+ }
248
+ } else {
249
+ text += '[图片]';
250
+ }
251
+ }
252
+ };
253
+
254
+ if (Array.isArray(content)) {
255
+ await Promise.all(content.map(processItem));
256
+ } else {
257
+ await processItem(content);
258
+ }
259
+
260
+ return { text, imageAttachments };
261
+ }
262
+
263
+ async prepareChatRequest(request) {
264
+ const responses = await this.transformMessages(request.messages);
265
+ const conversationId = await ConversationManager.generateNewId();
266
+
267
+ return {
268
+ responses,
269
+ systemPromptName: "",
270
+ grokModelOptionId: this.modelId,
271
+ conversationId,
272
+ returnSearchResults: this.modelType.isReasoning,
273
+ returnCitations: this.modelType.isReasoning,
274
+ promptMetadata: {
275
+ promptSource: "NATURAL",
276
+ action: "INPUT"
277
+ },
278
+ imageGenerationCount: 1,
279
+ requestFeatures: {
280
+ eagerTweets: false,
281
+ serverHistory: false
282
+ },
283
+ enableCustomization: true,
284
+ enableSideBySide: false,
285
+ toolOverrides: {},
286
+ isDeepsearch: this.modelType.isDeepSearch,
287
+ isReasoning: this.modelType.isReasoning
288
+ };
289
+ }
290
+ }
291
+
292
+ // 响应处理类
293
+ class ResponseHandler {
294
+ static async handleStreamResponse(response, model, res) {
295
+ res.setHeader('Content-Type', 'text/event-stream');
296
+ res.setHeader('Cache-Control', 'no-cache');
297
+ res.setHeader('Connection', 'keep-alive');
298
+
299
+ const reader = response.body;
300
+ let buffer = '';
301
+ CONFIG.IS_IMG_GEN = false;
302
+ CONFIG.IS_THINKING = false;
303
+
304
+ try {
305
+ for await (const chunk of reader) {
306
+ const lines = (buffer + chunk.toString()).split('\n');
307
+ buffer = lines.pop() || '';
308
+
309
+ for (const line of lines) {
310
+ if (!line.trim()) continue;
311
+ await this.processStreamLine(JSON.parse(line), model, res);
312
+ }
313
+ }
314
+
315
+ res.write('data: [DONE]\n\n');
316
+ res.end();
317
+ } catch (error) {
318
+ Logger.error('Stream response error:', error, 'ChatAPI');
319
+ throw error;
320
+ }
321
+ }
322
+
323
+ static async processStreamLine(jsonData, model, res) {
324
+ if (jsonData.result?.doImgGen) {
325
+ CONFIG.IS_IMG_GEN = true;
326
+ return;
327
+ }
328
+
329
+ if (CONFIG.IS_IMG_GEN && jsonData.result?.event?.imageAttachmentUpdate?.progress === 100) {
330
+ await this.handleImageGeneration(jsonData, model, res);
331
+ return;
332
+ }
333
+
334
+ if (!CONFIG.IS_IMG_GEN && jsonData.result?.message) {
335
+ await this.handleTextMessage(jsonData, model, res);
336
+ }
337
+ }
338
+
339
+ static async handleImageGeneration(jsonData, model, res) {
340
+ const imageUrl = jsonData.result.event.imageAttachmentUpdate.imageUrl;
341
+ const imageResponse = await fetch(imageUrl, {
342
+ method: 'GET',
343
+ headers: Utils.createAuthHeaders()
344
+ });
345
+
346
+ if (!imageResponse.ok) {
347
+ throw new Error(`Image request failed: ${imageResponse.status}`);
348
+ }
349
+
350
+ const imageBuffer = await imageResponse.arrayBuffer();
351
+ const base64Image = Buffer.from(imageBuffer).toString('base64');
352
+ const imageContentType = imageResponse.headers.get('content-type');
353
+ const message = `![image](data:${imageContentType};base64,${base64Image})`;
354
+
355
+ const responseData = MessageProcessor.createChatResponse(message, model, true);
356
+ res.write(`data: ${JSON.stringify(responseData)}\n\n`);
357
+ }
358
+
359
+ static async handleTextMessage(jsonData, model, res) {
360
+ let message = jsonData.result.message;
361
+
362
+ switch (model) {
363
+ case "grok-3-reasoning":
364
+ if (!CONFIG.IS_THINKING && jsonData.result?.isThinking) {
365
+ message = "<think>" + message;
366
+ CONFIG.IS_THINKING = true;
367
+ } else if (CONFIG.IS_THINKING && !jsonData.result?.isThinking) {
368
+ message = "</think>" + message;
369
+ CONFIG.IS_THINKING = false;
370
+ }
371
+ break;
372
+ case "grok-3-deepsearch":
373
+ if (jsonData.result?.messageTag !== "final") return;
374
+ break;
375
+ }
376
+
377
+ const responseData = MessageProcessor.createChatResponse(message, model, true);
378
+ res.write(`data: ${JSON.stringify(responseData)}\n\n`);
379
+ }
380
+
381
+ static async handleNormalResponse(response, model, res) {
382
+ const reader = response.body;
383
+ let buffer = '';
384
+ let fullResponse = '';
385
+ let imageUrl = null;
386
+
387
+ try {
388
+ for await (const chunk of reader) {
389
+ const lines = (buffer + chunk.toString()).split('\n');
390
+ buffer = lines.pop() || '';
391
+
392
+ for (const line of lines) {
393
+ if (!line.trim()) continue;
394
+ const result = await this.processNormalLine(JSON.parse(line), model, CONFIG.IS_THINKING);
395
+ fullResponse += result.text || '';
396
+ imageUrl = result.imageUrl || imageUrl;
397
+ CONFIG.IS_THINKING = result.isThinking;
398
+ }
399
+ }
400
+
401
+ if (imageUrl) {
402
+ await this.sendImageResponse(imageUrl, model, res);
403
+ } else {
404
+ const responseData = MessageProcessor.createChatResponse(fullResponse, model);
405
+ res.json(responseData);
406
+ }
407
+ } catch (error) {
408
+ Logger.error('Normal response error:', error, 'ChatAPI');
409
+ throw error;
410
+ }
411
+ }
412
+
413
+ static async processNormalLine(jsonData, model, isThinking) {
414
+ let result = { text: '', imageUrl: null, isThinking };
415
+
416
+ if (jsonData.result?.message) {
417
+ switch (model) {
418
+ case "grok-3-reasoning":
419
+ result = this.processReasoningMessage(jsonData, isThinking);
420
+ break;
421
+ case "grok-3-deepsearch":
422
+ if (jsonData.result?.messageTag === "final") {
423
+ result.text = jsonData.result.message;
424
+ }
425
+ break;
426
+ default:
427
+ result.text = jsonData.result.message;
428
+ }
429
+ }
430
+
431
+ if (jsonData.result?.event?.imageAttachmentUpdate?.progress === 100) {
432
+ result.imageUrl = jsonData.result.event.imageAttachmentUpdate.imageUrl;
433
+ }
434
+
435
+ return result;
436
+ }
437
+
438
+ static processReasoningMessage(jsonData, isThinking) {
439
+ let result = { text: '', isThinking };
440
+
441
+ if (jsonData.result?.isThinking && !isThinking) {
442
+ result.text = "<think>" + jsonData.result.message;
443
+ result.isThinking = true;
444
+ } else if (isThinking && !jsonData.result?.isThinking) {
445
+ result.text = "</think>" + jsonData.result.message;
446
+ result.isThinking = false;
447
+ } else {
448
+ result.text = jsonData.result.message;
449
+ }
450
+
451
+ return result;
452
+ }
453
+
454
+ static async sendImageResponse(imageUrl, model, res) {
455
+ const response = await fetch(imageUrl, {
456
+ method: 'GET',
457
+ headers: Utils.createAuthHeaders()
458
+ });
459
+
460
+ if (!response.ok) {
461
+ throw new Error(`Image request failed: ${response.status}`);
462
+ }
463
+
464
+ const imageBuffer = await response.arrayBuffer();
465
+ const base64Image = Buffer.from(imageBuffer).toString('base64');
466
+ const imageContentType = response.headers.get('content-type');
467
+
468
+ const responseData = MessageProcessor.createChatResponse(
469
+ `![image](data:${imageContentType};base64,${base64Image})`,
470
+ model
471
+ );
472
+ res.json(responseData);
473
+ }
474
+ }
475
+
476
+ // Express 应用配置
477
+ const app = express();
478
+ app.use(Logger.requestLogger);
479
+ app.use(cors(CONFIG.SERVER.CORS_OPTIONS));
480
+ app.use(express.json({ limit: CONFIG.SERVER.BODY_LIMIT }));
481
+ app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
482
+
483
+ // API 路由
484
+ app.get('/hf/v1/models', (req, res) => {
485
+ res.json({
486
+ object: "list",
487
+ data: Object.keys(CONFIG.MODELS).map(model => ({
488
+ id: model,
489
+ object: "model",
490
+ created: Math.floor(Date.now() / 1000),
491
+ owned_by: "xgrok",
492
+ }))
493
+ });
494
+ });
495
+
496
+ app.post('/hf/v1/chat/completions', async (req, res) => {
497
+ try {
498
+ const authToken = req.headers.authorization?.replace('Bearer ', '');
499
+ if (authToken !== CONFIG.API.API_KEY) {
500
+ return res.status(401).json({ error: 'Unauthorized' });
501
+ }
502
+
503
+ const grokClient = new TwitterGrokApiClient(req.body.model);
504
+ const requestPayload = await grokClient.prepareChatRequest(req.body);
505
+
506
+ const response = await fetch(CONFIG.API.ENDPOINTS.CHAT, {
507
+ method: 'POST',
508
+ headers: Utils.createAuthHeaders(),
509
+ body: JSON.stringify(requestPayload)
510
+ });
511
+
512
+ if (!response.ok) {
513
+ throw new Error(`上游服务请求失败! status: ${response.status}`);
514
+ }
515
+
516
+ await (req.body.stream
517
+ ? ResponseHandler.handleStreamResponse(response, req.body.model, res)
518
+ : ResponseHandler.handleNormalResponse(response, req.body.model, res));
519
+
520
+ } catch (error) {
521
+ Logger.error('Chat Completions Request Error', error, 'ChatAPI');
522
+ res.status(500).json({
523
+ error: {
524
+ message: error.message,
525
+ type: 'server_error',
526
+ param: null,
527
+ code: error.code || null
528
+ }
529
+ });
530
+ } finally {
531
+ if (req.body.conversationId) {
532
+ await ConversationManager.deleteConversation(req.body.conversationId);
533
+ }
534
+ }
535
+ });
536
+
537
+ // 404 处理
538
+ app.use((req, res) => {
539
+ res.status(404).json({ message: '服务运行中' });
540
+ });
541
+
542
+ // 启动服务器
543
+ app.listen(CONFIG.SERVER.PORT, () => {
544
+ Logger.info(`服务器运行在端口 ${CONFIG.SERVER.PORT}`, 'Server');
545
+ });
logger.js ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import chalk from 'chalk';
2
+ import moment from 'moment';
3
+
4
+ const LogLevel = {
5
+ INFO: 'INFO',
6
+ WARN: 'WARN',
7
+ ERROR: 'ERROR',
8
+ DEBUG: 'DEBUG'
9
+ };
10
+
11
+ class Logger {
12
+ static formatMessage(level, message) {
13
+ const timestamp = moment().format('YYYY-MM-DD HH:mm:ss');
14
+
15
+ switch(level) {
16
+ case LogLevel.INFO:
17
+ return chalk.blue(`[${timestamp}] [${level}] ${message}`);
18
+ case LogLevel.WARN:
19
+ return chalk.yellow(`[${timestamp}] [${level}] ${message}`);
20
+ case LogLevel.ERROR:
21
+ return chalk.red(`[${timestamp}] [${level}] ${message}`);
22
+ case LogLevel.DEBUG:
23
+ return chalk.gray(`[${timestamp}] [${level}] ${message}`);
24
+ default:
25
+ return message;
26
+ }
27
+ }
28
+
29
+ static info(message, context) {
30
+ console.log(this.formatMessage(LogLevel.INFO, context ? `[${context}] ${message}` : message));
31
+ }
32
+
33
+ static warn(message, context) {
34
+ console.warn(this.formatMessage(LogLevel.WARN, context ? `[${context}] ${message}` : message));
35
+ }
36
+
37
+ static error(message, error, context) {
38
+ const errorMessage = error ? ` - ${error.message}` : '';
39
+ console.error(this.formatMessage(LogLevel.ERROR, `${context ? `[${context}] ` : ''}${message}${errorMessage}`));
40
+ }
41
+
42
+ static debug(message, context) {
43
+ if (process.env.NODE_ENV === 'development') {
44
+ console.debug(this.formatMessage(LogLevel.DEBUG, context ? `[${context}] ${message}` : message));
45
+ }
46
+ }
47
+
48
+ static requestLogger(req, res, next) {
49
+ const startTime = Date.now();
50
+
51
+ res.on('finish', () => {
52
+ const duration = Date.now() - startTime;
53
+ const logMessage = `${req.method} ${req.path} - ${res.statusCode} (${duration}ms)`;
54
+
55
+ if (res.statusCode >= 400) {
56
+ Logger.error(logMessage, undefined, 'HTTP');
57
+ } else {
58
+ Logger.info(logMessage, 'HTTP');
59
+ }
60
+ });
61
+
62
+ next();
63
+ }
64
+ }
65
+
66
+ export default Logger;
package.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "xgrokServicee",
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
+ "cors": "^2.8.5",
13
+ "dotenv": "^16.3.1",
14
+ "uuid": "^9.0.0",
15
+ "chalk": "^5.4.1",
16
+ "moment": "^2.30.1",
17
+ "node-fetch": "^3.3.2",
18
+ "form-data": "^4.0.0"
19
+ }
20
+ }