├── .env.example ├── .github └── workflows │ ├── build.yml │ ├── build_docker.yml │ └── release.yml ├── .gitignore ├── Dockerfile ├── README.md ├── README_CN.md ├── README_JA.md ├── README_ZH.md ├── auth.go ├── cases └── sider │ ├── Sider-Config.md │ └── Sider-example.png ├── conversion ├── requests │ └── chatgpt │ │ └── convert.go └── response │ └── chatgpt │ └── convert.go ├── docker-compose.yml ├── docs ├── Docker_CN.md ├── GUIDE_CN.md ├── TOKEN_CN.md └── admin.md ├── go.mod ├── go.sum ├── handlers.go ├── internal ├── bard │ ├── lib.go │ ├── request.go │ └── utilities.go ├── chatgpt │ └── request.go └── tokens │ └── tokens.go ├── main.go ├── middleware.go ├── tools ├── authenticator │ ├── README.md │ ├── auth │ │ └── auth.go │ ├── go.mod │ ├── go.sum │ ├── main.go │ ├── remove_duplicates.py │ └── tojson.sh ├── plugin_check │ └── check_access.go └── proxy_check │ └── proxy_check.go └── typings ├── chatgpt ├── request.go └── response.go ├── official ├── request.go └── response.go └── typings.go /.env.example: -------------------------------------------------------------------------------- 1 | ADMIN_PASSWORD= 2 | OPENAI_EMAIL= 3 | OPENAI_PASSWORD= -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Go Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | include: 17 | - goos: windows 18 | goarch: 386 19 | - goos: windows 20 | goarch: amd64 21 | - goos: windows 22 | goarch: arm64 23 | - goos: linux 24 | goarch: 386 25 | - goos: linux 26 | goarch: amd64 27 | - goos: linux 28 | goarch: arm64 29 | steps: 30 | - name: Checkout code 31 | uses: actions/checkout@v3 32 | 33 | - name: Setup Go 34 | uses: actions/setup-go@v4 35 | with: 36 | go-version: '1.20' 37 | 38 | - name: Build 39 | run: | 40 | echo "Building for ${{ matrix.goos }} ${{ matrix.goarch }}" 41 | suffix="" 42 | if [ "${{ matrix.goos }}" == "windows" ]; then 43 | suffix=".exe" 44 | fi 45 | GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -o ./build/freechatgpt-${{ matrix.goos }}-${{ matrix.goarch }}$suffix 46 | 47 | - name: Upload artifacts 48 | uses: actions/upload-artifact@v3 49 | with: 50 | name: freechatgpt-${{ matrix.goos }}-${{ matrix.goarch }} 51 | path: ./build/freechatgpt-${{ matrix.goos }}-${{ matrix.goarch }}* 52 | -------------------------------------------------------------------------------- /.github/workflows/build_docker.yml: -------------------------------------------------------------------------------- 1 | name: build_docker 2 | 3 | on: 4 | release: 5 | types: [created] # 表示在创建新的 Release 时触发 6 | 7 | jobs: 8 | build_docker: 9 | name: Build docker 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | 15 | - run: | 16 | echo "本次构建的版本为:${GITHUB_REF_NAME} (但是这个变量目前上下文中无法获取到)" 17 | echo 本次构建的版本为:${{ github.ref_name }} 18 | env 19 | 20 | - name: Set up QEMU 21 | uses: docker/setup-qemu-action@v2 22 | - name: Set up Docker Buildx 23 | uses: docker/setup-buildx-action@v2 24 | - name: Login to DockerHub 25 | uses: docker/login-action@v2 26 | with: 27 | username: ${{ secrets.DOCKERHUB_USERNAME }} 28 | password: ${{ secrets.DOCKERHUB_TOKEN }} 29 | - name: Build and push 30 | id: docker_build 31 | uses: docker/build-push-action@v4 32 | with: 33 | context: . 34 | push: true 35 | labels: ${{ steps.meta.outputs.labels }} 36 | platforms: linux/amd64,linux/arm64 37 | tags: | 38 | ${{ secrets.DOCKERHUB_USERNAME }}/chatgpt-to-api:${{ github.ref_name }} 39 | ${{ secrets.DOCKERHUB_USERNAME }}/chatgpt-to-api:latest 40 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Release Workflow 5 | on: 6 | release: 7 | types: 8 | - created 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v3 21 | with: 22 | go-version: 1.19 23 | 24 | - name: Build 25 | run: go build -o bin/ . 26 | 27 | - name: recursively list files 28 | run: ls -R 29 | 30 | - name: Get existing release body 31 | id: get_release_body 32 | run: | 33 | echo "::set-output name=body::$(curl -s -H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' https://api.github.com/repos/${{ github.repository }}/releases/tags/${{ github.ref_path }} | jq -r '.body')" 34 | 35 | - name: Upload release artifact 36 | uses: svenstaro/upload-release-action@v2 37 | with: 38 | file: bin/* 39 | file_glob: true 40 | tag: ${{ github.ref }} 41 | body: | 42 | ${{ steps.get_release_body.outputs.body }} 43 | repo_token: ${{ secrets.GITHUB_TOKEN }} 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tools/authenticator/100-ACCOUNTS_COMPILED.txt 2 | tools/authenticator/accounts.txt 3 | tools/authenticator/proxies.txt 4 | tools/authenticator/authenticated_accounts.txt 5 | tools/authenticator/access_tokens.txt 6 | *.txt 7 | access_tokens.json 8 | freechatgpt 9 | chatgpttoapi 10 | tools/authenticator/.proxies.txt.swp 11 | .env 12 | *.har -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official Golang image as the builder 2 | FROM golang:1.20.3-alpine as builder 3 | 4 | # Enable CGO to use C libraries (set to 0 to disable it) 5 | # We set it to 0 to build a fully static binary for our final image 6 | ENV CGO_ENABLED=0 7 | 8 | # Set the working directory 9 | WORKDIR /app 10 | 11 | # Copy the Go Modules manifests (go.mod and go.sum files) 12 | COPY go.mod go.sum ./ 13 | 14 | # Download the dependencies 15 | RUN go mod download 16 | 17 | # Copy the source code 18 | COPY . . 19 | 20 | # Build the Go application and output the binary to /app/ChatGPT-Proxy-V4 21 | RUN go build -o /app/ChatGPT-To-API . 22 | 23 | # Use a scratch image as the final distroless image 24 | FROM scratch 25 | 26 | # Set the working directory 27 | WORKDIR /app 28 | 29 | # Copy the built Go binary from the builder stage 30 | COPY --from=builder /app/ChatGPT-To-API /app/ChatGPT-To-API 31 | 32 | # Expose the port where the application is running 33 | EXPOSE 8080 34 | 35 | # Start the application 36 | CMD [ "./ChatGPT-To-API" ] 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatGPT-to-API 2 | Create a fake API using ChatGPT's website 3 | 4 | > ## IMPORTANT 5 | > You will not get free support for this repository. This was made for my own personal use and documentation will continue to be limited as I don't really need documentation. You will find more detailed documentation in the Chinese docs by a contributor. 6 | 7 | **API endpoint: http://127.0.0.1:8080/v1/chat/completions.** 8 | 9 | [中文文档(Chinese Docs)](https://github.com/acheong08/ChatGPT-to-API/blob/master/README_ZH.md) 10 | ## Setup 11 | 12 | ### Authentication 13 | 14 | Access token and PUID(only for PLUS account) retrieval has been automated by [OpenAIAuth](https://github.com/acheong08/OpenAIAuth/) with account email & password. 15 | 16 | `accounts.txt` - A list of accounts separated by new line 17 | 18 | Format: 19 | ``` 20 | email:password 21 | ... 22 | ``` 23 | 24 | All authenticated access tokens and PUID will store in `access_tokens.json` 25 | 26 | Auto renew access tokens and PUID after 7 days 27 | 28 | Caution! please use unblocked ip for authentication, first login to `https://chat.openai.com/` to check ip availability if you can. 29 | 30 | ### GPT-4 Model (Optional) 31 | 32 | If you configured a PLUS account and use the GPT-4 model, a HAR file (`chat.openai.com.har`) is required to complete CAPTCHA verification 33 | 34 | 1. Use a chromium-based browser (Chrome, Edge) or Safari to login to `https://chat.openai.com/`, then open the browser developer tools (F12), and switch to the Network tab. 35 | 36 | 2. Create a new chat and select the GPT-4 model, ask a question at will, click the Export HAR button under the Network tab, export the file `chat.openai.com.har` 37 | 38 | ### API Authentication (Optional) 39 | 40 | Custom API keys for this fake API, just like OpenAI api 41 | 42 | `api_keys.txt` - A list of API keys separated by new line 43 | 44 | Format: 45 | ``` 46 | sk-123456 47 | 88888888 48 | ... 49 | ``` 50 | 51 | ## Getting set up 52 | ``` 53 | git clone https://github.com/acheong08/ChatGPT-to-API 54 | cd ChatGPT-to-API 55 | go build 56 | ./freechatgpt 57 | ``` 58 | 59 | ### Environment variables 60 | - `PUID` - A cookie found on chat.openai.com for Plus users. This gets around Cloudflare rate limits 61 | - `SERVER_HOST` - Set to 127.0.0.1 by default 62 | - `SERVER_PORT` - Set to 8080 by default 63 | - `ENABLE_HISTORY` - Set to true by default 64 | 65 | ### Files (Optional) 66 | - `proxies.txt` - A list of proxies separated by new line 67 | 68 | ``` 69 | http://127.0.0.1:8888 70 | ... 71 | ``` 72 | - `access_tokens.json` - A JSON array of access tokens for cycling (Alternatively, send a PATCH request to the [correct endpoint](https://github.com/acheong08/ChatGPT-to-API/blob/master/docs/admin.md)) 73 | ``` 74 | [{token:"access_token1", puid:"puid1"}, {token:"access_token2", puid:"puid2"}...] 75 | ``` 76 | 77 | ## Admin API docs 78 | https://github.com/acheong08/ChatGPT-to-API/blob/master/docs/admin.md 79 | 80 | ## API usage docs 81 | https://platform.openai.com/docs/api-reference/chat 82 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # ChatGPT-to-API 2 | 创建一个模拟API(通过ChatGPT网页版)。使用AccessToken把ChatGPT模拟成OpenAI API,从而在各类应用程序中使用OpenAI的API且不需要为API额外付费,因为模拟成网页版的使用了,和官方API基本互相兼容。 3 | 4 | 本中文手册由 [@BlueSkyXN](https://github.com/BlueSkyXN) 编写 5 | 6 | [英文文档(English Docs)](README.md) 7 | 8 | ## 认证和各项准备工作 9 | 10 | 在使用之前,你需要完成一系列准备工作 11 | 12 | 1. 准备ChatGPT账号,最好的PLUS订阅的,有没有开API不重要 13 | 2. 完善的运行环境和网络环境(否则你总是要寻找方法绕过) 14 | 3. Access Token和PUID,下面会教你怎么获取 15 | 4. 选择一个代理后端或者自行搭建 16 | 5. 你可以在 https://github.com/BlueSkyXN/OpenAI-Quick-DEV 项目找到一些常用组件以及一些快速运行的教程或程序。 17 | 18 | ### 获取PUID 19 | 20 | `_puid` cookie. 21 | 22 | ### 获取Access Token 23 | 目前有多种方法和原理,这部分内容可以参考 [TOKEN中文手册](docs/TOKEN_CN.md) 24 | 25 | ## 安装和运行 26 | 27 | 作者在[英文版介绍](README.md) 通过GO编译来构建二进制程序,但是我猜测这可能需要一个GO编译环境。所以我建议基于作者的Compose配置文件来Docker运行。 28 | 29 | 有关docker的指导请阅读 [DOCKER中文手册](docs/Docker_CN.md) 30 | 31 | 安装好Docker和Docker-Compase后,通过Compase来启动 32 | 33 | ```docker-compose up -d``` 34 | 35 | 注意,启动之前你需要配置 yml 配置文件,主要是端口和环境变量,各项参数、用法请参考 [中文指导手册](docs/GUIDE_CN.md) 36 | 37 | 最后的API端点(Endpoint)是 38 | 39 | ```http://127.0.0.1:8080/v1/chat/completions``` 40 | 41 | 注意域名/IP和端口要改成你自己的 42 | 43 | ### 环境变量 44 | - `PUID` - 用户ID 45 | - `http_proxy` - SOCKS5 或 HTTP 代理 `socks5://HOST:PORT` 46 | - `SERVER_HOST` - (default)比如 127.0.0.1 47 | - `SERVER_PORT` - (default)比如 8080 by 48 | 49 | ### 文件选项 50 | - `access_tokens.json` - 附带AccessToken的Json文件 51 | - `proxies.txt` - 代理表 (格式: `USERNAME:PASSWORD:HOST:PORT`) 52 | 53 | -------------------------------------------------------------------------------- /README_JA.md: -------------------------------------------------------------------------------- 1 | # ChatGPT-to-API 2 | ChatGPT のウェブサイトを使って偽 API を作る 3 | 4 | > ## 重要 5 | > このリポジトリに対する無償のサポートは受けられません。これは私個人の使用のために作られたもので、ドキュメントは本当に必要ないので、ドキュメントは制限され続けます。貢献者による中国語のドキュメントに、より詳細なドキュメントがあります。 6 | 7 | **API エンドポイント: http://127.0.0.1:8080/v1/chat/completions.** 8 | 9 | [英語ドキュメント(English Docs)](README.md) 10 | [中国語ドキュメント(Chinese Docs)](https://github.com/acheong08/ChatGPT-to-API/blob/master/README_ZH.md) 11 | ## セットアップ 12 | 13 | ### 認証 14 | 15 | アクセストークンの取得は [OpenAIAuth](https://github.com/acheong08/OpenAIAuth/) により、アカウントのメールアドレスとパスワードで自動化されています。 16 | 17 | `accounts.txt` - 改行で区切られたアカウントのリスト 18 | 19 | フォーマット: 20 | ``` 21 | email:password 22 | ... 23 | ``` 24 | 25 | すべての認証されたアクセストークンは `access_tokens.json` に保存されます 26 | 27 | アクセストークンは 7 日後に自動更新されます 28 | 29 | 注意!認証にはブロックされていない ip を使用してください。可能であれば、まず `https://chat.openai.com/` にログインして ip の可用性を確認してください。 30 | 31 | ### API認証(オプション) 32 | 33 | OpenAI の API と同じような、この偽 API 用のカスタム API キー 34 | 35 | `api_keys.txt` - 改行で区切られた API キーのリスト 36 | 37 | フォーマット: 38 | ``` 39 | sk-123456 40 | 88888888 41 | ... 42 | ``` 43 | 44 | ## 準備 45 | ``` 46 | git clone https://github.com/acheong08/ChatGPT-to-API 47 | cd ChatGPT-to-API 48 | go build 49 | ./freechatgpt 50 | ``` 51 | 52 | ### 環境変数 53 | - `PUID` - chat.openai.com の Plus ユーザー向けのクッキーです。これは Cloudflare のレート制限を回避します 54 | - `SERVER_HOST` - デフォルトで 127.0.0.1 に設定 55 | - `SERVER_PORT` - デフォルトで 8080 に設定 56 | - `OPENAI_EMAIL` と `OPENAI_PASSWORD` - PUID が設定されている場合、自動的に更新されます 57 | - `ENABLE_HISTORY` - デフォルトで true に設定 58 | 59 | ### ファイル(オプション) 60 | - `proxies.txt` - 改行で区切られたプロキシのリスト 61 | 62 | ``` 63 | http://127.0.0.1:8888 64 | ... 65 | ``` 66 | - `access_tokens.json` - サイクリング用のアクセストークンの JSON 配列(あるいは、[正しいエンドポイント](https://github.com/acheong08/ChatGPT-to-API/blob/master/docs/admin.md)に PATCH リクエストを送る) 67 | ``` 68 | ["access_token1", "access_token2"...] 69 | ``` 70 | 71 | ## Admin API ドキュメント 72 | https://github.com/acheong08/ChatGPT-to-API/blob/master/docs/admin.md 73 | 74 | ## API 使用方法ドキュメント 75 | https://platform.openai.com/docs/api-reference/chat 76 | -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | # ChatGPT-to-API 2 | 从ChatGPT网站模拟使用API 3 | 4 | **模拟API地址: http://127.0.0.1:8080/v1/chat/completions.** 5 | 6 | ## 使用 7 | 8 | ### 设置 9 | 10 | 配置账户邮箱和密码,自动生成和更新Access tokens 和 PUID(仅PLUS账户)(使用[OpenAIAuth](https://github.com/acheong08/OpenAIAuth/)) 11 | 12 | `accounts.txt` - 存放OpenAI账号邮箱和密码的文件 13 | 14 | 格式: 15 | ``` 16 | 邮箱:密码 17 | 邮箱:密码 18 | ... 19 | ``` 20 | 21 | 所有登录后的Access tokens和PUID会存放在`access_tokens.json` 22 | 23 | 每7天自动更新Access tokens和PUID 24 | 25 | 注意! 请使用未封锁的ip登录账号,请先打开浏览器登录`https://chat.openai.com/`以检查ip是否可用 26 | 27 | ### GPT-4 设置(可选) 28 | 29 | 如果配置PLUS账户并使用GPT-4模型,则需要HAR文件(`chat.openai.com.har`)以完成captcha验证 30 | 31 | 1. 使用基于chromium的浏览器(Chrome,Edge)或Safari浏览器 登录`https://chat.openai.com/`,然后打开浏览器开发者工具(F12),并切换到网络标签页。 32 | 33 | 2. 新建聊天并选择GPT-4模型,随意问一个问题,点击网络标签页下的导出HAR按钮,导出文件`chat.openai.com.har` 34 | 35 | ### API 密钥(可选) 36 | 37 | 如OpenAI的官方API一样,可给模拟的API添加API密钥认证 38 | 39 | `api_keys.txt` - 存放API密钥的文件 40 | 41 | 格式: 42 | ``` 43 | sk-123456 44 | 88888888 45 | ... 46 | ``` 47 | 48 | ## 开始 49 | ``` 50 | git clone https://github.com/acheong08/ChatGPT-to-API 51 | cd ChatGPT-to-API 52 | go build 53 | ./freechatgpt 54 | ``` 55 | 56 | ### 环境变量 57 | - `PUID` - Plus账户可在`chat.openai.com`的cookies里找到,用于绕过cf的频率限制 58 | - `SERVER_HOST` - 默认127.0.0.1 59 | - `SERVER_PORT` - 默认8080 60 | - `ENABLE_HISTORY` - 默认true,允许网页端历史记录 61 | 62 | ### 可选文件配置 63 | - `proxies.txt` - 存放代理地址的文件 64 | 65 | ``` 66 | http://127.0.0.1:8888 67 | socks5://127.0.0.1:9999 68 | ... 69 | ``` 70 | - `access_tokens.json` - 一个存放Access tokens 和PUID JSON数组的文件 (可使用 PATCH请求更新Access tokens [correct endpoint](https://github.com/acheong08/ChatGPT-to-API/blob/master/docs/admin.md)) 71 | ``` 72 | [{token:"access_token1", puid:"puid1"}, {token:"access_token2", puid:"puid2"}...] 73 | ``` 74 | 75 | ## 用户管理文档 76 | https://github.com/acheong08/ChatGPT-to-API/blob/master/docs/admin.md 77 | 78 | ## API使用说明 79 | https://platform.openai.com/docs/api-reference/chat 80 | -------------------------------------------------------------------------------- /auth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "strings" 10 | "time" 11 | 12 | "freechatgpt/internal/tokens" 13 | 14 | "github.com/acheong08/OpenAIAuth/auth" 15 | ) 16 | 17 | var accounts []Account 18 | 19 | type Account struct { 20 | Email string `json:"username"` 21 | Password string `json:"password"` 22 | } 23 | 24 | // Read accounts.txt and create a list of accounts 25 | func readAccounts() { 26 | accounts = []Account{} 27 | // Read accounts.txt and create a list of accounts 28 | if _, err := os.Stat("accounts.txt"); err == nil { 29 | // Each line is a proxy, put in proxies array 30 | file, _ := os.Open("accounts.txt") 31 | defer file.Close() 32 | scanner := bufio.NewScanner(file) 33 | for scanner.Scan() { 34 | // Split by : 35 | line := strings.Split(scanner.Text(), ":") 36 | // Create an account 37 | account := Account{ 38 | Email: line[0], 39 | Password: line[1], 40 | } 41 | // Append to accounts 42 | accounts = append(accounts, account) 43 | } 44 | } 45 | } 46 | func scheduleTokenPUID() { 47 | // Check if access_tokens.json exists 48 | if stat, err := os.Stat("access_tokens.json"); os.IsNotExist(err) { 49 | // Create the file 50 | file, err := os.Create("access_tokens.json") 51 | if err != nil { 52 | panic(err) 53 | } 54 | defer file.Close() 55 | updateToken() 56 | } else { 57 | nowTime := time.Now() 58 | usedTime := nowTime.Sub(stat.ModTime()) 59 | // update access token 7 days after last modify token file 60 | toExpire := 6.048e14 - usedTime 61 | if toExpire > 0 { 62 | file, err := os.Open("access_tokens.json") 63 | if err != nil { 64 | panic(err) 65 | } 66 | defer file.Close() 67 | decoder := json.NewDecoder(file) 68 | var token_list []tokens.Secret 69 | err = decoder.Decode(&token_list) 70 | if err != nil { 71 | updateToken() 72 | return 73 | } 74 | if len(token_list) == 0 { 75 | updateToken() 76 | } else { 77 | ACCESS_TOKENS = tokens.NewAccessToken(token_list, false) 78 | time.AfterFunc(toExpire, updateToken) 79 | } 80 | } else { 81 | updateToken() 82 | } 83 | } 84 | } 85 | 86 | func updateToken() { 87 | token_list := []tokens.Secret{} 88 | // Loop through each account 89 | for _, account := range accounts { 90 | if os.Getenv("CF_PROXY") != "" { 91 | // exec warp-cli disconnect and connect 92 | exec.Command("warp-cli", "disconnect").Run() 93 | exec.Command("warp-cli", "connect").Run() 94 | time.Sleep(5 * time.Second) 95 | } 96 | println("Updating access token for " + account.Email) 97 | var proxy_url string 98 | if len(proxies) == 0 { 99 | proxy_url = "" 100 | } else { 101 | proxy_url = proxies[0] 102 | // Push used proxy to the back of the list 103 | proxies = append(proxies[1:], proxies[0]) 104 | } 105 | authenticator := auth.NewAuthenticator(account.Email, account.Password, proxy_url) 106 | err := authenticator.Begin() 107 | if err != nil { 108 | // println("Error: " + err.Details) 109 | println("Location: " + err.Location) 110 | println("Status code: " + fmt.Sprint(err.StatusCode)) 111 | println("Details: " + err.Details) 112 | println("Embedded error: " + err.Error.Error()) 113 | return 114 | } 115 | access_token := authenticator.GetAccessToken() 116 | puid, _ := authenticator.GetPUID() 117 | token_list = append(token_list, tokens.Secret{access_token, puid}) 118 | println("Success!") 119 | // Write authenticated account to authenticated_accounts.txt 120 | f, go_err := os.OpenFile("authenticated_accounts.txt", os.O_APPEND|os.O_WRONLY, 0600) 121 | if go_err != nil { 122 | continue 123 | } 124 | defer f.Close() 125 | if _, go_err = f.WriteString(account.Email + ":" + account.Password + "\n"); go_err != nil { 126 | continue 127 | } 128 | // Remove accounts.txt 129 | os.Remove("accounts.txt") 130 | // Create accounts.txt 131 | f, go_err = os.Create("accounts.txt") 132 | if go_err != nil { 133 | continue 134 | } 135 | defer f.Close() 136 | // Remove account from accounts 137 | accounts = accounts[1:] 138 | // Write unauthenticated accounts to accounts.txt 139 | for _, acc := range accounts { 140 | // Check if account is authenticated 141 | if acc.Email == account.Email { 142 | continue 143 | } 144 | if _, go_err = f.WriteString(acc.Email + ":" + acc.Password + "\n"); go_err != nil { 145 | continue 146 | } 147 | } 148 | } 149 | // Append access token to access_tokens.json 150 | ACCESS_TOKENS = tokens.NewAccessToken(token_list, true) 151 | time.AfterFunc(6.048e14, updateToken) 152 | } 153 | -------------------------------------------------------------------------------- /cases/sider/Sider-Config.md: -------------------------------------------------------------------------------- 1 | # 这是一个指导你如何在Sider程序中应用本程序的API 2 | Sider ,一个浏览器插件 3 | 4 | https://chrome.google.com/webstore/detail/difoiogjjojoaoomphldepapgpbgkhkb 5 | 6 | --- 7 | # 设置 8 | 9 | 打开插件自己的设置页,大概是 chrome-extension://difoiogjjojoaoomphldepapgpbgkhkb/options.html?section=general 10 | 11 | 找到通用设置-如何访问 ChatGPT 并在任何地方使用它-OpenAI KEY 12 | 13 | 在API KEY中填写你的API管理密码 14 | 15 | 模型默认即可,获取不到的(接口不一样) 16 | 17 | URL写你的域名/IP+端口,比如 http://127.0.0.1:8080 ,原先完整URL后面的 /v1 什么的不需要填写 18 | 19 | 然后直接就能用了 20 | 21 | 示例图 22 | 23 | ![Sider-example](./Sider-example.png) 24 | 25 | 26 | # 不足 27 | 28 | - Sider的策略是开启一个聊天,然后始终重复编辑这个对话,使用了本项目API后会导致一直会新建对话,不会删除或编辑旧的对话,所以你需要自行清理。 -------------------------------------------------------------------------------- /cases/sider/Sider-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acheong08/ChatGPT-to-API/1df9d25da870fdd5787cce874ac1f6284006672b/cases/sider/Sider-example.png -------------------------------------------------------------------------------- /conversion/requests/chatgpt/convert.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | import ( 4 | "fmt" 5 | chatgpt_types "freechatgpt/typings/chatgpt" 6 | official_types "freechatgpt/typings/official" 7 | "strings" 8 | 9 | arkose "github.com/acheong08/funcaptcha" 10 | ) 11 | 12 | func ConvertAPIRequest(api_request official_types.APIRequest, puid string, proxy string) chatgpt_types.ChatGPTRequest { 13 | chatgpt_request := chatgpt_types.NewChatGPTRequest() 14 | if strings.HasPrefix(api_request.Model, "gpt-3.5") { 15 | chatgpt_request.Model = "text-davinci-002-render-sha" 16 | } 17 | if strings.HasPrefix(api_request.Model, "gpt-4") { 18 | token, _, err := arkose.GetOpenAIToken(puid, proxy) 19 | if err == nil { 20 | chatgpt_request.ArkoseToken = token 21 | } else { 22 | fmt.Println("Error getting Arkose token: ", err) 23 | } 24 | chatgpt_request.Model = api_request.Model 25 | // Cover some models like gpt-4-32k 26 | if len(api_request.Model) >= 7 && api_request.Model[6] >= 48 && api_request.Model[6] <= 57 { 27 | chatgpt_request.Model = "gpt-4" 28 | } 29 | } 30 | if api_request.PluginIDs != nil { 31 | chatgpt_request.PluginIDs = api_request.PluginIDs 32 | chatgpt_request.Model = "gpt-4-plugins" 33 | } 34 | for _, api_message := range api_request.Messages { 35 | if api_message.Role == "system" { 36 | api_message.Role = "critic" 37 | } 38 | chatgpt_request.AddMessage(api_message.Role, api_message.Content) 39 | } 40 | return chatgpt_request 41 | } 42 | -------------------------------------------------------------------------------- /conversion/response/chatgpt/convert.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | import ( 4 | "freechatgpt/typings" 5 | chatgpt_types "freechatgpt/typings/chatgpt" 6 | official_types "freechatgpt/typings/official" 7 | "strings" 8 | ) 9 | 10 | func ConvertToString(chatgpt_response *chatgpt_types.ChatGPTResponse, previous_text *typings.StringStruct, role bool) string { 11 | translated_response := official_types.NewChatCompletionChunk(strings.ReplaceAll(chatgpt_response.Message.Content.Parts[0], *&previous_text.Text, "")) 12 | if role { 13 | translated_response.Choices[0].Delta.Role = chatgpt_response.Message.Author.Role 14 | } 15 | previous_text.Text = chatgpt_response.Message.Content.Parts[0] 16 | return "data: " + translated_response.String() + "\n\n" 17 | 18 | } 19 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | app: 5 | image: acheong08/chatgpt-to-api # 总是使用latest,更新时重新pull该tag镜像即可 6 | container_name: chatgpttoapi 7 | restart: unless-stopped 8 | ports: 9 | - '8080:8080' 10 | environment: 11 | SERVER_HOST: 0.0.0.0 12 | SERVER_PORT: 8080 13 | ADMIN_PASSWORD: TotallySecurePassword 14 | # If the parameter API_REVERSE_PROXY is empty, the default request URL is https://chat.openai.com/backend-api/conversation, and the PUID is equired. 15 | PUID: xxx 16 | -------------------------------------------------------------------------------- /docs/Docker_CN.md: -------------------------------------------------------------------------------- 1 | # 使用阿里源实现Docker安装 2 | 3 | 移除旧的 4 | 5 | ```yum remove -y docker docker-common docker-selinux docker-engine``` 6 | 7 | 安装依赖 8 | 9 | ```yum install -y yum-utils device-mapper-persistent-data lvm2``` 10 | 11 | 配置Docker安装源(阿里) 12 | 13 | ```yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo``` 14 | 15 | 检查可用的Docker-CE版本 16 | 17 | ```yum list docker-ce --showduplicates | sort -r``` 18 | 19 | 安装Docker-CE 20 | 21 | ```yum -y install docker-ce``` 22 | 23 | 运行Docker(默认不运行) 24 | 25 | ```systemctl start docker``` 26 | 27 | 配置开机启动Docker 28 | 29 | ```systemctl enable docker``` 30 | 31 | # 使用官方二进制包安装Docker-Compase 32 | 33 | 下载 Docker-Compose 的二进制文件 34 | ```sudo curl -L "https://github.com/docker/compose/releases/download/v2.18.1/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose``` 35 | 36 | 添加可执行权限 37 | 38 | ```sudo chmod +x /usr/local/bin/docker-compose``` 39 | 40 | 验证 Docker-Compose 是否安装成功 41 | 42 | ```docker-compose --version``` 43 | 44 | 启动容器 45 | 46 | ```docker-compose up -d``` 47 | 48 | 关闭容器 49 | 50 | ```docker-compose down``` 51 | 52 | 查看容器(如果启动了这里没有说明启动失败) 53 | 54 | ```docker ps``` 55 | 56 | # ChatGPT-TO-API的Docker-Compase文件 57 | 58 | ``` 59 | ports: 60 | - '31480:31480' 61 | environment: 62 | SERVER_HOST: 0.0.0.0 63 | SERVER_PORT: 31480 64 | ADMIN_PASSWORD: TotallySecurePassword 65 | # Reverse Proxy - Available on accessToken 66 | #API_REVERSE_PROXY: https://bypass.churchless.tech/api/conversation 67 | #API_REVERSE_PROXY: https://ai.fakeopen.com/api/conversation 68 | PUID: user-7J4tdvHySlcilVgjFIrAtK1k 69 | 70 | ``` 71 | 72 | - 这里的ports,左边是外部端口,用于外部访问。右边的Docker端口,需要匹配下面程序设置的监听Port。 73 | - 如果参数`API_REVERSE_PROXY`为空,则默认的请求URL为`https://chat.openai.com/backend-api/conversation`,并且需要提供PUID。PUID的获取参考 [README_CN.md](../README_CN.md) 74 | - 这个密码需要自定义,我们构建请求的时候需要它来鉴权。默认是```TotallySecurePassword``` 75 | 76 | -------------------------------------------------------------------------------- /docs/GUIDE_CN.md: -------------------------------------------------------------------------------- 1 | # 中文指导手册 2 | 3 | 本中文手册由 [@BlueSkyXN](https://github.com/BlueSkyXN) 编写 4 | 5 | 6 | [中文文档(Chinese Docs)](../README_CN.md) 7 | [英文文档(English Docs)](../README.md) 8 | 9 | # 基本配置 10 | 11 | 有关docker的指导请阅读 [DOCKER中文手册](Docker_CN.md) 12 | 13 | 有关Token的指导请阅读 [TOKEN中文手册](TOKEN_CN.md) 14 | 15 | ## Docker-Compose配置 16 | 17 | ``` 18 | version: '3' 19 | 20 | services: 21 | app: 22 | image: acheong08/chatgpt-to-api 23 | container_name: chatgpttoapi 24 | restart: unless-stopped 25 | ports: 26 | - '10080:10080' 27 | environment: 28 | SERVER_HOST: 0.0.0.0 29 | SERVER_PORT: 10080 30 | ADMIN_PASSWORD: TotallySecurePassword 31 | API_REVERSE_PROXY: https://ai.fakeopen.com/api/conversation 32 | PUID: user-X 33 | 34 | ``` 35 | - ports 左边是外部端口,右边是内部端口,内部端口要和下面环境变量的Server port一致。 36 | - Server host/port:监听配置,默认0000监听某一端口。 37 | - ADMIN_PASSWORD:管理员密码,HTTP请求时候需要验证。 38 | - API_REVERSE_PROXY:接口的反向代理,具体介绍请看下文的后端代理介绍部分。 39 | - PUID: user-X,请看[中文文档(Chinese Docs)](../README_CN.md) 的介绍 40 | 41 | 其他可以不需要设置,包括预设的AccessToken和代理表、HTTP/S5代理。 42 | 43 | 44 | # 后端代理 45 | 目前使用PUID+官网URL的方式不是很可靠,建议使用第三方程序或者网站绕过这个WAF限制。 46 | 47 | 48 | ## 公共代理 49 | 温馨提醒,由于OpenAI用的强力CloudFlareWAF,所以7层转发是无效的(不过4层在浏览器还是可以的) 50 | 51 | 目前根据几个大项目的介绍,我找到了这个介绍页 https://github.com/transitive-bullshit/chatgpt-api#reverse-proxy 52 | 最后得知主要是这两个 53 | 54 | | Reverse Proxy URL | Author | Rate Limits | Last Checked | 55 | |-----------------------------------------------|--------------|--------------------------------|--------------| 56 | | https://ai.fakeopen.com/api/conversation | @pengzhile | 5 req / 10 seconds by IP | 4/18/2023 | 57 | | https://api.pawan.krd/backend-api/conversation | @PawanOsman | 50 req / 15 seconds (~3 r/s) | 3/23/2023 | 58 | 59 | 60 | ## 自建方案 61 | 62 | 我经过测试,发现Pandora的API不行,原因可能是发起对话后的返回值会一次性返回一堆信息导致提取失败。不过我亲测GO-ChatGPT-API是可以的。 63 | 64 | GO-ChatGPT-API项目 https://github.com/linweiyuan/go-chatgpt-api 65 | 66 | 我是注释掉 ##- GO_CHATGPT_API_PROXY= 的环境变量、换个外部端口后用Docker-Compose启动即可。然后不需要对这个代理接口做其他操作,包括登录。 67 | 68 | 搭建好之后最好测试下基本调用能不能用,下面是一个示例,你需要根据实际情况修改。 69 | 70 | 71 | ``` 72 | curl http://127.0.0.1:8080/chatgpt/backend-api/conversation \ 73 | -H "Content-Type: application/json" \ 74 | -d '{ 75 | "model": "gpt-3.5-turbo", 76 | "messages": [{"role": "user", "content": "Say this is a test!"}], 77 | "temperature": 0.7 78 | }' 79 | 80 | ``` 81 | 82 | 如果得到缺少认证的提示比如 ```{"errorMessage":"Missing accessToken."}``` 就说明已经正常跑了 83 | 84 | # 用例 85 | 86 | ## 基本提问 87 | ``` 88 | curl http://127.0.0.1:10080/v1/chat/completions \ 89 | -d '{ 90 | "model": "text-davinci-002-render-sha", 91 | "messages": [{"role": "user", "content": "你是什么模型,是GPT3.5吗"}] 92 | }' 93 | ``` 94 | 95 | 参考回复如下 96 | 97 | ``` 98 | {"id":"chatcmpl-QXlha2FBbmROaXhpZUFyZUF3XXXXXX","object":"chat.completion","created":0,"model":"gpt-3.5-turbo-0301","usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0},"choices":[{"index":0,"message":{"role":"assistant","content":"是的,我是一个基于GPT-3.5架构的语言模型,被称为ChatGPT。我可以回答各种问题,提供信息和进行对话。尽管我会尽力提供准确和有用的回答,但请记住,我并不是完美的,有时候可能会出现错误或者误导性的答案。"},"finish_reason":null}]} 99 | ``` 100 | 101 | 请注意无论什么模型提问都只会显示为模型是GPT3.5T-0301。你在网页版看不到消息记录(可能是删除了),Chat不支持并发提问,你需要token轮询。 102 | 103 | ## 提交Token 104 | 通过文件提交 105 | 106 | ``` 107 | curl -X PATCH \ 108 | -H "Content-Type: application/json" \ 109 | -H "Authorization: TotallySecurePassword" \ 110 | -d "@/root/access_tokens.json" \ 111 | http://127.0.0.1:10080/admin/tokens 112 | 113 | ``` 114 | 115 | 直接提交 116 | 117 | ``` 118 | curl -X PATCH \ 119 | -H "Content-Type: application/json" \ 120 | -H "Authorization: TotallySecurePassword" \ 121 | -d '["eyJhbXXX"]' \ 122 | http://127.0.0.1:10080/admin/tokens 123 | ``` 124 | 125 | 要清理Token直接停用删除Docker容器后重新构建运行容器即可 126 | -------------------------------------------------------------------------------- /docs/TOKEN_CN.md: -------------------------------------------------------------------------------- 1 | # 获取Token 2 | --- 3 | # 参考Pandora项目的作者指导 4 | 5 | https://github.com/pengzhile/pandora 6 | 7 | 获取Token的技术原理 https://zhile.io/2023/05/19/how-to-get-chatgpt-access-token-via-pkce.html 8 | 9 | ## 第三方接口获取Token 10 | http://ai.fakeopen.com/auth 11 | 12 | 你需要在这个新的网站的指导下安装浏览器插件,官方说明的有效期是14天。支持谷歌微软等第三方登录。(我谷歌注册的OpenAI就可以用这个) 13 | 14 | ## 官网获取 Token 15 | https://chat.openai.com/api/auth/session 16 | 17 | 打开后是个JSON,你需要先登录官方的ChatGPT网页版。里面有一个参数就是AccessToken。 18 | 19 | # 参考go-chatgpt-api项目的作者指导 20 | https://github.com/linweiyuan/go-chatgpt-api 21 | 22 | ChatGPT 登录(返回 accessToken)(目前仅支持 ChatGPT 账号,谷歌或微软账号没有测试) 23 | 24 | ```POST /chatgpt/login``` -------------------------------------------------------------------------------- /docs/admin.md: -------------------------------------------------------------------------------- 1 | # API Documentation: 2 | ## openaiHandler: 3 | 4 | This API endpoint receives a POST request with a JSON body that contains OpenAI credentials. The API updates the environment variables "OPENAI_EMAIL" and "OPENAI_PASSWORD" with the provided credentials. 5 | 6 | HTTP method: PATCH 7 | 8 | Endpoint: /openai 9 | 10 | Request body: 11 | 12 | ```json 13 | { 14 | "OpenAI_Email": "string", 15 | "OpenAI_Password": "string" 16 | } 17 | ``` 18 | 19 | Response status codes: 20 | - 200 OK: The OpenAI credentials were successfully updated. 21 | - 400 Bad Request: The JSON in the request body is invalid. 22 | 23 | ## passwordHandler: 24 | 25 | This API endpoint receives a POST request with a JSON body that contains a new password. The API updates the global variable "ADMIN_PASSWORD" and the environment variable "ADMIN_PASSWORD" with the new password. 26 | 27 | HTTP method: PATCH 28 | 29 | Endpoint: /password 30 | 31 | Request body: 32 | 33 | ```json 34 | { 35 | "password": "string" 36 | } 37 | ``` 38 | 39 | Response status codes: 40 | - 200 OK: The password was successfully updated. 41 | - 400 Bad Request: The password is missing or not provided in the request body. 42 | 43 | ## puidHandler: 44 | 45 | This API endpoint receives a POST request with a JSON body that contains a new PUID (Personal User ID). The API updates the environment variable "PUID" with the new PUID. 46 | 47 | HTTP method: PATCH 48 | 49 | Endpoint: /puid 50 | 51 | Request body: 52 | 53 | ```json 54 | { 55 | "puid": "string" 56 | } 57 | ``` 58 | 59 | Response status codes: 60 | - 200 OK: The PUID was successfully updated. 61 | - 400 Bad Request: The PUID is missing or not provided in the request body. 62 | 63 | ## tokensHandler: 64 | 65 | This API endpoint receives a POST request with a JSON body that contains an array of request tokens. The API updates the value of the global variable "ACCESS_TOKENS" with a new access token generated from the request tokens provided in the request body. 66 | 67 | HTTP method: PATCH 68 | 69 | Endpoint: /tokens 70 | 71 | Request body: 72 | 73 | ```json 74 | [ 75 | "string", "..." 76 | ] 77 | ``` 78 | 79 | Response status codes: 80 | - 200 OK: The ACCESS_TOKENS variable was successfully updated. 81 | - 400 Bad Request: The request tokens are missing or not provided in the request body. 82 | 83 | 84 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module freechatgpt 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/acheong08/OpenAIAuth v0.0.0-20230719092354-c8cd4e19491b 7 | github.com/acheong08/endless v0.0.0-20230615162514-90545c7793fd 8 | github.com/acheong08/funcaptcha v1.9.3-0.20230803133445-f4d081d60ac7 9 | github.com/bogdanfinn/fhttp v0.5.23 10 | github.com/bogdanfinn/tls-client v1.4.0 11 | github.com/gin-gonic/gin v1.9.1 12 | github.com/go-resty/resty/v2 v2.7.0 13 | github.com/google/uuid v1.3.0 14 | github.com/joho/godotenv v1.5.1 15 | github.com/tidwall/gjson v1.14.4 16 | k8s.io/apimachinery v0.27.2 17 | ) 18 | 19 | require ( 20 | github.com/andybalholm/brotli v1.0.5 // indirect 21 | github.com/bogdanfinn/utls v1.5.16 // indirect 22 | github.com/bytedance/sonic v1.9.1 // indirect 23 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 24 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 25 | github.com/gin-contrib/sse v0.1.0 // indirect 26 | github.com/go-playground/locales v0.14.1 // indirect 27 | github.com/go-playground/universal-translator v0.18.1 // indirect 28 | github.com/go-playground/validator/v10 v10.14.1 // indirect 29 | github.com/goccy/go-json v0.10.2 // indirect 30 | github.com/json-iterator/go v1.1.12 // indirect 31 | github.com/klauspost/compress v1.16.6 // indirect 32 | github.com/klauspost/cpuid/v2 v2.2.5 // indirect 33 | github.com/leodido/go-urn v1.2.4 // indirect 34 | github.com/mattn/go-isatty v0.0.19 // indirect 35 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 36 | github.com/modern-go/reflect2 v1.0.2 // indirect 37 | github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4c // indirect 38 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 39 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 // indirect 40 | github.com/tidwall/match v1.1.1 // indirect 41 | github.com/tidwall/pretty v1.2.0 // indirect 42 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 43 | github.com/ugorji/go/codec v1.2.11 // indirect 44 | golang.org/x/arch v0.3.0 // indirect 45 | golang.org/x/crypto v0.10.0 // indirect 46 | golang.org/x/net v0.11.0 // indirect 47 | golang.org/x/sys v0.9.0 // indirect 48 | golang.org/x/text v0.10.0 // indirect 49 | google.golang.org/protobuf v1.30.0 // indirect 50 | gopkg.in/yaml.v3 v3.0.1 // indirect 51 | ) 52 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/acheong08/OpenAIAuth v0.0.0-20230719092354-c8cd4e19491b h1:Jg+UV8bO5dXaisG/loeYFewewqmUJjjHGSxB9XZVqoc= 2 | github.com/acheong08/OpenAIAuth v0.0.0-20230719092354-c8cd4e19491b/go.mod h1:bkiXtklBFVpWHyWTys6Zhqb521i/gtT8cIUKWVx2m/M= 3 | github.com/acheong08/endless v0.0.0-20230615162514-90545c7793fd h1:oIpfrRhD7Jus41dotbK+SQjWSFRnf1cLZUYCZpF/o/4= 4 | github.com/acheong08/endless v0.0.0-20230615162514-90545c7793fd/go.mod h1:0yO7neMeJLvKk/B/fq5votDY8rByrOPDubpvU+6saKo= 5 | github.com/acheong08/funcaptcha v1.9.3-0.20230803133445-f4d081d60ac7 h1:lnvGKeyLfHKZOeYhUFRZE/GJjCAV1k3jW50e1UsZcVk= 6 | github.com/acheong08/funcaptcha v1.9.3-0.20230803133445-f4d081d60ac7/go.mod h1:azmXv2Mfbw5eBOnMw5e1yTMNVyku17klWhGtkHwm7PY= 7 | github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= 8 | github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 9 | github.com/bogdanfinn/fhttp v0.5.23 h1:4Xb5OjYArB8GpnUw4A4r5jmt8UW0/Cvey3R9nS2dC9U= 10 | github.com/bogdanfinn/fhttp v0.5.23/go.mod h1:brqi5woc5eSCVHdKYBV8aZLbO7HGqpwyDLeXW+fT18I= 11 | github.com/bogdanfinn/tls-client v1.4.0 h1:ptZmkvVyRTjMFPc3Kevholf+ioePkCM5oj3qkOmOuoM= 12 | github.com/bogdanfinn/tls-client v1.4.0/go.mod h1:lgtqsHjoJYQMPz6H08bc8t30bmUaYnVjwtfVEzMGJDs= 13 | github.com/bogdanfinn/utls v1.5.16 h1:NhhWkegEcYETBMj9nvgO4lwvc6NcLH+znrXzO3gnw4M= 14 | github.com/bogdanfinn/utls v1.5.16/go.mod h1:mHeRCi69cUiEyVBkKONB1cAbLjRcZnlJbGzttmiuK4o= 15 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 16 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 17 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 18 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 19 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 20 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 21 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 23 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 24 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 25 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 26 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 27 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 28 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 29 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 30 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 31 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 32 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 33 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 34 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 35 | github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= 36 | github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 37 | github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= 38 | github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= 39 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 40 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 41 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 42 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 43 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 44 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 45 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 46 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 47 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 48 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 49 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 50 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 51 | github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk= 52 | github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 53 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 54 | github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= 55 | github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 56 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 57 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 58 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 59 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 60 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 61 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 62 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 63 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 64 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 65 | github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4c h1:4RYnE0ISVwRxm9Dfo7utw1dh0kdRDEmVYq2MFVLy5zI= 66 | github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4c/go.mod h1:DvuJJ/w1Y59rG8UTDxsMk5U+UJXJwuvUgbiJSm9yhX8= 67 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 68 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 69 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 70 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 71 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 72 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 73 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 74 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 75 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 76 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 77 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 78 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 79 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 80 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= 81 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 82 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 h1:YqAladjX7xpA6BM04leXMWAEjS0mTZ5kUU9KRBriQJc= 83 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5/go.mod h1:2JjD2zLQYH5HO74y5+aE3remJQvl6q4Sn6aWA2wD1Ng= 84 | github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= 85 | github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 86 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 87 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 88 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= 89 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 90 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 91 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 92 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 93 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 94 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 95 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 96 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 97 | golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= 98 | golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= 99 | golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 100 | golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= 101 | golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= 102 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 103 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 104 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 105 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 106 | golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= 107 | golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 108 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 109 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 110 | golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= 111 | golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 112 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 113 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 114 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 115 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 116 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 117 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 118 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 119 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 120 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 121 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 122 | k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg= 123 | k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= 124 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 125 | -------------------------------------------------------------------------------- /handlers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | chatgpt_request_converter "freechatgpt/conversion/requests/chatgpt" 5 | chatgpt "freechatgpt/internal/chatgpt" 6 | "freechatgpt/internal/tokens" 7 | official_types "freechatgpt/typings/official" 8 | "os" 9 | "strings" 10 | 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func openaiHandler(c *gin.Context) { 15 | var authorizations struct { 16 | OpenAI_Email string `json:"openai_email"` 17 | OpenAI_Password string `json:"openai_password"` 18 | Official_API_Key string `json:"official_api_key"` 19 | } 20 | err := c.BindJSON(&authorizations) 21 | if err != nil { 22 | c.JSON(400, gin.H{"error": "JSON invalid"}) 23 | } 24 | if authorizations.OpenAI_Email != "" && authorizations.OpenAI_Password != "" { 25 | os.Setenv("OPENAI_EMAIL", authorizations.OpenAI_Email) 26 | os.Setenv("OPENAI_PASSWORD", authorizations.OpenAI_Password) 27 | } 28 | if authorizations.Official_API_Key != "" { 29 | os.Setenv("OFFICIAL_API_KEY", authorizations.Official_API_Key) 30 | } 31 | if authorizations.OpenAI_Email == "" && authorizations.OpenAI_Password == "" && authorizations.Official_API_Key == "" { 32 | c.JSON(400, gin.H{"error": "JSON invalid"}) 33 | return 34 | } 35 | c.String(200, "OpenAI credentials updated") 36 | } 37 | 38 | func passwordHandler(c *gin.Context) { 39 | // Get the password from the request (json) and update the password 40 | type password_struct struct { 41 | Password string `json:"password"` 42 | } 43 | var password password_struct 44 | err := c.BindJSON(&password) 45 | if err != nil { 46 | c.String(400, "password not provided") 47 | return 48 | } 49 | ADMIN_PASSWORD = password.Password 50 | // Set environment variable 51 | os.Setenv("ADMIN_PASSWORD", ADMIN_PASSWORD) 52 | c.String(200, "password updated") 53 | } 54 | 55 | func puidHandler(c *gin.Context) { 56 | // Get the password from the request (json) and update the password 57 | type puid_struct struct { 58 | PUID string `json:"puid"` 59 | } 60 | var puid puid_struct 61 | err := c.BindJSON(&puid) 62 | if err != nil { 63 | c.String(400, "puid not provided") 64 | return 65 | } 66 | // Set environment variable 67 | os.Setenv("PUID", puid.PUID) 68 | c.String(200, "puid updated") 69 | } 70 | 71 | func tokensHandler(c *gin.Context) { 72 | // Get the request_tokens from the request (json) and update the request_tokens 73 | var request_tokens []tokens.Secret 74 | err := c.BindJSON(&request_tokens) 75 | if err != nil { 76 | c.String(400, "tokens not provided") 77 | return 78 | } 79 | ACCESS_TOKENS = tokens.NewAccessToken(request_tokens, true) 80 | c.String(200, "tokens updated") 81 | } 82 | func optionsHandler(c *gin.Context) { 83 | // Set headers for CORS 84 | c.Header("Access-Control-Allow-Origin", "*") 85 | c.Header("Access-Control-Allow-Methods", "POST") 86 | c.Header("Access-Control-Allow-Headers", "*") 87 | c.JSON(200, gin.H{ 88 | "message": "pong", 89 | }) 90 | } 91 | func nightmare(c *gin.Context) { 92 | var original_request official_types.APIRequest 93 | err := c.BindJSON(&original_request) 94 | if err != nil { 95 | c.JSON(400, gin.H{"error": gin.H{ 96 | "message": "Request must be proper JSON", 97 | "type": "invalid_request_error", 98 | "param": nil, 99 | "code": err.Error(), 100 | }}) 101 | return 102 | } 103 | 104 | authHeader := c.GetHeader("Authorization") 105 | token, puid := ACCESS_TOKENS.GetSecret() 106 | if authHeader != "" { 107 | customAccessToken := strings.Replace(authHeader, "Bearer ", "", 1) 108 | // Check if customAccessToken starts with sk- 109 | if strings.HasPrefix(customAccessToken, "eyJhbGciOiJSUzI1NiI") { 110 | token = customAccessToken 111 | } 112 | } 113 | var proxy_url string 114 | if len(proxies) == 0 { 115 | proxy_url = "" 116 | } else { 117 | proxy_url = proxies[0] 118 | // Push used proxy to the back of the list 119 | proxies = append(proxies[1:], proxies[0]) 120 | } 121 | 122 | // Convert the chat request to a ChatGPT request 123 | translated_request := chatgpt_request_converter.ConvertAPIRequest(original_request, puid, proxy_url) 124 | 125 | response, err := chatgpt.POSTconversation(translated_request, token, puid, proxy_url) 126 | if err != nil { 127 | c.JSON(500, gin.H{ 128 | "error": "error sending request", 129 | }) 130 | return 131 | } 132 | defer response.Body.Close() 133 | if chatgpt.Handle_request_error(c, response) { 134 | return 135 | } 136 | var full_response string 137 | for i := 3; i > 0; i-- { 138 | var continue_info *chatgpt.ContinueInfo 139 | var response_part string 140 | response_part, continue_info = chatgpt.Handler(c, response, token, translated_request, original_request.Stream) 141 | full_response += response_part 142 | if continue_info == nil { 143 | break 144 | } 145 | println("Continuing conversation") 146 | translated_request.Messages = nil 147 | translated_request.Action = "continue" 148 | translated_request.ConversationID = continue_info.ConversationID 149 | translated_request.ParentMessageID = continue_info.ParentID 150 | response, err = chatgpt.POSTconversation(translated_request, token, puid, proxy_url) 151 | if err != nil { 152 | c.JSON(500, gin.H{ 153 | "error": "error sending request", 154 | }) 155 | return 156 | } 157 | defer response.Body.Close() 158 | if chatgpt.Handle_request_error(c, response) { 159 | return 160 | } 161 | } 162 | if !original_request.Stream { 163 | c.JSON(200, official_types.NewChatCompletion(full_response)) 164 | } else { 165 | c.String(200, "data: [DONE]\n\n") 166 | } 167 | 168 | } 169 | 170 | func engines_handler(c *gin.Context) { 171 | resp, status, err := chatgpt.GETengines() 172 | if err != nil { 173 | c.JSON(500, gin.H{ 174 | "error": "error sending request", 175 | }) 176 | return 177 | } 178 | c.JSON(status, resp) 179 | } 180 | -------------------------------------------------------------------------------- /internal/bard/lib.go: -------------------------------------------------------------------------------- 1 | package bard 2 | 3 | // By @mosajjal at https://github.com/mosajjal/bard-cli/blob/main/bard/bard.go 4 | import ( 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "os" 10 | "regexp" 11 | "strings" 12 | "time" 13 | 14 | "github.com/tidwall/gjson" 15 | "k8s.io/apimachinery/pkg/util/rand" 16 | 17 | nhttp "net/http" 18 | 19 | http "github.com/bogdanfinn/fhttp" 20 | tls_client "github.com/bogdanfinn/tls-client" 21 | resty "github.com/go-resty/resty/v2" 22 | ) 23 | 24 | var ( 25 | jar = tls_client.NewCookieJar() 26 | options = []tls_client.HttpClientOption{ 27 | tls_client.WithTimeoutSeconds(360), 28 | tls_client.WithClientProfile(tls_client.Safari_IOS_15_5), 29 | tls_client.WithNotFollowRedirects(), 30 | tls_client.WithCookieJar(jar), // create cookieJar instance and pass it as argument 31 | // Disable SSL verification 32 | tls_client.WithInsecureSkipVerify(), 33 | } 34 | client, _ = tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...) 35 | http_proxy = os.Getenv("http_proxy") 36 | resty_client = resty.New() 37 | ) 38 | 39 | var headers map[string]string = map[string]string{ 40 | "Host": "bard.google.com", 41 | "X-Same-Domain": "1", 42 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.4472.114 Safari/537.36", 43 | "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", 44 | "Origin": "https://bard.google.com", 45 | "Referer": "https://bard.google.com/", 46 | } 47 | 48 | func init() { 49 | if http_proxy != "" { 50 | client.SetProxy(http_proxy) 51 | } 52 | } 53 | 54 | const bardURL string = "https://bard.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate" 55 | 56 | type Answer struct { 57 | Content string `json:"content"` 58 | // FactualityQueries []string `json:"factualityQueries"` 59 | Choices []string `json:"choices"` 60 | } 61 | 62 | // Bard is the main struct for the Bard AI 63 | type Bard struct { 64 | Cookie string 65 | ChoiceID string 66 | ConversationID string 67 | ResponseID string 68 | SNlM0e string 69 | LastInteractionTime time.Time 70 | } 71 | 72 | // New creates a new Bard AI instance. Cookie is the __Secure-1PSID cookie from Google 73 | func New(cookie string) (*Bard, error) { 74 | b := &Bard{ 75 | Cookie: cookie, 76 | } 77 | err := b.getSNlM0e() 78 | return b, err 79 | } 80 | 81 | func (b *Bard) getSNlM0e() error { 82 | req, _ := http.NewRequest("GET", "https://bard.google.com/", nil) 83 | for k, v := range headers { 84 | req.Header.Add(k, v) 85 | } 86 | req.AddCookie(&http.Cookie{ 87 | Name: "__Secure-1PSID", 88 | Value: b.Cookie, 89 | }) 90 | // in response text, the value shows. in python: 91 | r := regexp.MustCompile(`SNlM0e\":\"(.*?)\"`) 92 | resp, err := client.Do(req) 93 | if err != nil { 94 | return err 95 | } 96 | defer resp.Body.Close() 97 | body, _ := io.ReadAll(resp.Body) 98 | tmpValues := r.FindStringSubmatch(string(body)) 99 | if len(tmpValues) < 2 { 100 | return fmt.Errorf("failed to find snim0e value. possibly misconfigured cookies?") 101 | } 102 | b.SNlM0e = tmpValues[1] 103 | return nil 104 | } 105 | 106 | // Ask generates a Bard AI response and returns it to the user 107 | func (b *Bard) Ask(prompt string) (*Answer, error) { 108 | b.LastInteractionTime = time.Now() 109 | 110 | // req paramters for the actual request 111 | reqParams := map[string]string{ 112 | "bl": "boq_assistant-bard-web-server_20230606.12_p0", 113 | "_reqid": fmt.Sprintf("%d", rand.IntnRange(100000, 999999)), 114 | "rt": "c", 115 | } 116 | 117 | req := fmt.Sprintf(`[null, "[[\"%s\"], null, [\"%s\", \"%s\", \"%s\"]]"]`, 118 | //prompt, b.answer.ConversationID, b.answer.ResponseID, b.answer.ChoiceID) 119 | prompt, b.ConversationID, b.ResponseID, b.ChoiceID) 120 | 121 | reqData := map[string]string{ 122 | "f.req": string(req), 123 | "at": b.SNlM0e, 124 | } 125 | resty_client.SetHeaders(headers) 126 | resty_client.SetCookie(&nhttp.Cookie{ 127 | Name: "__Secure-1PSID", 128 | Value: b.Cookie, 129 | }) 130 | resty_client.SetBaseURL(bardURL) 131 | resty_client.SetTimeout(60 * time.Second) 132 | resty_client.SetFormData(reqData) 133 | resty_client.SetQueryParams(reqParams) 134 | resty_client.SetDoNotParseResponse(true) 135 | resp, err := resty_client.R().Post("") 136 | if err != nil { 137 | return nil, err 138 | } 139 | if resp.StatusCode() != 200 { 140 | // curl, _ := http2curl.GetCurlCommand(resp.Request.EnableTrace().RawRequest) 141 | // fmt.Println(curl) 142 | return nil, fmt.Errorf("status code is not 200: %d", resp.StatusCode()) 143 | } 144 | 145 | // this is the Go version 146 | buf := new(bytes.Buffer) 147 | _, _ = buf.ReadFrom(resp.RawResponse.Body) 148 | 149 | respLines := strings.Split(buf.String(), "\n") 150 | respJSON := respLines[3] 151 | 152 | var fullRes [][]interface{} 153 | err = json.Unmarshal([]byte(respJSON), &fullRes) 154 | if err != nil { 155 | return nil, err 156 | } 157 | 158 | // get the main answer 159 | res, ok := fullRes[0][2].(string) 160 | if !ok { 161 | return nil, fmt.Errorf("failed to get answer from bard") 162 | } 163 | 164 | answer := Answer{} 165 | 166 | answer.Content = gjson.Get(res, "0.0").String() 167 | b.ConversationID = gjson.Get(res, "1.0").String() 168 | b.ResponseID = gjson.Get(res, "1.1").String() 169 | choices := gjson.Get(res, "4").Array() 170 | answer.Choices = make([]string, len(choices)) 171 | for i, choice := range choices { 172 | answer.Choices[i] = choice.Array()[0].String() 173 | } 174 | b.ChoiceID = choices[0].Array()[0].String() 175 | 176 | return &answer, nil 177 | } 178 | -------------------------------------------------------------------------------- /internal/bard/request.go: -------------------------------------------------------------------------------- 1 | package bard 2 | 3 | import "time" 4 | 5 | type BardCache struct { 6 | Bards map[string]*Bard 7 | } 8 | 9 | var cache *BardCache 10 | 11 | func init() { 12 | cache = &BardCache{ 13 | Bards: make(map[string]*Bard), 14 | } 15 | go func() { 16 | for { 17 | GarbageCollectCache(cache) 18 | time.Sleep(time.Minute) 19 | } 20 | }() 21 | } 22 | -------------------------------------------------------------------------------- /internal/bard/utilities.go: -------------------------------------------------------------------------------- 1 | package bard 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "time" 7 | ) 8 | 9 | func HashConversation(conversation []string) string { 10 | hash := md5.New() 11 | for _, message := range conversation { 12 | hash.Write([]byte(message)) 13 | } 14 | return hex.EncodeToString(hash.Sum(nil)) 15 | } 16 | 17 | func GarbageCollectCache(cache *BardCache) { 18 | for k, v := range cache.Bards { 19 | if time.Since(v.LastInteractionTime) > time.Minute*5 { 20 | delete(cache.Bards, k) 21 | } 22 | } 23 | } 24 | 25 | func UpdateBardHash(old_hash, hash string) { 26 | if _, ok := cache.Bards[old_hash]; ok { 27 | cache.Bards[hash] = cache.Bards[old_hash] 28 | delete(cache.Bards, old_hash) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /internal/chatgpt/request.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "freechatgpt/typings" 8 | chatgpt_types "freechatgpt/typings/chatgpt" 9 | "io" 10 | "os" 11 | "strings" 12 | 13 | arkose "github.com/acheong08/funcaptcha" 14 | http "github.com/bogdanfinn/fhttp" 15 | tls_client "github.com/bogdanfinn/tls-client" 16 | "github.com/gin-gonic/gin" 17 | 18 | chatgpt_response_converter "freechatgpt/conversion/response/chatgpt" 19 | 20 | // chatgpt "freechatgpt/internal/chatgpt" 21 | 22 | official_types "freechatgpt/typings/official" 23 | ) 24 | 25 | var ( 26 | jar = tls_client.NewCookieJar() 27 | options = []tls_client.HttpClientOption{ 28 | tls_client.WithTimeoutSeconds(360), 29 | tls_client.WithClientProfile(tls_client.Okhttp4Android13), 30 | tls_client.WithNotFollowRedirects(), 31 | tls_client.WithCookieJar(jar), // create cookieJar instance and pass it as argument 32 | // Disable SSL verification 33 | tls_client.WithInsecureSkipVerify(), 34 | } 35 | client, _ = tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...) 36 | API_REVERSE_PROXY = os.Getenv("API_REVERSE_PROXY") 37 | ) 38 | 39 | func init() { 40 | arkose.SetTLSClient(&client) 41 | } 42 | 43 | func POSTconversation(message chatgpt_types.ChatGPTRequest, access_token string, puid string, proxy string) (*http.Response, error) { 44 | if proxy != "" { 45 | client.SetProxy(proxy) 46 | } 47 | 48 | apiUrl := "https://chat.openai.com/backend-api/conversation" 49 | if API_REVERSE_PROXY != "" { 50 | apiUrl = API_REVERSE_PROXY 51 | } 52 | 53 | // JSONify the body and add it to the request 54 | body_json, err := json.Marshal(message) 55 | if err != nil { 56 | return &http.Response{}, err 57 | } 58 | 59 | request, err := http.NewRequest(http.MethodPost, apiUrl, bytes.NewBuffer(body_json)) 60 | if err != nil { 61 | return &http.Response{}, err 62 | } 63 | // Clear cookies 64 | if puid != "" { 65 | request.Header.Set("Cookie", "_puid="+puid+";") 66 | } 67 | request.Header.Set("Content-Type", "application/json") 68 | request.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36") 69 | request.Header.Set("Accept", "*/*") 70 | if access_token != "" { 71 | request.Header.Set("Authorization", "Bearer "+access_token) 72 | } 73 | if err != nil { 74 | return &http.Response{}, err 75 | } 76 | response, err := client.Do(request) 77 | return response, err 78 | } 79 | 80 | // Returns whether an error was handled 81 | func Handle_request_error(c *gin.Context, response *http.Response) bool { 82 | if response.StatusCode != 200 { 83 | // Try read response body as JSON 84 | var error_response map[string]interface{} 85 | err := json.NewDecoder(response.Body).Decode(&error_response) 86 | if err != nil { 87 | // Read response body 88 | body, _ := io.ReadAll(response.Body) 89 | c.JSON(500, gin.H{"error": gin.H{ 90 | "message": "Unknown error", 91 | "type": "internal_server_error", 92 | "param": nil, 93 | "code": "500", 94 | "details": string(body), 95 | }}) 96 | return true 97 | } 98 | c.JSON(response.StatusCode, gin.H{"error": gin.H{ 99 | "message": error_response["detail"], 100 | "type": response.Status, 101 | "param": nil, 102 | "code": "error", 103 | }}) 104 | return true 105 | } 106 | return false 107 | } 108 | 109 | type ContinueInfo struct { 110 | ConversationID string `json:"conversation_id"` 111 | ParentID string `json:"parent_id"` 112 | } 113 | 114 | func Handler(c *gin.Context, response *http.Response, token string, translated_request chatgpt_types.ChatGPTRequest, stream bool) (string, *ContinueInfo) { 115 | max_tokens := false 116 | 117 | // Create a bufio.Reader from the response body 118 | reader := bufio.NewReader(response.Body) 119 | 120 | // Read the response byte by byte until a newline character is encountered 121 | if stream { 122 | // Response content type is text/event-stream 123 | c.Header("Content-Type", "text/event-stream") 124 | } else { 125 | // Response content type is application/json 126 | c.Header("Content-Type", "application/json") 127 | } 128 | var finish_reason string 129 | var previous_text typings.StringStruct 130 | var original_response chatgpt_types.ChatGPTResponse 131 | var isRole = true 132 | for { 133 | line, err := reader.ReadString('\n') 134 | if err != nil { 135 | if err == io.EOF { 136 | break 137 | } 138 | return "", nil 139 | } 140 | if len(line) < 6 { 141 | continue 142 | } 143 | // Remove "data: " from the beginning of the line 144 | line = line[6:] 145 | // Check if line starts with [DONE] 146 | if !strings.HasPrefix(line, "[DONE]") { 147 | // Parse the line as JSON 148 | 149 | err = json.Unmarshal([]byte(line), &original_response) 150 | if err != nil { 151 | continue 152 | } 153 | if original_response.Error != nil { 154 | c.JSON(500, gin.H{"error": original_response.Error}) 155 | return "", nil 156 | } 157 | if original_response.Message.Author.Role != "assistant" || original_response.Message.Content.Parts == nil { 158 | continue 159 | } 160 | if original_response.Message.Metadata.MessageType != "next" && original_response.Message.Metadata.MessageType != "continue" || original_response.Message.EndTurn != nil { 161 | continue 162 | } 163 | response_string := chatgpt_response_converter.ConvertToString(&original_response, &previous_text, isRole) 164 | isRole = false 165 | if stream { 166 | _, err = c.Writer.WriteString(response_string) 167 | if err != nil { 168 | return "", nil 169 | } 170 | } 171 | // Flush the response writer buffer to ensure that the client receives each line as it's written 172 | c.Writer.Flush() 173 | 174 | if original_response.Message.Metadata.FinishDetails != nil { 175 | if original_response.Message.Metadata.FinishDetails.Type == "max_tokens" { 176 | max_tokens = true 177 | } 178 | finish_reason = original_response.Message.Metadata.FinishDetails.Type 179 | } 180 | 181 | } else { 182 | if stream { 183 | final_line := official_types.StopChunk(finish_reason) 184 | c.Writer.WriteString("data: " + final_line.String() + "\n\n") 185 | } 186 | } 187 | } 188 | if !max_tokens { 189 | return previous_text.Text, nil 190 | } 191 | return previous_text.Text, &ContinueInfo{ 192 | ConversationID: original_response.ConversationID, 193 | ParentID: original_response.Message.ID, 194 | } 195 | } 196 | 197 | func GETengines() (interface{}, int, error) { 198 | url := "https://api.openai.com/v1/models" 199 | req, _ := http.NewRequest("GET", url, nil) 200 | req.Header.Add("Authorization", "Bearer "+os.Getenv("OFFICIAL_API_KEY")) 201 | resp, err := client.Do(req) 202 | if err != nil { 203 | return nil, 0, err 204 | } 205 | defer resp.Body.Close() 206 | var result interface{} 207 | json.NewDecoder(resp.Body).Decode(&result) 208 | return result, resp.StatusCode, nil 209 | } 210 | -------------------------------------------------------------------------------- /internal/tokens/tokens.go: -------------------------------------------------------------------------------- 1 | package tokens 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "sync" 7 | ) 8 | 9 | type Secret struct { 10 | Token string `json:"token"` 11 | PUID string `json:"puid"` 12 | } 13 | type AccessToken struct { 14 | tokens []Secret 15 | lock sync.Mutex 16 | } 17 | 18 | func NewAccessToken(tokens []Secret, save bool) AccessToken { 19 | // Save the tokens to a file 20 | if _, err := os.Stat("access_tokens.json"); os.IsNotExist(err) { 21 | // Create the file 22 | file, err := os.Create("access_tokens.json") 23 | if err != nil { 24 | return AccessToken{} 25 | } 26 | defer file.Close() 27 | } 28 | if save { 29 | saved := Save(tokens) 30 | if saved == false { 31 | return AccessToken{} 32 | } 33 | } 34 | return AccessToken{ 35 | tokens: tokens, 36 | } 37 | } 38 | 39 | func Save(tokens []Secret) bool { 40 | file, err := os.OpenFile("access_tokens.json", os.O_WRONLY|os.O_TRUNC, 0644) 41 | if err != nil { 42 | return false 43 | } 44 | defer file.Close() 45 | encoder := json.NewEncoder(file) 46 | err = encoder.Encode(tokens) 47 | if err != nil { 48 | return false 49 | } 50 | return true 51 | } 52 | 53 | func (a *AccessToken) GetSecret() (string, string) { 54 | a.lock.Lock() 55 | defer a.lock.Unlock() 56 | 57 | if len(a.tokens) == 0 { 58 | return "", "" 59 | } 60 | 61 | secret := a.tokens[0] 62 | a.tokens = append(a.tokens[1:], secret) 63 | return secret.Token, secret.PUID 64 | } 65 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "freechatgpt/internal/tokens" 6 | "os" 7 | "strings" 8 | 9 | "github.com/acheong08/endless" 10 | "github.com/gin-gonic/gin" 11 | "github.com/joho/godotenv" 12 | ) 13 | 14 | var HOST string 15 | var PORT string 16 | var ACCESS_TOKENS tokens.AccessToken 17 | var proxies []string 18 | 19 | func checkProxy() { 20 | // first check for proxies.txt 21 | proxies = []string{} 22 | if _, err := os.Stat("proxies.txt"); err == nil { 23 | // Each line is a proxy, put in proxies array 24 | file, _ := os.Open("proxies.txt") 25 | defer file.Close() 26 | scanner := bufio.NewScanner(file) 27 | for scanner.Scan() { 28 | // Split line by : 29 | proxy := scanner.Text() 30 | proxy_parts := strings.Split(proxy, ":") 31 | if len(proxy_parts) > 1 { 32 | proxies = append(proxies, proxy) 33 | } else { 34 | continue 35 | } 36 | } 37 | } 38 | // if no proxies, then check env http_proxy 39 | if len(proxies) == 0 { 40 | proxy := os.Getenv("http_proxy") 41 | if proxy != "" { 42 | proxies = append(proxies, proxy) 43 | } 44 | } 45 | } 46 | 47 | func init() { 48 | _ = godotenv.Load(".env") 49 | 50 | HOST = os.Getenv("SERVER_HOST") 51 | PORT = os.Getenv("SERVER_PORT") 52 | if PORT == "" { 53 | PORT = os.Getenv("PORT") 54 | } 55 | if HOST == "" { 56 | HOST = "127.0.0.1" 57 | } 58 | if PORT == "" { 59 | PORT = "8080" 60 | } 61 | checkProxy() 62 | readAccounts() 63 | scheduleTokenPUID() 64 | } 65 | func main() { 66 | router := gin.Default() 67 | 68 | router.Use(cors) 69 | 70 | router.GET("/ping", func(c *gin.Context) { 71 | c.JSON(200, gin.H{ 72 | "message": "pong", 73 | }) 74 | }) 75 | 76 | admin_routes := router.Group("/admin") 77 | admin_routes.Use(adminCheck) 78 | 79 | /// Admin routes 80 | admin_routes.PATCH("/password", passwordHandler) 81 | admin_routes.PATCH("/tokens", tokensHandler) 82 | admin_routes.PATCH("/puid", puidHandler) 83 | admin_routes.PATCH("/openai", openaiHandler) 84 | /// Public routes 85 | router.OPTIONS("/v1/chat/completions", optionsHandler) 86 | router.POST("/v1/chat/completions", Authorization, nightmare) 87 | router.GET("/v1/engines", Authorization, engines_handler) 88 | router.GET("/v1/models", Authorization, engines_handler) 89 | endless.ListenAndServe(HOST+":"+PORT, router) 90 | } 91 | -------------------------------------------------------------------------------- /middleware.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | "strings" 7 | 8 | gin "github.com/gin-gonic/gin" 9 | ) 10 | 11 | var ADMIN_PASSWORD string 12 | var API_KEYS map[string]bool 13 | 14 | func init() { 15 | ADMIN_PASSWORD = os.Getenv("ADMIN_PASSWORD") 16 | if ADMIN_PASSWORD == "" { 17 | ADMIN_PASSWORD = "TotallySecurePassword" 18 | } 19 | } 20 | 21 | func adminCheck(c *gin.Context) { 22 | password := c.Request.Header.Get("Authorization") 23 | if password != ADMIN_PASSWORD { 24 | c.String(401, "Unauthorized") 25 | c.Abort() 26 | return 27 | } 28 | c.Next() 29 | } 30 | 31 | func cors(c *gin.Context) { 32 | c.Header("Access-Control-Allow-Origin", "*") 33 | c.Header("Access-Control-Allow-Methods", "*") 34 | c.Header("Access-Control-Allow-Headers", "*") 35 | c.Next() 36 | } 37 | 38 | func Authorization(c *gin.Context) { 39 | if API_KEYS == nil { 40 | API_KEYS = make(map[string]bool) 41 | if _, err := os.Stat("api_keys.txt"); err == nil { 42 | file, _ := os.Open("api_keys.txt") 43 | defer file.Close() 44 | scanner := bufio.NewScanner(file) 45 | for scanner.Scan() { 46 | key := scanner.Text() 47 | if key != "" { 48 | API_KEYS["Bearer "+key] = true 49 | } 50 | } 51 | } 52 | } 53 | if len(API_KEYS) != 0 && !API_KEYS[c.Request.Header.Get("Authorization")] { 54 | if c.Request.Header.Get("Authorization") == "" { 55 | c.JSON(401, gin.H{"error": "No API key provided. Get one at https://discord.gg/9K2BvbXEHT"}) 56 | } else if strings.HasPrefix(c.Request.Header.Get("Authorization"), "Bearer sk-") { 57 | c.JSON(401, gin.H{"error": "You tried to use the official API key which is not supported."}) 58 | } else if strings.HasPrefix(c.Request.Header.Get("Authorization"), "Bearer eyJhbGciOiJSUzI1NiI") { 59 | return 60 | } else { 61 | c.JSON(401, gin.H{"error": "Invalid API key."}) 62 | } 63 | c.Abort() 64 | return 65 | } 66 | c.Next() 67 | } 68 | -------------------------------------------------------------------------------- /tools/authenticator/README.md: -------------------------------------------------------------------------------- 1 | # Automated authentication for ChatGPT 2 | Fetches access tokens from a large number of accounts 3 | 4 | ## Setup 5 | ### `proxies.txt` 6 | Format: 7 | ``` 8 | IP:Port:User:Password 9 | ... 10 | ``` 11 | 12 | ### `accounts.txt` 13 | Format: 14 | ``` 15 | email:password 16 | ... 17 | ``` 18 | 19 | Remember to: 20 | `touch access_tokens.txt authenticated_accounts.txt` 21 | -------------------------------------------------------------------------------- /tools/authenticator/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/url" 9 | "regexp" 10 | "strings" 11 | 12 | http "github.com/bogdanfinn/fhttp" 13 | tls_client "github.com/bogdanfinn/tls-client" 14 | pkce "github.com/nirasan/go-oauth-pkce-code-verifier" 15 | ) 16 | 17 | type Error struct { 18 | Location string 19 | StatusCode int 20 | Details string 21 | Error error 22 | } 23 | 24 | func NewError(location string, statusCode int, details string, err error) *Error { 25 | return &Error{ 26 | Location: location, 27 | StatusCode: statusCode, 28 | Details: details, 29 | Error: err, 30 | } 31 | } 32 | 33 | type Authenticator struct { 34 | EmailAddress string 35 | Password string 36 | Proxy string 37 | Session tls_client.HttpClient 38 | UserAgent string 39 | State string 40 | URL string 41 | Verifier_code string 42 | Verifier_challenge string 43 | AuthResult AuthResult 44 | } 45 | 46 | type AuthResult struct { 47 | AccessToken string `json:"access_token"` 48 | PUID string `json:"puid"` 49 | } 50 | 51 | func NewAuthenticator(emailAddress, password, proxy string) *Authenticator { 52 | auth := &Authenticator{ 53 | EmailAddress: emailAddress, 54 | Password: password, 55 | Proxy: proxy, 56 | UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36", 57 | } 58 | jar := tls_client.NewCookieJar() 59 | options := []tls_client.HttpClientOption{ 60 | tls_client.WithTimeoutSeconds(20), 61 | tls_client.WithClientProfile(tls_client.Okhttp4Android13), 62 | tls_client.WithNotFollowRedirects(), 63 | tls_client.WithCookieJar(jar), // create cookieJar instance and pass it as argument 64 | // Proxy 65 | tls_client.WithProxyUrl(proxy), 66 | } 67 | auth.Session, _ = tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...) 68 | 69 | // PKCE 70 | verifier, _ := pkce.CreateCodeVerifier() 71 | auth.Verifier_code = verifier.String() 72 | auth.Verifier_challenge = verifier.CodeChallengeS256() 73 | 74 | return auth 75 | } 76 | 77 | func (auth *Authenticator) URLEncode(str string) string { 78 | return url.QueryEscape(str) 79 | } 80 | 81 | func (auth *Authenticator) Begin() *Error { 82 | 83 | url := "https://chat.openai.com/api/auth/csrf" 84 | req, err := http.NewRequest("GET", url, nil) 85 | if err != nil { 86 | return NewError("begin", 0, "", err) 87 | } 88 | 89 | req.Header.Set("Host", "chat.openai.com") 90 | req.Header.Set("Accept", "*/*") 91 | req.Header.Set("Connection", "keep-alive") 92 | req.Header.Set("User-Agent", auth.UserAgent) 93 | req.Header.Set("Accept-Language", "en-GB,en-US;q=0.9,en;q=0.8") 94 | req.Header.Set("Referer", "https://chat.openai.com/auth/login") 95 | req.Header.Set("Accept-Encoding", "gzip, deflate, br") 96 | 97 | resp, err := auth.Session.Do(req) 98 | if err != nil { 99 | return NewError("begin", 0, "", err) 100 | } 101 | defer resp.Body.Close() 102 | 103 | body, err := io.ReadAll(resp.Body) 104 | if err != nil { 105 | return NewError("begin", 0, "", err) 106 | } 107 | 108 | if resp.StatusCode == 200 && strings.Contains(resp.Header.Get("Content-Type"), "json") { 109 | 110 | var csrfTokenResponse struct { 111 | CsrfToken string `json:"csrfToken"` 112 | } 113 | err = json.Unmarshal(body, &csrfTokenResponse) 114 | if err != nil { 115 | return NewError("begin", 0, "", err) 116 | } 117 | 118 | csrfToken := csrfTokenResponse.CsrfToken 119 | return auth.partOne(csrfToken) 120 | } else { 121 | err := NewError("begin", resp.StatusCode, string(body), fmt.Errorf("error: Check details")) 122 | return err 123 | } 124 | } 125 | 126 | func (auth *Authenticator) partOne(csrfToken string) *Error { 127 | 128 | auth_url := "https://chat.openai.com/api/auth/signin/auth0?prompt=login" 129 | headers := map[string]string{ 130 | "Host": "chat.openai.com", 131 | "User-Agent": auth.UserAgent, 132 | "Content-Type": "application/x-www-form-urlencoded", 133 | "Accept": "*/*", 134 | "Sec-Gpc": "1", 135 | "Accept-Language": "en-US,en;q=0.8", 136 | "Origin": "https://chat.openai.com", 137 | "Sec-Fetch-Site": "same-origin", 138 | "Sec-Fetch-Mode": "cors", 139 | "Sec-Fetch-Dest": "empty", 140 | "Referer": "https://chat.openai.com/auth/login", 141 | "Accept-Encoding": "gzip, deflate", 142 | } 143 | 144 | // Construct payload 145 | payload := fmt.Sprintf("callbackUrl=%%2F&csrfToken=%s&json=true", csrfToken) 146 | req, _ := http.NewRequest("POST", auth_url, strings.NewReader(payload)) 147 | 148 | for k, v := range headers { 149 | req.Header.Set(k, v) 150 | } 151 | 152 | resp, err := auth.Session.Do(req) 153 | if err != nil { 154 | return NewError("part_one", 0, "Failed to send request", err) 155 | } 156 | defer resp.Body.Close() 157 | body, err := io.ReadAll(resp.Body) 158 | if err != nil { 159 | return NewError("part_one", 0, "Failed to read body", err) 160 | } 161 | 162 | if resp.StatusCode == 200 && strings.Contains(resp.Header.Get("Content-Type"), "json") { 163 | var urlResponse struct { 164 | URL string `json:"url"` 165 | } 166 | err = json.Unmarshal(body, &urlResponse) 167 | if err != nil { 168 | return NewError("part_one", 0, "Failed to decode JSON", err) 169 | } 170 | if urlResponse.URL == "https://chat.openai.com/api/auth/error?error=OAuthSignin" || strings.Contains(urlResponse.URL, "error") { 171 | err := NewError("part_one", resp.StatusCode, "You have been rate limited. Please try again later.", fmt.Errorf("error: Check details")) 172 | return err 173 | } 174 | return auth.partTwo(urlResponse.URL) 175 | } else { 176 | return NewError("part_one", resp.StatusCode, string(body), fmt.Errorf("error: Check details")) 177 | } 178 | } 179 | 180 | func (auth *Authenticator) partTwo(url string) *Error { 181 | 182 | headers := map[string]string{ 183 | "Host": "auth0.openai.com", 184 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 185 | "Connection": "keep-alive", 186 | "User-Agent": auth.UserAgent, 187 | "Accept-Language": "en-US,en;q=0.9", 188 | } 189 | 190 | req, _ := http.NewRequest("GET", url, nil) 191 | for k, v := range headers { 192 | req.Header.Set(k, v) 193 | } 194 | 195 | resp, err := auth.Session.Do(req) 196 | if err != nil { 197 | return NewError("part_two", 0, "Failed to make request", err) 198 | } 199 | defer resp.Body.Close() 200 | body, _ := io.ReadAll(resp.Body) 201 | 202 | if resp.StatusCode == 302 || resp.StatusCode == 200 { 203 | 204 | stateRegex := regexp.MustCompile(`state=(.*)`) 205 | stateMatch := stateRegex.FindStringSubmatch(string(body)) 206 | if len(stateMatch) < 2 { 207 | return NewError("part_two", 0, "Could not find state in response", fmt.Errorf("error: Check details")) 208 | } 209 | 210 | state := strings.Split(stateMatch[1], `"`)[0] 211 | return auth.partThree(state) 212 | } else { 213 | return NewError("part_two", resp.StatusCode, string(body), fmt.Errorf("error: Check details")) 214 | 215 | } 216 | } 217 | func (auth *Authenticator) partThree(state string) *Error { 218 | 219 | url := fmt.Sprintf("https://auth0.openai.com/u/login/identifier?state=%s", state) 220 | emailURLEncoded := auth.URLEncode(auth.EmailAddress) 221 | 222 | payload := fmt.Sprintf( 223 | "state=%s&username=%s&js-available=false&webauthn-available=true&is-brave=false&webauthn-platform-available=true&action=default", 224 | state, emailURLEncoded, 225 | ) 226 | 227 | headers := map[string]string{ 228 | "Host": "auth0.openai.com", 229 | "Origin": "https://auth0.openai.com", 230 | "Connection": "keep-alive", 231 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 232 | "User-Agent": auth.UserAgent, 233 | "Referer": fmt.Sprintf("https://auth0.openai.com/u/login/identifier?state=%s", state), 234 | "Accept-Language": "en-US,en;q=0.9", 235 | "Content-Type": "application/x-www-form-urlencoded", 236 | } 237 | 238 | req, _ := http.NewRequest("POST", url, strings.NewReader(payload)) 239 | 240 | for k, v := range headers { 241 | req.Header.Set(k, v) 242 | } 243 | 244 | resp, err := auth.Session.Do(req) 245 | if err != nil { 246 | return NewError("part_three", 0, "Failed to send request", err) 247 | } 248 | defer resp.Body.Close() 249 | 250 | if resp.StatusCode == 302 || resp.StatusCode == 200 { 251 | return auth.partFour(state) 252 | } else { 253 | return NewError("part_three", resp.StatusCode, "Your email address is invalid.", fmt.Errorf("error: Check details")) 254 | 255 | } 256 | 257 | } 258 | func (auth *Authenticator) partFour(state string) *Error { 259 | 260 | url := fmt.Sprintf("https://auth0.openai.com/u/login/password?state=%s", state) 261 | emailURLEncoded := auth.URLEncode(auth.EmailAddress) 262 | passwordURLEncoded := auth.URLEncode(auth.Password) 263 | payload := fmt.Sprintf("state=%s&username=%s&password=%s&action=default", state, emailURLEncoded, passwordURLEncoded) 264 | 265 | headers := map[string]string{ 266 | "Host": "auth0.openai.com", 267 | "Origin": "https://auth0.openai.com", 268 | "Connection": "keep-alive", 269 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 270 | "User-Agent": auth.UserAgent, 271 | "Referer": fmt.Sprintf("https://auth0.openai.com/u/login/password?state=%s", state), 272 | "Accept-Language": "en-US,en;q=0.9", 273 | "Content-Type": "application/x-www-form-urlencoded", 274 | } 275 | 276 | req, _ := http.NewRequest("POST", url, strings.NewReader(payload)) 277 | 278 | for k, v := range headers { 279 | req.Header.Set(k, v) 280 | } 281 | 282 | resp, err := auth.Session.Do(req) 283 | if err != nil { 284 | return NewError("part_four", 0, "Failed to send request", err) 285 | } 286 | defer resp.Body.Close() 287 | 288 | if resp.StatusCode == 302 { 289 | redirectURL := resp.Header.Get("Location") 290 | return auth.partFive(state, redirectURL) 291 | } else { 292 | body := bytes.NewBuffer(nil) 293 | body.ReadFrom(resp.Body) 294 | return NewError("part_four", resp.StatusCode, body.String(), fmt.Errorf("error: Check details")) 295 | 296 | } 297 | 298 | } 299 | func (auth *Authenticator) partFive(oldState string, redirectURL string) *Error { 300 | 301 | url := "https://auth0.openai.com" + redirectURL 302 | 303 | headers := map[string]string{ 304 | "Host": "auth0.openai.com", 305 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 306 | "Connection": "keep-alive", 307 | "User-Agent": auth.UserAgent, 308 | "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8", 309 | "Referer": fmt.Sprintf("https://auth0.openai.com/u/login/password?state=%s", oldState), 310 | } 311 | 312 | req, _ := http.NewRequest("GET", url, nil) 313 | 314 | for k, v := range headers { 315 | req.Header.Set(k, v) 316 | } 317 | 318 | resp, err := auth.Session.Do(req) 319 | if err != nil { 320 | return NewError("part_five", 0, "Failed to send request", err) 321 | } 322 | defer resp.Body.Close() 323 | 324 | if resp.StatusCode == 302 { 325 | return auth.partSix(resp.Header.Get("Location"), url) 326 | } else { 327 | return NewError("part_five", resp.StatusCode, resp.Status, fmt.Errorf("error: Check details")) 328 | 329 | } 330 | 331 | } 332 | func (auth *Authenticator) partSix(url, redirect_url string) *Error { 333 | req, _ := http.NewRequest("GET", url, nil) 334 | for k, v := range map[string]string{ 335 | "Host": "chat.openai.com", 336 | "Accept": "application/json", 337 | "Connection": "keep-alive", 338 | "User-Agent": auth.UserAgent, 339 | "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8", 340 | "Referer": redirect_url, 341 | } { 342 | req.Header.Set(k, v) 343 | } 344 | resp, err := auth.Session.Do(req) 345 | if err != nil { 346 | return NewError("part_six", 0, "Failed to send request", err) 347 | } 348 | defer resp.Body.Close() 349 | if err != nil { 350 | return NewError("part_six", 0, "Response was not JSON", err) 351 | } 352 | if resp.StatusCode != 302 { 353 | return NewError("part_six", resp.StatusCode, url, fmt.Errorf("incorrect response code")) 354 | } 355 | // Check location header 356 | if location := resp.Header.Get("Location"); location != "https://chat.openai.com/" { 357 | return NewError("part_six", resp.StatusCode, location, fmt.Errorf("incorrect redirect")) 358 | } 359 | 360 | url = "https://chat.openai.com/api/auth/session" 361 | 362 | req, _ = http.NewRequest("GET", url, nil) 363 | 364 | // Set user agent 365 | req.Header.Set("User-Agent", auth.UserAgent) 366 | 367 | resp, err = auth.Session.Do(req) 368 | if err != nil { 369 | return NewError("get_access_token", 0, "Failed to send request", err) 370 | } 371 | 372 | if resp.StatusCode != 200 { 373 | return NewError("get_access_token", resp.StatusCode, "Incorrect response code", fmt.Errorf("error: Check details")) 374 | } 375 | var result map[string]interface{} 376 | if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { 377 | return NewError("get_access_token", 0, "", err) 378 | } 379 | 380 | // Check if access token in data 381 | if _, ok := result["accessToken"]; !ok { 382 | result_string := fmt.Sprintf("%v", result) 383 | return NewError("part_six", 0, result_string, fmt.Errorf("missing access token")) 384 | } 385 | auth.AuthResult.AccessToken = result["accessToken"].(string) 386 | 387 | return nil 388 | } 389 | 390 | func (auth *Authenticator) GetAccessToken() string { 391 | return auth.AuthResult.AccessToken 392 | } 393 | 394 | func (auth *Authenticator) GetPUID() (string, *Error) { 395 | // Check if user has access token 396 | if auth.AuthResult.AccessToken == "" { 397 | return "", NewError("get_puid", 0, "Missing access token", fmt.Errorf("error: Check details")) 398 | } 399 | // Make request to https://chat.openai.com/backend-api/models 400 | req, _ := http.NewRequest("GET", "https://chat.openai.com/backend-api/models", nil) 401 | // Add headers 402 | req.Header.Add("Authorization", "Bearer "+auth.AuthResult.AccessToken) 403 | req.Header.Add("User-Agent", auth.UserAgent) 404 | req.Header.Add("Accept", "application/json") 405 | req.Header.Add("Accept-Language", "en-US,en;q=0.9") 406 | req.Header.Add("Referer", "https://chat.openai.com/") 407 | req.Header.Add("Origin", "https://chat.openai.com") 408 | req.Header.Add("Connection", "keep-alive") 409 | 410 | resp, err := auth.Session.Do(req) 411 | if err != nil { 412 | return "", NewError("get_puid", 0, "Failed to make request", err) 413 | } 414 | defer resp.Body.Close() 415 | if resp.StatusCode != 200 { 416 | return "", NewError("get_puid", resp.StatusCode, "Failed to make request", fmt.Errorf("error: Check details")) 417 | } 418 | // Find `_puid` cookie in response 419 | for _, cookie := range resp.Cookies() { 420 | if cookie.Name == "_puid" { 421 | auth.AuthResult.PUID = cookie.Value 422 | return cookie.Value, nil 423 | } 424 | } 425 | // If cookie not found, return error 426 | return "", NewError("get_puid", 0, "PUID cookie not found", fmt.Errorf("error: Check details")) 427 | } 428 | 429 | func (auth *Authenticator) GetAuthResult() AuthResult { 430 | return auth.AuthResult 431 | } 432 | -------------------------------------------------------------------------------- /tools/authenticator/go.mod: -------------------------------------------------------------------------------- 1 | module authenticator 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/bogdanfinn/fhttp v0.5.23 7 | github.com/bogdanfinn/tls-client v1.5.0 8 | github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4c 9 | ) 10 | 11 | require ( 12 | github.com/andybalholm/brotli v1.0.5 // indirect 13 | github.com/bogdanfinn/utls v1.5.16 // indirect 14 | github.com/klauspost/compress v1.16.7 // indirect 15 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 // indirect 16 | golang.org/x/crypto v0.11.0 // indirect 17 | golang.org/x/net v0.12.0 // indirect 18 | golang.org/x/sys v0.10.0 // indirect 19 | golang.org/x/text v0.11.0 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /tools/authenticator/go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= 2 | github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 3 | github.com/bogdanfinn/fhttp v0.5.23 h1:4Xb5OjYArB8GpnUw4A4r5jmt8UW0/Cvey3R9nS2dC9U= 4 | github.com/bogdanfinn/fhttp v0.5.23/go.mod h1:brqi5woc5eSCVHdKYBV8aZLbO7HGqpwyDLeXW+fT18I= 5 | github.com/bogdanfinn/tls-client v1.5.0 h1:L/d4AL+8RRw+6o9wwhpii45I6RO2CEkgRV4NeE0NAxo= 6 | github.com/bogdanfinn/tls-client v1.5.0/go.mod h1:lgtqsHjoJYQMPz6H08bc8t30bmUaYnVjwtfVEzMGJDs= 7 | github.com/bogdanfinn/utls v1.5.16 h1:NhhWkegEcYETBMj9nvgO4lwvc6NcLH+znrXzO3gnw4M= 8 | github.com/bogdanfinn/utls v1.5.16/go.mod h1:mHeRCi69cUiEyVBkKONB1cAbLjRcZnlJbGzttmiuK4o= 9 | github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= 10 | github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 11 | github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4c h1:4RYnE0ISVwRxm9Dfo7utw1dh0kdRDEmVYq2MFVLy5zI= 12 | github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4c/go.mod h1:DvuJJ/w1Y59rG8UTDxsMk5U+UJXJwuvUgbiJSm9yhX8= 13 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 h1:YqAladjX7xpA6BM04leXMWAEjS0mTZ5kUU9KRBriQJc= 14 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5/go.mod h1:2JjD2zLQYH5HO74y5+aE3remJQvl6q4Sn6aWA2wD1Ng= 15 | golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= 16 | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= 17 | golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= 18 | golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= 19 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= 20 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 21 | golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= 22 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 23 | -------------------------------------------------------------------------------- /tools/authenticator/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | "time" 10 | 11 | auth "authenticator/auth" 12 | ) 13 | 14 | type Account struct { 15 | Email string `json:"username"` 16 | Password string `json:"password"` 17 | } 18 | type Proxy struct { 19 | IP string `json:"ip"` 20 | Port string `json:"port"` 21 | User string `json:"user"` 22 | Pass string `json:"pass"` 23 | } 24 | 25 | func (p Proxy) Socks5URL() string { 26 | // Returns proxy URL (socks5) 27 | if p.User == "" && p.Pass == "" { 28 | return fmt.Sprintf("socks5://%s:%s", p.IP, p.Port) 29 | } 30 | return fmt.Sprintf("socks5://%s:%s@%s:%s", p.User, p.Pass, p.IP, p.Port) 31 | } 32 | 33 | // Read accounts.txt and create a list of accounts 34 | func readAccounts() []Account { 35 | accounts := []Account{} 36 | // Read accounts.txt and create a list of accounts 37 | file, err := os.Open("accounts.txt") 38 | if err != nil { 39 | panic(err) 40 | } 41 | defer file.Close() 42 | // Loop through each line in the file 43 | scanner := bufio.NewScanner(file) 44 | for scanner.Scan() { 45 | // Split by : 46 | line := strings.Split(scanner.Text(), ":") 47 | // Create an account 48 | account := Account{ 49 | Email: line[0], 50 | Password: line[1], 51 | } 52 | // Append to accounts 53 | accounts = append(accounts, account) 54 | } 55 | return accounts 56 | } 57 | 58 | // Read proxies from proxies.txt and create a list of proxies 59 | func readProxies() []Proxy { 60 | proxies := []Proxy{} 61 | // Read proxies.txt and create a list of proxies 62 | file, err := os.Open("proxies.txt") 63 | if err != nil { 64 | return []Proxy{} 65 | } 66 | defer file.Close() 67 | // Loop through each line in the file 68 | scanner := bufio.NewScanner(file) 69 | for scanner.Scan() { 70 | // Split by : 71 | lines := strings.Split(scanner.Text(), ":") 72 | var proxy Proxy 73 | if len(lines) == 4 { 74 | // Create a proxy 75 | proxy = Proxy{ 76 | IP: lines[0], 77 | Port: lines[1], 78 | User: lines[2], 79 | Pass: lines[3], 80 | } 81 | } else if len(lines) == 2 { 82 | proxy = Proxy{ 83 | IP: lines[0], 84 | Port: lines[1], 85 | } 86 | } else { 87 | continue 88 | } 89 | // Append to proxies 90 | proxies = append(proxies, proxy) 91 | } 92 | return proxies 93 | } 94 | 95 | func main() { 96 | // Read accounts and proxies 97 | accounts := readAccounts() 98 | proxies := readProxies() 99 | 100 | // Loop through each account 101 | for _, account := range accounts { 102 | if os.Getenv("CF_PROXY") != "" { 103 | // exec warp-cli disconnect and connect 104 | exec.Command("warp-cli", "disconnect").Run() 105 | exec.Command("warp-cli", "connect").Run() 106 | time.Sleep(5 * time.Second) 107 | } 108 | println(account.Email) 109 | println(account.Password) 110 | var proxy_url string 111 | if len(proxies) == 0 { 112 | if os.Getenv("http_proxy") != "" { 113 | proxy_url = os.Getenv("http_proxy") 114 | } 115 | } else { 116 | proxy_url = proxies[0].Socks5URL() 117 | // Push used proxy to the back of the list 118 | proxies = append(proxies[1:], proxies[0]) 119 | } 120 | println(proxy_url) 121 | authenticator := auth.NewAuthenticator(account.Email, account.Password, proxy_url) 122 | err := authenticator.Begin() 123 | if err != nil { 124 | // println("Error: " + err.Details) 125 | println("Location: " + err.Location) 126 | println("Status code: " + fmt.Sprint(err.StatusCode)) 127 | println("Details: " + err.Details) 128 | println("Embedded error: " + err.Error.Error()) 129 | // Sleep for 10 seconds 130 | panic(err) 131 | } 132 | access_token := authenticator.GetAccessToken() 133 | // Append access token to access_tokens.txt 134 | f, go_err := os.OpenFile("access_tokens.txt", os.O_APPEND|os.O_WRONLY, 0600) 135 | if go_err != nil { 136 | continue 137 | } 138 | defer f.Close() 139 | if _, go_err = f.WriteString(access_token + "\n"); go_err != nil { 140 | continue 141 | } 142 | // Write authenticated account to authenticated_accounts.txt 143 | f, go_err = os.OpenFile("authenticated_accounts.txt", os.O_APPEND|os.O_WRONLY, 0600) 144 | if go_err != nil { 145 | continue 146 | } 147 | defer f.Close() 148 | if _, go_err = f.WriteString(account.Email + ":" + account.Password + "\n"); go_err != nil { 149 | continue 150 | } 151 | // Remove accounts.txt 152 | os.Remove("accounts.txt") 153 | // Create accounts.txt 154 | f, go_err = os.Create("accounts.txt") 155 | if go_err != nil { 156 | continue 157 | } 158 | defer f.Close() 159 | // Remove account from accounts 160 | accounts = accounts[1:] 161 | // Write unauthenticated accounts to accounts.txt 162 | for _, acc := range accounts { 163 | // Check if account is authenticated 164 | if acc.Email == account.Email { 165 | continue 166 | } 167 | if _, go_err = f.WriteString(acc.Email + ":" + acc.Password + "\n"); go_err != nil { 168 | continue 169 | } 170 | } 171 | 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /tools/authenticator/remove_duplicates.py: -------------------------------------------------------------------------------- 1 | # Removes duplicate lines from a file 2 | # Usage: python remove_duplicates.py 3 | 4 | import sys 5 | import json 6 | 7 | 8 | def remove_duplicates(file_lines): 9 | """ 10 | Removes duplicate lines from a file 11 | """ 12 | lines_set = set() 13 | for lin in file_lines: 14 | #if json.loads(lin)["output"] == "": 15 | # continue 16 | lines_set.add(lin) 17 | return lines_set 18 | 19 | 20 | if __name__ == "__main__": 21 | if len(sys.argv) != 2: 22 | print("Usage: python remove_duplicates.py ") 23 | sys.exit(1) 24 | orig_file = open(sys.argv[1], "r", encoding="utf-8").readlines() 25 | lines = remove_duplicates(orig_file) 26 | file = open("clean_" + sys.argv[1], "w", encoding="utf-8") 27 | for line in lines: 28 | file.write(line) 29 | file.close() 30 | # Print difference 31 | print(len(orig_file) - len(lines)) 32 | -------------------------------------------------------------------------------- /tools/authenticator/tojson.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if a file name is provided as an argument 4 | if [ $# -eq 0 ]; then 5 | echo "Usage: $0 " 6 | exit 1 7 | fi 8 | 9 | file="$1" 10 | output="$2" 11 | # Declare an empty array 12 | lines=() 13 | 14 | # Read the file line by line and add each line to the array 15 | while IFS= read -r line; do 16 | lines+=("\"$line\"") 17 | done < "$file" 18 | 19 | # Join array elements with commas and print the result enclosed in square brackets 20 | result="[" 21 | for ((i = 0; i < ${#lines[@]}; i++)); do 22 | result+="${lines[i]}" 23 | if ((i < ${#lines[@]} - 1)); then 24 | result+="," 25 | fi 26 | done 27 | result+="]" 28 | if [ $# -eq 1 ]; then 29 | echo "$result" 30 | fi 31 | if [ $# -eq 2 ]; then 32 | echo "$result" | tee $output 33 | fi 34 | 35 | -------------------------------------------------------------------------------- /tools/plugin_check/check_access.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "net/http" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | var access_tokens []string 12 | // Read access_tokens.txt and split by new line 13 | file, err := os.Open("access_tokens.txt") 14 | if err != nil { 15 | panic(err) 16 | } 17 | defer file.Close() 18 | scanner := bufio.NewScanner(file) 19 | for scanner.Scan() { 20 | access_tokens = append(access_tokens, scanner.Text()) 21 | } 22 | if err := scanner.Err(); err != nil { 23 | panic(err) 24 | } 25 | // Go routine to check access for each token (limit to 20 simultaneous) 26 | sem := make(chan bool, 20) 27 | for _, token := range access_tokens { 28 | sem <- true 29 | go func(token string) { 30 | defer func() { <-sem }() 31 | if check_access(token) { 32 | println(token) 33 | } 34 | }(token) 35 | } 36 | for i := 0; i < cap(sem); i++ { 37 | sem <- true 38 | } 39 | } 40 | 41 | func check_access(token string) bool { 42 | print(".") 43 | req, _ := http.NewRequest("GET", "https://ai.fakeopen.com/api/accounts/check", nil) 44 | req.Header.Set("Authorization", "Bearer "+token) 45 | client := &http.Client{} 46 | resp, err := client.Do(req) 47 | if err != nil { 48 | panic(err) 49 | } 50 | defer resp.Body.Close() 51 | if resp.StatusCode == 200 { 52 | // Parse response body as JSON 53 | var result map[string]interface{} 54 | json.NewDecoder(resp.Body).Decode(&result) 55 | // Check if "tool1", "tool2", or "tool3" is in the features array 56 | for _, feature := range result["features"].([]interface{}) { 57 | if feature == "tools1" || feature == "tools2" || feature == "tools3" { 58 | return true 59 | } 60 | } 61 | return false 62 | } 63 | println(resp.StatusCode) 64 | return false 65 | } 66 | -------------------------------------------------------------------------------- /tools/proxy_check/proxy_check.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "sync" 9 | 10 | tls_client "github.com/bogdanfinn/tls-client" 11 | ) 12 | 13 | var proxies []string 14 | 15 | // Read proxies.txt and check if they work 16 | func init() { 17 | // Check for proxies.txt 18 | if _, err := os.Stat("proxies.txt"); err == nil { 19 | // Each line is a proxy, put in proxies array 20 | file, _ := os.Open("proxies.txt") 21 | defer file.Close() 22 | scanner := bufio.NewScanner(file) 23 | for scanner.Scan() { 24 | // Split line by : 25 | proxy := scanner.Text() 26 | proxy_parts := strings.Split(proxy, ":") 27 | if len(proxy_parts) == 2 { 28 | proxy = "socks5://" + proxy 29 | } else if len(proxy_parts) == 4 { 30 | proxy = "socks5://" + proxy_parts[2] + ":" + proxy_parts[3] + "@" + proxy_parts[0] + ":" + proxy_parts[1] 31 | } else { 32 | continue 33 | } 34 | proxies = append(proxies, proxy) 35 | } 36 | } 37 | 38 | } 39 | 40 | func main() { 41 | wg := sync.WaitGroup{} 42 | for _, proxy := range proxies { 43 | wg.Add(1) 44 | go func(proxy string) { 45 | defer wg.Done() 46 | jar := tls_client.NewCookieJar() 47 | options := []tls_client.HttpClientOption{ 48 | tls_client.WithTimeoutSeconds(360), 49 | tls_client.WithClientProfile(tls_client.Chrome_110), 50 | tls_client.WithNotFollowRedirects(), 51 | tls_client.WithCookieJar(jar), // create cookieJar instance and pass it as argument 52 | // Disable SSL verification 53 | tls_client.WithInsecureSkipVerify(), 54 | } 55 | client, _ := tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...) 56 | 57 | client.SetProxy(proxy) 58 | resp, err := client.Get("https://example.com") 59 | if err != nil { 60 | fmt.Println("Error: ", err) 61 | fmt.Println("Proxy: ", proxy) 62 | return 63 | } 64 | if resp.StatusCode != 200 { 65 | fmt.Println("Error: ", resp.StatusCode) 66 | fmt.Println("Proxy: ", proxy) 67 | return 68 | } else { 69 | fmt.Println(".") 70 | } 71 | }(proxy) 72 | } 73 | wg.Wait() 74 | } 75 | -------------------------------------------------------------------------------- /typings/chatgpt/request.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/google/uuid" 7 | ) 8 | 9 | type chatgpt_message struct { 10 | ID uuid.UUID `json:"id"` 11 | Author chatgpt_author `json:"author"` 12 | Content chatgpt_content `json:"content"` 13 | } 14 | 15 | type chatgpt_content struct { 16 | ContentType string `json:"content_type"` 17 | Parts []string `json:"parts"` 18 | } 19 | 20 | type chatgpt_author struct { 21 | Role string `json:"role"` 22 | } 23 | 24 | type ChatGPTRequest struct { 25 | Action string `json:"action"` 26 | Messages []chatgpt_message `json:"messages"` 27 | ParentMessageID string `json:"parent_message_id,omitempty"` 28 | ConversationID string `json:"conversation_id,omitempty"` 29 | Model string `json:"model"` 30 | HistoryAndTrainingDisabled bool `json:"history_and_training_disabled"` 31 | ArkoseToken string `json:"arkose_token,omitempty"` 32 | PluginIDs []string `json:"plugin_ids,omitempty"` 33 | } 34 | 35 | func NewChatGPTRequest() ChatGPTRequest { 36 | enable_history := os.Getenv("ENABLE_HISTORY") == "" 37 | return ChatGPTRequest{ 38 | Action: "next", 39 | ParentMessageID: uuid.NewString(), 40 | Model: "text-davinci-002-render-sha", 41 | HistoryAndTrainingDisabled: !enable_history, 42 | } 43 | } 44 | 45 | func (c *ChatGPTRequest) AddMessage(role string, content string) { 46 | c.Messages = append(c.Messages, chatgpt_message{ 47 | ID: uuid.New(), 48 | Author: chatgpt_author{Role: role}, 49 | Content: chatgpt_content{ContentType: "text", Parts: []string{content}}, 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /typings/chatgpt/response.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | type ChatGPTResponse struct { 4 | Message Message `json:"message"` 5 | ConversationID string `json:"conversation_id"` 6 | Error interface{} `json:"error"` 7 | } 8 | 9 | type Message struct { 10 | ID string `json:"id"` 11 | Author Author `json:"author"` 12 | CreateTime float64 `json:"create_time"` 13 | UpdateTime interface{} `json:"update_time"` 14 | Content Content `json:"content"` 15 | EndTurn interface{} `json:"end_turn"` 16 | Weight float64 `json:"weight"` 17 | Metadata Metadata `json:"metadata"` 18 | Recipient string `json:"recipient"` 19 | } 20 | 21 | type Content struct { 22 | ContentType string `json:"content_type"` 23 | Parts []string `json:"parts"` 24 | } 25 | 26 | type Author struct { 27 | Role string `json:"role"` 28 | Name interface{} `json:"name"` 29 | Metadata map[string]interface{} `json:"metadata"` 30 | } 31 | 32 | type Metadata struct { 33 | Timestamp string `json:"timestamp_"` 34 | MessageType string `json:"message_type"` 35 | FinishDetails *FinishDetails `json:"finish_details"` 36 | ModelSlug string `json:"model_slug"` 37 | Recipient string `json:"recipient"` 38 | } 39 | 40 | type FinishDetails struct { 41 | Type string `json:"type"` 42 | Stop string `json:"stop"` 43 | } 44 | -------------------------------------------------------------------------------- /typings/official/request.go: -------------------------------------------------------------------------------- 1 | package official 2 | 3 | type APIRequest struct { 4 | Messages []api_message `json:"messages"` 5 | Stream bool `json:"stream"` 6 | Model string `json:"model"` 7 | PluginIDs []string `json:"plugin_ids"` 8 | } 9 | 10 | type api_message struct { 11 | Role string `json:"role"` 12 | Content string `json:"content"` 13 | } 14 | -------------------------------------------------------------------------------- /typings/official/response.go: -------------------------------------------------------------------------------- 1 | package official 2 | 3 | import "encoding/json" 4 | 5 | type ChatCompletionChunk struct { 6 | ID string `json:"id"` 7 | Object string `json:"object"` 8 | Created int64 `json:"created"` 9 | Model string `json:"model"` 10 | Choices []Choices `json:"choices"` 11 | } 12 | 13 | func (chunk *ChatCompletionChunk) String() string { 14 | resp, _ := json.Marshal(chunk) 15 | return string(resp) 16 | } 17 | 18 | type Choices struct { 19 | Delta Delta `json:"delta"` 20 | Index int `json:"index"` 21 | FinishReason interface{} `json:"finish_reason"` 22 | } 23 | 24 | type Delta struct { 25 | Content string `json:"content,omitempty"` 26 | Role string `json:"role,omitempty"` 27 | } 28 | 29 | func NewChatCompletionChunk(text string) ChatCompletionChunk { 30 | return ChatCompletionChunk{ 31 | ID: "chatcmpl-QXlha2FBbmROaXhpZUFyZUF3ZXNvbWUK", 32 | Object: "chat.completion.chunk", 33 | Created: 0, 34 | Model: "gpt-3.5-turbo-0301", 35 | Choices: []Choices{ 36 | { 37 | Index: 0, 38 | Delta: Delta{ 39 | Content: text, 40 | }, 41 | FinishReason: nil, 42 | }, 43 | }, 44 | } 45 | } 46 | 47 | func StopChunk(reason string) ChatCompletionChunk { 48 | return ChatCompletionChunk{ 49 | ID: "chatcmpl-QXlha2FBbmROaXhpZUFyZUF3ZXNvbWUK", 50 | Object: "chat.completion.chunk", 51 | Created: 0, 52 | Model: "gpt-3.5-turbo-0301", 53 | Choices: []Choices{ 54 | { 55 | Index: 0, 56 | FinishReason: reason, 57 | }, 58 | }, 59 | } 60 | } 61 | 62 | type ChatCompletion struct { 63 | ID string `json:"id"` 64 | Object string `json:"object"` 65 | Created int64 `json:"created"` 66 | Model string `json:"model"` 67 | Usage usage `json:"usage"` 68 | Choices []Choice `json:"choices"` 69 | } 70 | type Msg struct { 71 | Role string `json:"role"` 72 | Content string `json:"content"` 73 | } 74 | type Choice struct { 75 | Index int `json:"index"` 76 | Message Msg `json:"message"` 77 | FinishReason interface{} `json:"finish_reason"` 78 | } 79 | type usage struct { 80 | PromptTokens int `json:"prompt_tokens"` 81 | CompletionTokens int `json:"completion_tokens"` 82 | TotalTokens int `json:"total_tokens"` 83 | } 84 | 85 | func NewChatCompletion(full_test string) ChatCompletion { 86 | return ChatCompletion{ 87 | ID: "chatcmpl-QXlha2FBbmROaXhpZUFyZUF3ZXNvbWUK", 88 | Object: "chat.completion", 89 | Created: int64(0), 90 | Model: "gpt-3.5-turbo-0301", 91 | Usage: usage{ 92 | PromptTokens: 0, 93 | CompletionTokens: 0, 94 | TotalTokens: 0, 95 | }, 96 | Choices: []Choice{ 97 | { 98 | Message: Msg{ 99 | Content: full_test, 100 | Role: "assistant", 101 | }, 102 | Index: 0, 103 | }, 104 | }, 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /typings/typings.go: -------------------------------------------------------------------------------- 1 | package typings 2 | 3 | type GenericResponseLine struct { 4 | Line string `json:"line"` 5 | Error string `json:"error"` 6 | } 7 | 8 | type StringStruct struct { 9 | Text string `json:"text"` 10 | } 11 | --------------------------------------------------------------------------------