Spaces:
				
			
			
	
			
			
		Runtime error
		
	
	
	
			
			
	
	
	
	
		
		
		Runtime error
		
	Upload 25 files
Browse files- Dockerfile +28 -0
- LICENSE +21 -0
- README.md +442 -4
- README_en.md +436 -0
- api/chatgpt/access_token.go +176 -0
- api/chatgpt/api.go +300 -0
- api/chatgpt/constant.go +26 -0
- api/chatgpt/health_check.go +75 -0
- api/chatgpt/typings.go +54 -0
- api/common.go +110 -0
- api/platform/access_token.go +116 -0
- api/platform/api.go +224 -0
- api/platform/constant.go +35 -0
- api/platform/typings.go +93 -0
- compose.yaml +11 -0
- env/env.go +10 -0
- example/chatgpt.http +54 -0
- example/http-client.env.json +8 -0
- example/platform.http +8 -0
- go.mod +44 -0
- go.sum +136 -0
- main.go +119 -0
- middleware/check_header.go +22 -0
- middleware/cors.go +22 -0
- util/logger/logger.go +37 -0
    	
        Dockerfile
    ADDED
    
    | @@ -0,0 +1,28 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            FROM linweiyuan/chatgpt-proxy-server-warp
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            ENV SUDO_USER_NAME dvc890
         | 
| 4 | 
            +
            ENV MIRROR_URL 'https://mirrors.bfsu.edu.cn/archlinux/$repo/os/$arch'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            WORKDIR /app
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            RUN pacman -Sy --needed --noconfirm go
         | 
| 9 | 
            +
            ENV PATH="/usr/local/go/bin:${PATH}"
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            COPY . .
         | 
| 12 | 
            +
            RUN go build -ldflags="-w -s" -o go-chatgpt-api main.go
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            COPY --from=builder /app/go-chatgpt-api .
         | 
| 15 | 
            +
            RUN apk add --no-cache tzdata
         | 
| 16 | 
            +
            ENV TZ=Asia/Shanghai
         | 
| 17 | 
            +
            EXPOSE 8080
         | 
| 18 | 
            +
            EXPOSE 9515
         | 
| 19 | 
            +
            EXPOSE 40000
         | 
| 20 | 
            +
            EXPOSE 65535
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            ENV CHATGPT_PROXY_SERVER=http://localhost:9515
         | 
| 23 | 
            +
            ENV GO_CHATGPT_API_PROXY=socks5://0.0.0.0:65535
         | 
| 24 | 
            +
            ENV LOG_LEVEL=OFF
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            RUN mkdir -p /var/lib/cloudflare-warp
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            CMD ["bash", "-c", "/bin/bash /run.sh & sleep 3 && exec /app/go-chatgpt-api"]
         | 
    	
        LICENSE
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            MIT License
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Copyright (c) 2023 linweiyuan
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            +
            of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            +
            in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            +
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            +
            copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            +
            furnished to do so, subject to the following conditions:
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            The above copyright notice and this permission notice shall be included in all
         | 
| 13 | 
            +
            copies or substantial portions of the Software.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            +
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            +
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            +
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            +
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         | 
| 20 | 
            +
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         | 
| 21 | 
            +
            SOFTWARE.
         | 
    	
        README.md
    CHANGED
    
    | @@ -1,10 +1,448 @@ | |
| 1 | 
             
            ---
         | 
| 2 | 
             
            title: Go Chatgpt Api
         | 
| 3 | 
            -
            emoji:  | 
| 4 | 
            -
            colorFrom:  | 
| 5 | 
            -
            colorTo:  | 
| 6 | 
             
            sdk: docker
         | 
| 7 | 
             
            pinned: false
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 8 | 
             
            ---
         | 
| 9 |  | 
| 10 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
             
            ---
         | 
| 2 | 
             
            title: Go Chatgpt Api
         | 
| 3 | 
            +
            emoji: 💩
         | 
| 4 | 
            +
            colorFrom: green
         | 
| 5 | 
            +
            colorTo: indigo
         | 
| 6 | 
             
            sdk: docker
         | 
| 7 | 
             
            pinned: false
         | 
| 8 | 
            +
            app_port: 8080
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            # go-chatgpt-api
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            ### [English Doc](README_en.md)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            ## 一个尝试绕过 `Cloudflare 403` 和 `Access denied` 的正向代理程序
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            ### 实验性质项目,不保证稳定性和向后兼容,使用风险自负
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            ---
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            ### 使用的过程中遇到问题应该如何解决
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            汇总贴:https://github.com/linweiyuan/go-chatgpt-api/issues/74
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            ---
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            ### 支持的 API(URL 和参数基本保持着和官网一致,部分接口有些许改动)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            ---
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            ### ChatGPT APIs
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            ---
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            - `ChatGPT` 登录(返回 `accessToken`)(目前仅支持 `ChatGPT` 账号,谷歌或微软账号没有测试)
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            `POST /chatgpt/login`
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            <details>
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            ```json
         | 
| 41 | 
            +
            {
         | 
| 42 | 
            +
              "username": "email",
         | 
| 43 | 
            +
              "password": "password"
         | 
| 44 | 
            +
            }
         | 
| 45 | 
            +
            ```
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            </details>
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            ---
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            - 获取对话列表(历史记录)
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            `GET /chatgpt/conversations?offset=0&limit=20`
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            `offset` 不传默认为 0, `limit` 不传默认为 20 (最大为 100)
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            ---
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            - 获取对话内容
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            `GET /chatgpt/conversation/{conversationID}`
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            ---
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            - 新建对话
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            `POST /chatgpt/conversation`
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            <details>
         | 
| 70 | 
            +
             | 
| 71 | 
            +
            ```json
         | 
| 72 | 
            +
            {
         | 
| 73 | 
            +
              "action": "next",
         | 
| 74 | 
            +
              "messages": [
         | 
| 75 | 
            +
                {
         | 
| 76 | 
            +
                  "id": "message id",
         | 
| 77 | 
            +
                  "author": {
         | 
| 78 | 
            +
                    "role": "user"
         | 
| 79 | 
            +
                  },
         | 
| 80 | 
            +
                  "content": {
         | 
| 81 | 
            +
                    "content_type": "text",
         | 
| 82 | 
            +
                    "parts": [
         | 
| 83 | 
            +
                      "Hello World"
         | 
| 84 | 
            +
                    ]
         | 
| 85 | 
            +
                  }
         | 
| 86 | 
            +
                }
         | 
| 87 | 
            +
              ],
         | 
| 88 | 
            +
              "parent_message_id": "parent message id",
         | 
| 89 | 
            +
              "conversation_id": "conversation id",
         | 
| 90 | 
            +
              "model": "text-davinci-002-render-sha",
         | 
| 91 | 
            +
              "timezone_offset_min": -480,
         | 
| 92 | 
            +
              "history_and_training_disabled": false
         | 
| 93 | 
            +
            }
         | 
| 94 | 
            +
            ```
         | 
| 95 | 
            +
             | 
| 96 | 
            +
            </details>
         | 
| 97 | 
            +
             | 
| 98 | 
            +
            ---
         | 
| 99 | 
            +
             | 
| 100 | 
            +
            - 生成对话标题
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            `POST /chatgpt/conversation/gen_title/{conversationID}`
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            <details>
         | 
| 105 | 
            +
             | 
| 106 | 
            +
            ```json
         | 
| 107 | 
            +
            {
         | 
| 108 | 
            +
              "message_id": "role assistant response message id"
         | 
| 109 | 
            +
            }
         | 
| 110 | 
            +
            ```
         | 
| 111 | 
            +
             | 
| 112 | 
            +
            </details>
         | 
| 113 | 
            +
             | 
| 114 | 
             
            ---
         | 
| 115 |  | 
| 116 | 
            +
            - 重命名对话标题
         | 
| 117 | 
            +
             | 
| 118 | 
            +
            `PATCH /chatgpt/conversation/{conversationID}`
         | 
| 119 | 
            +
             | 
| 120 | 
            +
            <details>
         | 
| 121 | 
            +
             | 
| 122 | 
            +
            ```json
         | 
| 123 | 
            +
            {
         | 
| 124 | 
            +
              "title": "new title"
         | 
| 125 | 
            +
            }
         | 
| 126 | 
            +
            ```
         | 
| 127 | 
            +
             | 
| 128 | 
            +
            </details>
         | 
| 129 | 
            +
             | 
| 130 | 
            +
            ---
         | 
| 131 | 
            +
             | 
| 132 | 
            +
            - 删除单个对话
         | 
| 133 | 
            +
             | 
| 134 | 
            +
            `PATCH /chatgpt/conversation/{conversationID}`
         | 
| 135 | 
            +
             | 
| 136 | 
            +
            <details>
         | 
| 137 | 
            +
             | 
| 138 | 
            +
            ```json
         | 
| 139 | 
            +
            {
         | 
| 140 | 
            +
              "is_visible": false
         | 
| 141 | 
            +
            }
         | 
| 142 | 
            +
            ```
         | 
| 143 | 
            +
             | 
| 144 | 
            +
            </details>
         | 
| 145 | 
            +
             | 
| 146 | 
            +
            ---
         | 
| 147 | 
            +
             | 
| 148 | 
            +
            - 删除所有对话
         | 
| 149 | 
            +
             | 
| 150 | 
            +
            `PATCH /chatgpt/conversations`
         | 
| 151 | 
            +
             | 
| 152 | 
            +
            <details>
         | 
| 153 | 
            +
             | 
| 154 | 
            +
            ```json
         | 
| 155 | 
            +
            {
         | 
| 156 | 
            +
              "is_visible": false
         | 
| 157 | 
            +
            }
         | 
| 158 | 
            +
            ```
         | 
| 159 | 
            +
             | 
| 160 | 
            +
            </details>
         | 
| 161 | 
            +
             | 
| 162 | 
            +
            ---
         | 
| 163 | 
            +
             | 
| 164 | 
            +
            - 消息反馈
         | 
| 165 | 
            +
             | 
| 166 | 
            +
            `POST /chatgpt/conversation/message_feedback`
         | 
| 167 | 
            +
             | 
| 168 | 
            +
            <details>
         | 
| 169 | 
            +
             | 
| 170 | 
            +
            ```json
         | 
| 171 | 
            +
            {
         | 
| 172 | 
            +
              "message_id": "message id",
         | 
| 173 | 
            +
              "conversation_id": "conversation id",
         | 
| 174 | 
            +
              "rating": "thumbsUp/thumbsDown"
         | 
| 175 | 
            +
            }
         | 
| 176 | 
            +
            ```
         | 
| 177 | 
            +
             | 
| 178 | 
            +
            </details>
         | 
| 179 | 
            +
             | 
| 180 | 
            +
            ---
         | 
| 181 | 
            +
             | 
| 182 | 
            +
            ### Platform APIs
         | 
| 183 | 
            +
             | 
| 184 | 
            +
            ---
         | 
| 185 | 
            +
             | 
| 186 | 
            +
            - `platform` 登录(返回 `sessionKey`)
         | 
| 187 | 
            +
             | 
| 188 | 
            +
            `POST /platform/login`
         | 
| 189 | 
            +
             | 
| 190 | 
            +
            <details>
         | 
| 191 | 
            +
             | 
| 192 | 
            +
            ```json
         | 
| 193 | 
            +
            {
         | 
| 194 | 
            +
              "username": "email",
         | 
| 195 | 
            +
              "password": "password"
         | 
| 196 | 
            +
            }
         | 
| 197 | 
            +
            ```
         | 
| 198 | 
            +
             | 
| 199 | 
            +
            </details>
         | 
| 200 | 
            +
             | 
| 201 | 
            +
            ---
         | 
| 202 | 
            +
             | 
| 203 | 
            +
            - [List models](https://platform.openai.com/docs/api-reference/models/list)
         | 
| 204 | 
            +
             | 
| 205 | 
            +
            `GET /platform/v1/models`
         | 
| 206 | 
            +
             | 
| 207 | 
            +
            ---
         | 
| 208 | 
            +
             | 
| 209 | 
            +
            - [Retrieve model](https://platform.openai.com/docs/api-reference/models/retrieve)
         | 
| 210 | 
            +
             | 
| 211 | 
            +
            `GET /platform/v1/models/{model}`
         | 
| 212 | 
            +
             | 
| 213 | 
            +
            ---
         | 
| 214 | 
            +
             | 
| 215 | 
            +
            - [Create completion](https://platform.openai.com/docs/api-reference/completions/create)
         | 
| 216 | 
            +
             | 
| 217 | 
            +
            `POST /platform/v1/completions`
         | 
| 218 | 
            +
             | 
| 219 | 
            +
            <details>
         | 
| 220 | 
            +
             | 
| 221 | 
            +
            ```json
         | 
| 222 | 
            +
            {
         | 
| 223 | 
            +
              "model": "text-davinci-003",
         | 
| 224 | 
            +
              "prompt": "Say this is a test",
         | 
| 225 | 
            +
              "max_tokens": 7,
         | 
| 226 | 
            +
              "temperature": 0,
         | 
| 227 | 
            +
              "stream": true
         | 
| 228 | 
            +
            }
         | 
| 229 | 
            +
            ```
         | 
| 230 | 
            +
             | 
| 231 | 
            +
            </details>
         | 
| 232 | 
            +
             | 
| 233 | 
            +
            ---
         | 
| 234 | 
            +
             | 
| 235 | 
            +
            - [Create chat completion](https://platform.openai.com/docs/api-reference/chat/create)
         | 
| 236 | 
            +
             | 
| 237 | 
            +
            `POST /platform/v1/chat/completions`
         | 
| 238 | 
            +
             | 
| 239 | 
            +
            <details>
         | 
| 240 | 
            +
             | 
| 241 | 
            +
            ```json
         | 
| 242 | 
            +
            {
         | 
| 243 | 
            +
              "messages": [
         | 
| 244 | 
            +
                {
         | 
| 245 | 
            +
                  "role": "user",
         | 
| 246 | 
            +
                  "content": "Hello World"
         | 
| 247 | 
            +
                }
         | 
| 248 | 
            +
              ],
         | 
| 249 | 
            +
              "model": "gpt-3.5-turbo",
         | 
| 250 | 
            +
              "stream": true
         | 
| 251 | 
            +
            }
         | 
| 252 | 
            +
            ```
         | 
| 253 | 
            +
             | 
| 254 | 
            +
            </details>
         | 
| 255 | 
            +
             | 
| 256 | 
            +
            ---
         | 
| 257 | 
            +
             | 
| 258 | 
            +
            - [Create edit](https://platform.openai.com/docs/api-reference/edits/create)
         | 
| 259 | 
            +
             | 
| 260 | 
            +
            `POST /platform/v1/edits`
         | 
| 261 | 
            +
             | 
| 262 | 
            +
            <details>
         | 
| 263 | 
            +
             | 
| 264 | 
            +
            ```json
         | 
| 265 | 
            +
            {
         | 
| 266 | 
            +
              "model": "text-davinci-edit-001",
         | 
| 267 | 
            +
              "input": "What day of the wek is it?",
         | 
| 268 | 
            +
              "instruction": "Fix the spelling mistakes"
         | 
| 269 | 
            +
            }
         | 
| 270 | 
            +
            ```
         | 
| 271 | 
            +
             | 
| 272 | 
            +
            </details>
         | 
| 273 | 
            +
             | 
| 274 | 
            +
            ---
         | 
| 275 | 
            +
             | 
| 276 | 
            +
            - [Create image](https://platform.openai.com/docs/api-reference/images/create)
         | 
| 277 | 
            +
             | 
| 278 | 
            +
            `POST /platform/v1/images/generations`
         | 
| 279 | 
            +
             | 
| 280 | 
            +
            <details>
         | 
| 281 | 
            +
             | 
| 282 | 
            +
            ```json
         | 
| 283 | 
            +
            {
         | 
| 284 | 
            +
              "prompt": "A cute dog",
         | 
| 285 | 
            +
              "n": 2,
         | 
| 286 | 
            +
              "size": "1024x1024"
         | 
| 287 | 
            +
            }
         | 
| 288 | 
            +
            ```
         | 
| 289 | 
            +
             | 
| 290 | 
            +
            </details>
         | 
| 291 | 
            +
             | 
| 292 | 
            +
            ---
         | 
| 293 | 
            +
             | 
| 294 | 
            +
            - [Create embeddings](https://platform.openai.com/docs/api-reference/embeddings/create)
         | 
| 295 | 
            +
             | 
| 296 | 
            +
            `POST /platform/v1/embeddings`
         | 
| 297 | 
            +
             | 
| 298 | 
            +
            <details>
         | 
| 299 | 
            +
             | 
| 300 | 
            +
            ```json
         | 
| 301 | 
            +
            {
         | 
| 302 | 
            +
              "model": "text-embedding-ada-002",
         | 
| 303 | 
            +
              "input": "The food was delicious and the waiter..."
         | 
| 304 | 
            +
            }
         | 
| 305 | 
            +
            ```
         | 
| 306 | 
            +
             | 
| 307 | 
            +
            </details>
         | 
| 308 | 
            +
             | 
| 309 | 
            +
            ---
         | 
| 310 | 
            +
             | 
| 311 | 
            +
            - [Create moderations](https://platform.openai.com/docs/api-reference/moderations/create)
         | 
| 312 | 
            +
             | 
| 313 | 
            +
            `POST /platform/v1/moderations`
         | 
| 314 | 
            +
             | 
| 315 | 
            +
            <details>
         | 
| 316 | 
            +
             | 
| 317 | 
            +
            ```json
         | 
| 318 | 
            +
            {
         | 
| 319 | 
            +
              "model": "text-moderation-stable",
         | 
| 320 | 
            +
              "input": "I want to kill them."
         | 
| 321 | 
            +
            }
         | 
| 322 | 
            +
            ```
         | 
| 323 | 
            +
             | 
| 324 | 
            +
            </details>
         | 
| 325 | 
            +
             | 
| 326 | 
            +
            ---
         | 
| 327 | 
            +
             | 
| 328 | 
            +
            - [List files](https://platform.openai.com/docs/api-reference/files/list)
         | 
| 329 | 
            +
             | 
| 330 | 
            +
            `GET /platform/v1/files`
         | 
| 331 | 
            +
             | 
| 332 | 
            +
            ---
         | 
| 333 | 
            +
             | 
| 334 | 
            +
            - 获取 `credit grants` (只能传 `sessionKey`)
         | 
| 335 | 
            +
             | 
| 336 | 
            +
            `GET /platform/dashboard/billing/credit_grants`
         | 
| 337 | 
            +
             | 
| 338 | 
            +
            ---
         | 
| 339 | 
            +
             | 
| 340 | 
            +
            - 获取 `subscription` (只能传 `sessionKey`)
         | 
| 341 | 
            +
             | 
| 342 | 
            +
            `GET /platform/dashboard/billing/subscription`
         | 
| 343 | 
            +
             | 
| 344 | 
            +
            ---
         | 
| 345 | 
            +
             | 
| 346 | 
            +
            - 获取 `api keys` (只能传 `sessionKey`)
         | 
| 347 | 
            +
             | 
| 348 | 
            +
            `GET /platform/dashboard/user/api_keys`
         | 
| 349 | 
            +
             | 
| 350 | 
            +
            ---
         | 
| 351 | 
            +
             | 
| 352 | 
            +
            如需设置代理,可以设置环境变量 `GO_CHATGPT_API_PROXY`,比如 `GO_CHATGPT_API_PROXY=http://127.0.0.1:20171`
         | 
| 353 | 
            +
            或者 `GO_CHATGPT_API_PROXY=socks5://127.0.0.1:20170`,注释掉或者留空则不启用
         | 
| 354 | 
            +
             | 
| 355 | 
            +
            如需配合 `warp` 使用:`GO_CHATGPT_API_PROXY=socks5://chatgpt-proxy-server-warp:65535`,因为需要设置 `warp`
         | 
| 356 | 
            +
            的场景已经默认可以直接访问 `ChatGPT` 官网,因此共用一个变量不冲突
         | 
| 357 | 
            +
             | 
| 358 | 
            +
            ---
         | 
| 359 | 
            +
             | 
| 360 | 
            +
            `docker-compose` 配置文件:
         | 
| 361 | 
            +
             | 
| 362 | 
            +
            ```yaml
         | 
| 363 | 
            +
            services:
         | 
| 364 | 
            +
              go-chatgpt-api:
         | 
| 365 | 
            +
                container_name: go-chatgpt-api
         | 
| 366 | 
            +
                image: linweiyuan/go-chatgpt-api
         | 
| 367 | 
            +
                ports:
         | 
| 368 | 
            +
                  - 8080:8080
         | 
| 369 | 
            +
                environment:
         | 
| 370 | 
            +
                  - GO_CHATGPT_API_PROXY=
         | 
| 371 | 
            +
                restart: unless-stopped
         | 
| 372 | 
            +
            ```
         | 
| 373 | 
            +
             | 
| 374 | 
            +
            我仅仅在 `Arch Linux` 上进行开发和测试,这是一个滚动更新的版本,意味着系统上所有东西都是最新的,如果你在使用的过程中 `yaml`
         | 
| 375 | 
            +
            报错了,则可以加上 `version: '3'` 在 `services:` 前面
         | 
| 376 | 
            +
             | 
| 377 | 
            +
            如果遇到 `Access denied`,但是你的服务器确实在[被支持的国家或地区](https://platform.openai.com/docs/supported-countries)
         | 
| 378 | 
            +
            ,尝试一下这个配置(不保证能解决问题,比如你的服务器在 A 地区,但 A 地不在支持列表内,即使用上了 `warp` 后是 `Cloudflare IP`
         | 
| 379 | 
            +
            ,结果也会是 `403`):
         | 
| 380 | 
            +
             | 
| 381 | 
            +
            ```yaml
         | 
| 382 | 
            +
            services:
         | 
| 383 | 
            +
              go-chatgpt-api:
         | 
| 384 | 
            +
                container_name: go-chatgpt-api
         | 
| 385 | 
            +
                image: linweiyuan/go-chatgpt-api
         | 
| 386 | 
            +
                ports:
         | 
| 387 | 
            +
                  - 8080:8080
         | 
| 388 | 
            +
                environment:
         | 
| 389 | 
            +
                  - GO_CHATGPT_API_PROXY=socks5://chatgpt-proxy-server-warp:65535
         | 
| 390 | 
            +
                depends_on:
         | 
| 391 | 
            +
                  - chatgpt-proxy-server-warp
         | 
| 392 | 
            +
                restart: unless-stopped
         | 
| 393 | 
            +
             | 
| 394 | 
            +
              chatgpt-proxy-server-warp:
         | 
| 395 | 
            +
                container_name: chatgpt-proxy-server-warp
         | 
| 396 | 
            +
                image: linweiyuan/chatgpt-proxy-server-warp
         | 
| 397 | 
            +
                environment:
         | 
| 398 | 
            +
                  - LOG_LEVEL=OFF
         | 
| 399 | 
            +
                restart: unless-stopped
         | 
| 400 | 
            +
            ```
         | 
| 401 | 
            +
             | 
| 402 | 
            +
            如果你知道什么是 `teams-enroll-token`,可以通过环境变量 `TEAMS_ENROLL_TOKEN` 设置它的值
         | 
| 403 | 
            +
             | 
| 404 | 
            +
            然后利用这条命令来检查是否生效:
         | 
| 405 | 
            +
             | 
| 406 | 
            +
            `docker-compose exec chatgpt-proxy-server-warp warp-cli --accept-tos account | awk 'NR==1'`
         | 
| 407 | 
            +
             | 
| 408 | 
            +
            ```
         | 
| 409 | 
            +
            Account type: Free (没有生效)
         | 
| 410 | 
            +
             | 
| 411 | 
            +
            Account type: Team (设置正常)
         | 
| 412 | 
            +
            ```
         | 
| 413 | 
            +
             | 
| 414 | 
            +
            ---
         | 
| 415 | 
            +
             | 
| 416 | 
            +
            如果要让运行的镜像总是保持最新,可以配合这个一起使用:
         | 
| 417 | 
            +
             | 
| 418 | 
            +
            ```yaml
         | 
| 419 | 
            +
            services:
         | 
| 420 | 
            +
              watchtower:
         | 
| 421 | 
            +
                container_name: watchtower
         | 
| 422 | 
            +
                image: containrrr/watchtower
         | 
| 423 | 
            +
                volumes:
         | 
| 424 | 
            +
                  - /var/run/docker.sock:/var/run/docker.sock
         | 
| 425 | 
            +
                command: --interval 3600
         | 
| 426 | 
            +
                restart: unless-stopped
         | 
| 427 | 
            +
            ```
         | 
| 428 | 
            +
             | 
| 429 | 
            +
            <details>
         | 
| 430 | 
            +
             | 
| 431 | 
            +
            <summary>广告位</summary>
         | 
| 432 | 
            +
             | 
| 433 | 
            +
            `Vultr` 推荐链接:https://www.vultr.com/?ref=7372562
         | 
| 434 | 
            +
             | 
| 435 | 
            +
            ---
         | 
| 436 | 
            +
             | 
| 437 | 
            +
            个人微信(没有验证,谁都能加,添加即通过,不用打招呼,直接把问题发出来,日常和私人问题不聊,不进群;可以解答程序使用问题,但最好自己要有一定的基础;可以远程调试,仅限 `SSH`
         | 
| 438 | 
            +
            或`ToDesk`,但不保证能解决):
         | 
| 439 | 
            +
             | 
| 440 | 
            +
            
         | 
| 441 | 
            +
             | 
| 442 | 
            +
            ---
         | 
| 443 | 
            +
             | 
| 444 | 
            +
            微信赞赏码(经济条件允许的可以考虑支持下):
         | 
| 445 | 
            +
             | 
| 446 | 
            +
            
         | 
| 447 | 
            +
             | 
| 448 | 
            +
            </details>
         | 
    	
        README_en.md
    ADDED
    
    | @@ -0,0 +1,436 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # go-chatgpt-api
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            ### [中文文档](README.md)
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ## A forward proxy program attempting to bypass `Cloudflare 403` and `Access Denied`.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ### Experimental project, with no guarantee of stability and backward compatibility, use at your own risk.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            ---
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            ### Troubleshooting
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            English countries does not have the "Great Firewall", so many issues are gone.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            More details: https://github.com/linweiyuan/go-chatgpt-api/issues/74
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            ---
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            ### Supported APIs (URL and parameters are mostly consistent with the official website, with slight modifications to some interfaces).
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            ---
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            ## ChatGPT APIs
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            ---
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            - `ChatGPT` user login (`accessToken` will be returned) (currently `Google` or `Microsoft` accounts are not supported).
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            `POST /chatgpt/login`
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            <details>
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            ```json
         | 
| 34 | 
            +
            {
         | 
| 35 | 
            +
              "username": "email",
         | 
| 36 | 
            +
              "password": "password"
         | 
| 37 | 
            +
            }
         | 
| 38 | 
            +
            ```
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            </details>
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            ---
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            - get conversation list
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            `GET /chatgpt/conversations?offset=0&limit=20`
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            `offset` defaults to 0, `limit` defaults to 20 (max 100).
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            ---
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            - get conversation content
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            `GET /chatgpt/conversation/{conversationID}`
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            ---
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            - create conversation
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            `POST /chatgpt/conversation`
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            <details>
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            ```json
         | 
| 65 | 
            +
            {
         | 
| 66 | 
            +
              "action": "next",
         | 
| 67 | 
            +
              "messages": [
         | 
| 68 | 
            +
                {
         | 
| 69 | 
            +
                  "id": "message id",
         | 
| 70 | 
            +
                  "author": {
         | 
| 71 | 
            +
                    "role": "user"
         | 
| 72 | 
            +
                  },
         | 
| 73 | 
            +
                  "content": {
         | 
| 74 | 
            +
                    "content_type": "text",
         | 
| 75 | 
            +
                    "parts": [
         | 
| 76 | 
            +
                      "Hello World"
         | 
| 77 | 
            +
                    ]
         | 
| 78 | 
            +
                  }
         | 
| 79 | 
            +
                }
         | 
| 80 | 
            +
              ],
         | 
| 81 | 
            +
              "parent_message_id": "parent message id",
         | 
| 82 | 
            +
              "conversation_id": "conversation id",
         | 
| 83 | 
            +
              "model": "text-davinci-002-render-sha",
         | 
| 84 | 
            +
              "timezone_offset_min": -480,
         | 
| 85 | 
            +
              "history_and_training_disabled": false
         | 
| 86 | 
            +
            }
         | 
| 87 | 
            +
            ```
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            </details>
         | 
| 90 | 
            +
             | 
| 91 | 
            +
            ---
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            - generate conversation title
         | 
| 94 | 
            +
             | 
| 95 | 
            +
            `POST /chatgpt/conversation/gen_title/{conversationID}`
         | 
| 96 | 
            +
             | 
| 97 | 
            +
            <details>
         | 
| 98 | 
            +
             | 
| 99 | 
            +
            ```json
         | 
| 100 | 
            +
            {
         | 
| 101 | 
            +
              "message_id": "role assistant response message id"
         | 
| 102 | 
            +
            }
         | 
| 103 | 
            +
            ```
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            </details>
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            ---
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            - rename conversation
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            `PATCH /chatgpt/conversation/{conversationID}`
         | 
| 112 | 
            +
             | 
| 113 | 
            +
            <details>
         | 
| 114 | 
            +
             | 
| 115 | 
            +
            ```json
         | 
| 116 | 
            +
            {
         | 
| 117 | 
            +
              "title": "new title"
         | 
| 118 | 
            +
            }
         | 
| 119 | 
            +
            ```
         | 
| 120 | 
            +
             | 
| 121 | 
            +
            </details>
         | 
| 122 | 
            +
             | 
| 123 | 
            +
            ---
         | 
| 124 | 
            +
             | 
| 125 | 
            +
            - delete conversation
         | 
| 126 | 
            +
             | 
| 127 | 
            +
            `PATCH /chatgpt/conversation/{conversationID}`
         | 
| 128 | 
            +
             | 
| 129 | 
            +
            <details>
         | 
| 130 | 
            +
             | 
| 131 | 
            +
            ```json
         | 
| 132 | 
            +
            {
         | 
| 133 | 
            +
              "is_visible": false
         | 
| 134 | 
            +
            }
         | 
| 135 | 
            +
            ```
         | 
| 136 | 
            +
             | 
| 137 | 
            +
            </details>
         | 
| 138 | 
            +
             | 
| 139 | 
            +
            ---
         | 
| 140 | 
            +
             | 
| 141 | 
            +
            - delete all conversations
         | 
| 142 | 
            +
             | 
| 143 | 
            +
            `PATCH /chatgpt/conversations`
         | 
| 144 | 
            +
             | 
| 145 | 
            +
            <details>
         | 
| 146 | 
            +
             | 
| 147 | 
            +
            ```json
         | 
| 148 | 
            +
            {
         | 
| 149 | 
            +
              "is_visible": false
         | 
| 150 | 
            +
            }
         | 
| 151 | 
            +
            ```
         | 
| 152 | 
            +
             | 
| 153 | 
            +
            </details>
         | 
| 154 | 
            +
             | 
| 155 | 
            +
            ---
         | 
| 156 | 
            +
             | 
| 157 | 
            +
            - feedback message
         | 
| 158 | 
            +
             | 
| 159 | 
            +
            `POST /chatgpt/conversation/message_feedback`
         | 
| 160 | 
            +
             | 
| 161 | 
            +
            <details>
         | 
| 162 | 
            +
             | 
| 163 | 
            +
            ```json
         | 
| 164 | 
            +
            {
         | 
| 165 | 
            +
              "message_id": "message id",
         | 
| 166 | 
            +
              "conversation_id": "conversation id",
         | 
| 167 | 
            +
              "rating": "thumbsUp/thumbsDown"
         | 
| 168 | 
            +
            }
         | 
| 169 | 
            +
            ```
         | 
| 170 | 
            +
             | 
| 171 | 
            +
            </details>
         | 
| 172 | 
            +
             | 
| 173 | 
            +
            ---
         | 
| 174 | 
            +
             | 
| 175 | 
            +
            ## Platform APIs
         | 
| 176 | 
            +
             | 
| 177 | 
            +
            ---
         | 
| 178 | 
            +
             | 
| 179 | 
            +
            - `platform` user login (`sessionKey` will be returned)
         | 
| 180 | 
            +
             | 
| 181 | 
            +
            `POST /platform/login`
         | 
| 182 | 
            +
             | 
| 183 | 
            +
            <details>
         | 
| 184 | 
            +
             | 
| 185 | 
            +
            ```json
         | 
| 186 | 
            +
            {
         | 
| 187 | 
            +
              "username": "email",
         | 
| 188 | 
            +
              "password": "password"
         | 
| 189 | 
            +
            }
         | 
| 190 | 
            +
            ```
         | 
| 191 | 
            +
             | 
| 192 | 
            +
            </details>
         | 
| 193 | 
            +
             | 
| 194 | 
            +
            ---
         | 
| 195 | 
            +
             | 
| 196 | 
            +
            - [List models](https://platform.openai.com/docs/api-reference/models/list)
         | 
| 197 | 
            +
             | 
| 198 | 
            +
            `GET /platform/v1/models`
         | 
| 199 | 
            +
             | 
| 200 | 
            +
            ---
         | 
| 201 | 
            +
             | 
| 202 | 
            +
            - [Retrieve model](https://platform.openai.com/docs/api-reference/models/retrieve)
         | 
| 203 | 
            +
             | 
| 204 | 
            +
            `GET /platform/v1/models/{model}`
         | 
| 205 | 
            +
             | 
| 206 | 
            +
            ---
         | 
| 207 | 
            +
             | 
| 208 | 
            +
            - [Create completion](https://platform.openai.com/docs/api-reference/completions/create)
         | 
| 209 | 
            +
             | 
| 210 | 
            +
            `POST /platform/v1/completions`
         | 
| 211 | 
            +
             | 
| 212 | 
            +
            <details>
         | 
| 213 | 
            +
             | 
| 214 | 
            +
            ```json
         | 
| 215 | 
            +
            {
         | 
| 216 | 
            +
              "model": "text-davinci-003",
         | 
| 217 | 
            +
              "prompt": "Say this is a test",
         | 
| 218 | 
            +
              "max_tokens": 7,
         | 
| 219 | 
            +
              "temperature": 0,
         | 
| 220 | 
            +
              "stream": true
         | 
| 221 | 
            +
            }
         | 
| 222 | 
            +
            ```
         | 
| 223 | 
            +
             | 
| 224 | 
            +
            </details>
         | 
| 225 | 
            +
             | 
| 226 | 
            +
            ---
         | 
| 227 | 
            +
             | 
| 228 | 
            +
            - [Create chat completion](https://platform.openai.com/docs/api-reference/chat/create)
         | 
| 229 | 
            +
             | 
| 230 | 
            +
            `POST /platform/v1/chat/completions`
         | 
| 231 | 
            +
             | 
| 232 | 
            +
            <details>
         | 
| 233 | 
            +
             | 
| 234 | 
            +
            ```json
         | 
| 235 | 
            +
            {
         | 
| 236 | 
            +
              "messages": [
         | 
| 237 | 
            +
                {
         | 
| 238 | 
            +
                  "role": "user",
         | 
| 239 | 
            +
                  "content": "Hello World"
         | 
| 240 | 
            +
                }
         | 
| 241 | 
            +
              ],
         | 
| 242 | 
            +
              "model": "gpt-3.5-turbo",
         | 
| 243 | 
            +
              "stream": true
         | 
| 244 | 
            +
            }
         | 
| 245 | 
            +
            ```
         | 
| 246 | 
            +
             | 
| 247 | 
            +
            </details>
         | 
| 248 | 
            +
             | 
| 249 | 
            +
            ---
         | 
| 250 | 
            +
             | 
| 251 | 
            +
            - [Create edit](https://platform.openai.com/docs/api-reference/edits/create)
         | 
| 252 | 
            +
             | 
| 253 | 
            +
            `POST /platform/v1/edits`
         | 
| 254 | 
            +
             | 
| 255 | 
            +
            <details>
         | 
| 256 | 
            +
             | 
| 257 | 
            +
            ```json
         | 
| 258 | 
            +
            {
         | 
| 259 | 
            +
              "model": "text-davinci-edit-001",
         | 
| 260 | 
            +
              "input": "What day of the wek is it?",
         | 
| 261 | 
            +
              "instruction": "Fix the spelling mistakes"
         | 
| 262 | 
            +
            }
         | 
| 263 | 
            +
            ```
         | 
| 264 | 
            +
             | 
| 265 | 
            +
            </details>
         | 
| 266 | 
            +
             | 
| 267 | 
            +
            ---
         | 
| 268 | 
            +
             | 
| 269 | 
            +
            - [Create image](https://platform.openai.com/docs/api-reference/images/create)
         | 
| 270 | 
            +
             | 
| 271 | 
            +
            `POST /platform/v1/images/generations`
         | 
| 272 | 
            +
             | 
| 273 | 
            +
            <details>
         | 
| 274 | 
            +
             | 
| 275 | 
            +
            ```json
         | 
| 276 | 
            +
            {
         | 
| 277 | 
            +
              "prompt": "A cute dog",
         | 
| 278 | 
            +
              "n": 2,
         | 
| 279 | 
            +
              "size": "1024x1024"
         | 
| 280 | 
            +
            }
         | 
| 281 | 
            +
            ```
         | 
| 282 | 
            +
             | 
| 283 | 
            +
            </details>
         | 
| 284 | 
            +
             | 
| 285 | 
            +
            ---
         | 
| 286 | 
            +
             | 
| 287 | 
            +
            - [Create embeddings](https://platform.openai.com/docs/api-reference/embeddings/create)
         | 
| 288 | 
            +
             | 
| 289 | 
            +
            `POST /platform/v1/embeddings`
         | 
| 290 | 
            +
             | 
| 291 | 
            +
            <details>
         | 
| 292 | 
            +
             | 
| 293 | 
            +
            ```json
         | 
| 294 | 
            +
            {
         | 
| 295 | 
            +
              "model": "text-embedding-ada-002",
         | 
| 296 | 
            +
              "input": "The food was delicious and the waiter..."
         | 
| 297 | 
            +
            }
         | 
| 298 | 
            +
            ```
         | 
| 299 | 
            +
             | 
| 300 | 
            +
            </details>
         | 
| 301 | 
            +
             | 
| 302 | 
            +
            ---
         | 
| 303 | 
            +
             | 
| 304 | 
            +
            - [Create moderations](https://platform.openai.com/docs/api-reference/moderations/create)
         | 
| 305 | 
            +
             | 
| 306 | 
            +
            `POST /platform/v1/moderations`
         | 
| 307 | 
            +
             | 
| 308 | 
            +
            <details>
         | 
| 309 | 
            +
             | 
| 310 | 
            +
            ```json
         | 
| 311 | 
            +
            {
         | 
| 312 | 
            +
              "model": "text-moderation-stable",
         | 
| 313 | 
            +
              "input": "I want to kill them."
         | 
| 314 | 
            +
            }
         | 
| 315 | 
            +
            ```
         | 
| 316 | 
            +
             | 
| 317 | 
            +
            </details>
         | 
| 318 | 
            +
             | 
| 319 | 
            +
            ---
         | 
| 320 | 
            +
             | 
| 321 | 
            +
            - [List files](https://platform.openai.com/docs/api-reference/files/list)
         | 
| 322 | 
            +
             | 
| 323 | 
            +
            `GET /platform/v1/files`
         | 
| 324 | 
            +
             | 
| 325 | 
            +
            ---
         | 
| 326 | 
            +
             | 
| 327 | 
            +
            - get `credit grants` (only support `sessionkey`)
         | 
| 328 | 
            +
             | 
| 329 | 
            +
            `GET /platform/dashboard/billing/credit_grants`
         | 
| 330 | 
            +
             | 
| 331 | 
            +
            ---
         | 
| 332 | 
            +
             | 
| 333 | 
            +
            - get `subscription` (only support `sessionkey`)
         | 
| 334 | 
            +
             | 
| 335 | 
            +
            `GET /platform/dashboard/billing/subscription`
         | 
| 336 | 
            +
             | 
| 337 | 
            +
            ---
         | 
| 338 | 
            +
             | 
| 339 | 
            +
            - get `api keys` (only support `sessionkey`)
         | 
| 340 | 
            +
             | 
| 341 | 
            +
            `GET /platform/dashboard/user/api_keys`
         | 
| 342 | 
            +
             | 
| 343 | 
            +
            ---
         | 
| 344 | 
            +
             | 
| 345 | 
            +
            ### Configuration
         | 
| 346 | 
            +
             | 
| 347 | 
            +
            To set a proxy, you can use the environment variable `GO_CHATGPT_API_PROXY`, such
         | 
| 348 | 
            +
            as `GO_CHATGPT_API_PROXY=http://127.0.0.1:20171` or `GO_CHATGPT_API_PROXY=socks5://127.0.0.1:20170`. If it is commented
         | 
| 349 | 
            +
            out or left blank, it will not be enabled.
         | 
| 350 | 
            +
             | 
| 351 | 
            +
            To use with `warp`: `GO_CHATGPT_API_PROXY=socks5://chatgpt-proxy-server-warp:65535`. Since the scenario that requires
         | 
| 352 | 
            +
            setting up `warp` can directly access the `ChatGPT` website by default, using the same variable will not cause
         | 
| 353 | 
            +
            conflicts.
         | 
| 354 | 
            +
             | 
| 355 | 
            +
            ---
         | 
| 356 | 
            +
             | 
| 357 | 
            +
            `docker-compose.yaml`:
         | 
| 358 | 
            +
             | 
| 359 | 
            +
            ```yaml
         | 
| 360 | 
            +
            services:
         | 
| 361 | 
            +
              go-chatgpt-api:
         | 
| 362 | 
            +
                container_name: go-chatgpt-api
         | 
| 363 | 
            +
                image: linweiyuan/go-chatgpt-api
         | 
| 364 | 
            +
                ports:
         | 
| 365 | 
            +
                  - 8080:8080
         | 
| 366 | 
            +
                environment:
         | 
| 367 | 
            +
                  - GO_CHATGPT_API_PROXY=
         | 
| 368 | 
            +
                restart: unless-stopped
         | 
| 369 | 
            +
            ```
         | 
| 370 | 
            +
             | 
| 371 | 
            +
            I only develop and test on `Arch Linux`, which is a `rolling` release version, meaning that everything on the system is
         | 
| 372 | 
            +
            `up-to-date`. If you encounter a `yaml` error while using it, you can add `version: '3'` in front of `services:`.
         | 
| 373 | 
            +
             | 
| 374 | 
            +
            If you encounter an `Access denied` error, but your server is indeed
         | 
| 375 | 
            +
            in [Supported countries and territories](https://platform.openai.com/docs/supported-countries), try this
         | 
| 376 | 
            +
            configuration (it is not guaranteed to solve the problem, for example, if your server is in `Zone A`, but `Zone A`
         | 
| 377 | 
            +
            is not on the list of supported countries, even if you use `warp` to change to a `Cloudflare IP`, the result will still
         | 
| 378 | 
            +
            be
         | 
| 379 | 
            +
            `403`):
         | 
| 380 | 
            +
             | 
| 381 | 
            +
            ```yaml
         | 
| 382 | 
            +
            services:
         | 
| 383 | 
            +
              go-chatgpt-api:
         | 
| 384 | 
            +
                container_name: go-chatgpt-api
         | 
| 385 | 
            +
                image: linweiyuan/go-chatgpt-api
         | 
| 386 | 
            +
                ports:
         | 
| 387 | 
            +
                  - 8080:8080
         | 
| 388 | 
            +
                environment:
         | 
| 389 | 
            +
                  - GO_CHATGPT_API_PROXY=socks5://chatgpt-proxy-server-warp:65535
         | 
| 390 | 
            +
                depends_on:
         | 
| 391 | 
            +
                  - chatgpt-proxy-server-warp
         | 
| 392 | 
            +
                restart: unless-stopped
         | 
| 393 | 
            +
             | 
| 394 | 
            +
              chatgpt-proxy-server-warp:
         | 
| 395 | 
            +
                container_name: chatgpt-proxy-server-warp
         | 
| 396 | 
            +
                image: linweiyuan/chatgpt-proxy-server-warp
         | 
| 397 | 
            +
                environment:
         | 
| 398 | 
            +
                  - LOG_LEVEL=OFF
         | 
| 399 | 
            +
                restart: unless-stopped
         | 
| 400 | 
            +
            ```
         | 
| 401 | 
            +
             | 
| 402 | 
            +
            If you know what `teams-enroll-token` is and want to set its value, you can do so through the environment
         | 
| 403 | 
            +
            variable `TEAMS_ENROLL_TOKEN`.
         | 
| 404 | 
            +
             | 
| 405 | 
            +
            Run this command to check the result:
         | 
| 406 | 
            +
             | 
| 407 | 
            +
            `docker-compose exec chatgpt-proxy-server-warp warp-cli --accept-tos account | awk 'NR==1'`
         | 
| 408 | 
            +
             | 
| 409 | 
            +
            ```
         | 
| 410 | 
            +
            Account type: Free (wrong)
         | 
| 411 | 
            +
             | 
| 412 | 
            +
            Account type: Team (correct)
         | 
| 413 | 
            +
            ```
         | 
| 414 | 
            +
             | 
| 415 | 
            +
            ---
         | 
| 416 | 
            +
             | 
| 417 | 
            +
            If you want to make sure the image is always latest, try this:
         | 
| 418 | 
            +
             | 
| 419 | 
            +
            ```yaml
         | 
| 420 | 
            +
            services:
         | 
| 421 | 
            +
              watchtower:
         | 
| 422 | 
            +
                container_name: watchtower
         | 
| 423 | 
            +
                image: containrrr/watchtower
         | 
| 424 | 
            +
                volumes:
         | 
| 425 | 
            +
                  - /var/run/docker.sock:/var/run/docker.sock
         | 
| 426 | 
            +
                command: --interval 3600
         | 
| 427 | 
            +
                restart: unless-stopped
         | 
| 428 | 
            +
            ```
         | 
| 429 | 
            +
             | 
| 430 | 
            +
            <details>
         | 
| 431 | 
            +
             | 
| 432 | 
            +
            <summary>AD</summary>
         | 
| 433 | 
            +
             | 
| 434 | 
            +
            `Vultr` Referral Program: https://www.vultr.com/?ref=7372562
         | 
| 435 | 
            +
             | 
| 436 | 
            +
            </details>
         | 
    	
        api/chatgpt/access_token.go
    ADDED
    
    | @@ -0,0 +1,176 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            package chatgpt
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import (
         | 
| 4 | 
            +
            	"encoding/json"
         | 
| 5 | 
            +
            	"errors"
         | 
| 6 | 
            +
            	"fmt"
         | 
| 7 | 
            +
            	"io"
         | 
| 8 | 
            +
            	"strings"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            	"github.com/PuerkitoBio/goquery"
         | 
| 11 | 
            +
            	"github.com/linweiyuan/go-chatgpt-api/api"
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            	http "github.com/bogdanfinn/fhttp"
         | 
| 14 | 
            +
            )
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            //goland:noinspection GoUnhandledErrorResult,GoErrorStringFormat
         | 
| 17 | 
            +
            func (userLogin *UserLogin) GetAuthorizedUrl(csrfToken string) (string, int, error) {
         | 
| 18 | 
            +
            	params := fmt.Sprintf(
         | 
| 19 | 
            +
            		"callbackUrl=/&csrfToken=%s&json=true",
         | 
| 20 | 
            +
            		csrfToken,
         | 
| 21 | 
            +
            	)
         | 
| 22 | 
            +
            	req, err := http.NewRequest(http.MethodPost, promptLoginUrl, strings.NewReader(params))
         | 
| 23 | 
            +
            	req.Header.Set("Content-Type", api.ContentType)
         | 
| 24 | 
            +
            	req.Header.Set("User-Agent", api.UserAgent)
         | 
| 25 | 
            +
            	resp, err := userLogin.client.Do(req)
         | 
| 26 | 
            +
            	if err != nil {
         | 
| 27 | 
            +
            		return "", http.StatusInternalServerError, err
         | 
| 28 | 
            +
            	}
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            	defer resp.Body.Close()
         | 
| 31 | 
            +
            	if resp.StatusCode != http.StatusOK {
         | 
| 32 | 
            +
            		return "", resp.StatusCode, errors.New(api.GetAuthorizedUrlErrorMessage)
         | 
| 33 | 
            +
            	}
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            	responseMap := make(map[string]string)
         | 
| 36 | 
            +
            	json.NewDecoder(resp.Body).Decode(&responseMap)
         | 
| 37 | 
            +
            	return responseMap["url"], http.StatusOK, nil
         | 
| 38 | 
            +
            }
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            //goland:noinspection GoUnhandledErrorResult,GoErrorStringFormat
         | 
| 41 | 
            +
            func (userLogin *UserLogin) GetState(authorizedUrl string) (string, int, error) {
         | 
| 42 | 
            +
            	req, err := http.NewRequest(http.MethodGet, authorizedUrl, nil)
         | 
| 43 | 
            +
            	req.Header.Set("Content-Type", api.ContentType)
         | 
| 44 | 
            +
            	req.Header.Set("User-Agent", api.UserAgent)
         | 
| 45 | 
            +
            	resp, err := userLogin.client.Do(req)
         | 
| 46 | 
            +
            	if err != nil {
         | 
| 47 | 
            +
            		return "", http.StatusInternalServerError, err
         | 
| 48 | 
            +
            	}
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            	defer resp.Body.Close()
         | 
| 51 | 
            +
            	if resp.StatusCode != http.StatusOK {
         | 
| 52 | 
            +
            		return "", resp.StatusCode, errors.New(api.GetStateErrorMessage)
         | 
| 53 | 
            +
            	}
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            	doc, _ := goquery.NewDocumentFromReader(resp.Body)
         | 
| 56 | 
            +
            	state, _ := doc.Find("input[name=state]").Attr("value")
         | 
| 57 | 
            +
            	return state, http.StatusOK, nil
         | 
| 58 | 
            +
            }
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            //goland:noinspection GoUnhandledErrorResult,GoErrorStringFormat
         | 
| 61 | 
            +
            func (userLogin *UserLogin) CheckUsername(state string, username string) (int, error) {
         | 
| 62 | 
            +
            	formParams := fmt.Sprintf(
         | 
| 63 | 
            +
            		"state=%s&username=%s&js-available=true&webauthn-available=true&is-brave=false&webauthn-platform-available=false&action=default",
         | 
| 64 | 
            +
            		state,
         | 
| 65 | 
            +
            		username,
         | 
| 66 | 
            +
            	)
         | 
| 67 | 
            +
            	req, _ := http.NewRequest(http.MethodPost, api.LoginUsernameUrl+state, strings.NewReader(formParams))
         | 
| 68 | 
            +
            	req.Header.Set("Content-Type", api.ContentType)
         | 
| 69 | 
            +
            	req.Header.Set("User-Agent", api.UserAgent)
         | 
| 70 | 
            +
            	resp, err := userLogin.client.Do(req)
         | 
| 71 | 
            +
            	if err != nil {
         | 
| 72 | 
            +
            		return http.StatusInternalServerError, err
         | 
| 73 | 
            +
            	}
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            	defer resp.Body.Close()
         | 
| 76 | 
            +
            	if resp.StatusCode != http.StatusOK {
         | 
| 77 | 
            +
            		return resp.StatusCode, errors.New(api.EmailInvalidErrorMessage)
         | 
| 78 | 
            +
            	}
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            	return http.StatusOK, nil
         | 
| 81 | 
            +
            }
         | 
| 82 | 
            +
             | 
| 83 | 
            +
            //goland:noinspection GoUnhandledErrorResult,GoErrorStringFormat
         | 
| 84 | 
            +
            func (userLogin *UserLogin) CheckPassword(state string, username string, password string) (string, int, error) {
         | 
| 85 | 
            +
            	formParams := fmt.Sprintf(
         | 
| 86 | 
            +
            		"state=%s&username=%s&password=%s&action=default",
         | 
| 87 | 
            +
            		state,
         | 
| 88 | 
            +
            		username,
         | 
| 89 | 
            +
            		password,
         | 
| 90 | 
            +
            	)
         | 
| 91 | 
            +
            	req, err := http.NewRequest(http.MethodPost, api.LoginPasswordUrl+state, strings.NewReader(formParams))
         | 
| 92 | 
            +
            	req.Header.Set("Content-Type", api.ContentType)
         | 
| 93 | 
            +
            	req.Header.Set("User-Agent", api.UserAgent)
         | 
| 94 | 
            +
            	userLogin.client.SetFollowRedirect(false)
         | 
| 95 | 
            +
            	resp, err := userLogin.client.Do(req)
         | 
| 96 | 
            +
            	if err != nil {
         | 
| 97 | 
            +
            		return "", http.StatusInternalServerError, err
         | 
| 98 | 
            +
            	}
         | 
| 99 | 
            +
             | 
| 100 | 
            +
            	defer resp.Body.Close()
         | 
| 101 | 
            +
            	if resp.StatusCode == http.StatusBadRequest {
         | 
| 102 | 
            +
            		doc, _ := goquery.NewDocumentFromReader(resp.Body)
         | 
| 103 | 
            +
            		alert := doc.Find("#prompt-alert").Text()
         | 
| 104 | 
            +
            		if alert != "" {
         | 
| 105 | 
            +
            			return "", resp.StatusCode, errors.New(strings.TrimSpace(alert))
         | 
| 106 | 
            +
            		}
         | 
| 107 | 
            +
             | 
| 108 | 
            +
            		return "", resp.StatusCode, errors.New(api.EmailOrPasswordInvalidErrorMessage)
         | 
| 109 | 
            +
            	}
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            	if resp.StatusCode == http.StatusFound {
         | 
| 112 | 
            +
            		req, _ := http.NewRequest(http.MethodGet, api.Auth0Url+resp.Header.Get("Location"), nil)
         | 
| 113 | 
            +
            		req.Header.Set("User-Agent", api.UserAgent)
         | 
| 114 | 
            +
            		resp, err := userLogin.client.Do(req)
         | 
| 115 | 
            +
            		if err != nil {
         | 
| 116 | 
            +
            			return "", http.StatusInternalServerError, err
         | 
| 117 | 
            +
            		}
         | 
| 118 | 
            +
             | 
| 119 | 
            +
            		defer resp.Body.Close()
         | 
| 120 | 
            +
            		if resp.StatusCode == http.StatusFound {
         | 
| 121 | 
            +
            			location := resp.Header.Get("Location")
         | 
| 122 | 
            +
            			if strings.HasPrefix(location, "/u/mfa-otp-challenge") {
         | 
| 123 | 
            +
            				return "", http.StatusBadRequest, errors.New("Login with two-factor authentication enabled is not supported currently.")
         | 
| 124 | 
            +
            			}
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            			req, _ := http.NewRequest(http.MethodGet, location, nil)
         | 
| 127 | 
            +
            			req.Header.Set("User-Agent", api.UserAgent)
         | 
| 128 | 
            +
            			resp, err := userLogin.client.Do(req)
         | 
| 129 | 
            +
            			if err != nil {
         | 
| 130 | 
            +
            				return "", http.StatusInternalServerError, err
         | 
| 131 | 
            +
            			}
         | 
| 132 | 
            +
             | 
| 133 | 
            +
            			defer resp.Body.Close()
         | 
| 134 | 
            +
            			if resp.StatusCode == http.StatusFound {
         | 
| 135 | 
            +
            				return "", http.StatusOK, nil
         | 
| 136 | 
            +
            			}
         | 
| 137 | 
            +
             | 
| 138 | 
            +
            			if resp.StatusCode == http.StatusTemporaryRedirect {
         | 
| 139 | 
            +
            				errorDescription := req.URL.Query().Get("error_description")
         | 
| 140 | 
            +
            				if errorDescription != "" {
         | 
| 141 | 
            +
            					return "", resp.StatusCode, errors.New(errorDescription)
         | 
| 142 | 
            +
            				}
         | 
| 143 | 
            +
            			}
         | 
| 144 | 
            +
             | 
| 145 | 
            +
            			return "", resp.StatusCode, errors.New(api.GetAccessTokenErrorMessage)
         | 
| 146 | 
            +
            		}
         | 
| 147 | 
            +
             | 
| 148 | 
            +
            		return "", resp.StatusCode, errors.New(api.EmailOrPasswordInvalidErrorMessage)
         | 
| 149 | 
            +
            	}
         | 
| 150 | 
            +
             | 
| 151 | 
            +
            	return "", resp.StatusCode, nil
         | 
| 152 | 
            +
            }
         | 
| 153 | 
            +
             | 
| 154 | 
            +
            //goland:noinspection GoUnhandledErrorResult,GoErrorStringFormat,GoUnusedParameter
         | 
| 155 | 
            +
            func (userLogin *UserLogin) GetAccessToken(code string) (string, int, error) {
         | 
| 156 | 
            +
            	req, err := http.NewRequest(http.MethodGet, authSessionUrl, nil)
         | 
| 157 | 
            +
            	req.Header.Set("User-Agent", api.UserAgent)
         | 
| 158 | 
            +
            	resp, err := userLogin.client.Do(req)
         | 
| 159 | 
            +
            	if err != nil {
         | 
| 160 | 
            +
            		return "", http.StatusInternalServerError, err
         | 
| 161 | 
            +
            	}
         | 
| 162 | 
            +
             | 
| 163 | 
            +
            	defer resp.Body.Close()
         | 
| 164 | 
            +
            	if resp.StatusCode != http.StatusOK {
         | 
| 165 | 
            +
            		if resp.StatusCode == http.StatusTooManyRequests {
         | 
| 166 | 
            +
            			responseMap := make(map[string]string)
         | 
| 167 | 
            +
            			json.NewDecoder(resp.Body).Decode(&responseMap)
         | 
| 168 | 
            +
            			return "", resp.StatusCode, errors.New(responseMap["detail"])
         | 
| 169 | 
            +
            		}
         | 
| 170 | 
            +
             | 
| 171 | 
            +
            		return "", resp.StatusCode, errors.New(api.GetAccessTokenErrorMessage)
         | 
| 172 | 
            +
            	}
         | 
| 173 | 
            +
             | 
| 174 | 
            +
            	data, _ := io.ReadAll(resp.Body)
         | 
| 175 | 
            +
            	return string(data), http.StatusOK, nil
         | 
| 176 | 
            +
            }
         | 
    	
        api/chatgpt/api.go
    ADDED
    
    | @@ -0,0 +1,300 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            package chatgpt
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import (
         | 
| 4 | 
            +
            	"bytes"
         | 
| 5 | 
            +
            	"encoding/json"
         | 
| 6 | 
            +
            	"fmt"
         | 
| 7 | 
            +
            	"io"
         | 
| 8 | 
            +
            	"strings"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            	"github.com/PuerkitoBio/goquery"
         | 
| 11 | 
            +
            	"github.com/gin-gonic/gin"
         | 
| 12 | 
            +
            	"github.com/linweiyuan/go-chatgpt-api/api"
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            	http "github.com/bogdanfinn/fhttp"
         | 
| 15 | 
            +
            )
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 18 | 
            +
            func GetConversations(c *gin.Context) {
         | 
| 19 | 
            +
            	offset, ok := c.GetQuery("offset")
         | 
| 20 | 
            +
            	if !ok {
         | 
| 21 | 
            +
            		offset = "0"
         | 
| 22 | 
            +
            	}
         | 
| 23 | 
            +
            	limit, ok := c.GetQuery("limit")
         | 
| 24 | 
            +
            	if !ok {
         | 
| 25 | 
            +
            		limit = "20"
         | 
| 26 | 
            +
            	}
         | 
| 27 | 
            +
            	handleGet(c, apiPrefix+"/conversations?offset="+offset+"&limit="+limit, getConversationsErrorMessage)
         | 
| 28 | 
            +
            }
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 31 | 
            +
            func CreateConversation(c *gin.Context) {
         | 
| 32 | 
            +
            	var request CreateConversationRequest
         | 
| 33 | 
            +
            	if err := c.BindJSON(&request); err != nil {
         | 
| 34 | 
            +
            		c.AbortWithStatusJSON(http.StatusBadRequest, api.ReturnMessage(parseJsonErrorMessage))
         | 
| 35 | 
            +
            		return
         | 
| 36 | 
            +
            	}
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            	if request.ConversationID == nil || *request.ConversationID == "" {
         | 
| 39 | 
            +
            		request.ConversationID = nil
         | 
| 40 | 
            +
            	}
         | 
| 41 | 
            +
            	if request.Messages[0].Author.Role == "" {
         | 
| 42 | 
            +
            		request.Messages[0].Author.Role = defaultRole
         | 
| 43 | 
            +
            	}
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            	if request.Model == gpt4Model {
         | 
| 46 | 
            +
            		formParams := fmt.Sprintf(
         | 
| 47 | 
            +
            			"public_key=%s",
         | 
| 48 | 
            +
            			gpt4PublicKey,
         | 
| 49 | 
            +
            		)
         | 
| 50 | 
            +
            		req, _ := http.NewRequest(http.MethodPost, gpt4TokenUrl, strings.NewReader(formParams))
         | 
| 51 | 
            +
            		req.Header.Set("Content-Type", api.ContentType)
         | 
| 52 | 
            +
            		resp, err := api.Client.Do(req)
         | 
| 53 | 
            +
            		if err != nil {
         | 
| 54 | 
            +
            			c.AbortWithStatusJSON(http.StatusInternalServerError, api.ReturnMessage(err.Error()))
         | 
| 55 | 
            +
            			return
         | 
| 56 | 
            +
            		}
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            		responseMap := make(map[string]string)
         | 
| 59 | 
            +
            		json.NewDecoder(resp.Body).Decode(&responseMap)
         | 
| 60 | 
            +
            		request.ArkoseToken = responseMap["token"]
         | 
| 61 | 
            +
            	}
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            	jsonBytes, _ := json.Marshal(request)
         | 
| 64 | 
            +
            	req, _ := http.NewRequest(http.MethodPost, apiPrefix+"/conversation", bytes.NewBuffer(jsonBytes))
         | 
| 65 | 
            +
            	req.Header.Set("User-Agent", api.UserAgent)
         | 
| 66 | 
            +
            	req.Header.Set("Authorization", api.GetAccessToken(c.GetHeader(api.AuthorizationHeader)))
         | 
| 67 | 
            +
            	req.Header.Set("Accept", "text/event-stream")
         | 
| 68 | 
            +
            	resp, err := api.Client.Do(req)
         | 
| 69 | 
            +
            	if err != nil {
         | 
| 70 | 
            +
            		c.AbortWithStatusJSON(http.StatusInternalServerError, api.ReturnMessage(err.Error()))
         | 
| 71 | 
            +
            		return
         | 
| 72 | 
            +
            	}
         | 
| 73 | 
            +
             | 
| 74 | 
            +
            	defer resp.Body.Close()
         | 
| 75 | 
            +
            	if resp.StatusCode != http.StatusOK {
         | 
| 76 | 
            +
            		responseMap := make(map[string]interface{})
         | 
| 77 | 
            +
            		json.NewDecoder(resp.Body).Decode(&responseMap)
         | 
| 78 | 
            +
            		c.AbortWithStatusJSON(resp.StatusCode, responseMap)
         | 
| 79 | 
            +
            		return
         | 
| 80 | 
            +
            	}
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            	api.HandleConversationResponse(c, resp)
         | 
| 83 | 
            +
            }
         | 
| 84 | 
            +
             | 
| 85 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 86 | 
            +
            func GenerateTitle(c *gin.Context) {
         | 
| 87 | 
            +
            	var request GenerateTitleRequest
         | 
| 88 | 
            +
            	if err := c.BindJSON(&request); err != nil {
         | 
| 89 | 
            +
            		c.AbortWithStatusJSON(http.StatusBadRequest, api.ReturnMessage(parseJsonErrorMessage))
         | 
| 90 | 
            +
            		return
         | 
| 91 | 
            +
            	}
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            	jsonBytes, _ := json.Marshal(request)
         | 
| 94 | 
            +
            	handlePost(c, apiPrefix+"/conversation/gen_title/"+c.Param("id"), string(jsonBytes), generateTitleErrorMessage)
         | 
| 95 | 
            +
            }
         | 
| 96 | 
            +
             | 
| 97 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 98 | 
            +
            func GetConversation(c *gin.Context) {
         | 
| 99 | 
            +
            	handleGet(c, apiPrefix+"/conversation/"+c.Param("id"), getContentErrorMessage)
         | 
| 100 | 
            +
            }
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 103 | 
            +
            func UpdateConversation(c *gin.Context) {
         | 
| 104 | 
            +
            	var request PatchConversationRequest
         | 
| 105 | 
            +
            	if err := c.BindJSON(&request); err != nil {
         | 
| 106 | 
            +
            		c.AbortWithStatusJSON(http.StatusBadRequest, api.ReturnMessage(parseJsonErrorMessage))
         | 
| 107 | 
            +
            		return
         | 
| 108 | 
            +
            	}
         | 
| 109 | 
            +
             | 
| 110 | 
            +
            	// bool default to false, then will hide (delete) the conversation
         | 
| 111 | 
            +
            	if request.Title != nil {
         | 
| 112 | 
            +
            		request.IsVisible = true
         | 
| 113 | 
            +
            	}
         | 
| 114 | 
            +
            	jsonBytes, _ := json.Marshal(request)
         | 
| 115 | 
            +
            	handlePatch(c, apiPrefix+"/conversation/"+c.Param("id"), string(jsonBytes), updateConversationErrorMessage)
         | 
| 116 | 
            +
            }
         | 
| 117 | 
            +
             | 
| 118 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 119 | 
            +
            func FeedbackMessage(c *gin.Context) {
         | 
| 120 | 
            +
            	var request FeedbackMessageRequest
         | 
| 121 | 
            +
            	if err := c.BindJSON(&request); err != nil {
         | 
| 122 | 
            +
            		c.AbortWithStatusJSON(http.StatusBadRequest, api.ReturnMessage(parseJsonErrorMessage))
         | 
| 123 | 
            +
            		return
         | 
| 124 | 
            +
            	}
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            	jsonBytes, _ := json.Marshal(request)
         | 
| 127 | 
            +
            	handlePost(c, apiPrefix+"/conversation/message_feedback", string(jsonBytes), feedbackMessageErrorMessage)
         | 
| 128 | 
            +
            }
         | 
| 129 | 
            +
             | 
| 130 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 131 | 
            +
            func ClearConversations(c *gin.Context) {
         | 
| 132 | 
            +
            	jsonBytes, _ := json.Marshal(PatchConversationRequest{
         | 
| 133 | 
            +
            		IsVisible: false,
         | 
| 134 | 
            +
            	})
         | 
| 135 | 
            +
            	handlePatch(c, apiPrefix+"/conversations", string(jsonBytes), clearConversationsErrorMessage)
         | 
| 136 | 
            +
            }
         | 
| 137 | 
            +
             | 
| 138 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 139 | 
            +
            func GetModels(c *gin.Context) {
         | 
| 140 | 
            +
            	handleGet(c, apiPrefix+"/models", getModelsErrorMessage)
         | 
| 141 | 
            +
            }
         | 
| 142 | 
            +
             | 
| 143 | 
            +
            func GetAccountCheck(c *gin.Context) {
         | 
| 144 | 
            +
            	handleGet(c, apiPrefix+"/accounts/check", getAccountCheckErrorMessage)
         | 
| 145 | 
            +
            }
         | 
| 146 | 
            +
             | 
| 147 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 148 | 
            +
            func Login(c *gin.Context) {
         | 
| 149 | 
            +
            	var loginInfo api.LoginInfo
         | 
| 150 | 
            +
            	if err := c.ShouldBindJSON(&loginInfo); err != nil {
         | 
| 151 | 
            +
            		c.AbortWithStatusJSON(http.StatusBadRequest, api.ReturnMessage(api.ParseUserInfoErrorMessage))
         | 
| 152 | 
            +
            		return
         | 
| 153 | 
            +
            	}
         | 
| 154 | 
            +
             | 
| 155 | 
            +
            	userLogin := UserLogin{
         | 
| 156 | 
            +
            		client: api.NewHttpClient(),
         | 
| 157 | 
            +
            	}
         | 
| 158 | 
            +
             | 
| 159 | 
            +
            	// get csrf token
         | 
| 160 | 
            +
            	req, _ := http.NewRequest(http.MethodGet, csrfUrl, nil)
         | 
| 161 | 
            +
            	req.Header.Set("User-Agent", api.UserAgent)
         | 
| 162 | 
            +
            	resp, err := userLogin.client.Do(req)
         | 
| 163 | 
            +
            	if err != nil {
         | 
| 164 | 
            +
            		c.AbortWithStatusJSON(http.StatusInternalServerError, api.ReturnMessage(err.Error()))
         | 
| 165 | 
            +
            		return
         | 
| 166 | 
            +
            	}
         | 
| 167 | 
            +
             | 
| 168 | 
            +
            	defer resp.Body.Close()
         | 
| 169 | 
            +
            	if resp.StatusCode != http.StatusOK {
         | 
| 170 | 
            +
            		if resp.StatusCode == http.StatusForbidden {
         | 
| 171 | 
            +
            			doc, _ := goquery.NewDocumentFromReader(resp.Body)
         | 
| 172 | 
            +
            			alert := doc.Find(".message").Text()
         | 
| 173 | 
            +
            			if alert != "" {
         | 
| 174 | 
            +
            				c.AbortWithStatusJSON(resp.StatusCode, api.ReturnMessage(strings.TrimSpace(alert)))
         | 
| 175 | 
            +
            				return
         | 
| 176 | 
            +
            			}
         | 
| 177 | 
            +
            		}
         | 
| 178 | 
            +
             | 
| 179 | 
            +
            		c.AbortWithStatusJSON(resp.StatusCode, api.ReturnMessage(getCsrfTokenErrorMessage))
         | 
| 180 | 
            +
            		return
         | 
| 181 | 
            +
            	}
         | 
| 182 | 
            +
             | 
| 183 | 
            +
            	// get authorized url
         | 
| 184 | 
            +
            	responseMap := make(map[string]string)
         | 
| 185 | 
            +
            	json.NewDecoder(resp.Body).Decode(&responseMap)
         | 
| 186 | 
            +
            	authorizedUrl, statusCode, err := userLogin.GetAuthorizedUrl(responseMap["csrfToken"])
         | 
| 187 | 
            +
            	if err != nil {
         | 
| 188 | 
            +
            		c.AbortWithStatusJSON(statusCode, api.ReturnMessage(err.Error()))
         | 
| 189 | 
            +
            		return
         | 
| 190 | 
            +
            	}
         | 
| 191 | 
            +
             | 
| 192 | 
            +
            	// get state
         | 
| 193 | 
            +
            	state, statusCode, err := userLogin.GetState(authorizedUrl)
         | 
| 194 | 
            +
            	if err != nil {
         | 
| 195 | 
            +
            		c.AbortWithStatusJSON(statusCode, api.ReturnMessage(err.Error()))
         | 
| 196 | 
            +
            		return
         | 
| 197 | 
            +
            	}
         | 
| 198 | 
            +
             | 
| 199 | 
            +
            	// check username
         | 
| 200 | 
            +
            	statusCode, err = userLogin.CheckUsername(state, loginInfo.Username)
         | 
| 201 | 
            +
            	if err != nil {
         | 
| 202 | 
            +
            		c.AbortWithStatusJSON(statusCode, api.ReturnMessage(err.Error()))
         | 
| 203 | 
            +
            		return
         | 
| 204 | 
            +
            	}
         | 
| 205 | 
            +
             | 
| 206 | 
            +
            	// check password
         | 
| 207 | 
            +
            	_, statusCode, err = userLogin.CheckPassword(state, loginInfo.Username, loginInfo.Password)
         | 
| 208 | 
            +
            	if err != nil {
         | 
| 209 | 
            +
            		c.AbortWithStatusJSON(statusCode, api.ReturnMessage(err.Error()))
         | 
| 210 | 
            +
            		return
         | 
| 211 | 
            +
            	}
         | 
| 212 | 
            +
             | 
| 213 | 
            +
            	// get access token
         | 
| 214 | 
            +
            	accessToken, statusCode, err := userLogin.GetAccessToken("")
         | 
| 215 | 
            +
            	if err != nil {
         | 
| 216 | 
            +
            		c.AbortWithStatusJSON(statusCode, api.ReturnMessage(err.Error()))
         | 
| 217 | 
            +
            		return
         | 
| 218 | 
            +
            	}
         | 
| 219 | 
            +
             | 
| 220 | 
            +
            	c.Writer.WriteString(accessToken)
         | 
| 221 | 
            +
            }
         | 
| 222 | 
            +
             | 
| 223 | 
            +
            func Fallback(c *gin.Context) {
         | 
| 224 | 
            +
            	method := c.Request.Method
         | 
| 225 | 
            +
            	url := apiPrefix + c.Request.URL.Path
         | 
| 226 | 
            +
            	queryParams := c.Request.URL.Query().Encode()
         | 
| 227 | 
            +
            	if queryParams != "" {
         | 
| 228 | 
            +
            		url += "?" + queryParams
         | 
| 229 | 
            +
            	}
         | 
| 230 | 
            +
             | 
| 231 | 
            +
            	var requestBody string
         | 
| 232 | 
            +
            	if c.Request.Method == http.MethodPost || c.Request.Method == http.MethodPatch {
         | 
| 233 | 
            +
            		body, _ := io.ReadAll(c.Request.Body)
         | 
| 234 | 
            +
            		requestBody = string(body)
         | 
| 235 | 
            +
            	}
         | 
| 236 | 
            +
             | 
| 237 | 
            +
            	c.Status(http.StatusOK)
         | 
| 238 | 
            +
             | 
| 239 | 
            +
            	switch method {
         | 
| 240 | 
            +
            	case http.MethodGet:
         | 
| 241 | 
            +
            		handleGet(c, url, fallbackErrorMessage)
         | 
| 242 | 
            +
            	case http.MethodPost:
         | 
| 243 | 
            +
            		handlePost(c, url, requestBody, fallbackErrorMessage)
         | 
| 244 | 
            +
            	case http.MethodPatch:
         | 
| 245 | 
            +
            		handlePatch(c, url, requestBody, fallbackErrorMessage)
         | 
| 246 | 
            +
            	default:
         | 
| 247 | 
            +
            		c.JSON(http.StatusMethodNotAllowed, gin.H{"message": fallbackMethodNotAllowedMessage})
         | 
| 248 | 
            +
            	}
         | 
| 249 | 
            +
            }
         | 
| 250 | 
            +
             | 
| 251 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 252 | 
            +
            func handleGet(c *gin.Context, url string, errorMessage string) {
         | 
| 253 | 
            +
            	req, _ := http.NewRequest(http.MethodGet, url, nil)
         | 
| 254 | 
            +
            	req.Header.Set("User-Agent", api.UserAgent)
         | 
| 255 | 
            +
            	req.Header.Set("Authorization", api.GetAccessToken(c.GetHeader(api.AuthorizationHeader)))
         | 
| 256 | 
            +
            	resp, err := api.Client.Do(req)
         | 
| 257 | 
            +
            	if err != nil {
         | 
| 258 | 
            +
            		c.AbortWithStatusJSON(http.StatusInternalServerError, api.ReturnMessage(err.Error()))
         | 
| 259 | 
            +
            		return
         | 
| 260 | 
            +
            	}
         | 
| 261 | 
            +
             | 
| 262 | 
            +
            	defer resp.Body.Close()
         | 
| 263 | 
            +
            	if resp.StatusCode != http.StatusOK {
         | 
| 264 | 
            +
            		c.AbortWithStatusJSON(resp.StatusCode, api.ReturnMessage(errorMessage))
         | 
| 265 | 
            +
            		return
         | 
| 266 | 
            +
            	}
         | 
| 267 | 
            +
             | 
| 268 | 
            +
            	io.Copy(c.Writer, resp.Body)
         | 
| 269 | 
            +
            }
         | 
| 270 | 
            +
             | 
| 271 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 272 | 
            +
            func handlePost(c *gin.Context, url string, requestBody string, errorMessage string) {
         | 
| 273 | 
            +
            	req, _ := http.NewRequest(http.MethodPost, url, strings.NewReader(requestBody))
         | 
| 274 | 
            +
            	handlePostOrPatch(c, req, errorMessage)
         | 
| 275 | 
            +
            }
         | 
| 276 | 
            +
             | 
| 277 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 278 | 
            +
            func handlePatch(c *gin.Context, url string, requestBody string, errorMessage string) {
         | 
| 279 | 
            +
            	req, _ := http.NewRequest(http.MethodPatch, url, strings.NewReader(requestBody))
         | 
| 280 | 
            +
            	handlePostOrPatch(c, req, errorMessage)
         | 
| 281 | 
            +
            }
         | 
| 282 | 
            +
             | 
| 283 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 284 | 
            +
            func handlePostOrPatch(c *gin.Context, req *http.Request, errorMessage string) {
         | 
| 285 | 
            +
            	req.Header.Set("User-Agent", api.UserAgent)
         | 
| 286 | 
            +
            	req.Header.Set("Authorization", api.GetAccessToken(c.GetHeader(api.AuthorizationHeader)))
         | 
| 287 | 
            +
            	resp, err := api.Client.Do(req)
         | 
| 288 | 
            +
            	if err != nil {
         | 
| 289 | 
            +
            		c.AbortWithStatusJSON(http.StatusInternalServerError, api.ReturnMessage(err.Error()))
         | 
| 290 | 
            +
            		return
         | 
| 291 | 
            +
            	}
         | 
| 292 | 
            +
             | 
| 293 | 
            +
            	defer resp.Body.Close()
         | 
| 294 | 
            +
            	if resp.StatusCode != http.StatusOK {
         | 
| 295 | 
            +
            		c.AbortWithStatusJSON(resp.StatusCode, api.ReturnMessage(errorMessage))
         | 
| 296 | 
            +
            		return
         | 
| 297 | 
            +
            	}
         | 
| 298 | 
            +
             | 
| 299 | 
            +
            	io.Copy(c.Writer, resp.Body)
         | 
| 300 | 
            +
            }
         | 
    	
        api/chatgpt/constant.go
    ADDED
    
    | @@ -0,0 +1,26 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            package chatgpt
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            const (
         | 
| 4 | 
            +
            	apiPrefix                       = "https://chat.openai.com/backend-api"
         | 
| 5 | 
            +
            	defaultRole                     = "user"
         | 
| 6 | 
            +
            	getConversationsErrorMessage    = "Failed to get conversations."
         | 
| 7 | 
            +
            	generateTitleErrorMessage       = "Failed to generate title."
         | 
| 8 | 
            +
            	getContentErrorMessage          = "Failed to get content."
         | 
| 9 | 
            +
            	updateConversationErrorMessage  = "Failed to update conversation."
         | 
| 10 | 
            +
            	clearConversationsErrorMessage  = "Failed to clear conversations."
         | 
| 11 | 
            +
            	feedbackMessageErrorMessage     = "Failed to add feedback."
         | 
| 12 | 
            +
            	getModelsErrorMessage           = "Failed to get models."
         | 
| 13 | 
            +
            	getAccountCheckErrorMessage     = "Check failed." // Placeholder. Never encountered.
         | 
| 14 | 
            +
            	parseJsonErrorMessage           = "Failed to parse json request body."
         | 
| 15 | 
            +
            	fallbackErrorMessage            = "Fallback failed."
         | 
| 16 | 
            +
            	fallbackMethodNotAllowedMessage = "Fallback method not allowed."
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            	csrfUrl                  = "https://chat.openai.com/api/auth/csrf"
         | 
| 19 | 
            +
            	promptLoginUrl           = "https://chat.openai.com/api/auth/signin/auth0?prompt=login"
         | 
| 20 | 
            +
            	getCsrfTokenErrorMessage = "Failed to get CSRF token."
         | 
| 21 | 
            +
            	authSessionUrl           = "https://chat.openai.com/api/auth/session"
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            	gpt4Model     = "gpt-4"
         | 
| 24 | 
            +
            	gpt4PublicKey = "35536E1E-65B4-4D96-9D97-6ADB7EFF8147"
         | 
| 25 | 
            +
            	gpt4TokenUrl  = "https://tcr9i.chat.openai.com/fc/gt2/public_key/" + gpt4PublicKey
         | 
| 26 | 
            +
            )
         | 
    	
        api/chatgpt/health_check.go
    ADDED
    
    | @@ -0,0 +1,75 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            package chatgpt
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import (
         | 
| 4 | 
            +
            	"os"
         | 
| 5 | 
            +
            	"time"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            	"github.com/PuerkitoBio/goquery"
         | 
| 8 | 
            +
            	"github.com/linweiyuan/go-chatgpt-api/api"
         | 
| 9 | 
            +
            	"github.com/linweiyuan/go-chatgpt-api/util/logger"
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            	http "github.com/bogdanfinn/fhttp"
         | 
| 12 | 
            +
            )
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            //goland:noinspection SpellCheckingInspection
         | 
| 15 | 
            +
            const (
         | 
| 16 | 
            +
            	healthCheckUrl = "https://chat.openai.com/backend-api/accounts/check"
         | 
| 17 | 
            +
            	readyHint      = "Service go-chatgpt-api is ready."
         | 
| 18 | 
            +
            	errorHintBlock = "Looks like you have bean blocked -> curl https://chat.openai.com | grep '<p>' | awk '{$1=$1;print}'"
         | 
| 19 | 
            +
            	errorHint403   = "Failed to handle 403, have a look at https://github.com/linweiyuan/java-chatgpt-api or use other more powerful alternatives (do not raise new issue about 403)."
         | 
| 20 | 
            +
            	sleepHours     = 8760 // 365 days
         | 
| 21 | 
            +
            )
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            //goland:noinspection GoUnhandledErrorResult,SpellCheckingInspection
         | 
| 24 | 
            +
            func init() {
         | 
| 25 | 
            +
            	proxyUrl := os.Getenv("GO_CHATGPT_API_PROXY")
         | 
| 26 | 
            +
            	if proxyUrl != "" {
         | 
| 27 | 
            +
            		logger.Info("GO_CHATGPT_API_PROXY: " + proxyUrl)
         | 
| 28 | 
            +
            		api.Client.SetProxy(proxyUrl)
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            		for {
         | 
| 31 | 
            +
            			resp, err := healthCheck()
         | 
| 32 | 
            +
            			if err != nil {
         | 
| 33 | 
            +
            				// wait for proxy to be ready
         | 
| 34 | 
            +
            				time.Sleep(time.Second)
         | 
| 35 | 
            +
            				continue
         | 
| 36 | 
            +
            			}
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            			checkHealthCheckStatus(resp)
         | 
| 39 | 
            +
            			break
         | 
| 40 | 
            +
            		}
         | 
| 41 | 
            +
            	} else {
         | 
| 42 | 
            +
            		resp, err := healthCheck()
         | 
| 43 | 
            +
            		if err != nil {
         | 
| 44 | 
            +
            			logger.Error("Health check failed: " + err.Error())
         | 
| 45 | 
            +
            			os.Exit(1)
         | 
| 46 | 
            +
            		}
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            		checkHealthCheckStatus(resp)
         | 
| 49 | 
            +
            	}
         | 
| 50 | 
            +
            }
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            func healthCheck() (resp *http.Response, err error) {
         | 
| 53 | 
            +
            	req, _ := http.NewRequest(http.MethodGet, healthCheckUrl, nil)
         | 
| 54 | 
            +
            	req.Header.Set("User-Agent", api.UserAgent)
         | 
| 55 | 
            +
            	resp, err = api.Client.Do(req)
         | 
| 56 | 
            +
            	return
         | 
| 57 | 
            +
            }
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 60 | 
            +
            func checkHealthCheckStatus(resp *http.Response) {
         | 
| 61 | 
            +
            	defer resp.Body.Close()
         | 
| 62 | 
            +
            	if resp != nil && resp.StatusCode == http.StatusUnauthorized {
         | 
| 63 | 
            +
            		logger.Info(readyHint)
         | 
| 64 | 
            +
            	} else {
         | 
| 65 | 
            +
            		doc, _ := goquery.NewDocumentFromReader(resp.Body)
         | 
| 66 | 
            +
            		alert := doc.Find(".message").Text()
         | 
| 67 | 
            +
            		if alert != "" {
         | 
| 68 | 
            +
            			logger.Error(errorHintBlock)
         | 
| 69 | 
            +
            		} else {
         | 
| 70 | 
            +
            			logger.Error(errorHint403)
         | 
| 71 | 
            +
            		}
         | 
| 72 | 
            +
            		time.Sleep(time.Hour * sleepHours)
         | 
| 73 | 
            +
            		os.Exit(1)
         | 
| 74 | 
            +
            	}
         | 
| 75 | 
            +
            }
         | 
    	
        api/chatgpt/typings.go
    ADDED
    
    | @@ -0,0 +1,54 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            package chatgpt
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            //goland:noinspection GoSnakeCaseUsage
         | 
| 4 | 
            +
            import tls_client "github.com/bogdanfinn/tls-client"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            type UserLogin struct {
         | 
| 7 | 
            +
            	client tls_client.HttpClient
         | 
| 8 | 
            +
            }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            type CreateConversationRequest struct {
         | 
| 11 | 
            +
            	Action            string    `json:"action"`
         | 
| 12 | 
            +
            	Messages          []Message `json:"messages"`
         | 
| 13 | 
            +
            	Model             string    `json:"model"`
         | 
| 14 | 
            +
            	ParentMessageID   string    `json:"parent_message_id"`
         | 
| 15 | 
            +
            	ConversationID    *string   `json:"conversation_id"`
         | 
| 16 | 
            +
            	TimezoneOffsetMin int       `json:"timezone_offset_min"`
         | 
| 17 | 
            +
            	ArkoseToken       string    `json:"arkose_token"`
         | 
| 18 | 
            +
            }
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            type Message struct {
         | 
| 21 | 
            +
            	Author  Author  `json:"author"`
         | 
| 22 | 
            +
            	Content Content `json:"content"`
         | 
| 23 | 
            +
            	ID      string  `json:"id"`
         | 
| 24 | 
            +
            }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            type Author struct {
         | 
| 27 | 
            +
            	Role string `json:"role"`
         | 
| 28 | 
            +
            }
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            type Content struct {
         | 
| 31 | 
            +
            	ContentType string   `json:"content_type"`
         | 
| 32 | 
            +
            	Parts       []string `json:"parts"`
         | 
| 33 | 
            +
            }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            type FeedbackMessageRequest struct {
         | 
| 36 | 
            +
            	MessageID      string `json:"message_id"`
         | 
| 37 | 
            +
            	ConversationID string `json:"conversation_id"`
         | 
| 38 | 
            +
            	Rating         string `json:"rating"`
         | 
| 39 | 
            +
            }
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            type GenerateTitleRequest struct {
         | 
| 42 | 
            +
            	MessageID string `json:"message_id"`
         | 
| 43 | 
            +
            }
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            type PatchConversationRequest struct {
         | 
| 46 | 
            +
            	Title     *string `json:"title"`
         | 
| 47 | 
            +
            	IsVisible bool    `json:"is_visible"`
         | 
| 48 | 
            +
            }
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            type Cookie struct {
         | 
| 51 | 
            +
            	Name   string `json:"name"`
         | 
| 52 | 
            +
            	Value  string `json:"value"`
         | 
| 53 | 
            +
            	Expiry int64  `json:"expiry"`
         | 
| 54 | 
            +
            }
         | 
    	
        api/common.go
    ADDED
    
    | @@ -0,0 +1,110 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            package api
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            //goland:noinspection GoSnakeCaseUsage
         | 
| 4 | 
            +
            import (
         | 
| 5 | 
            +
            	"bufio"
         | 
| 6 | 
            +
            	"os"
         | 
| 7 | 
            +
            	"strings"
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            	"github.com/gin-gonic/gin"
         | 
| 10 | 
            +
            	_ "github.com/linweiyuan/go-chatgpt-api/env"
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            	http "github.com/bogdanfinn/fhttp"
         | 
| 13 | 
            +
            	tls_client "github.com/bogdanfinn/tls-client"
         | 
| 14 | 
            +
            )
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            const (
         | 
| 17 | 
            +
            	defaultErrorMessageKey             = "errorMessage"
         | 
| 18 | 
            +
            	AuthorizationHeader                = "Authorization"
         | 
| 19 | 
            +
            	ContentType                        = "application/x-www-form-urlencoded"
         | 
| 20 | 
            +
            	UserAgent                          = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
         | 
| 21 | 
            +
            	Auth0Url                           = "https://auth0.openai.com"
         | 
| 22 | 
            +
            	LoginUsernameUrl                   = Auth0Url + "/u/login/identifier?state="
         | 
| 23 | 
            +
            	LoginPasswordUrl                   = Auth0Url + "/u/login/password?state="
         | 
| 24 | 
            +
            	ParseUserInfoErrorMessage          = "Failed to parse user login info."
         | 
| 25 | 
            +
            	GetAuthorizedUrlErrorMessage       = "Failed to get authorized url."
         | 
| 26 | 
            +
            	GetStateErrorMessage               = "Failed to get state."
         | 
| 27 | 
            +
            	EmailInvalidErrorMessage           = "Email is not valid."
         | 
| 28 | 
            +
            	EmailOrPasswordInvalidErrorMessage = "Email or password is not correct."
         | 
| 29 | 
            +
            	GetAccessTokenErrorMessage         = "Failed to get access token."
         | 
| 30 | 
            +
            	defaultTimeoutSeconds              = 300 // 5 minutes
         | 
| 31 | 
            +
            )
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            var Client tls_client.HttpClient
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            type LoginInfo struct {
         | 
| 36 | 
            +
            	Username string `json:"username"`
         | 
| 37 | 
            +
            	Password string `json:"password"`
         | 
| 38 | 
            +
            }
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            type AuthLogin interface {
         | 
| 41 | 
            +
            	GetAuthorizedUrl(csrfToken string) (string, int, error)
         | 
| 42 | 
            +
            	GetState(authorizedUrl string) (string, int, error)
         | 
| 43 | 
            +
            	CheckUsername(state string, username string) (int, error)
         | 
| 44 | 
            +
            	CheckPassword(state string, username string, password string) (string, int, error)
         | 
| 45 | 
            +
            	GetAccessToken(code string) (string, int, error)
         | 
| 46 | 
            +
            }
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 49 | 
            +
            func init() {
         | 
| 50 | 
            +
            	Client, _ = tls_client.NewHttpClient(tls_client.NewNoopLogger(), []tls_client.HttpClientOption{
         | 
| 51 | 
            +
            		tls_client.WithCookieJar(tls_client.NewCookieJar()),
         | 
| 52 | 
            +
            		tls_client.WithTimeoutSeconds(defaultTimeoutSeconds),
         | 
| 53 | 
            +
            		tls_client.WithClientProfile(tls_client.Okhttp4Android13),
         | 
| 54 | 
            +
            	}...)
         | 
| 55 | 
            +
            }
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            func ReturnMessage(msg string) gin.H {
         | 
| 58 | 
            +
            	return gin.H{
         | 
| 59 | 
            +
            		defaultErrorMessageKey: msg,
         | 
| 60 | 
            +
            	}
         | 
| 61 | 
            +
            }
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            func GetAccessToken(accessToken string) string {
         | 
| 64 | 
            +
            	if !strings.HasPrefix(accessToken, "Bearer") {
         | 
| 65 | 
            +
            		return "Bearer " + accessToken
         | 
| 66 | 
            +
            	}
         | 
| 67 | 
            +
            	return accessToken
         | 
| 68 | 
            +
            }
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 71 | 
            +
            func HandleConversationResponse(c *gin.Context, resp *http.Response) {
         | 
| 72 | 
            +
            	c.Writer.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
         | 
| 73 | 
            +
             | 
| 74 | 
            +
            	reader := bufio.NewReader(resp.Body)
         | 
| 75 | 
            +
            	for {
         | 
| 76 | 
            +
            		if c.Request.Context().Err() != nil {
         | 
| 77 | 
            +
            			break
         | 
| 78 | 
            +
            		}
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            		line, err := reader.ReadString('\n')
         | 
| 81 | 
            +
            		if err != nil {
         | 
| 82 | 
            +
            			break
         | 
| 83 | 
            +
            		}
         | 
| 84 | 
            +
             | 
| 85 | 
            +
            		line = strings.TrimSpace(line)
         | 
| 86 | 
            +
            		if strings.HasPrefix(line, "event") ||
         | 
| 87 | 
            +
            			strings.HasPrefix(line, "data: 20") ||
         | 
| 88 | 
            +
            			line == "" {
         | 
| 89 | 
            +
            			continue
         | 
| 90 | 
            +
            		}
         | 
| 91 | 
            +
             | 
| 92 | 
            +
            		c.Writer.Write([]byte(line + "\n\n"))
         | 
| 93 | 
            +
            		c.Writer.Flush()
         | 
| 94 | 
            +
            	}
         | 
| 95 | 
            +
            }
         | 
| 96 | 
            +
             | 
| 97 | 
            +
            //goland:noinspection GoUnhandledErrorResult,SpellCheckingInspection
         | 
| 98 | 
            +
            func NewHttpClient() tls_client.HttpClient {
         | 
| 99 | 
            +
            	client, _ := tls_client.NewHttpClient(tls_client.NewNoopLogger(), []tls_client.HttpClientOption{
         | 
| 100 | 
            +
            		tls_client.WithCookieJar(tls_client.NewCookieJar()),
         | 
| 101 | 
            +
            		tls_client.WithClientProfile(tls_client.Okhttp4Android13),
         | 
| 102 | 
            +
            	}...)
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            	proxyUrl := os.Getenv("GO_CHATGPT_API_PROXY")
         | 
| 105 | 
            +
            	if proxyUrl != "" {
         | 
| 106 | 
            +
            		client.SetProxy(proxyUrl)
         | 
| 107 | 
            +
            	}
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            	return client
         | 
| 110 | 
            +
            }
         | 
    	
        api/platform/access_token.go
    ADDED
    
    | @@ -0,0 +1,116 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            package platform
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import (
         | 
| 4 | 
            +
            	"encoding/json"
         | 
| 5 | 
            +
            	"errors"
         | 
| 6 | 
            +
            	"fmt"
         | 
| 7 | 
            +
            	"io"
         | 
| 8 | 
            +
            	"net/url"
         | 
| 9 | 
            +
            	"strings"
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            	"github.com/linweiyuan/go-chatgpt-api/api"
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            	http "github.com/bogdanfinn/fhttp"
         | 
| 14 | 
            +
            )
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            //goland:noinspection GoUnhandledErrorResult,GoErrorStringFormat,GoUnusedParameter
         | 
| 17 | 
            +
            func (userLogin *UserLogin) GetAuthorizedUrl(csrfToken string) (string, int, error) {
         | 
| 18 | 
            +
            	urlParams := url.Values{
         | 
| 19 | 
            +
            		"client_id":     {platformAuthClientID},
         | 
| 20 | 
            +
            		"audience":      {platformAuthAudience},
         | 
| 21 | 
            +
            		"redirect_uri":  {platformAuthRedirectURL},
         | 
| 22 | 
            +
            		"scope":         {platformAuthScope},
         | 
| 23 | 
            +
            		"response_type": {platformAuthResponseType},
         | 
| 24 | 
            +
            	}
         | 
| 25 | 
            +
            	req, _ := http.NewRequest(http.MethodGet, platformAuth0Url+urlParams.Encode(), nil)
         | 
| 26 | 
            +
            	req.Header.Set("Content-Type", api.ContentType)
         | 
| 27 | 
            +
            	req.Header.Set("User-Agent", api.UserAgent)
         | 
| 28 | 
            +
            	resp, err := userLogin.client.Do(req)
         | 
| 29 | 
            +
            	if err != nil {
         | 
| 30 | 
            +
            		return "", http.StatusInternalServerError, err
         | 
| 31 | 
            +
            	}
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            	defer resp.Body.Close()
         | 
| 34 | 
            +
            	if resp.StatusCode != http.StatusOK {
         | 
| 35 | 
            +
            		return "", resp.StatusCode, errors.New(api.GetAuthorizedUrlErrorMessage)
         | 
| 36 | 
            +
            	}
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            	return resp.Request.URL.String(), http.StatusOK, nil
         | 
| 39 | 
            +
            }
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            func (userLogin *UserLogin) GetState(authorizedUrl string) (string, int, error) {
         | 
| 42 | 
            +
            	split := strings.Split(authorizedUrl, "=")
         | 
| 43 | 
            +
            	return split[1], http.StatusOK, nil
         | 
| 44 | 
            +
            }
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            //goland:noinspection GoUnhandledErrorResult,GoErrorStringFormat
         | 
| 47 | 
            +
            func (userLogin *UserLogin) CheckUsername(state string, username string) (int, error) {
         | 
| 48 | 
            +
            	formParams := fmt.Sprintf(
         | 
| 49 | 
            +
            		"state=%s&username=%s&js-available=true&webauthn-available=true&is-brave=false&webauthn-platform-available=false&action=default",
         | 
| 50 | 
            +
            		state,
         | 
| 51 | 
            +
            		username,
         | 
| 52 | 
            +
            	)
         | 
| 53 | 
            +
            	req, err := http.NewRequest(http.MethodPost, api.LoginUsernameUrl+state, strings.NewReader(formParams))
         | 
| 54 | 
            +
            	req.Header.Set("Content-Type", api.ContentType)
         | 
| 55 | 
            +
            	req.Header.Set("User-Agent", api.UserAgent)
         | 
| 56 | 
            +
            	resp, err := userLogin.client.Do(req)
         | 
| 57 | 
            +
            	if err != nil {
         | 
| 58 | 
            +
            		return http.StatusInternalServerError, err
         | 
| 59 | 
            +
            	}
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            	defer resp.Body.Close()
         | 
| 62 | 
            +
            	if resp.StatusCode != http.StatusOK {
         | 
| 63 | 
            +
            		return resp.StatusCode, errors.New(api.EmailInvalidErrorMessage)
         | 
| 64 | 
            +
            	}
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            	return http.StatusOK, nil
         | 
| 67 | 
            +
            }
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            //goland:noinspection GoUnhandledErrorResult,GoErrorStringFormat
         | 
| 70 | 
            +
            func (userLogin *UserLogin) CheckPassword(state string, username string, password string) (string, int, error) {
         | 
| 71 | 
            +
            	formParams := fmt.Sprintf(
         | 
| 72 | 
            +
            		"state=%s&username=%s&password=%s&action=default",
         | 
| 73 | 
            +
            		state,
         | 
| 74 | 
            +
            		username,
         | 
| 75 | 
            +
            		password,
         | 
| 76 | 
            +
            	)
         | 
| 77 | 
            +
            	req, err := http.NewRequest(http.MethodPost, api.LoginPasswordUrl+state, strings.NewReader(formParams))
         | 
| 78 | 
            +
            	req.Header.Set("Content-Type", api.ContentType)
         | 
| 79 | 
            +
            	req.Header.Set("User-Agent", api.UserAgent)
         | 
| 80 | 
            +
            	resp, err := userLogin.client.Do(req)
         | 
| 81 | 
            +
            	if err != nil {
         | 
| 82 | 
            +
            		return "", http.StatusInternalServerError, err
         | 
| 83 | 
            +
            	}
         | 
| 84 | 
            +
             | 
| 85 | 
            +
            	defer resp.Body.Close()
         | 
| 86 | 
            +
            	if resp.StatusCode != http.StatusOK {
         | 
| 87 | 
            +
            		return "", resp.StatusCode, errors.New(api.EmailOrPasswordInvalidErrorMessage)
         | 
| 88 | 
            +
            	}
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            	return resp.Request.URL.Query().Get("code"), http.StatusOK, nil
         | 
| 91 | 
            +
            }
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            //goland:noinspection GoUnhandledErrorResult,GoErrorStringFormat
         | 
| 94 | 
            +
            func (userLogin *UserLogin) GetAccessToken(code string) (string, int, error) {
         | 
| 95 | 
            +
            	jsonBytes, _ := json.Marshal(GetAccessTokenRequest{
         | 
| 96 | 
            +
            		ClientID:    platformAuthClientID,
         | 
| 97 | 
            +
            		Code:        code,
         | 
| 98 | 
            +
            		GrantType:   platformAuthGrantType,
         | 
| 99 | 
            +
            		RedirectURI: platformAuthRedirectURL,
         | 
| 100 | 
            +
            	})
         | 
| 101 | 
            +
            	req, err := http.NewRequest(http.MethodPost, getTokenUrl, strings.NewReader(string(jsonBytes)))
         | 
| 102 | 
            +
            	req.Header.Set("Content-Type", "application/json")
         | 
| 103 | 
            +
            	req.Header.Set("User-Agent", api.UserAgent)
         | 
| 104 | 
            +
            	resp, err := userLogin.client.Do(req)
         | 
| 105 | 
            +
            	if err != nil {
         | 
| 106 | 
            +
            		return "", http.StatusInternalServerError, err
         | 
| 107 | 
            +
            	}
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            	defer resp.Body.Close()
         | 
| 110 | 
            +
            	if resp.StatusCode != http.StatusOK {
         | 
| 111 | 
            +
            		return "", resp.StatusCode, errors.New(api.GetAccessTokenErrorMessage)
         | 
| 112 | 
            +
            	}
         | 
| 113 | 
            +
             | 
| 114 | 
            +
            	data, _ := io.ReadAll(resp.Body)
         | 
| 115 | 
            +
            	return string(data), http.StatusOK, nil
         | 
| 116 | 
            +
            }
         | 
    	
        api/platform/api.go
    ADDED
    
    | @@ -0,0 +1,224 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            package platform
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import (
         | 
| 4 | 
            +
            	"bytes"
         | 
| 5 | 
            +
            	"encoding/json"
         | 
| 6 | 
            +
            	"fmt"
         | 
| 7 | 
            +
            	"io"
         | 
| 8 | 
            +
            	"strings"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            	"github.com/gin-gonic/gin"
         | 
| 11 | 
            +
            	"github.com/linweiyuan/go-chatgpt-api/api"
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            	http "github.com/bogdanfinn/fhttp"
         | 
| 14 | 
            +
            )
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            func ListModels(c *gin.Context) {
         | 
| 17 | 
            +
            	handleGet(c, apiListModels)
         | 
| 18 | 
            +
            }
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            func RetrieveModel(c *gin.Context) {
         | 
| 21 | 
            +
            	model := c.Param("model")
         | 
| 22 | 
            +
            	handleGet(c, fmt.Sprintf(apiRetrieveModel, model))
         | 
| 23 | 
            +
            }
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 26 | 
            +
            func CreateCompletions(c *gin.Context) {
         | 
| 27 | 
            +
            	var request CreateCompletionsRequest
         | 
| 28 | 
            +
            	c.ShouldBindJSON(&request)
         | 
| 29 | 
            +
            	data, _ := json.Marshal(request)
         | 
| 30 | 
            +
            	resp, err := handlePost(c, apiCreateCompletions, data, request.Stream)
         | 
| 31 | 
            +
            	if err != nil {
         | 
| 32 | 
            +
            		return
         | 
| 33 | 
            +
            	}
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            	defer resp.Body.Close()
         | 
| 36 | 
            +
            	if request.Stream {
         | 
| 37 | 
            +
            		api.HandleConversationResponse(c, resp)
         | 
| 38 | 
            +
            	} else {
         | 
| 39 | 
            +
            		io.Copy(c.Writer, resp.Body)
         | 
| 40 | 
            +
            	}
         | 
| 41 | 
            +
            }
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 44 | 
            +
            func CreateChatCompletions(c *gin.Context) {
         | 
| 45 | 
            +
            	var request ChatCompletionsRequest
         | 
| 46 | 
            +
            	c.ShouldBindJSON(&request)
         | 
| 47 | 
            +
            	data, _ := json.Marshal(request)
         | 
| 48 | 
            +
            	resp, err := handlePost(c, apiCreataeChatCompletions, data, request.Stream)
         | 
| 49 | 
            +
            	if err != nil {
         | 
| 50 | 
            +
            		return
         | 
| 51 | 
            +
            	}
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            	defer resp.Body.Close()
         | 
| 54 | 
            +
            	if request.Stream {
         | 
| 55 | 
            +
            		api.HandleConversationResponse(c, resp)
         | 
| 56 | 
            +
            	} else {
         | 
| 57 | 
            +
            		io.Copy(c.Writer, resp.Body)
         | 
| 58 | 
            +
            	}
         | 
| 59 | 
            +
            }
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 62 | 
            +
            func CreateEdit(c *gin.Context) {
         | 
| 63 | 
            +
            	var request CreateEditRequest
         | 
| 64 | 
            +
            	c.ShouldBindJSON(&request)
         | 
| 65 | 
            +
            	data, _ := json.Marshal(request)
         | 
| 66 | 
            +
            	resp, err := handlePost(c, apiCreateEdit, data, false)
         | 
| 67 | 
            +
            	if err != nil {
         | 
| 68 | 
            +
            		return
         | 
| 69 | 
            +
            	}
         | 
| 70 | 
            +
             | 
| 71 | 
            +
            	defer resp.Body.Close()
         | 
| 72 | 
            +
            	io.Copy(c.Writer, resp.Body)
         | 
| 73 | 
            +
            }
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 76 | 
            +
            func CreateImage(c *gin.Context) {
         | 
| 77 | 
            +
            	var request CreateImageRequest
         | 
| 78 | 
            +
            	c.ShouldBindJSON(&request)
         | 
| 79 | 
            +
            	data, _ := json.Marshal(request)
         | 
| 80 | 
            +
            	resp, err := handlePost(c, apiCreateImage, data, false)
         | 
| 81 | 
            +
            	if err != nil {
         | 
| 82 | 
            +
            		return
         | 
| 83 | 
            +
            	}
         | 
| 84 | 
            +
             | 
| 85 | 
            +
            	defer resp.Body.Close()
         | 
| 86 | 
            +
            	io.Copy(c.Writer, resp.Body)
         | 
| 87 | 
            +
            }
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 90 | 
            +
            func CreateEmbeddings(c *gin.Context) {
         | 
| 91 | 
            +
            	var request CreateEmbeddingsRequest
         | 
| 92 | 
            +
            	c.ShouldBindJSON(&request)
         | 
| 93 | 
            +
            	data, _ := json.Marshal(request)
         | 
| 94 | 
            +
            	resp, err := handlePost(c, apiCreateEmbeddings, data, false)
         | 
| 95 | 
            +
            	if err != nil {
         | 
| 96 | 
            +
            		return
         | 
| 97 | 
            +
            	}
         | 
| 98 | 
            +
             | 
| 99 | 
            +
            	defer resp.Body.Close()
         | 
| 100 | 
            +
            	io.Copy(c.Writer, resp.Body)
         | 
| 101 | 
            +
            }
         | 
| 102 | 
            +
             | 
| 103 | 
            +
            func CreateModeration(c *gin.Context) {
         | 
| 104 | 
            +
            	var request CreateModerationRequest
         | 
| 105 | 
            +
            	c.ShouldBindJSON(&request)
         | 
| 106 | 
            +
            	data, _ := json.Marshal(request)
         | 
| 107 | 
            +
            	resp, err := handlePost(c, apiCreateModeration, data, false)
         | 
| 108 | 
            +
            	if err != nil {
         | 
| 109 | 
            +
            		return
         | 
| 110 | 
            +
            	}
         | 
| 111 | 
            +
             | 
| 112 | 
            +
            	defer resp.Body.Close()
         | 
| 113 | 
            +
            	io.Copy(c.Writer, resp.Body)
         | 
| 114 | 
            +
            }
         | 
| 115 | 
            +
             | 
| 116 | 
            +
            func ListFiles(c *gin.Context) {
         | 
| 117 | 
            +
            	handleGet(c, apiListFiles)
         | 
| 118 | 
            +
            }
         | 
| 119 | 
            +
             | 
| 120 | 
            +
            func GetCreditGrants(c *gin.Context) {
         | 
| 121 | 
            +
            	handleGet(c, apiGetCreditGrants)
         | 
| 122 | 
            +
            }
         | 
| 123 | 
            +
             | 
| 124 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 125 | 
            +
            func Login(c *gin.Context) {
         | 
| 126 | 
            +
            	var loginInfo api.LoginInfo
         | 
| 127 | 
            +
            	if err := c.ShouldBindJSON(&loginInfo); err != nil {
         | 
| 128 | 
            +
            		c.AbortWithStatusJSON(http.StatusBadRequest, api.ReturnMessage(api.ParseUserInfoErrorMessage))
         | 
| 129 | 
            +
            		return
         | 
| 130 | 
            +
            	}
         | 
| 131 | 
            +
             | 
| 132 | 
            +
            	userLogin := UserLogin{
         | 
| 133 | 
            +
            		client: api.NewHttpClient(),
         | 
| 134 | 
            +
            	}
         | 
| 135 | 
            +
             | 
| 136 | 
            +
            	// hard refresh cookies
         | 
| 137 | 
            +
            	resp, _ := userLogin.client.Get(auth0LogoutUrl)
         | 
| 138 | 
            +
            	defer resp.Body.Close()
         | 
| 139 | 
            +
             | 
| 140 | 
            +
            	// get authorized url
         | 
| 141 | 
            +
            	authorizedUrl, statusCode, err := userLogin.GetAuthorizedUrl("")
         | 
| 142 | 
            +
            	if err != nil {
         | 
| 143 | 
            +
            		c.AbortWithStatusJSON(statusCode, api.ReturnMessage(err.Error()))
         | 
| 144 | 
            +
            		return
         | 
| 145 | 
            +
            	}
         | 
| 146 | 
            +
             | 
| 147 | 
            +
            	// get state
         | 
| 148 | 
            +
            	state, _, _ := userLogin.GetState(authorizedUrl)
         | 
| 149 | 
            +
             | 
| 150 | 
            +
            	// check username
         | 
| 151 | 
            +
            	statusCode, err = userLogin.CheckUsername(state, loginInfo.Username)
         | 
| 152 | 
            +
            	if err != nil {
         | 
| 153 | 
            +
            		c.AbortWithStatusJSON(statusCode, api.ReturnMessage(err.Error()))
         | 
| 154 | 
            +
            		return
         | 
| 155 | 
            +
            	}
         | 
| 156 | 
            +
             | 
| 157 | 
            +
            	// check password
         | 
| 158 | 
            +
            	code, statusCode, err := userLogin.CheckPassword(state, loginInfo.Username, loginInfo.Password)
         | 
| 159 | 
            +
            	if err != nil {
         | 
| 160 | 
            +
            		c.AbortWithStatusJSON(statusCode, api.ReturnMessage(err.Error()))
         | 
| 161 | 
            +
            		return
         | 
| 162 | 
            +
            	}
         | 
| 163 | 
            +
             | 
| 164 | 
            +
            	// get access token
         | 
| 165 | 
            +
            	accessToken, statusCode, err := userLogin.GetAccessToken(code)
         | 
| 166 | 
            +
            	if err != nil {
         | 
| 167 | 
            +
            		c.AbortWithStatusJSON(statusCode, api.ReturnMessage(err.Error()))
         | 
| 168 | 
            +
            		return
         | 
| 169 | 
            +
            	}
         | 
| 170 | 
            +
             | 
| 171 | 
            +
            	// get session key
         | 
| 172 | 
            +
            	var getAccessTokenResponse GetAccessTokenResponse
         | 
| 173 | 
            +
            	json.Unmarshal([]byte(accessToken), &getAccessTokenResponse)
         | 
| 174 | 
            +
            	req, _ := http.NewRequest(http.MethodPost, dashboardLoginUrl, strings.NewReader("{}"))
         | 
| 175 | 
            +
            	req.Header.Set("Content-Type", "application/json")
         | 
| 176 | 
            +
            	req.Header.Set("User-Agent", api.UserAgent)
         | 
| 177 | 
            +
            	req.Header.Set("Authorization", api.GetAccessToken(getAccessTokenResponse.AccessToken))
         | 
| 178 | 
            +
            	resp, err = userLogin.client.Do(req)
         | 
| 179 | 
            +
            	if err != nil {
         | 
| 180 | 
            +
            		c.AbortWithStatusJSON(http.StatusInternalServerError, api.ReturnMessage(err.Error()))
         | 
| 181 | 
            +
            		return
         | 
| 182 | 
            +
            	}
         | 
| 183 | 
            +
             | 
| 184 | 
            +
            	defer resp.Body.Close()
         | 
| 185 | 
            +
            	if resp.StatusCode != http.StatusOK {
         | 
| 186 | 
            +
            		c.AbortWithStatusJSON(resp.StatusCode, api.ReturnMessage(getSessionKeyErrorMessage))
         | 
| 187 | 
            +
            		return
         | 
| 188 | 
            +
            	}
         | 
| 189 | 
            +
             | 
| 190 | 
            +
            	io.Copy(c.Writer, resp.Body)
         | 
| 191 | 
            +
            }
         | 
| 192 | 
            +
             | 
| 193 | 
            +
            func GetSubscription(c *gin.Context) {
         | 
| 194 | 
            +
            	handleGet(c, apiGetSubscription)
         | 
| 195 | 
            +
            }
         | 
| 196 | 
            +
             | 
| 197 | 
            +
            func GetApiKeys(c *gin.Context) {
         | 
| 198 | 
            +
            	handleGet(c, apiGetApiKeys)
         | 
| 199 | 
            +
            }
         | 
| 200 | 
            +
             | 
| 201 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 202 | 
            +
            func handleGet(c *gin.Context, url string) {
         | 
| 203 | 
            +
            	req, _ := http.NewRequest(http.MethodGet, url, nil)
         | 
| 204 | 
            +
            	req.Header.Set("Authorization", api.GetAccessToken(c.GetHeader(api.AuthorizationHeader)))
         | 
| 205 | 
            +
            	resp, _ := api.Client.Do(req)
         | 
| 206 | 
            +
            	defer resp.Body.Close()
         | 
| 207 | 
            +
            	io.Copy(c.Writer, resp.Body)
         | 
| 208 | 
            +
            }
         | 
| 209 | 
            +
             | 
| 210 | 
            +
            func handlePost(c *gin.Context, url string, data []byte, stream bool) (*http.Response, error) {
         | 
| 211 | 
            +
            	req, _ := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
         | 
| 212 | 
            +
            	req.Header.Set("Authorization", api.GetAccessToken(c.GetHeader(api.AuthorizationHeader)))
         | 
| 213 | 
            +
            	if stream {
         | 
| 214 | 
            +
            		req.Header.Set("Accept", "text/event-stream")
         | 
| 215 | 
            +
            	}
         | 
| 216 | 
            +
            	req.Header.Set("Content-Type", "application/json")
         | 
| 217 | 
            +
            	resp, err := api.Client.Do(req)
         | 
| 218 | 
            +
            	if err != nil {
         | 
| 219 | 
            +
            		c.AbortWithStatusJSON(http.StatusInternalServerError, api.ReturnMessage(err.Error()))
         | 
| 220 | 
            +
            		return nil, err
         | 
| 221 | 
            +
            	}
         | 
| 222 | 
            +
             | 
| 223 | 
            +
            	return resp, nil
         | 
| 224 | 
            +
            }
         | 
    	
        api/platform/constant.go
    ADDED
    
    | @@ -0,0 +1,35 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            package platform
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import "github.com/linweiyuan/go-chatgpt-api/api"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            //goland:noinspection SpellCheckingInspection
         | 
| 6 | 
            +
            const (
         | 
| 7 | 
            +
            	apiUrl = "https://api.openai.com"
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            	apiListModels             = apiUrl + "/v1/models"
         | 
| 10 | 
            +
            	apiRetrieveModel          = apiUrl + "/v1/models/%s"
         | 
| 11 | 
            +
            	apiCreateCompletions      = apiUrl + "/v1/completions"
         | 
| 12 | 
            +
            	apiCreataeChatCompletions = apiUrl + "/v1/chat/completions"
         | 
| 13 | 
            +
            	apiCreateEdit             = apiUrl + "/v1/edits"
         | 
| 14 | 
            +
            	apiCreateImage            = apiUrl + "/v1/images/generations"
         | 
| 15 | 
            +
            	apiCreateEmbeddings       = apiUrl + "/v1/embeddings"
         | 
| 16 | 
            +
            	apiListFiles              = apiUrl + "/v1/files"
         | 
| 17 | 
            +
            	apiCreateModeration       = apiUrl + "/v1/moderations"
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            	apiGetCreditGrants = apiUrl + "/dashboard/billing/credit_grants"
         | 
| 20 | 
            +
            	apiGetSubscription = apiUrl + "/dashboard/billing/subscription"
         | 
| 21 | 
            +
            	apiGetApiKeys      = apiUrl + "/dashboard/user/api_keys"
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            	platformAuthClientID      = "DRivsnm2Mu42T3KOpqdtwB3NYviHYzwD"
         | 
| 24 | 
            +
            	platformAuthAudience      = "https://api.openai.com/v1"
         | 
| 25 | 
            +
            	platformAuthRedirectURL   = "https://platform.openai.com/auth/callback"
         | 
| 26 | 
            +
            	platformAuthScope         = "openid profile email offline_access"
         | 
| 27 | 
            +
            	platformAuthResponseType  = "code"
         | 
| 28 | 
            +
            	platformAuthGrantType     = "authorization_code"
         | 
| 29 | 
            +
            	platformAuth0Url          = api.Auth0Url + "/authorize?"
         | 
| 30 | 
            +
            	getTokenUrl               = api.Auth0Url + "/oauth/token"
         | 
| 31 | 
            +
            	auth0Client               = "eyJuYW1lIjoiYXV0aDAtc3BhLWpzIiwidmVyc2lvbiI6IjEuMjEuMCJ9" // '{"name":"auth0-spa-js","version":"1.21.0"}'
         | 
| 32 | 
            +
            	auth0LogoutUrl            = api.Auth0Url + "/v2/logout?returnTo=https%3A%2F%2Fplatform.openai.com%2Floggedout&client_id=" + platformAuthClientID + "&auth0Client=" + auth0Client
         | 
| 33 | 
            +
            	dashboardLoginUrl         = "https://api.openai.com/dashboard/onboarding/login"
         | 
| 34 | 
            +
            	getSessionKeyErrorMessage = "Failed to get session key."
         | 
| 35 | 
            +
            )
         | 
    	
        api/platform/typings.go
    ADDED
    
    | @@ -0,0 +1,93 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            package platform
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            //goland:noinspection GoSnakeCaseUsage
         | 
| 4 | 
            +
            import tls_client "github.com/bogdanfinn/tls-client"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            type UserLogin struct {
         | 
| 7 | 
            +
            	client tls_client.HttpClient
         | 
| 8 | 
            +
            }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            type GetAccessTokenRequest struct {
         | 
| 11 | 
            +
            	ClientID    string `json:"client_id"`
         | 
| 12 | 
            +
            	GrantType   string `json:"grant_type"`
         | 
| 13 | 
            +
            	Code        string `json:"code"`
         | 
| 14 | 
            +
            	RedirectURI string `json:"redirect_uri"`
         | 
| 15 | 
            +
            }
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            type GetAccessTokenResponse struct {
         | 
| 18 | 
            +
            	AccessToken  string `json:"access_token"`
         | 
| 19 | 
            +
            	RefreshToken string `json:"refresh_token"`
         | 
| 20 | 
            +
            	IDToken      string `json:"id_token"`
         | 
| 21 | 
            +
            	Scope        string `json:"scope"`
         | 
| 22 | 
            +
            	ExpiresIn    int    `json:"expires_in"`
         | 
| 23 | 
            +
            	TokenType    string `json:"token_type"`
         | 
| 24 | 
            +
            }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            //goland:noinspection SpellCheckingInspection
         | 
| 27 | 
            +
            type CreateCompletionsRequest struct {
         | 
| 28 | 
            +
            	Model            string                 `json:"model"`
         | 
| 29 | 
            +
            	Prompt           string                 `json:"prompt,omitempty"`
         | 
| 30 | 
            +
            	Suffix           string                 `json:"suffix,omitempty"`
         | 
| 31 | 
            +
            	MaxTokens        int                    `json:"max_tokens,omitempty"`
         | 
| 32 | 
            +
            	Temperature      int                    `json:"temperature,omitempty"`
         | 
| 33 | 
            +
            	TopP             int                    `json:"top_p,omitempty"`
         | 
| 34 | 
            +
            	N                int                    `json:"n,omitempty"`
         | 
| 35 | 
            +
            	Stream           bool                   `json:"stream,omitempty"`
         | 
| 36 | 
            +
            	Logprobs         int                    `json:"logprobs,omitempty"`
         | 
| 37 | 
            +
            	Echo             bool                   `json:"echo,omitempty"`
         | 
| 38 | 
            +
            	Stop             string                 `json:"stop,omitempty"`
         | 
| 39 | 
            +
            	PresencePenalty  int                    `json:"presence_penalty,omitempty"`
         | 
| 40 | 
            +
            	FrequencyPenalty int                    `json:"frequency_penalty,omitempty"`
         | 
| 41 | 
            +
            	BestOf           int                    `json:"best_of,omitempty"`
         | 
| 42 | 
            +
            	LogitBias        map[string]interface{} `json:"logit_bias,omitempty"`
         | 
| 43 | 
            +
            	User             string                 `json:"user,omitempty"`
         | 
| 44 | 
            +
            }
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            type ChatCompletionsRequest struct {
         | 
| 47 | 
            +
            	Model            string                   `json:"model"`
         | 
| 48 | 
            +
            	Messages         []ChatCompletionsMessage `json:"messages"`
         | 
| 49 | 
            +
            	Temperature      int                      `json:"temperature,omitempty"`
         | 
| 50 | 
            +
            	TopP             int                      `json:"top_p,omitempty"`
         | 
| 51 | 
            +
            	N                int                      `json:"n,omitempty"`
         | 
| 52 | 
            +
            	Stream           bool                     `json:"stream,omitempty"`
         | 
| 53 | 
            +
            	Stop             string                   `json:"stop,omitempty"`
         | 
| 54 | 
            +
            	MaxTokens        int                      `json:"max_tokens,omitempty"`
         | 
| 55 | 
            +
            	PresencePenalty  int                      `json:"presence_penalty,omitempty"`
         | 
| 56 | 
            +
            	FrequencyPenalty int                      `json:"frequency_penalty,omitempty"`
         | 
| 57 | 
            +
            	LogitBias        map[string]interface{}   `json:"logit_bias,omitempty"`
         | 
| 58 | 
            +
            	User             string                   `json:"user,omitempty"`
         | 
| 59 | 
            +
            }
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            type ChatCompletionsMessage struct {
         | 
| 62 | 
            +
            	Role    string `json:"role"`
         | 
| 63 | 
            +
            	Content string `json:"content"`
         | 
| 64 | 
            +
            	Name    string `json:"name,omitempty"`
         | 
| 65 | 
            +
            }
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            type CreateEditRequest struct {
         | 
| 68 | 
            +
            	Model       string `json:"model"`
         | 
| 69 | 
            +
            	Input       string `json:"input"`
         | 
| 70 | 
            +
            	Instruction string `json:"instruction"`
         | 
| 71 | 
            +
            	N           int    `json:"n,omitempty"`
         | 
| 72 | 
            +
            	Temperature int    `json:"temperature,omitempty"`
         | 
| 73 | 
            +
            	TopP        int    `json:"top_p,omitempty"`
         | 
| 74 | 
            +
            }
         | 
| 75 | 
            +
             | 
| 76 | 
            +
            type CreateImageRequest struct {
         | 
| 77 | 
            +
            	Prompt         string `json:"prompt"`
         | 
| 78 | 
            +
            	N              int    `json:"n,omitempty"`
         | 
| 79 | 
            +
            	Size           string `json:"size,omitempty"`
         | 
| 80 | 
            +
            	ResponseFormat string `json:"response_format,omitempty"`
         | 
| 81 | 
            +
            	User           string `json:"user,omitempty"`
         | 
| 82 | 
            +
            }
         | 
| 83 | 
            +
             | 
| 84 | 
            +
            type CreateEmbeddingsRequest struct {
         | 
| 85 | 
            +
            	Model string `json:"model"`
         | 
| 86 | 
            +
            	Input string `json:"input"`
         | 
| 87 | 
            +
            	User  string `json:"user,omitempty"`
         | 
| 88 | 
            +
            }
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            type CreateModerationRequest struct {
         | 
| 91 | 
            +
            	Model string `json:"model"`
         | 
| 92 | 
            +
            	Input string `json:"input"`
         | 
| 93 | 
            +
            }
         | 
    	
        compose.yaml
    ADDED
    
    | @@ -0,0 +1,11 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            services:
         | 
| 2 | 
            +
              go-chatgpt-api:
         | 
| 3 | 
            +
                build: .
         | 
| 4 | 
            +
                container_name: go-chatgpt-api
         | 
| 5 | 
            +
                image: linweiyuan/go-chatgpt-api
         | 
| 6 | 
            +
                ports:
         | 
| 7 | 
            +
                  - 8080:8080
         | 
| 8 | 
            +
                environment:
         | 
| 9 | 
            +
                  - TZ=Asia/Shanghai
         | 
| 10 | 
            +
                  - GO_CHATGPT_API_PROXY=
         | 
| 11 | 
            +
                restart: unless-stopped
         | 
    	
        env/env.go
    ADDED
    
    | @@ -0,0 +1,10 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            package env
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import (
         | 
| 4 | 
            +
            	"github.com/joho/godotenv"
         | 
| 5 | 
            +
            )
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            //goland:noinspection GoUnhandledErrorResult
         | 
| 8 | 
            +
            func init() {
         | 
| 9 | 
            +
            	godotenv.Load()
         | 
| 10 | 
            +
            }
         | 
    	
        example/chatgpt.http
    ADDED
    
    | @@ -0,0 +1,54 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            ### login
         | 
| 2 | 
            +
            POST http://127.0.0.1:8080/chatgpt/login
         | 
| 3 | 
            +
            Content-Type: application/json
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            {
         | 
| 6 | 
            +
              "username": "{{username}}",
         | 
| 7 | 
            +
              "password": "{{password}}"
         | 
| 8 | 
            +
            }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            ### get conversations
         | 
| 11 | 
            +
            GET http://127.0.0.1:8080/chatgpt/conversations
         | 
| 12 | 
            +
            Authorization: Bearer {{accessToken}}
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            ### create conversation
         | 
| 15 | 
            +
            POST http://127.0.0.1:8080/chatgpt/conversation
         | 
| 16 | 
            +
            Authorization: Bearer {{accessToken}}
         | 
| 17 | 
            +
            Content-Type: application/json
         | 
| 18 | 
            +
            Accept: text/event-stream
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            {
         | 
| 21 | 
            +
              "action": "next",
         | 
| 22 | 
            +
              "messages": [
         | 
| 23 | 
            +
                {
         | 
| 24 | 
            +
                  "id": "{{$random.uuid}}",
         | 
| 25 | 
            +
                  "author": {
         | 
| 26 | 
            +
                    "role": "user"
         | 
| 27 | 
            +
                  },
         | 
| 28 | 
            +
                  "content": {
         | 
| 29 | 
            +
                    "content_type": "text",
         | 
| 30 | 
            +
                    "parts": [
         | 
| 31 | 
            +
                      "hello"
         | 
| 32 | 
            +
                    ]
         | 
| 33 | 
            +
                  },
         | 
| 34 | 
            +
                  "metadata": {}
         | 
| 35 | 
            +
                }
         | 
| 36 | 
            +
              ],
         | 
| 37 | 
            +
              "model": "gpt-4",
         | 
| 38 | 
            +
              "timezone_offset_min": -480,
         | 
| 39 | 
            +
              "history_and_training_disabled": false
         | 
| 40 | 
            +
            }
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            > {%
         | 
| 43 | 
            +
                response.body.onEachLine((data) => {
         | 
| 44 | 
            +
                    client.log(data.toString());
         | 
| 45 | 
            +
                })
         | 
| 46 | 
            +
            %}
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            ### get models
         | 
| 49 | 
            +
            GET http://127.0.0.1:8080/chatgpt/models
         | 
| 50 | 
            +
            Authorization: Bearer {{accessToken}}
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            ### check account
         | 
| 53 | 
            +
            GET http://127.0.0.1:8080/chatgpt/accounts/check
         | 
| 54 | 
            +
            Authorization: Bearer {{accessToken}}
         | 
    	
        example/http-client.env.json
    ADDED
    
    | @@ -0,0 +1,8 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            {
         | 
| 2 | 
            +
              "dev": {
         | 
| 3 | 
            +
                "username": "",
         | 
| 4 | 
            +
                "password": "",
         | 
| 5 | 
            +
                "accessToken": "",
         | 
| 6 | 
            +
                "apiKey": ""
         | 
| 7 | 
            +
              }
         | 
| 8 | 
            +
            }
         | 
    	
        example/platform.http
    ADDED
    
    | @@ -0,0 +1,8 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            ### login
         | 
| 2 | 
            +
            POST http://127.0.0.1:8080/platform/login
         | 
| 3 | 
            +
            Content-Type: application/json
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            {
         | 
| 6 | 
            +
              "username": "{{username}}",
         | 
| 7 | 
            +
              "password": "{{password}}"
         | 
| 8 | 
            +
            }
         | 
    	
        go.mod
    ADDED
    
    | @@ -0,0 +1,44 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            module github.com/linweiyuan/go-chatgpt-api
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            go 1.20
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require (
         | 
| 6 | 
            +
            	github.com/PuerkitoBio/goquery v1.8.1
         | 
| 7 | 
            +
            	github.com/bogdanfinn/fhttp v0.5.22
         | 
| 8 | 
            +
            	github.com/bogdanfinn/tls-client v1.3.11
         | 
| 9 | 
            +
            	github.com/gin-gonic/gin v1.9.1
         | 
| 10 | 
            +
            	github.com/joho/godotenv v1.5.1
         | 
| 11 | 
            +
            	github.com/sirupsen/logrus v1.9.0
         | 
| 12 | 
            +
            )
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            require (
         | 
| 15 | 
            +
            	github.com/andybalholm/brotli v1.0.4 // indirect
         | 
| 16 | 
            +
            	github.com/andybalholm/cascadia v1.3.1 // indirect
         | 
| 17 | 
            +
            	github.com/bogdanfinn/utls v1.5.16 // indirect
         | 
| 18 | 
            +
            	github.com/bytedance/sonic v1.9.1 // indirect
         | 
| 19 | 
            +
            	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
         | 
| 20 | 
            +
            	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
         | 
| 21 | 
            +
            	github.com/gin-contrib/sse v0.1.0 // indirect
         | 
| 22 | 
            +
            	github.com/go-playground/locales v0.14.1 // indirect
         | 
| 23 | 
            +
            	github.com/go-playground/universal-translator v0.18.1 // indirect
         | 
| 24 | 
            +
            	github.com/go-playground/validator/v10 v10.14.0 // indirect
         | 
| 25 | 
            +
            	github.com/goccy/go-json v0.10.2 // indirect
         | 
| 26 | 
            +
            	github.com/json-iterator/go v1.1.12 // indirect
         | 
| 27 | 
            +
            	github.com/klauspost/compress v1.15.12 // indirect
         | 
| 28 | 
            +
            	github.com/klauspost/cpuid/v2 v2.2.4 // indirect
         | 
| 29 | 
            +
            	github.com/leodido/go-urn v1.2.4 // indirect
         | 
| 30 | 
            +
            	github.com/mattn/go-isatty v0.0.19 // indirect
         | 
| 31 | 
            +
            	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
         | 
| 32 | 
            +
            	github.com/modern-go/reflect2 v1.0.2 // indirect
         | 
| 33 | 
            +
            	github.com/pelletier/go-toml/v2 v2.0.8 // indirect
         | 
| 34 | 
            +
            	github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 // indirect
         | 
| 35 | 
            +
            	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
         | 
| 36 | 
            +
            	github.com/ugorji/go/codec v1.2.11 // indirect
         | 
| 37 | 
            +
            	golang.org/x/arch v0.3.0 // indirect
         | 
| 38 | 
            +
            	golang.org/x/crypto v0.9.0 // indirect
         | 
| 39 | 
            +
            	golang.org/x/net v0.10.0 // indirect
         | 
| 40 | 
            +
            	golang.org/x/sys v0.8.0 // indirect
         | 
| 41 | 
            +
            	golang.org/x/text v0.9.0 // indirect
         | 
| 42 | 
            +
            	google.golang.org/protobuf v1.30.0 // indirect
         | 
| 43 | 
            +
            	gopkg.in/yaml.v3 v3.0.1 // indirect
         | 
| 44 | 
            +
            )
         | 
    	
        go.sum
    ADDED
    
    | @@ -0,0 +1,136 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
         | 
| 2 | 
            +
            github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
         | 
| 3 | 
            +
            github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
         | 
| 4 | 
            +
            github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
         | 
| 5 | 
            +
            github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
         | 
| 6 | 
            +
            github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
         | 
| 7 | 
            +
            github.com/bogdanfinn/fhttp v0.5.22 h1:U1jhZRtuaOanWWcm1WdMFnwMvSxUQgvO6berqAVTc5o=
         | 
| 8 | 
            +
            github.com/bogdanfinn/fhttp v0.5.22/go.mod h1:brqi5woc5eSCVHdKYBV8aZLbO7HGqpwyDLeXW+fT18I=
         | 
| 9 | 
            +
            github.com/bogdanfinn/tls-client v1.3.11 h1:3rI+ysCEtnLdmDYlL7cPq2kF3Sj+bSvhgaHPmjLbjOE=
         | 
| 10 | 
            +
            github.com/bogdanfinn/tls-client v1.3.11/go.mod h1:+TLNqnOtUQmYu/qrd/qMuLleoHoTRCcZazkmvNYuiVc=
         | 
| 11 | 
            +
            github.com/bogdanfinn/utls v1.5.16 h1:NhhWkegEcYETBMj9nvgO4lwvc6NcLH+znrXzO3gnw4M=
         | 
| 12 | 
            +
            github.com/bogdanfinn/utls v1.5.16/go.mod h1:mHeRCi69cUiEyVBkKONB1cAbLjRcZnlJbGzttmiuK4o=
         | 
| 13 | 
            +
            github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
         | 
| 14 | 
            +
            github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
         | 
| 15 | 
            +
            github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
         | 
| 16 | 
            +
            github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
         | 
| 17 | 
            +
            github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
         | 
| 18 | 
            +
            github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
         | 
| 19 | 
            +
            github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
         | 
| 20 | 
            +
            github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
         | 
| 21 | 
            +
            github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
         | 
| 22 | 
            +
            github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
         | 
| 23 | 
            +
            github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
         | 
| 24 | 
            +
            github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
         | 
| 25 | 
            +
            github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
         | 
| 26 | 
            +
            github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
         | 
| 27 | 
            +
            github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
         | 
| 28 | 
            +
            github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
         | 
| 29 | 
            +
            github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
         | 
| 30 | 
            +
            github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
         | 
| 31 | 
            +
            github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
         | 
| 32 | 
            +
            github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
         | 
| 33 | 
            +
            github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
         | 
| 34 | 
            +
            github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
         | 
| 35 | 
            +
            github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
         | 
| 36 | 
            +
            github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
         | 
| 37 | 
            +
            github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
         | 
| 38 | 
            +
            github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
         | 
| 39 | 
            +
            github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
         | 
| 40 | 
            +
            github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
         | 
| 41 | 
            +
            github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
         | 
| 42 | 
            +
            github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
         | 
| 43 | 
            +
            github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
         | 
| 44 | 
            +
            github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
         | 
| 45 | 
            +
            github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM=
         | 
| 46 | 
            +
            github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
         | 
| 47 | 
            +
            github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
         | 
| 48 | 
            +
            github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
         | 
| 49 | 
            +
            github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
         | 
| 50 | 
            +
            github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
         | 
| 51 | 
            +
            github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
         | 
| 52 | 
            +
            github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
         | 
| 53 | 
            +
            github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
         | 
| 54 | 
            +
            github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
         | 
| 55 | 
            +
            github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
         | 
| 56 | 
            +
            github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
         | 
| 57 | 
            +
            github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
         | 
| 58 | 
            +
            github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
         | 
| 59 | 
            +
            github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
         | 
| 60 | 
            +
            github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
         | 
| 61 | 
            +
            github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
         | 
| 62 | 
            +
            github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
         | 
| 63 | 
            +
            github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
         | 
| 64 | 
            +
            github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
         | 
| 65 | 
            +
            github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
         | 
| 66 | 
            +
            github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
         | 
| 67 | 
            +
            github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
         | 
| 68 | 
            +
            github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
         | 
| 69 | 
            +
            github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
         | 
| 70 | 
            +
            github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
         | 
| 71 | 
            +
            github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
         | 
| 72 | 
            +
            github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
         | 
| 73 | 
            +
            github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
         | 
| 74 | 
            +
            github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
         | 
| 75 | 
            +
            github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
         | 
| 76 | 
            +
            github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 h1:YqAladjX7xpA6BM04leXMWAEjS0mTZ5kUU9KRBriQJc=
         | 
| 77 | 
            +
            github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5/go.mod h1:2JjD2zLQYH5HO74y5+aE3remJQvl6q4Sn6aWA2wD1Ng=
         | 
| 78 | 
            +
            github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
         | 
| 79 | 
            +
            github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
         | 
| 80 | 
            +
            github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
         | 
| 81 | 
            +
            github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
         | 
| 82 | 
            +
            github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
         | 
| 83 | 
            +
            golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
         | 
| 84 | 
            +
            golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
         | 
| 85 | 
            +
            golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
         | 
| 86 | 
            +
            golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
         | 
| 87 | 
            +
            golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
         | 
| 88 | 
            +
            golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
         | 
| 89 | 
            +
            golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
         | 
| 90 | 
            +
            golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
         | 
| 91 | 
            +
            golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
         | 
| 92 | 
            +
            golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
         | 
| 93 | 
            +
            golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
         | 
| 94 | 
            +
            golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
         | 
| 95 | 
            +
            golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
         | 
| 96 | 
            +
            golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
         | 
| 97 | 
            +
            golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
         | 
| 98 | 
            +
            golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
         | 
| 99 | 
            +
            golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
         | 
| 100 | 
            +
            golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
         | 
| 101 | 
            +
            golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
         | 
| 102 | 
            +
            golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
         | 
| 103 | 
            +
            golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
         | 
| 104 | 
            +
            golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
         | 
| 105 | 
            +
            golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
         | 
| 106 | 
            +
            golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
         | 
| 107 | 
            +
            golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
         | 
| 108 | 
            +
            golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
         | 
| 109 | 
            +
            golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
         | 
| 110 | 
            +
            golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
         | 
| 111 | 
            +
            golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
         | 
| 112 | 
            +
            golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
         | 
| 113 | 
            +
            golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
         | 
| 114 | 
            +
            golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
         | 
| 115 | 
            +
            golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
         | 
| 116 | 
            +
            golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
         | 
| 117 | 
            +
            golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
         | 
| 118 | 
            +
            golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
         | 
| 119 | 
            +
            golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
         | 
| 120 | 
            +
            golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
         | 
| 121 | 
            +
            golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
         | 
| 122 | 
            +
            golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
         | 
| 123 | 
            +
            golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
         | 
| 124 | 
            +
            golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
         | 
| 125 | 
            +
            golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
         | 
| 126 | 
            +
            golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
         | 
| 127 | 
            +
            golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
         | 
| 128 | 
            +
            google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
         | 
| 129 | 
            +
            google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
         | 
| 130 | 
            +
            google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
         | 
| 131 | 
            +
            gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
         | 
| 132 | 
            +
            gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
         | 
| 133 | 
            +
            gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
         | 
| 134 | 
            +
            gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
         | 
| 135 | 
            +
            gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
         | 
| 136 | 
            +
            rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
         | 
    	
        main.go
    ADDED
    
    | @@ -0,0 +1,119 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            package main
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import (
         | 
| 4 | 
            +
            	"log"
         | 
| 5 | 
            +
            	"net/http"
         | 
| 6 | 
            +
            	"os"
         | 
| 7 | 
            +
            	"strings"
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            	"github.com/gin-gonic/gin"
         | 
| 10 | 
            +
            	"github.com/linweiyuan/go-chatgpt-api/api/chatgpt"
         | 
| 11 | 
            +
            	"github.com/linweiyuan/go-chatgpt-api/api/platform"
         | 
| 12 | 
            +
            	_ "github.com/linweiyuan/go-chatgpt-api/env"
         | 
| 13 | 
            +
            	"github.com/linweiyuan/go-chatgpt-api/middleware"
         | 
| 14 | 
            +
            )
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            func init() {
         | 
| 17 | 
            +
            	gin.ForceConsoleColor()
         | 
| 18 | 
            +
            	gin.SetMode(gin.ReleaseMode)
         | 
| 19 | 
            +
            }
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            func main() {
         | 
| 22 | 
            +
            	router := gin.Default()
         | 
| 23 | 
            +
            	router.Use(middleware.CORSMiddleware())
         | 
| 24 | 
            +
            	router.Use(middleware.CheckHeaderMiddleware())
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            	setupChatGPTAPIs(router)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            	setupPlatformAPIs(router)
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            	router.NoRoute(handleFallbackRoute)
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            	port := os.Getenv("GO_CHATGPT_API_PORT")
         | 
| 33 | 
            +
            	if port == "" {
         | 
| 34 | 
            +
            		port = "8080"
         | 
| 35 | 
            +
            	}
         | 
| 36 | 
            +
            	err := router.Run(":" + port)
         | 
| 37 | 
            +
            	if err != nil {
         | 
| 38 | 
            +
            		log.Fatal("Failed to start server: " + err.Error())
         | 
| 39 | 
            +
            	}
         | 
| 40 | 
            +
            }
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            func setupChatGPTAPIs(router *gin.Engine) {
         | 
| 43 | 
            +
            	chatgptGroup := router.Group("/chatgpt")
         | 
| 44 | 
            +
            	{
         | 
| 45 | 
            +
            		chatgptGroup.POST("/login", chatgpt.Login)
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            		conversationsGroup := chatgptGroup.Group("/conversations")
         | 
| 48 | 
            +
            		{
         | 
| 49 | 
            +
            			conversationsGroup.GET("", chatgpt.GetConversations)
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            			// PATCH is official method, POST is added for Java support
         | 
| 52 | 
            +
            			conversationsGroup.PATCH("", chatgpt.ClearConversations)
         | 
| 53 | 
            +
            			conversationsGroup.POST("", chatgpt.ClearConversations)
         | 
| 54 | 
            +
            		}
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            		conversationGroup := chatgptGroup.Group("/conversation")
         | 
| 57 | 
            +
            		{
         | 
| 58 | 
            +
            			conversationGroup.POST("", chatgpt.CreateConversation)
         | 
| 59 | 
            +
            			conversationGroup.POST("/gen_title/:id", chatgpt.GenerateTitle)
         | 
| 60 | 
            +
            			conversationGroup.GET("/:id", chatgpt.GetConversation)
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            			// rename or delete conversation use a same API with different parameters
         | 
| 63 | 
            +
            			conversationGroup.PATCH("/:id", chatgpt.UpdateConversation)
         | 
| 64 | 
            +
            			conversationGroup.POST("/:id", chatgpt.UpdateConversation)
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            			conversationGroup.POST("/message_feedback", chatgpt.FeedbackMessage)
         | 
| 67 | 
            +
            		}
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            		// misc
         | 
| 70 | 
            +
            		chatgptGroup.GET("/models", chatgpt.GetModels)
         | 
| 71 | 
            +
            		chatgptGroup.GET("/accounts/check", chatgpt.GetAccountCheck)
         | 
| 72 | 
            +
            	}
         | 
| 73 | 
            +
            }
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            func setupPlatformAPIs(router *gin.Engine) {
         | 
| 76 | 
            +
            	platformGroup := router.Group("/platform")
         | 
| 77 | 
            +
            	{
         | 
| 78 | 
            +
            		platformGroup.POST("/login", platform.Login)
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            		apiGroup := platformGroup.Group("/v1")
         | 
| 81 | 
            +
            		{
         | 
| 82 | 
            +
            			apiGroup.GET("/models", platform.ListModels)
         | 
| 83 | 
            +
            			apiGroup.GET("/models/:model", platform.RetrieveModel)
         | 
| 84 | 
            +
            			apiGroup.POST("/completions", platform.CreateCompletions)
         | 
| 85 | 
            +
            			apiGroup.POST("/chat/completions", platform.CreateChatCompletions)
         | 
| 86 | 
            +
            			apiGroup.POST("/edits", platform.CreateEdit)
         | 
| 87 | 
            +
            			apiGroup.POST("/images/generations", platform.CreateImage)
         | 
| 88 | 
            +
            			apiGroup.POST("/embeddings", platform.CreateEmbeddings)
         | 
| 89 | 
            +
            			apiGroup.GET("/files", platform.ListFiles)
         | 
| 90 | 
            +
            			apiGroup.POST("/moderations", platform.CreateModeration)
         | 
| 91 | 
            +
            		}
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            		dashboardGroup := platformGroup.Group("/dashboard")
         | 
| 94 | 
            +
            		{
         | 
| 95 | 
            +
            			billingGroup := dashboardGroup.Group("/billing")
         | 
| 96 | 
            +
            			{
         | 
| 97 | 
            +
            				billingGroup.GET("/credit_grants", platform.GetCreditGrants)
         | 
| 98 | 
            +
            				billingGroup.GET("/subscription", platform.GetSubscription)
         | 
| 99 | 
            +
            			}
         | 
| 100 | 
            +
             | 
| 101 | 
            +
            			userGroup := dashboardGroup.Group("/user")
         | 
| 102 | 
            +
            			{
         | 
| 103 | 
            +
            				userGroup.GET("/api_keys", platform.GetApiKeys)
         | 
| 104 | 
            +
            			}
         | 
| 105 | 
            +
            		}
         | 
| 106 | 
            +
            	}
         | 
| 107 | 
            +
            }
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            func handleFallbackRoute(c *gin.Context) {
         | 
| 110 | 
            +
            	path := c.Request.URL.Path
         | 
| 111 | 
            +
             | 
| 112 | 
            +
            	if strings.HasPrefix(path, "/chatgpt") {
         | 
| 113 | 
            +
            		trimmedPath := strings.TrimPrefix(path, "/chatgpt")
         | 
| 114 | 
            +
            		c.Request.URL.Path = trimmedPath
         | 
| 115 | 
            +
            		chatgpt.Fallback(c)
         | 
| 116 | 
            +
            	} else {
         | 
| 117 | 
            +
            		c.JSON(http.StatusNotFound, gin.H{"message": "Route not found"})
         | 
| 118 | 
            +
            	}
         | 
| 119 | 
            +
            }
         | 
    	
        middleware/check_header.go
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            package middleware
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import (
         | 
| 4 | 
            +
            	"net/http"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            	"github.com/gin-gonic/gin"
         | 
| 7 | 
            +
            	"github.com/linweiyuan/go-chatgpt-api/api"
         | 
| 8 | 
            +
            )
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            func CheckHeaderMiddleware() gin.HandlerFunc {
         | 
| 11 | 
            +
            	return func(c *gin.Context) {
         | 
| 12 | 
            +
            		if c.GetHeader(api.AuthorizationHeader) == "" &&
         | 
| 13 | 
            +
            			c.Request.URL.Path != "/chatgpt/login" &&
         | 
| 14 | 
            +
            			c.Request.URL.Path != "/platform/login" {
         | 
| 15 | 
            +
            			c.AbortWithStatusJSON(http.StatusUnauthorized, api.ReturnMessage("Missing accessToken."))
         | 
| 16 | 
            +
            			return
         | 
| 17 | 
            +
            		}
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            		c.Header("Content-Type", "application/json")
         | 
| 20 | 
            +
            		c.Next()
         | 
| 21 | 
            +
            	}
         | 
| 22 | 
            +
            }
         | 
    	
        middleware/cors.go
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            package middleware
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import (
         | 
| 4 | 
            +
            	"github.com/gin-gonic/gin"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            	http "github.com/bogdanfinn/fhttp"
         | 
| 7 | 
            +
            )
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            func CORSMiddleware() gin.HandlerFunc {
         | 
| 10 | 
            +
            	return func(c *gin.Context) {
         | 
| 11 | 
            +
            		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
         | 
| 12 | 
            +
            		c.Writer.Header().Set("Access-Control-Allow-Headers", "*")
         | 
| 13 | 
            +
            		c.Writer.Header().Set("Access-Control-Allow-Methods", "*")
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            		if c.Request.Method == http.MethodOptions {
         | 
| 16 | 
            +
            			c.AbortWithStatus(http.StatusNoContent)
         | 
| 17 | 
            +
            			return
         | 
| 18 | 
            +
            		}
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            		c.Next()
         | 
| 21 | 
            +
            	}
         | 
| 22 | 
            +
            }
         | 
    	
        util/logger/logger.go
    ADDED
    
    | @@ -0,0 +1,37 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            package logger
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import (
         | 
| 4 | 
            +
            	"fmt"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            	"github.com/sirupsen/logrus"
         | 
| 7 | 
            +
            )
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            func init() {
         | 
| 10 | 
            +
            	logrus.SetFormatter(&logrus.TextFormatter{
         | 
| 11 | 
            +
            		ForceColors: true,
         | 
| 12 | 
            +
            	})
         | 
| 13 | 
            +
            }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            func Ansi(colorString string) func(...interface{}) string {
         | 
| 16 | 
            +
            	return func(args ...interface{}) string {
         | 
| 17 | 
            +
            		return fmt.Sprintf(colorString, fmt.Sprint(args...))
         | 
| 18 | 
            +
            	}
         | 
| 19 | 
            +
            }
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            var (
         | 
| 22 | 
            +
            	Green  = Ansi("\033[1;32m%s\033[0m")
         | 
| 23 | 
            +
            	Yellow = Ansi("\033[1;33m%s\033[0m")
         | 
| 24 | 
            +
            	Red    = Ansi("\033[1;31m%s\033[0m")
         | 
| 25 | 
            +
            )
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            func Info(msg string) {
         | 
| 28 | 
            +
            	logrus.Info(Green(msg))
         | 
| 29 | 
            +
            }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            func Warn(msg string) {
         | 
| 32 | 
            +
            	logrus.Warn(Yellow(msg))
         | 
| 33 | 
            +
            }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            func Error(msg string) {
         | 
| 36 | 
            +
            	logrus.Error(Red(msg))
         | 
| 37 | 
            +
            }
         |