1
1
import express , { Request , Response , NextFunction } from "express" ;
2
+ import { ChildProcessWithoutNullStreams , spawn } from "child_process" ;
3
+ import fs from "fs" ;
4
+ import path from "path" ;
2
5
import bodyParser from "body-parser" ;
3
6
import axios from "axios" ;
4
7
import https from "https" ;
8
+ import os from "os" ;
5
9
import { encode } from "gpt-3-encoder" ;
6
10
import { randomUUID } from "crypto" ;
7
11
import { config } from "dotenv" ;
@@ -14,6 +18,7 @@ const baseUrl = "https://chat.openai.com";
14
18
const apiUrl = `${ baseUrl } /backend-anon/conversation` ;
15
19
const refreshInterval = 60000 ; // Interval to refresh token in ms
16
20
const errorWait = 120000 ; // Wait time in ms after an error
21
+ let cloudflared : ChildProcessWithoutNullStreams ;
17
22
18
23
// Initialize global variables to store the session token and device ID
19
24
let token : string ;
@@ -133,7 +138,7 @@ async function handleChatCompletion(req: Request, res: Response) {
133
138
try {
134
139
const body = {
135
140
action : "next" ,
136
- messages : req . body . messages . map ( ( message ) => ( {
141
+ messages : req . body . messages . map ( ( message : { role : any ; content : any } ) => ( {
137
142
author : { role : message . role } ,
138
143
content : { content_type : "text" , parts : [ message . content ] } ,
139
144
} ) ) ,
@@ -322,17 +327,125 @@ app.use((req, res) =>
322
327
} )
323
328
) ;
324
329
330
+ async function DownloadCloudflared ( ) : Promise < string > {
331
+ const platform = os . platform ( ) ;
332
+ let url : string ;
333
+
334
+ if ( platform === "win32" ) {
335
+ const arch = os . arch ( ) === "x64" ? "amd64" : "386" ;
336
+ url = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-${ arch } .exe` ;
337
+ } else {
338
+ let arch = os . arch ( ) ;
339
+ switch ( arch ) {
340
+ case "x64" :
341
+ arch = "amd64" ;
342
+ break ;
343
+ case "arm" :
344
+ case "arm64" :
345
+ break ;
346
+ default :
347
+ arch = "amd64" ; // Default to amd64 if unknown architecture
348
+ }
349
+ const platformLower = platform . toLowerCase ( ) ;
350
+ url = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-${ platformLower } -${ arch } ` ;
351
+ }
352
+
353
+ const fileName = platform === "win32" ? "cloudflared.exe" : "cloudflared" ;
354
+ const filePath = path . resolve ( fileName ) ;
355
+
356
+ if ( fs . existsSync ( filePath ) ) {
357
+ return filePath ;
358
+ }
359
+
360
+ try {
361
+ const response = await axiosInstance ( {
362
+ method : "get" ,
363
+ url : url ,
364
+ responseType : "stream" ,
365
+ } ) ;
366
+
367
+ const writer = fs . createWriteStream ( filePath ) ;
368
+
369
+ response . data . pipe ( writer ) ;
370
+
371
+ return new Promise < string > ( ( resolve , reject ) => {
372
+ writer . on ( "finish" , ( ) => {
373
+ if ( platform !== "win32" ) {
374
+ fs . chmodSync ( filePath , 0o755 ) ;
375
+ }
376
+ resolve ( filePath ) ;
377
+ } ) ;
378
+
379
+ writer . on ( "error" , reject ) ;
380
+ } ) ;
381
+ } catch ( error : any ) {
382
+ // console.error("Failed to download file:", error.message);
383
+ return null ;
384
+ }
385
+ }
386
+
387
+ async function StartCloudflaredTunnel ( cloudflaredPath : string ) : Promise < string > {
388
+ const localUrl = `http://localhost:${ port } ` ;
389
+ return new Promise < string > ( ( resolve , reject ) => {
390
+ cloudflared = spawn ( cloudflaredPath , [ "tunnel" , "--url" , localUrl ] ) ;
391
+
392
+ cloudflared . stdout . on ( "data" , ( data : any ) => {
393
+ const output = data . toString ( ) ;
394
+ // console.log("Cloudflared Output:", output);
395
+
396
+ // Adjusted regex to specifically match URLs that end with .trycloudflare.com
397
+ const urlMatch = output . match ( / h t t p s : \/ \/ [ ^ \s ] + \. t r y c l o u d f l a r e \. c o m / ) ;
398
+ if ( urlMatch ) {
399
+ let url = urlMatch [ 0 ] ;
400
+ resolve ( url ) ;
401
+ }
402
+ } ) ;
403
+
404
+ cloudflared . stderr . on ( "data" , ( data : any ) => {
405
+ const output = data . toString ( ) ;
406
+ // console.error("Error from cloudflared:", output);
407
+
408
+ const urlMatch = output . match ( / h t t p s : \/ \/ [ ^ \s ] + \. t r y c l o u d f l a r e \. c o m / ) ;
409
+ if ( urlMatch ) {
410
+ let url = urlMatch [ 0 ] ;
411
+ resolve ( url ) ;
412
+ }
413
+ } ) ;
414
+
415
+ cloudflared . on ( "close" , ( code : any ) => {
416
+ resolve ( null ) ;
417
+ // console.log(`Cloudflared tunnel process exited with code ${code}`);
418
+ } ) ;
419
+ } ) ;
420
+ }
421
+
325
422
// Start the server and the session ID refresh loop
326
- app . listen ( port , ( ) => {
423
+ app . listen ( port , async ( ) => {
424
+ if ( process . env . CLOUDFLARED === undefined ) process . env . CLOUDFLARED = "true" ;
425
+ let cloudflared = process . env . CLOUDFLARED === "true" ;
426
+ let filePath : string ;
427
+ let publicURL : string ;
428
+ if ( cloudflared ) {
429
+ filePath = await DownloadCloudflared ( ) ;
430
+ publicURL = await StartCloudflaredTunnel ( filePath ) ;
431
+ }
432
+
327
433
console . log ( `π‘ Server is running at http://localhost:${ port } ` ) ;
328
434
console . log ( ) ;
329
- console . log ( `π Base URL: http://localhost:${ port } /v1` ) ;
330
- console . log ( `π ChatCompletion Endpoint: http://localhost:${ port } /v1/chat/completions` ) ;
435
+ console . log ( `π Local Base URL: http://localhost:${ port } /v1` ) ;
436
+ console . log ( `π Local Endpoint: http://localhost:${ port } /v1/chat/completions` ) ;
331
437
console . log ( ) ;
438
+ if ( cloudflared && publicURL ) console . log ( `π Public Base URL: ${ publicURL } /v1` ) ;
439
+ if ( cloudflared && publicURL ) console . log ( `π Public Endpoint: ${ publicURL } /v1/chat/completions` ) ;
440
+ else if ( cloudflared && ! publicURL ) {
441
+ console . log ( "π Public Endpoint: (Failed to start cloudflared tunnel, please restart the server.)" ) ;
442
+ if ( filePath ) fs . unlinkSync ( filePath ) ;
443
+ }
444
+ if ( cloudflared && publicURL ) console . log ( ) ;
332
445
console . log ( "π Author: Pawan.Krd" ) ;
333
446
console . log ( `π Discord server: https://discord.gg/pawan` ) ;
334
447
console . log ( "π GitHub Repository: https://github.com/PawanOsman/ChatGPT" ) ;
335
- console . log ( `Don't forget to β star the repository if you like this project!` ) ;
448
+ console . log ( `π Don't forget to star the repository if you like this project!` ) ;
336
449
console . log ( ) ;
337
450
338
451
setTimeout ( async ( ) => {
0 commit comments