Spaces:
Paused
Paused
| package api | |
| import ( | |
| "bytes" | |
| "encoding/json" | |
| "fmt" | |
| "io" | |
| "os" | |
| "strings" | |
| "time" | |
| http "github.com/bogdanfinn/fhttp" | |
| tls_client "github.com/bogdanfinn/tls-client" | |
| "github.com/bogdanfinn/tls-client/profiles" | |
| "github.com/gin-gonic/gin" | |
| "github.com/xqdoo00o/OpenAIAuth/auth" | |
| "github.com/xqdoo00o/funcaptcha" | |
| "github.com/linweiyuan/go-logger/logger" | |
| ) | |
| const ( | |
| ChatGPTApiPrefix = "/chatgpt" | |
| ImitateApiPrefix = "/imitate/v1" | |
| ChatGPTApiUrlPrefix = "https://chat.openai.com" | |
| PlatformApiPrefix = "/platform" | |
| PlatformApiUrlPrefix = "https://api.openai.com" | |
| defaultErrorMessageKey = "errorMessage" | |
| AuthorizationHeader = "Authorization" | |
| XAuthorizationHeader = "X-Authorization" | |
| ContentType = "application/x-www-form-urlencoded" | |
| UserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36" | |
| Auth0Url = "https://auth0.openai.com" | |
| LoginUsernameUrl = Auth0Url + "/u/login/identifier?state=" | |
| LoginPasswordUrl = Auth0Url + "/u/login/password?state=" | |
| ParseUserInfoErrorMessage = "failed to parse user login info" | |
| GetAuthorizedUrlErrorMessage = "failed to get authorized url" | |
| EmailInvalidErrorMessage = "email is not valid" | |
| EmailOrPasswordInvalidErrorMessage = "email or password is not correct" | |
| GetAccessTokenErrorMessage = "failed to get access token" | |
| defaultTimeoutSeconds = 600 // 10 minutes | |
| EmailKey = "email" | |
| AccountDeactivatedErrorMessage = "account %s is deactivated" | |
| ReadyHint = "service go-chatgpt-api is ready" | |
| refreshPuidErrorMessage = "failed to refresh PUID" | |
| ) | |
| var ( | |
| Client tls_client.HttpClient | |
| ArkoseClient tls_client.HttpClient | |
| PUID string | |
| ProxyUrl string | |
| ) | |
| type LoginInfo struct { | |
| Username string `json:"username"` | |
| Password string `json:"password"` | |
| } | |
| type AuthLogin interface { | |
| GetAuthorizedUrl(csrfToken string) (string, int, error) | |
| GetState(authorizedUrl string) (string, int, error) | |
| CheckUsername(state string, username string) (int, error) | |
| CheckPassword(state string, username string, password string) (string, int, error) | |
| GetAccessToken(code string) (string, int, error) | |
| GetAccessTokenFromHeader(c *gin.Context) (string, int, error) | |
| } | |
| func init() { | |
| Client, _ = tls_client.NewHttpClient(tls_client.NewNoopLogger(), []tls_client.HttpClientOption{ | |
| tls_client.WithCookieJar(tls_client.NewCookieJar()), | |
| tls_client.WithTimeoutSeconds(defaultTimeoutSeconds), | |
| tls_client.WithClientProfile(profiles.Okhttp4Android13), | |
| }...) | |
| ArkoseClient = getHttpClient() | |
| setupPUID() | |
| } | |
| func NewHttpClient() tls_client.HttpClient { | |
| client := getHttpClient() | |
| ProxyUrl = os.Getenv("PROXY") | |
| if ProxyUrl != "" { | |
| client.SetProxy(ProxyUrl) | |
| } | |
| return client | |
| } | |
| func getHttpClient() tls_client.HttpClient { | |
| client, _ := tls_client.NewHttpClient(tls_client.NewNoopLogger(), []tls_client.HttpClientOption{ | |
| tls_client.WithCookieJar(tls_client.NewCookieJar()), | |
| tls_client.WithClientProfile(profiles.Okhttp4Android13), | |
| }...) | |
| return client | |
| } | |
| func Proxy(c *gin.Context) { | |
| url := c.Request.URL.Path | |
| if strings.Contains(url, ChatGPTApiPrefix) { | |
| url = strings.ReplaceAll(url, ChatGPTApiPrefix, ChatGPTApiUrlPrefix) | |
| } else if strings.Contains(url, ImitateApiPrefix) { | |
| url = strings.ReplaceAll(url, ImitateApiPrefix, ChatGPTApiUrlPrefix+"/backend-api") | |
| } else { | |
| url = strings.ReplaceAll(url, PlatformApiPrefix, PlatformApiUrlPrefix) | |
| } | |
| method := c.Request.Method | |
| queryParams := c.Request.URL.Query().Encode() | |
| if queryParams != "" { | |
| url += "?" + queryParams | |
| } | |
| // if not set, will return 404 | |
| c.Status(http.StatusOK) | |
| var req *http.Request | |
| if method == http.MethodGet { | |
| req, _ = http.NewRequest(http.MethodGet, url, nil) | |
| } else { | |
| body, _ := io.ReadAll(c.Request.Body) | |
| req, _ = http.NewRequest(method, url, bytes.NewReader(body)) | |
| } | |
| req.Header.Set("User-Agent", UserAgent) | |
| req.Header.Set(AuthorizationHeader, GetAccessToken(c)) | |
| resp, err := Client.Do(req) | |
| if err != nil { | |
| c.AbortWithStatusJSON(http.StatusInternalServerError, ReturnMessage(err.Error())) | |
| return | |
| } | |
| defer resp.Body.Close() | |
| if resp.StatusCode != http.StatusOK { | |
| if resp.StatusCode == http.StatusUnauthorized { | |
| logger.Error(fmt.Sprintf(AccountDeactivatedErrorMessage, c.GetString(EmailKey))) | |
| } | |
| responseMap := make(map[string]interface{}) | |
| json.NewDecoder(resp.Body).Decode(&responseMap) | |
| c.AbortWithStatusJSON(resp.StatusCode, responseMap) | |
| return | |
| } | |
| io.Copy(c.Writer, resp.Body) | |
| } | |
| func ReturnMessage(msg string) gin.H { | |
| logger.Warn(msg) | |
| return gin.H{ | |
| defaultErrorMessageKey: msg, | |
| } | |
| } | |
| func GetAccessToken(c *gin.Context) string { | |
| accessToken := c.GetString(AuthorizationHeader) | |
| if !strings.HasPrefix(accessToken, "Bearer") { | |
| return "Bearer " + accessToken | |
| } | |
| return accessToken | |
| } | |
| func GetArkoseToken() (string, error) { | |
| return funcaptcha.GetOpenAIToken(PUID, ProxyUrl) | |
| } | |
| func setupPUID() { | |
| username := os.Getenv("OPENAI_EMAIL") | |
| password := os.Getenv("OPENAI_PASSWORD") | |
| if username != "" && password != "" { | |
| go func() { | |
| for { | |
| authenticator := auth.NewAuthenticator(username, password, ProxyUrl) | |
| if err := authenticator.Begin(); err != nil { | |
| logger.Warn(fmt.Sprintf("%s: %s", refreshPuidErrorMessage, err.Details)) | |
| return | |
| } | |
| accessToken := authenticator.GetAccessToken() | |
| if accessToken == "" { | |
| logger.Error(refreshPuidErrorMessage) | |
| return | |
| } | |
| puid, err := authenticator.GetPUID() | |
| if err != nil { | |
| logger.Error(refreshPuidErrorMessage) | |
| return | |
| } | |
| PUID = puid | |
| time.Sleep(time.Hour * 24 * 7) | |
| } | |
| }() | |
| } | |
| } | |