letterm commited on
Commit
e90853e
·
verified ·
1 Parent(s): 1879b49

Upload 4 files

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