dvc890 commited on
Commit
f16d50c
·
1 Parent(s): 0cd28fc

Upload 30 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM golang:alpine AS builder
2
+ WORKDIR /app
3
+ COPY . .
4
+ RUN go build -ldflags="-w -s" -o go-chatgpt-api main.go
5
+
6
+ FROM alpine
7
+ WORKDIR /app
8
+ COPY --from=builder /app/go-chatgpt-api .
9
+ RUN apk add --no-cache tzdata
10
+ ENV TZ=Asia/Shanghai
11
+ EXPOSE 8080
12
+ CMD ["/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
@@ -6,5 +6,235 @@ colorTo: green
6
  sdk: docker
7
  pinned: false
8
  ---
 
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  sdk: docker
7
  pinned: false
8
  ---
9
+ # go-chatgpt-api
10
 
11
+ ## 一个尝试绕过 `Cloudflare` 来使用 `ChatGPT` 接口的程序
12
+
13
+ (本项目没什么问题的话,基本不会有什么大的更新了,够用了,同时欢迎 PR)
14
+
15
+ ---
16
+
17
+ ### 支持接口
18
+
19
+ - https://chat.openai.com/auth/login 登录返回 `accessToken`(谷歌和微软账号暂不支持登录,但可正常使用其他接口)
20
+ - 模型和插件查询
21
+ - `GPT-3.5` 和 `GPT-4` 对话增删改查及分享
22
+ - https://platform.openai.com/playground 登录返回 `apiKey`
23
+ - `apiKey` 余额查询
24
+ - 等等 ...
25
+ - 支持 `ChatGPT` 转 `API`,接口 `/imitate/v1/chat/completions`,利用 `accessToken` 模拟 `apiKey`,实现伪免费使用 `API`,从而支持集成仅支持 `apiKey` 调用的第三方客户端项目,分享一个好用的脚本测试 `web-to-api` (https://github.com/linweiyuan/go-chatgpt-api/issues/251)
26
+
27
+ ```python
28
+ import openai
29
+
30
+ openai.api_key = "这里填 access token,不是 api key"
31
+ openai.api_base = "http://127.0.0.1:8080/imitate/v1"
32
+
33
+ while True:
34
+ text = input("请输入问题:")
35
+ response = openai.ChatCompletion.create(
36
+ model='gpt-3.5-turbo',
37
+ messages=[
38
+ {'role': 'user', 'content': text},
39
+ ],
40
+ stream=True
41
+ )
42
+
43
+ for chunk in response:
44
+ print(chunk.choices[0].delta.get("content", ""), end="", flush=True)
45
+ print("\n")
46
+ ```
47
+
48
+ 范例(URL 和参数基本保持着和官网一致,部分接口有些许改动),部分例子,不是全部,**理论上**全部基于文本传输的接口都支持
49
+
50
+ https://github.com/linweiyuan/go-chatgpt-api/tree/main/example
51
+
52
+ ---
53
+
54
+ ### 使用的过程中遇到问题应该如何解决
55
+
56
+ 汇总贴:https://github.com/linweiyuan/go-chatgpt-api/issues/74
57
+
58
+ 如果有疑问而不是什么程序出错其实可以在 [Discussions](https://github.com/linweiyuan/go-chatgpt-api/discussions) 里发而不是新增 Issue
59
+
60
+ 群聊:https://github.com/linweiyuan/go-chatgpt-api/discussions/197
61
+
62
+ 再说一遍,不要来 `Issues` 提你的疑问(再提不回复直接关闭),有讨论区,有群,不要提脑残问题,反面教材:https://github.com/linweiyuan/go-chatgpt-api/issues/255
63
+
64
+ ---
65
+
66
+ ### 配置
67
+
68
+ 如需设置代理,可以设置环境变量 `PROXY`,比如 `PROXY=http://127.0.0.1:20171` 或者 `PROXY=socks5://127.0.0.1:20170`,注释掉或者留空则不启用
69
+
70
+ 如果代理需账号密码验证,则 `http://username:password@ip:port` 或者 `socks5://username:password@ip:port`
71
+
72
+ 如需配合 `warp` 使用:`PROXY=socks5://chatgpt-proxy-server-warp:65535`,因为需要设置 `warp` 的场景已经默认可以直接访问 `ChatGPT` 官网,因此共用一个变量不冲突(国内 `VPS` 不在讨论范围内,请自行配置网络环境,`warp` 服务在魔法环境下才能正常工作)
73
+
74
+ 家庭网络无需跑 `warp` 服务,跑了也没用,会报错,仅在服务器需要
75
+
76
+ `CONTINUE_SIGNAL=1`,开启 `/imitate` 接口自动继续会话功能,留空关闭,默认关闭
77
+
78
+ ---
79
+
80
+ `GPT-4` 相关模型目前需要验证 `arkose_token`,社区已经有很多解决方案,请自行查找,其中一个能用的:https://github.com/linweiyuan/go-chatgpt-api/issues/252
81
+
82
+ 参考配置视频(拉到文章最下面点开视频,需要自己有一定的动手能力,根据你的环境不同自行微调配置):[如何生成 GPT-4 arkose_token](https://linweiyuan.github.io/2023/06/24/%E5%A6%82%E4%BD%95%E7%94%9F%E6%88%90-GPT-4-arkose-token.html)
83
+
84
+ ---
85
+
86
+ 根据你的网络环境不同,可以展开查看对应配置,下面例子是基本参数,更多参数查看 [compose.yaml](https://github.com/linweiyuan/go-chatgpt-api/blob/main/compose.yaml)
87
+
88
+ <details>
89
+
90
+ <summary>直接利用现成的服务</summary>
91
+
92
+ 服务器不定时维护,不保证高可用,利用这些服务导致的账号安全问题,与本项目无关
93
+
94
+ - https://go-chatgpt-api.linweiyuan.com
95
+
96
+ </details>
97
+
98
+ <details>
99
+
100
+ <summary>网络在直连或者通过代理的情况下可以正常访问 ChatGPT</summary>
101
+
102
+ ```yaml
103
+ services:
104
+ go-chatgpt-api:
105
+ container_name: go-chatgpt-api
106
+ image: linweiyuan/go-chatgpt-api
107
+ ports:
108
+ - 8080:8080
109
+ environment:
110
+ - TZ=Asia/Shanghai
111
+ restart: unless-stopped
112
+ ```
113
+
114
+ </details>
115
+
116
+ <details>
117
+
118
+ <summary>服务器无法正常访问 ChatGPT</summary>
119
+
120
+ ```yaml
121
+ services:
122
+ go-chatgpt-api:
123
+ container_name: go-chatgpt-api
124
+ image: linweiyuan/go-chatgpt-api
125
+ ports:
126
+ - 8080:8080
127
+ environment:
128
+ - TZ=Asia/Shanghai
129
+ - PROXY=socks5://chatgpt-proxy-server-warp:65535
130
+ depends_on:
131
+ - chatgpt-proxy-server-warp
132
+ restart: unless-stopped
133
+
134
+ chatgpt-proxy-server-warp:
135
+ container_name: chatgpt-proxy-server-warp
136
+ image: linweiyuan/chatgpt-proxy-server-warp
137
+ restart: unless-stopped
138
+ ```
139
+
140
+ </details>
141
+
142
+ ---
143
+
144
+ 目前 `warp` 容器检测到流量超过 1G 会自动���启,如果你知道什么是 `teams-enroll-token` (不知道就跳过),可以通过环境变量 `TEAMS_ENROLL_TOKEN` 设置它的值,然后利用这条命令来检查是否生效
145
+
146
+ `docker-compose exec chatgpt-proxy-server-warp warp-cli --accept-tos account | awk 'NR==1'`
147
+
148
+ ```
149
+ Account type: Free (没有生效)
150
+
151
+ Account type: Team (设置正常)
152
+ ```
153
+
154
+ ---
155
+
156
+ ### Render部署
157
+
158
+ 点击下面的按钮一键部署,缺点是免费版本冷启动比较慢
159
+
160
+ [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/linweiyuan/go-chatgpt-api)
161
+
162
+ ---
163
+
164
+ ### 如何集成其他第三方客户端(下面的内容不一定是最新,有问题请去各自项目查看)
165
+
166
+ - [moeakwak/chatgpt-web-share](https://github.com/moeakwak/chatgpt-web-share)
167
+
168
+ 环境变量
169
+
170
+ ```
171
+ CHATGPT_BASE_URL=http://go-chatgpt-api:8080/chatgpt/backend-api/
172
+ ```
173
+
174
+ - [lss233/chatgpt-mirai-qq-bot](https://github.com/lss233/chatgpt-mirai-qq-bot)
175
+
176
+ `config.cfg`
177
+
178
+ ```
179
+ [openai]
180
+ browserless_endpoint = "http://go-chatgpt-api:8080/chatgpt/backend-api/"
181
+ ```
182
+
183
+ - [Kerwin1202/chatgpt-web](https://github.com/Kerwin1202/chatgpt-web) | [Chanzhaoyu/chatgpt-web](https://github.com/Chanzhaoyu/chatgpt-web)
184
+
185
+ 环境变量
186
+
187
+ ```
188
+ API_REVERSE_PROXY=http://go-chatgpt-api:8080/chatgpt/backend-api/conversation
189
+ ```
190
+
191
+ - [pengzhile/pandora](https://github.com/pengzhile/pandora)(不完全兼容)
192
+
193
+ 环境变量
194
+
195
+ ```
196
+ CHATGPT_API_PREFIX=http://go-chatgpt-api:8080
197
+ ```
198
+
199
+ ---
200
+
201
+ - [1130600015/feishu-chatgpt](https://github.com/1130600015/feishu-chatgpt)
202
+
203
+ `application.yaml`
204
+
205
+ ```yaml
206
+ proxy:
207
+ url: http://go-chatgpt-api:8080
208
+ ```
209
+
210
+ ---
211
+
212
+ - [Yidadaa/ChatGPT-Next-Web](https://github.com/Yidadaa/ChatGPT-Next-Web)
213
+
214
+ 环境变量
215
+
216
+ ```
217
+ BASE_URL=http://go-chatgpt-api:8080/imitate
218
+ ```
219
+
220
+ ---
221
+
222
+ ### 相关博客(程序更新很多次,文章的内容可能和现在的不一样,仅供参考):[ChatGPT](https://linweiyuan.github.io/categories/ChatGPT/)
223
+
224
+ - [如何生成 GPT-4 arkose_token](https://linweiyuan.github.io/2023/06/24/%E5%A6%82%E4%BD%95%E7%94%9F%E6%88%90-GPT-4-arkose-token.html)
225
+ - [利用 HTTP Client 来调试 go-chatgpt-api](https://linweiyuan.github.io/2023/06/18/%E5%88%A9%E7%94%A8-HTTP-Client-%E6%9D%A5%E8%B0%83%E8%AF%95-go-chatgpt-api.html)
226
+ - [一种解决 ChatGPT Access denied 的方法](https://linweiyuan.github.io/2023/04/15/%E4%B8%80%E7%A7%8D%E8%A7%A3%E5%86%B3-ChatGPT-Access-denied-%E7%9A%84%E6%96%B9%E6%B3%95.html)
227
+ - [ChatGPT 如何自建代理](https://linweiyuan.github.io/2023/04/08/ChatGPT-%E5%A6%82%E4%BD%95%E8%87%AA%E5%BB%BA%E4%BB%A3%E7%90%86.html)
228
+ - [一种取巧的方式绕过 Cloudflare v2 验证](https://linweiyuan.github.io/2023/03/14/%E4%B8%80%E7%A7%8D%E5%8F%96%E5%B7%A7%E7%9A%84%E6%96%B9%E5%BC%8F%E7%BB%95%E8%BF%87-Cloudflare-v2-%E9%AA%8C%E8%AF%81.html)
229
+
230
+ ---
231
+
232
+ ### 最后感谢各位同学
233
+
234
+ <a href="https://github.com/linweiyuan/go-chatgpt-api/graphs/contributors">
235
+ <img src="https://contrib.rocks/image?repo=linweiyuan/go-chatgpt-api" alt=""/>
236
+ </a>
237
+
238
+ ---
239
+
240
+ ![](https://linweiyuan.github.io/about/mm_reward_qrcode.png)
api/chatgpt/api.go ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package chatgpt
2
+
3
+ import (
4
+ "bufio"
5
+ "bytes"
6
+ "encoding/json"
7
+ "fmt"
8
+ "io"
9
+ "strings"
10
+
11
+ http "github.com/bogdanfinn/fhttp"
12
+ "github.com/gin-gonic/gin"
13
+
14
+ "github.com/linweiyuan/go-chatgpt-api/api"
15
+ "github.com/linweiyuan/go-logger/logger"
16
+ )
17
+
18
+ func CreateConversation(c *gin.Context) {
19
+ var request CreateConversationRequest
20
+ if err := c.BindJSON(&request); err != nil {
21
+ c.AbortWithStatusJSON(http.StatusBadRequest, api.ReturnMessage(parseJsonErrorMessage))
22
+ return
23
+ }
24
+
25
+ if request.ConversationID == nil || *request.ConversationID == "" {
26
+ request.ConversationID = nil
27
+ }
28
+
29
+ if len(request.Messages) != 0 {
30
+ message := request.Messages[0]
31
+ if message.Author.Role == "" {
32
+ message.Author.Role = defaultRole
33
+ }
34
+
35
+ if message.Metadata == nil {
36
+ message.Metadata = map[string]string{}
37
+ }
38
+
39
+ request.Messages[0] = message
40
+ }
41
+
42
+ if strings.HasPrefix(request.Model, gpt4Model) && request.ArkoseToken == "" {
43
+ arkoseToken, err := api.GetArkoseToken()
44
+ if err != nil || arkoseToken == "" {
45
+ c.AbortWithStatusJSON(http.StatusForbidden, api.ReturnMessage(err.Error()))
46
+ return
47
+ }
48
+
49
+ request.ArkoseToken = arkoseToken
50
+ }
51
+
52
+ resp, done := sendConversationRequest(c, request)
53
+ if done {
54
+ return
55
+ }
56
+
57
+ handleConversationResponse(c, resp, request)
58
+ }
59
+
60
+ func sendConversationRequest(c *gin.Context, request CreateConversationRequest) (*http.Response, bool) {
61
+ jsonBytes, _ := json.Marshal(request)
62
+ req, _ := http.NewRequest(http.MethodPost, api.ChatGPTApiUrlPrefix+"/backend-api/conversation", bytes.NewBuffer(jsonBytes))
63
+ req.Header.Set("User-Agent", api.UserAgent)
64
+ req.Header.Set(api.AuthorizationHeader, api.GetAccessToken(c))
65
+ req.Header.Set("Accept", "text/event-stream")
66
+ if api.PUID != "" {
67
+ req.Header.Set("Cookie", "_puid="+api.PUID)
68
+ }
69
+ resp, err := api.Client.Do(req)
70
+ if err != nil {
71
+ c.AbortWithStatusJSON(http.StatusInternalServerError, api.ReturnMessage(err.Error()))
72
+ return nil, true
73
+ }
74
+
75
+ if resp.StatusCode != http.StatusOK {
76
+ defer resp.Body.Close()
77
+
78
+ if resp.StatusCode == http.StatusUnauthorized {
79
+ logger.Error(fmt.Sprintf(api.AccountDeactivatedErrorMessage, c.GetString(api.EmailKey)))
80
+ responseMap := make(map[string]interface{})
81
+ json.NewDecoder(resp.Body).Decode(&responseMap)
82
+ c.AbortWithStatusJSON(resp.StatusCode, responseMap)
83
+ return nil, true
84
+ }
85
+
86
+ req, _ := http.NewRequest(http.MethodGet, api.ChatGPTApiUrlPrefix+"/backend-api/models?history_and_training_disabled=false", nil)
87
+ req.Header.Set("User-Agent", api.UserAgent)
88
+ req.Header.Set(api.AuthorizationHeader, api.GetAccessToken(c))
89
+ response, err := api.Client.Do(req)
90
+ if err != nil {
91
+ c.AbortWithStatusJSON(http.StatusInternalServerError, api.ReturnMessage(err.Error()))
92
+ return nil, true
93
+ }
94
+
95
+ defer response.Body.Close()
96
+ modelAvailable := false
97
+ var getModelsResponse GetModelsResponse
98
+ json.NewDecoder(response.Body).Decode(&getModelsResponse)
99
+ for _, model := range getModelsResponse.Models {
100
+ if model.Slug == request.Model {
101
+ modelAvailable = true
102
+ break
103
+ }
104
+ }
105
+ if !modelAvailable {
106
+ c.AbortWithStatusJSON(http.StatusForbidden, api.ReturnMessage(noModelPermissionErrorMessage))
107
+ return nil, true
108
+ }
109
+
110
+ data, _ := io.ReadAll(resp.Body)
111
+ logger.Warn(string(data))
112
+
113
+ responseMap := make(map[string]interface{})
114
+ json.NewDecoder(resp.Body).Decode(&responseMap)
115
+ c.AbortWithStatusJSON(resp.StatusCode, responseMap)
116
+ return nil, true
117
+ }
118
+
119
+ return resp, false
120
+ }
121
+
122
+ func handleConversationResponse(c *gin.Context, resp *http.Response, request CreateConversationRequest) {
123
+ c.Writer.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
124
+
125
+ isMaxTokens := false
126
+ continueParentMessageID := ""
127
+ continueConversationID := ""
128
+
129
+ defer resp.Body.Close()
130
+ reader := bufio.NewReader(resp.Body)
131
+ for {
132
+ if c.Request.Context().Err() != nil {
133
+ break
134
+ }
135
+
136
+ line, err := reader.ReadString('\n')
137
+ if err != nil {
138
+ break
139
+ }
140
+
141
+ line = strings.TrimSpace(line)
142
+ if strings.HasPrefix(line, "event") ||
143
+ strings.HasPrefix(line, "data: 20") ||
144
+ strings.HasPrefix(line, `data: {"conversation_id"`) ||
145
+ line == "" {
146
+ continue
147
+ }
148
+
149
+ responseJson := line[6:]
150
+ if strings.HasPrefix(responseJson, "[DONE]") && isMaxTokens && request.AutoContinue {
151
+ continue
152
+ }
153
+
154
+ // no need to unmarshal every time, but if response content has this "max_tokens", need to further check
155
+ if strings.TrimSpace(responseJson) != "" && strings.Contains(responseJson, responseTypeMaxTokens) {
156
+ var createConversationResponse CreateConversationResponse
157
+ json.Unmarshal([]byte(responseJson), &createConversationResponse)
158
+ message := createConversationResponse.Message
159
+ if message.Metadata.FinishDetails.Type == responseTypeMaxTokens && createConversationResponse.Message.Status == responseStatusFinishedSuccessfully {
160
+ isMaxTokens = true
161
+ continueParentMessageID = message.ID
162
+ continueConversationID = createConversationResponse.ConversationID
163
+ }
164
+ }
165
+
166
+ c.Writer.Write([]byte(line + "\n\n"))
167
+ c.Writer.Flush()
168
+ }
169
+
170
+ if isMaxTokens && request.AutoContinue {
171
+ continueConversationRequest := CreateConversationRequest{
172
+ ArkoseToken: request.ArkoseToken,
173
+ HistoryAndTrainingDisabled: request.HistoryAndTrainingDisabled,
174
+ Model: request.Model,
175
+ TimezoneOffsetMin: request.TimezoneOffsetMin,
176
+
177
+ Action: actionContinue,
178
+ ParentMessageID: continueParentMessageID,
179
+ ConversationID: &continueConversationID,
180
+ }
181
+ resp, done := sendConversationRequest(c, continueConversationRequest)
182
+ if done {
183
+ return
184
+ }
185
+
186
+ handleConversationResponse(c, resp, continueConversationRequest)
187
+ }
188
+ }
api/chatgpt/constant.go ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package chatgpt
2
+
3
+ const (
4
+ defaultRole = "user"
5
+ parseJsonErrorMessage = "failed to parse json request body"
6
+
7
+ gpt4Model = "gpt-4"
8
+ actionContinue = "continue"
9
+ responseTypeMaxTokens = "max_tokens"
10
+ responseStatusFinishedSuccessfully = "finished_successfully"
11
+ noModelPermissionErrorMessage = "you have no permission to use this model"
12
+ )
api/chatgpt/health_check.go ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package chatgpt
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "time"
7
+
8
+ "github.com/PuerkitoBio/goquery"
9
+ http "github.com/bogdanfinn/fhttp"
10
+
11
+ "github.com/linweiyuan/go-chatgpt-api/api"
12
+ "github.com/linweiyuan/go-logger/logger"
13
+ )
14
+
15
+ const (
16
+ healthCheckUrl = "https://chat.openai.com/backend-api/accounts/check"
17
+ errorHintBlock = "looks like you have bean blocked by OpenAI, please change to a new IP or have a try with WARP"
18
+ errorHintFailedToStart = "failed to start, please try again later: %s"
19
+ sleepHours = 8760 // 365 days
20
+ )
21
+
22
+ func init() {
23
+ proxyUrl := os.Getenv("PROXY")
24
+ if proxyUrl != "" {
25
+ logger.Info("PROXY: " + proxyUrl)
26
+ api.Client.SetProxy(proxyUrl)
27
+
28
+ for {
29
+ resp, err := healthCheck()
30
+ if err != nil {
31
+ // wait for proxy to be ready
32
+ time.Sleep(time.Second)
33
+ continue
34
+ }
35
+
36
+ checkHealthCheckStatus(resp)
37
+ break
38
+ }
39
+ } else {
40
+ resp, err := healthCheck()
41
+ if err != nil {
42
+ logger.Error("failed to health check: " + err.Error())
43
+ os.Exit(1)
44
+ }
45
+
46
+ checkHealthCheckStatus(resp)
47
+ }
48
+ }
49
+
50
+ func healthCheck() (resp *http.Response, err error) {
51
+ req, _ := http.NewRequest(http.MethodGet, healthCheckUrl, nil)
52
+ req.Header.Set("User-Agent", api.UserAgent)
53
+ resp, err = api.Client.Do(req)
54
+ return
55
+ }
56
+
57
+ func checkHealthCheckStatus(resp *http.Response) {
58
+ if resp != nil {
59
+ defer resp.Body.Close()
60
+
61
+ if resp.StatusCode == http.StatusUnauthorized {
62
+ logger.Info(api.ReadyHint)
63
+ } else {
64
+ doc, _ := goquery.NewDocumentFromReader(resp.Body)
65
+ alert := doc.Find(".message").Text()
66
+ if alert != "" {
67
+ logger.Error(errorHintBlock)
68
+ } else {
69
+ logger.Error(fmt.Sprintf(errorHintFailedToStart, resp.Status))
70
+ }
71
+ time.Sleep(time.Hour * sleepHours)
72
+ os.Exit(1)
73
+ }
74
+ }
75
+ }
api/chatgpt/login.go ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package chatgpt
2
+
3
+ import (
4
+ http "github.com/bogdanfinn/fhttp"
5
+ "github.com/gin-gonic/gin"
6
+ "github.com/xqdoo00o/OpenAIAuth/auth"
7
+
8
+ "github.com/linweiyuan/go-chatgpt-api/api"
9
+ )
10
+
11
+ func Login(c *gin.Context) {
12
+ var loginInfo api.LoginInfo
13
+ if err := c.ShouldBindJSON(&loginInfo); err != nil {
14
+ c.AbortWithStatusJSON(http.StatusBadRequest, api.ReturnMessage(api.ParseUserInfoErrorMessage))
15
+ return
16
+ }
17
+
18
+ authenticator := auth.NewAuthenticator(loginInfo.Username, loginInfo.Password, api.ProxyUrl)
19
+ if err := authenticator.Begin(); err != nil {
20
+ c.AbortWithStatusJSON(err.StatusCode, api.ReturnMessage(err.Details))
21
+ return
22
+ }
23
+
24
+ c.JSON(http.StatusOK, gin.H{
25
+ "accessToken": authenticator.GetAccessToken(),
26
+ })
27
+ }
api/chatgpt/typings.go ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package chatgpt
2
+
3
+ import (
4
+ "github.com/google/uuid"
5
+ )
6
+
7
+ type CreateConversationRequest struct {
8
+ Action string `json:"action"`
9
+ Messages []Message `json:"messages"`
10
+ Model string `json:"model"`
11
+ ParentMessageID string `json:"parent_message_id"`
12
+ ConversationID *string `json:"conversation_id"`
13
+ PluginIDs []string `json:"plugin_ids"`
14
+ TimezoneOffsetMin int `json:"timezone_offset_min"`
15
+ ArkoseToken string `json:"arkose_token"`
16
+ HistoryAndTrainingDisabled bool `json:"history_and_training_disabled"`
17
+ AutoContinue bool `json:"auto_continue"`
18
+ Suggestions []string `json:"suggestions"`
19
+ }
20
+
21
+ func (c *CreateConversationRequest) AddMessage(role string, content string) {
22
+ c.Messages = append(c.Messages, Message{
23
+ ID: uuid.New().String(),
24
+ Author: Author{Role: role},
25
+ Content: Content{ContentType: "text", Parts: []interface{}{content}},
26
+ Metadata: map[string]string{},
27
+ })
28
+ }
29
+
30
+ type Message struct {
31
+ Author Author `json:"author"`
32
+ Content Content `json:"content"`
33
+ ID string `json:"id"`
34
+ Metadata interface{} `json:"metadata"`
35
+ }
36
+
37
+ type Author struct {
38
+ Role string `json:"role"`
39
+ }
40
+
41
+ type Content struct {
42
+ ContentType string `json:"content_type"`
43
+ Parts []interface{} `json:"parts"`
44
+ }
45
+
46
+ type CreateConversationResponse struct {
47
+ Message struct {
48
+ ID string `json:"id"`
49
+ Author struct {
50
+ Role string `json:"role"`
51
+ Name interface{} `json:"name"`
52
+ Metadata struct {
53
+ } `json:"metadata"`
54
+ } `json:"author"`
55
+ CreateTime float64 `json:"create_time"`
56
+ UpdateTime interface{} `json:"update_time"`
57
+ Content struct {
58
+ ContentType string `json:"content_type"`
59
+ Parts []string `json:"parts"`
60
+ } `json:"content"`
61
+ Status string `json:"status"`
62
+ EndTurn bool `json:"end_turn"`
63
+ Weight float64 `json:"weight"`
64
+ Metadata struct {
65
+ MessageType string `json:"message_type"`
66
+ ModelSlug string `json:"model_slug"`
67
+ FinishDetails struct {
68
+ Type string `json:"type"`
69
+ } `json:"finish_details"`
70
+ } `json:"metadata"`
71
+ Recipient string `json:"recipient"`
72
+ } `json:"message"`
73
+ ConversationID string `json:"conversation_id"`
74
+ Error interface{} `json:"error"`
75
+ }
76
+
77
+ type GetModelsResponse struct {
78
+ Models []struct {
79
+ Slug string `json:"slug"`
80
+ MaxTokens int `json:"max_tokens"`
81
+ Title string `json:"title"`
82
+ Description string `json:"description"`
83
+ Tags []string `json:"tags"`
84
+ Capabilities struct {
85
+ } `json:"capabilities"`
86
+ EnabledTools []string `json:"enabled_tools,omitempty"`
87
+ } `json:"models"`
88
+ Categories []struct {
89
+ Category string `json:"category"`
90
+ HumanCategoryName string `json:"human_category_name"`
91
+ SubscriptionLevel string `json:"subscription_level"`
92
+ DefaultModel string `json:"default_model"`
93
+ CodeInterpreterModel string `json:"code_interpreter_model"`
94
+ PluginsModel string `json:"plugins_model"`
95
+ } `json:"categories"`
96
+ }
api/common.go ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package api
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "fmt"
7
+ "io"
8
+ "os"
9
+ "strings"
10
+ "time"
11
+
12
+ http "github.com/bogdanfinn/fhttp"
13
+ tls_client "github.com/bogdanfinn/tls-client"
14
+ "github.com/bogdanfinn/tls-client/profiles"
15
+ "github.com/gin-gonic/gin"
16
+ "github.com/xqdoo00o/OpenAIAuth/auth"
17
+ "github.com/xqdoo00o/funcaptcha"
18
+
19
+ "github.com/linweiyuan/go-logger/logger"
20
+ )
21
+
22
+ const (
23
+ ChatGPTApiPrefix = "/chatgpt"
24
+ ImitateApiPrefix = "/imitate/v1"
25
+ ChatGPTApiUrlPrefix = "https://chat.openai.com"
26
+
27
+ PlatformApiPrefix = "/platform"
28
+ PlatformApiUrlPrefix = "https://api.openai.com"
29
+
30
+ defaultErrorMessageKey = "errorMessage"
31
+ AuthorizationHeader = "Authorization"
32
+ XAuthorizationHeader = "X-Authorization"
33
+ ContentType = "application/x-www-form-urlencoded"
34
+ UserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36"
35
+ Auth0Url = "https://auth0.openai.com"
36
+ LoginUsernameUrl = Auth0Url + "/u/login/identifier?state="
37
+ LoginPasswordUrl = Auth0Url + "/u/login/password?state="
38
+ ParseUserInfoErrorMessage = "failed to parse user login info"
39
+ GetAuthorizedUrlErrorMessage = "failed to get authorized url"
40
+ EmailInvalidErrorMessage = "email is not valid"
41
+ EmailOrPasswordInvalidErrorMessage = "email or password is not correct"
42
+ GetAccessTokenErrorMessage = "failed to get access token"
43
+ defaultTimeoutSeconds = 600 // 10 minutes
44
+
45
+ EmailKey = "email"
46
+ AccountDeactivatedErrorMessage = "account %s is deactivated"
47
+
48
+ ReadyHint = "service go-chatgpt-api is ready"
49
+
50
+ refreshPuidErrorMessage = "failed to refresh PUID"
51
+ )
52
+
53
+ var (
54
+ Client tls_client.HttpClient
55
+ ArkoseClient tls_client.HttpClient
56
+ PUID string
57
+ ProxyUrl string
58
+ )
59
+
60
+ type LoginInfo struct {
61
+ Username string `json:"username"`
62
+ Password string `json:"password"`
63
+ }
64
+
65
+ type AuthLogin interface {
66
+ GetAuthorizedUrl(csrfToken string) (string, int, error)
67
+ GetState(authorizedUrl string) (string, int, error)
68
+ CheckUsername(state string, username string) (int, error)
69
+ CheckPassword(state string, username string, password string) (string, int, error)
70
+ GetAccessToken(code string) (string, int, error)
71
+ GetAccessTokenFromHeader(c *gin.Context) (string, int, error)
72
+ }
73
+
74
+ func init() {
75
+ Client, _ = tls_client.NewHttpClient(tls_client.NewNoopLogger(), []tls_client.HttpClientOption{
76
+ tls_client.WithCookieJar(tls_client.NewCookieJar()),
77
+ tls_client.WithTimeoutSeconds(defaultTimeoutSeconds),
78
+ tls_client.WithClientProfile(profiles.Okhttp4Android13),
79
+ }...)
80
+ ArkoseClient = getHttpClient()
81
+
82
+ setupPUID()
83
+ }
84
+
85
+ func NewHttpClient() tls_client.HttpClient {
86
+ client := getHttpClient()
87
+
88
+ ProxyUrl = os.Getenv("PROXY")
89
+ if ProxyUrl != "" {
90
+ client.SetProxy(ProxyUrl)
91
+ }
92
+
93
+ return client
94
+ }
95
+
96
+ func getHttpClient() tls_client.HttpClient {
97
+ client, _ := tls_client.NewHttpClient(tls_client.NewNoopLogger(), []tls_client.HttpClientOption{
98
+ tls_client.WithCookieJar(tls_client.NewCookieJar()),
99
+ tls_client.WithClientProfile(profiles.Okhttp4Android13),
100
+ }...)
101
+ return client
102
+ }
103
+
104
+ func Proxy(c *gin.Context) {
105
+ url := c.Request.URL.Path
106
+ if strings.Contains(url, ChatGPTApiPrefix) {
107
+ url = strings.ReplaceAll(url, ChatGPTApiPrefix, ChatGPTApiUrlPrefix)
108
+ } else if strings.Contains(url, ImitateApiPrefix) {
109
+ url = strings.ReplaceAll(url, ImitateApiPrefix, ChatGPTApiUrlPrefix+"/backend-api")
110
+ } else {
111
+ url = strings.ReplaceAll(url, PlatformApiPrefix, PlatformApiUrlPrefix)
112
+ }
113
+
114
+ method := c.Request.Method
115
+ queryParams := c.Request.URL.Query().Encode()
116
+ if queryParams != "" {
117
+ url += "?" + queryParams
118
+ }
119
+
120
+ // if not set, will return 404
121
+ c.Status(http.StatusOK)
122
+
123
+ var req *http.Request
124
+ if method == http.MethodGet {
125
+ req, _ = http.NewRequest(http.MethodGet, url, nil)
126
+ } else {
127
+ body, _ := io.ReadAll(c.Request.Body)
128
+ req, _ = http.NewRequest(method, url, bytes.NewReader(body))
129
+ }
130
+ req.Header.Set("User-Agent", UserAgent)
131
+ req.Header.Set(AuthorizationHeader, GetAccessToken(c))
132
+ resp, err := Client.Do(req)
133
+ if err != nil {
134
+ c.AbortWithStatusJSON(http.StatusInternalServerError, ReturnMessage(err.Error()))
135
+ return
136
+ }
137
+
138
+ defer resp.Body.Close()
139
+ if resp.StatusCode != http.StatusOK {
140
+ if resp.StatusCode == http.StatusUnauthorized {
141
+ logger.Error(fmt.Sprintf(AccountDeactivatedErrorMessage, c.GetString(EmailKey)))
142
+ }
143
+
144
+ responseMap := make(map[string]interface{})
145
+ json.NewDecoder(resp.Body).Decode(&responseMap)
146
+ c.AbortWithStatusJSON(resp.StatusCode, responseMap)
147
+ return
148
+ }
149
+
150
+ io.Copy(c.Writer, resp.Body)
151
+ }
152
+
153
+ func ReturnMessage(msg string) gin.H {
154
+ logger.Warn(msg)
155
+
156
+ return gin.H{
157
+ defaultErrorMessageKey: msg,
158
+ }
159
+ }
160
+
161
+ func GetAccessToken(c *gin.Context) string {
162
+ accessToken := c.GetString(AuthorizationHeader)
163
+ if !strings.HasPrefix(accessToken, "Bearer") {
164
+ return "Bearer " + accessToken
165
+ }
166
+
167
+ return accessToken
168
+ }
169
+
170
+ func GetArkoseToken() (string, error) {
171
+ return funcaptcha.GetOpenAIToken(PUID, ProxyUrl)
172
+ }
173
+
174
+ func setupPUID() {
175
+ username := os.Getenv("OPENAI_EMAIL")
176
+ password := os.Getenv("OPENAI_PASSWORD")
177
+ if username != "" && password != "" {
178
+ go func() {
179
+ for {
180
+ authenticator := auth.NewAuthenticator(username, password, ProxyUrl)
181
+ if err := authenticator.Begin(); err != nil {
182
+ logger.Warn(fmt.Sprintf("%s: %s", refreshPuidErrorMessage, err.Details))
183
+ return
184
+ }
185
+
186
+ accessToken := authenticator.GetAccessToken()
187
+ if accessToken == "" {
188
+ logger.Error(refreshPuidErrorMessage)
189
+ return
190
+ }
191
+
192
+ puid, err := authenticator.GetPUID()
193
+ if err != nil {
194
+ logger.Error(refreshPuidErrorMessage)
195
+ return
196
+ }
197
+
198
+ PUID = puid
199
+
200
+ time.Sleep(time.Hour * 24 * 7)
201
+ }
202
+ }()
203
+ }
204
+ }
api/imitate/api.go ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package imitate
2
+
3
+ import (
4
+ "bufio"
5
+ "bytes"
6
+ "encoding/base64"
7
+ "encoding/json"
8
+ "fmt"
9
+ "io"
10
+ "os"
11
+ "regexp"
12
+ "strings"
13
+
14
+ http "github.com/bogdanfinn/fhttp"
15
+ "github.com/gin-gonic/gin"
16
+ "github.com/google/uuid"
17
+
18
+ "github.com/linweiyuan/go-chatgpt-api/api"
19
+ "github.com/linweiyuan/go-chatgpt-api/api/chatgpt"
20
+ "github.com/linweiyuan/go-logger/logger"
21
+ )
22
+
23
+ var (
24
+ reg *regexp.Regexp
25
+ )
26
+
27
+ func init() {
28
+ reg, _ = regexp.Compile("[^a-zA-Z0-9]+")
29
+ }
30
+
31
+ func CreateChatCompletions(c *gin.Context) {
32
+ var originalRequest APIRequest
33
+ err := c.BindJSON(&originalRequest)
34
+ if err != nil {
35
+ c.JSON(400, gin.H{"error": gin.H{
36
+ "message": "Request must be proper JSON",
37
+ "type": "invalid_request_error",
38
+ "param": nil,
39
+ "code": err.Error(),
40
+ }})
41
+ return
42
+ }
43
+
44
+ authHeader := c.GetHeader(api.AuthorizationHeader)
45
+ token := os.Getenv("IMITATE_ACCESS_TOKEN")
46
+ if authHeader != "" {
47
+ customAccessToken := strings.Replace(authHeader, "Bearer ", "", 1)
48
+ // Check if customAccessToken starts with sk-
49
+ if strings.HasPrefix(customAccessToken, "eyJhbGciOiJSUzI1NiI") {
50
+ token = customAccessToken
51
+ }
52
+ }
53
+
54
+ // 将聊天请求转换为ChatGPT请求。
55
+ translatedRequest, model := convertAPIRequest(originalRequest)
56
+
57
+ response, done := sendConversationRequest(c, translatedRequest, token)
58
+ if done {
59
+ return
60
+ }
61
+
62
+ defer func(Body io.ReadCloser) {
63
+ err := Body.Close()
64
+ if err != nil {
65
+ return
66
+ }
67
+ }(response.Body)
68
+
69
+ if HandleRequestError(c, response) {
70
+ return
71
+ }
72
+
73
+ var fullResponse string
74
+
75
+ id := generateId()
76
+
77
+ for i := 3; i > 0; i-- {
78
+ var continueInfo *ContinueInfo
79
+ var responsePart string
80
+ var continueSignal string
81
+ responsePart, continueInfo = Handler(c, response, originalRequest.Stream, id, model)
82
+ fullResponse += responsePart
83
+ continueSignal = os.Getenv("CONTINUE_SIGNAL")
84
+ if continueInfo == nil || continueSignal == "" {
85
+ break
86
+ }
87
+ println("Continuing conversation")
88
+ translatedRequest.Messages = nil
89
+ translatedRequest.Action = "continue"
90
+ translatedRequest.ConversationID = &continueInfo.ConversationID
91
+ translatedRequest.ParentMessageID = continueInfo.ParentID
92
+ response, done = sendConversationRequest(c, translatedRequest, token)
93
+
94
+ if done {
95
+ return
96
+ }
97
+
98
+ // 以下修复代码来自ChatGPT
99
+ // 在循环内部创建一个局部作用域,并将资源的引用传递给匿名函数,保证资源将在每次迭代结束时被正确释放
100
+ func() {
101
+ defer func(Body io.ReadCloser) {
102
+ err := Body.Close()
103
+ if err != nil {
104
+ return
105
+ }
106
+ }(response.Body)
107
+ }()
108
+
109
+ if HandleRequestError(c, response) {
110
+ return
111
+ }
112
+ }
113
+
114
+ if !originalRequest.Stream {
115
+ c.JSON(200, newChatCompletion(fullResponse, model, id))
116
+ } else {
117
+ c.String(200, "data: [DONE]\n\n")
118
+ }
119
+ }
120
+
121
+ func generateId() string {
122
+ id := uuid.NewString()
123
+ id = strings.ReplaceAll(id, "-", "")
124
+ id = base64.StdEncoding.EncodeToString([]byte(id))
125
+ id = reg.ReplaceAllString(id, "")
126
+ return "chatcmpl-" + id
127
+ }
128
+
129
+ func convertAPIRequest(apiRequest APIRequest) (chatgpt.CreateConversationRequest, string) {
130
+ chatgptRequest := NewChatGPTRequest()
131
+
132
+ var model = "gpt-3.5-turbo-0613"
133
+
134
+ if strings.HasPrefix(apiRequest.Model, "gpt-3.5") {
135
+ chatgptRequest.Model = "text-davinci-002-render-sha"
136
+ }
137
+
138
+ if strings.HasPrefix(apiRequest.Model, "gpt-4") {
139
+ arkoseToken, err := api.GetArkoseToken()
140
+ if err == nil {
141
+ chatgptRequest.ArkoseToken = arkoseToken
142
+ } else {
143
+ fmt.Println("Error getting Arkose token: ", err)
144
+ }
145
+ chatgptRequest.Model = apiRequest.Model
146
+ model = "gpt-4-0613"
147
+ }
148
+
149
+ if apiRequest.PluginIDs != nil {
150
+ chatgptRequest.PluginIDs = apiRequest.PluginIDs
151
+ chatgptRequest.Model = "gpt-4-plugins"
152
+ }
153
+
154
+ for _, apiMessage := range apiRequest.Messages {
155
+ if apiMessage.Role == "system" {
156
+ apiMessage.Role = "critic"
157
+ }
158
+ chatgptRequest.AddMessage(apiMessage.Role, apiMessage.Content)
159
+ }
160
+
161
+ return chatgptRequest, model
162
+ }
163
+
164
+ func NewChatGPTRequest() chatgpt.CreateConversationRequest {
165
+ enableHistory := os.Getenv("ENABLE_HISTORY") == ""
166
+ return chatgpt.CreateConversationRequest{
167
+ Action: "next",
168
+ ParentMessageID: uuid.NewString(),
169
+ Model: "text-davinci-002-render-sha",
170
+ HistoryAndTrainingDisabled: !enableHistory,
171
+ }
172
+ }
173
+
174
+ func sendConversationRequest(c *gin.Context, request chatgpt.CreateConversationRequest, accessToken string) (*http.Response, bool) {
175
+ jsonBytes, _ := json.Marshal(request)
176
+ req, _ := http.NewRequest(http.MethodPost, api.ChatGPTApiUrlPrefix+"/backend-api/conversation", bytes.NewBuffer(jsonBytes))
177
+ req.Header.Set("User-Agent", api.UserAgent)
178
+ req.Header.Set(api.AuthorizationHeader, accessToken)
179
+ req.Header.Set("Accept", "text/event-stream")
180
+ if api.PUID != "" {
181
+ req.Header.Set("Cookie", "_puid="+api.PUID)
182
+ }
183
+ resp, err := api.Client.Do(req)
184
+ if err != nil {
185
+ c.AbortWithStatusJSON(http.StatusInternalServerError, api.ReturnMessage(err.Error()))
186
+ return nil, true
187
+ }
188
+
189
+ if resp.StatusCode != http.StatusOK {
190
+ if resp.StatusCode == http.StatusUnauthorized {
191
+ logger.Error(fmt.Sprintf(api.AccountDeactivatedErrorMessage, c.GetString(api.EmailKey)))
192
+ }
193
+
194
+ responseMap := make(map[string]interface{})
195
+ json.NewDecoder(resp.Body).Decode(&responseMap)
196
+ c.AbortWithStatusJSON(resp.StatusCode, responseMap)
197
+ return nil, true
198
+ }
199
+
200
+ return resp, false
201
+ }
202
+
203
+ func Handler(c *gin.Context, response *http.Response, stream bool, id string, model string) (string, *ContinueInfo) {
204
+ maxTokens := false
205
+
206
+ // Create a bufio.Reader from the response body
207
+ reader := bufio.NewReader(response.Body)
208
+
209
+ // Read the response byte by byte until a newline character is encountered
210
+ if stream {
211
+ // Response content type is text/event-stream
212
+ c.Header("Content-Type", "text/event-stream")
213
+ } else {
214
+ // Response content type is application/json
215
+ c.Header("Content-Type", "application/json")
216
+ }
217
+ var finishReason string
218
+ var previousText StringStruct
219
+ var originalResponse ChatGPTResponse
220
+ var isRole = true
221
+ for {
222
+ line, err := reader.ReadString('\n')
223
+ if err != nil {
224
+ if err == io.EOF {
225
+ break
226
+ }
227
+ return "", nil
228
+ }
229
+ if len(line) < 6 {
230
+ continue
231
+ }
232
+ // Remove "data: " from the beginning of the line
233
+ line = line[6:]
234
+ // Check if line starts with [DONE]
235
+ if !strings.HasPrefix(line, "[DONE]") {
236
+ // Parse the line as JSON
237
+
238
+ err = json.Unmarshal([]byte(line), &originalResponse)
239
+ if err != nil {
240
+ continue
241
+ }
242
+ if originalResponse.Error != nil {
243
+ c.JSON(500, gin.H{"error": originalResponse.Error})
244
+ return "", nil
245
+ }
246
+ if originalResponse.Message.Author.Role != "assistant" || originalResponse.Message.Content.Parts == nil {
247
+ continue
248
+ }
249
+ if originalResponse.Message.Metadata.MessageType != "next" && originalResponse.Message.Metadata.MessageType != "continue" || originalResponse.Message.EndTurn != nil {
250
+ continue
251
+ }
252
+ if (len(originalResponse.Message.Content.Parts) == 0 || originalResponse.Message.Content.Parts[0] == "") && !isRole {
253
+ continue
254
+ }
255
+ responseString := ConvertToString(&originalResponse, &previousText, isRole, id, model)
256
+ isRole = false
257
+ if stream {
258
+ _, err = c.Writer.WriteString(responseString)
259
+ if err != nil {
260
+ return "", nil
261
+ }
262
+ }
263
+ // Flush the response writer buffer to ensure that the client receives each line as it's written
264
+ c.Writer.Flush()
265
+
266
+ if originalResponse.Message.Metadata.FinishDetails != nil {
267
+ if originalResponse.Message.Metadata.FinishDetails.Type == "max_tokens" {
268
+ maxTokens = true
269
+ }
270
+ finishReason = originalResponse.Message.Metadata.FinishDetails.Type
271
+ }
272
+
273
+ } else {
274
+ if stream {
275
+ if finishReason == "" {
276
+ finishReason = "stop"
277
+ }
278
+ finalLine := StopChunk(finishReason, id, model)
279
+ _, err := c.Writer.WriteString("data: " + finalLine.String() + "\n\n")
280
+ if err != nil {
281
+ return "", nil
282
+ }
283
+ }
284
+ }
285
+ }
286
+ if !maxTokens {
287
+ return previousText.Text, nil
288
+ }
289
+ return previousText.Text, &ContinueInfo{
290
+ ConversationID: originalResponse.ConversationID,
291
+ ParentID: originalResponse.Message.ID,
292
+ }
293
+ }
api/imitate/convert.go ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package imitate
2
+
3
+ import (
4
+ "fmt"
5
+ "strings"
6
+ )
7
+
8
+ func ConvertToString(chatgptResponse *ChatGPTResponse, previousText *StringStruct, role bool, id string, model string) string {
9
+ var text string
10
+
11
+ if len(chatgptResponse.Message.Content.Parts) == 1 {
12
+ if part, ok := chatgptResponse.Message.Content.Parts[0].(string); ok {
13
+ text = strings.ReplaceAll(part, previousText.Text, "")
14
+ previousText.Text = part
15
+ } else {
16
+ text = fmt.Sprintf("%v", chatgptResponse.Message.Content.Parts[0])
17
+ }
18
+ } else {
19
+ // When using GPT-4 messages with images (multimodal_text), the length of 'parts' might be 2.
20
+ // Since the chatgpt API currently does not support multimodal content
21
+ // and there is no official format for multimodal content,
22
+ // the content is temporarily returned as is.
23
+ var parts []string
24
+ for _, part := range chatgptResponse.Message.Content.Parts {
25
+ parts = append(parts, fmt.Sprintf("%v", part))
26
+ }
27
+ text = strings.Join(parts, ", ")
28
+ }
29
+
30
+ translatedResponse := NewChatCompletionChunk(text, id, model)
31
+ if role {
32
+ translatedResponse.Choices[0].Delta.Role = chatgptResponse.Message.Author.Role
33
+ }
34
+
35
+ return "data: " + translatedResponse.String() + "\n\n"
36
+ }
api/imitate/request.go ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package imitate
2
+
3
+ import (
4
+ "encoding/json"
5
+ "io"
6
+
7
+ http "github.com/bogdanfinn/fhttp"
8
+ "github.com/gin-gonic/gin"
9
+ )
10
+
11
+ type ContinueInfo struct {
12
+ ConversationID string `json:"conversation_id"`
13
+ ParentID string `json:"parent_id"`
14
+ }
15
+
16
+ type APIRequest struct {
17
+ Messages []ApiMessage `json:"messages"`
18
+ Stream bool `json:"stream"`
19
+ Model string `json:"model"`
20
+ PluginIDs []string `json:"plugin_ids"`
21
+ }
22
+
23
+ type ApiMessage struct {
24
+ Role string `json:"role"`
25
+ Content string `json:"content"`
26
+ }
27
+
28
+ func HandleRequestError(c *gin.Context, response *http.Response) bool {
29
+ if response.StatusCode != 200 {
30
+ // Try read response body as JSON
31
+ var errorResponse map[string]interface{}
32
+ err := json.NewDecoder(response.Body).Decode(&errorResponse)
33
+ if err != nil {
34
+ // Read response body
35
+ body, _ := io.ReadAll(response.Body)
36
+ c.JSON(500, gin.H{"error": gin.H{
37
+ "message": "Unknown error",
38
+ "type": "internal_server_error",
39
+ "param": nil,
40
+ "code": "500",
41
+ "details": string(body),
42
+ }})
43
+ return true
44
+ }
45
+ c.JSON(response.StatusCode, gin.H{"error": gin.H{
46
+ "message": errorResponse["detail"],
47
+ "type": response.Status,
48
+ "param": nil,
49
+ "code": "error",
50
+ }})
51
+ return true
52
+ }
53
+ return false
54
+ }
api/imitate/response.go ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package imitate
2
+
3
+ import (
4
+ "encoding/json"
5
+ "time"
6
+
7
+ "github.com/linweiyuan/go-chatgpt-api/api/chatgpt"
8
+ )
9
+
10
+ type ChatCompletionChunk struct {
11
+ ID string `json:"id"`
12
+ Object string `json:"object"`
13
+ Created int64 `json:"created"`
14
+ Model string `json:"model"`
15
+ Choices []Choices `json:"choices"`
16
+ }
17
+
18
+ func (chunk *ChatCompletionChunk) String() string {
19
+ resp, _ := json.Marshal(chunk)
20
+ return string(resp)
21
+ }
22
+
23
+ type Choices struct {
24
+ Delta Delta `json:"delta"`
25
+ Index int `json:"index"`
26
+ FinishReason interface{} `json:"finish_reason"`
27
+ }
28
+
29
+ type Delta struct {
30
+ Content string `json:"content,omitempty"`
31
+ Role string `json:"role,omitempty"`
32
+ }
33
+
34
+ func NewChatCompletionChunk(text string, id string, model string) ChatCompletionChunk {
35
+ return ChatCompletionChunk{
36
+ ID: id,
37
+ Object: "chat.completion.chunk",
38
+ Created: time.Now().Unix(),
39
+ Model: model,
40
+ Choices: []Choices{
41
+ {
42
+ Index: 0,
43
+ Delta: Delta{
44
+ Content: text,
45
+ },
46
+ FinishReason: nil,
47
+ },
48
+ },
49
+ }
50
+ }
51
+
52
+ func StopChunk(reason string, id string, model string) ChatCompletionChunk {
53
+ return ChatCompletionChunk{
54
+ ID: id,
55
+ Object: "chat.completion.chunk",
56
+ Created: time.Now().Unix(),
57
+ Model: model,
58
+ Choices: []Choices{
59
+ {
60
+ Index: 0,
61
+ FinishReason: reason,
62
+ },
63
+ },
64
+ }
65
+ }
66
+
67
+ type ChatCompletion struct {
68
+ ID string `json:"id"`
69
+ Object string `json:"object"`
70
+ Created int64 `json:"created"`
71
+ Model string `json:"model"`
72
+ Usage usage `json:"usage"`
73
+ Choices []Choice `json:"choices"`
74
+ }
75
+ type Msg struct {
76
+ Role string `json:"role"`
77
+ Content string `json:"content"`
78
+ }
79
+ type Choice struct {
80
+ Index int `json:"index"`
81
+ Message Msg `json:"message"`
82
+ FinishReason interface{} `json:"finish_reason"`
83
+ }
84
+ type usage struct {
85
+ PromptTokens int `json:"prompt_tokens"`
86
+ CompletionTokens int `json:"completion_tokens"`
87
+ TotalTokens int `json:"total_tokens"`
88
+ }
89
+
90
+ type ChatGPTResponse struct {
91
+ Message Message `json:"message"`
92
+ ConversationID string `json:"conversation_id"`
93
+ Error interface{} `json:"error"`
94
+ }
95
+
96
+ type Message struct {
97
+ ID string `json:"id"`
98
+ Author chatgpt.Author `json:"author"`
99
+ CreateTime float64 `json:"create_time"`
100
+ UpdateTime interface{} `json:"update_time"`
101
+ Content chatgpt.Content `json:"content"`
102
+ EndTurn interface{} `json:"end_turn"`
103
+ Weight float64 `json:"weight"`
104
+ Metadata Metadata `json:"metadata"`
105
+ Recipient string `json:"recipient"`
106
+ }
107
+
108
+ type Metadata struct {
109
+ Timestamp string `json:"timestamp_"`
110
+ MessageType string `json:"message_type"`
111
+ FinishDetails *FinishDetails `json:"finish_details"`
112
+ ModelSlug string `json:"model_slug"`
113
+ Recipient string `json:"recipient"`
114
+ }
115
+
116
+ type FinishDetails struct {
117
+ Type string `json:"type"`
118
+ Stop string `json:"stop"`
119
+ }
120
+
121
+ type StringStruct struct {
122
+ Text string `json:"text"`
123
+ }
124
+
125
+ func newChatCompletion(fullTest, model string, id string) ChatCompletion {
126
+ return ChatCompletion{
127
+ ID: id,
128
+ Object: "chat.completion",
129
+ Created: time.Now().Unix(),
130
+ Model: model,
131
+ Usage: usage{
132
+ PromptTokens: 0,
133
+ CompletionTokens: 0,
134
+ TotalTokens: 0,
135
+ },
136
+ Choices: []Choice{
137
+ {
138
+ Message: Msg{
139
+ Content: fullTest,
140
+ Role: "assistant",
141
+ },
142
+ Index: 0,
143
+ },
144
+ },
145
+ }
146
+ }
api/platform/access_token.go ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package platform
2
+
3
+ import (
4
+ "encoding/json"
5
+ "errors"
6
+ "io"
7
+ "net/url"
8
+ "strings"
9
+
10
+ http "github.com/bogdanfinn/fhttp"
11
+
12
+ "github.com/linweiyuan/go-chatgpt-api/api"
13
+ )
14
+
15
+ func (userLogin *UserLogin) GetAuthorizedUrl(csrfToken string) (string, int, error) {
16
+ urlParams := url.Values{
17
+ "client_id": {platformAuthClientID},
18
+ "audience": {platformAuthAudience},
19
+ "redirect_uri": {platformAuthRedirectURL},
20
+ "scope": {platformAuthScope},
21
+ "response_type": {platformAuthResponseType},
22
+ }
23
+ req, _ := http.NewRequest(http.MethodGet, platformAuth0Url+urlParams.Encode(), nil)
24
+ req.Header.Set("Content-Type", api.ContentType)
25
+ req.Header.Set("User-Agent", api.UserAgent)
26
+ resp, err := userLogin.client.Do(req)
27
+ if err != nil {
28
+ return "", http.StatusInternalServerError, err
29
+ }
30
+
31
+ defer resp.Body.Close()
32
+ if resp.StatusCode != http.StatusOK {
33
+ return "", resp.StatusCode, errors.New(api.GetAuthorizedUrlErrorMessage)
34
+ }
35
+
36
+ return resp.Request.URL.String(), http.StatusOK, nil
37
+ }
38
+
39
+ func (userLogin *UserLogin) GetState(authorizedUrl string) (string, int, error) {
40
+ split := strings.Split(authorizedUrl, "=")
41
+ return split[1], http.StatusOK, nil
42
+ }
43
+
44
+ func (userLogin *UserLogin) CheckUsername(state string, username string) (int, error) {
45
+ formParams := url.Values{
46
+ "state": {state},
47
+ "username": {username},
48
+ "js-available": {"true"},
49
+ "webauthn-available": {"true"},
50
+ "is-brave": {"false"},
51
+ "webauthn-platform-available": {"false"},
52
+ "action": {"default"},
53
+ }
54
+ req, _ := http.NewRequest(http.MethodPost, api.LoginUsernameUrl+state, strings.NewReader(formParams.Encode()))
55
+ req.Header.Set("Content-Type", api.ContentType)
56
+ req.Header.Set("User-Agent", api.UserAgent)
57
+ resp, err := userLogin.client.Do(req)
58
+ if err != nil {
59
+ return http.StatusInternalServerError, err
60
+ }
61
+
62
+ defer resp.Body.Close()
63
+ if resp.StatusCode != http.StatusOK {
64
+ return resp.StatusCode, errors.New(api.EmailInvalidErrorMessage)
65
+ }
66
+
67
+ return http.StatusOK, nil
68
+ }
69
+
70
+ func (userLogin *UserLogin) CheckPassword(state string, username string, password string) (string, int, error) {
71
+ formParams := url.Values{
72
+ "state": {state},
73
+ "username": {username},
74
+ "password": {password},
75
+ "action": {"default"},
76
+ }
77
+ req, _ := http.NewRequest(http.MethodPost, api.LoginPasswordUrl+state, strings.NewReader(formParams.Encode()))
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
+ func (userLogin *UserLogin) GetAccessToken(code string) (string, int, error) {
94
+ jsonBytes, _ := json.Marshal(GetAccessTokenRequest{
95
+ ClientID: platformAuthClientID,
96
+ Code: code,
97
+ GrantType: platformAuthGrantType,
98
+ RedirectURI: platformAuthRedirectURL,
99
+ })
100
+ req, _ := http.NewRequest(http.MethodPost, getTokenUrl, strings.NewReader(string(jsonBytes)))
101
+ req.Header.Set("Content-Type", "application/json")
102
+ req.Header.Set("User-Agent", api.UserAgent)
103
+ resp, err := userLogin.client.Do(req)
104
+ if err != nil {
105
+ return "", http.StatusInternalServerError, err
106
+ }
107
+
108
+ defer resp.Body.Close()
109
+ if resp.StatusCode != http.StatusOK {
110
+ return "", resp.StatusCode, errors.New(api.GetAccessTokenErrorMessage)
111
+ }
112
+
113
+ data, _ := io.ReadAll(resp.Body)
114
+ return string(data), http.StatusOK, nil
115
+ }
api/platform/api.go ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package platform
2
+
3
+ import (
4
+ "bufio"
5
+ "bytes"
6
+ "encoding/json"
7
+ "fmt"
8
+ "io"
9
+ "strings"
10
+
11
+ http "github.com/bogdanfinn/fhttp"
12
+ "github.com/gin-gonic/gin"
13
+
14
+ "github.com/linweiyuan/go-chatgpt-api/api"
15
+ "github.com/linweiyuan/go-logger/logger"
16
+ )
17
+
18
+ func CreateChatCompletions(c *gin.Context) {
19
+ body, _ := io.ReadAll(c.Request.Body)
20
+ var request struct {
21
+ Stream bool `json:"stream"`
22
+ }
23
+ json.Unmarshal(body, &request)
24
+
25
+ url := c.Request.URL.Path
26
+ if strings.Contains(url, "/chat") {
27
+ url = apiCreateChatCompletions
28
+ } else {
29
+ url = apiCreateCompletions
30
+ }
31
+
32
+ resp, err := handlePost(c, url, body, request.Stream)
33
+ if err != nil {
34
+ return
35
+ }
36
+
37
+ defer resp.Body.Close()
38
+ if resp.StatusCode == http.StatusUnauthorized {
39
+ logger.Error(fmt.Sprintf(api.AccountDeactivatedErrorMessage, c.GetString(api.EmailKey)))
40
+ responseMap := make(map[string]interface{})
41
+ json.NewDecoder(resp.Body).Decode(&responseMap)
42
+ c.AbortWithStatusJSON(resp.StatusCode, responseMap)
43
+ return
44
+ }
45
+
46
+ if request.Stream {
47
+ handleCompletionsResponse(c, resp)
48
+ } else {
49
+ io.Copy(c.Writer, resp.Body)
50
+ }
51
+ }
52
+
53
+ func CreateCompletions(c *gin.Context) {
54
+ CreateChatCompletions(c)
55
+ }
56
+
57
+ func handleCompletionsResponse(c *gin.Context, resp *http.Response) {
58
+ c.Writer.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
59
+
60
+ reader := bufio.NewReader(resp.Body)
61
+ for {
62
+ if c.Request.Context().Err() != nil {
63
+ break
64
+ }
65
+
66
+ line, err := reader.ReadString('\n')
67
+ if err != nil {
68
+ break
69
+ }
70
+
71
+ line = strings.TrimSpace(line)
72
+ if strings.HasPrefix(line, "event") ||
73
+ strings.HasPrefix(line, "data: 20") ||
74
+ line == "" {
75
+ continue
76
+ }
77
+
78
+ c.Writer.Write([]byte(line + "\n\n"))
79
+ c.Writer.Flush()
80
+ }
81
+ }
82
+
83
+ func handlePost(c *gin.Context, url string, data []byte, stream bool) (*http.Response, error) {
84
+ req, _ := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
85
+ req.Header.Set(api.AuthorizationHeader, api.GetAccessToken(c))
86
+ if stream {
87
+ req.Header.Set("Accept", "text/event-stream")
88
+ }
89
+ req.Header.Set("Content-Type", "application/json")
90
+ resp, err := api.Client.Do(req)
91
+ if err != nil {
92
+ c.AbortWithStatusJSON(http.StatusInternalServerError, api.ReturnMessage(err.Error()))
93
+ return nil, err
94
+ }
95
+
96
+ return resp, nil
97
+ }
api/platform/constant.go ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package platform
2
+
3
+ import "github.com/linweiyuan/go-chatgpt-api/api"
4
+
5
+ const (
6
+ apiCreateChatCompletions = api.PlatformApiUrlPrefix + "/v1/chat/completions"
7
+ apiCreateCompletions = api.PlatformApiUrlPrefix + "/v1/completions"
8
+
9
+ platformAuthClientID = "DRivsnm2Mu42T3KOpqdtwB3NYviHYzwD"
10
+ platformAuthAudience = "https://api.openai.com/v1"
11
+ platformAuthRedirectURL = "https://platform.openai.com/auth/callback"
12
+ platformAuthScope = "openid profile email offline_access"
13
+ platformAuthResponseType = "code"
14
+ platformAuthGrantType = "authorization_code"
15
+ platformAuth0Url = api.Auth0Url + "/authorize?"
16
+ getTokenUrl = api.Auth0Url + "/oauth/token"
17
+ auth0Client = "eyJuYW1lIjoiYXV0aDAtc3BhLWpzIiwidmVyc2lvbiI6IjEuMjEuMCJ9" // '{"name":"auth0-spa-js","version":"1.21.0"}'
18
+ auth0LogoutUrl = api.Auth0Url + "/v2/logout?returnTo=https%3A%2F%2Fplatform.openai.com%2Floggedout&client_id=" + platformAuthClientID + "&auth0Client=" + auth0Client
19
+ dashboardLoginUrl = "https://api.openai.com/dashboard/onboarding/login"
20
+ getSessionKeyErrorMessage = "failed to get session key"
21
+ )
api/platform/login.go ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package platform
2
+
3
+ import (
4
+ "encoding/json"
5
+ "io"
6
+ "strings"
7
+
8
+ http "github.com/bogdanfinn/fhttp"
9
+ "github.com/gin-gonic/gin"
10
+
11
+ "github.com/linweiyuan/go-chatgpt-api/api"
12
+ )
13
+
14
+ func Login(c *gin.Context) {
15
+ var loginInfo api.LoginInfo
16
+ if err := c.ShouldBindJSON(&loginInfo); err != nil {
17
+ c.AbortWithStatusJSON(http.StatusBadRequest, api.ReturnMessage(api.ParseUserInfoErrorMessage))
18
+ return
19
+ }
20
+
21
+ userLogin := UserLogin{
22
+ client: api.NewHttpClient(),
23
+ }
24
+
25
+ // hard refresh cookies
26
+ resp, _ := userLogin.client.Get(auth0LogoutUrl)
27
+ defer resp.Body.Close()
28
+
29
+ // get authorized url
30
+ authorizedUrl, statusCode, err := userLogin.GetAuthorizedUrl("")
31
+ if err != nil {
32
+ c.AbortWithStatusJSON(statusCode, api.ReturnMessage(err.Error()))
33
+ return
34
+ }
35
+
36
+ // get state
37
+ state, _, _ := userLogin.GetState(authorizedUrl)
38
+
39
+ // check username
40
+ statusCode, err = userLogin.CheckUsername(state, loginInfo.Username)
41
+ if err != nil {
42
+ c.AbortWithStatusJSON(statusCode, api.ReturnMessage(err.Error()))
43
+ return
44
+ }
45
+
46
+ // check password
47
+ code, statusCode, err := userLogin.CheckPassword(state, loginInfo.Username, loginInfo.Password)
48
+ if err != nil {
49
+ c.AbortWithStatusJSON(statusCode, api.ReturnMessage(err.Error()))
50
+ return
51
+ }
52
+
53
+ // get access token
54
+ accessToken, statusCode, err := userLogin.GetAccessToken(code)
55
+ if err != nil {
56
+ c.AbortWithStatusJSON(statusCode, api.ReturnMessage(err.Error()))
57
+ return
58
+ }
59
+
60
+ // get session key
61
+ var getAccessTokenResponse GetAccessTokenResponse
62
+ json.Unmarshal([]byte(accessToken), &getAccessTokenResponse)
63
+ req, _ := http.NewRequest(http.MethodPost, dashboardLoginUrl, strings.NewReader("{}"))
64
+ req.Header.Set("Content-Type", "application/json")
65
+ req.Header.Set("User-Agent", api.UserAgent)
66
+ req.Header.Set(api.AuthorizationHeader, "Bearer "+getAccessTokenResponse.AccessToken)
67
+ resp, err = userLogin.client.Do(req)
68
+ if err != nil {
69
+ c.AbortWithStatusJSON(http.StatusInternalServerError, api.ReturnMessage(err.Error()))
70
+ return
71
+ }
72
+
73
+ defer resp.Body.Close()
74
+ if resp.StatusCode != http.StatusOK {
75
+ c.AbortWithStatusJSON(resp.StatusCode, api.ReturnMessage(getSessionKeyErrorMessage))
76
+ return
77
+ }
78
+
79
+ io.Copy(c.Writer, resp.Body)
80
+ }
api/platform/typings.go ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package platform
2
+
3
+ import tls_client "github.com/bogdanfinn/tls-client"
4
+
5
+ type UserLogin struct {
6
+ client tls_client.HttpClient
7
+ }
8
+
9
+ type GetAccessTokenRequest struct {
10
+ ClientID string `json:"client_id"`
11
+ GrantType string `json:"grant_type"`
12
+ Code string `json:"code"`
13
+ RedirectURI string `json:"redirect_uri"`
14
+ }
15
+
16
+ type GetAccessTokenResponse struct {
17
+ AccessToken string `json:"access_token"`
18
+ RefreshToken string `json:"refresh_token"`
19
+ IDToken string `json:"id_token"`
20
+ Scope string `json:"scope"`
21
+ ExpiresIn int `json:"expires_in"`
22
+ TokenType string `json:"token_type"`
23
+ }
compose.yaml ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ - PORT=
10
+ - TZ=Asia/Shanghai
11
+ - PROXY=
12
+ - OPENAI_EMAIL=
13
+ - OPENAI_PASSWORD=
14
+ - CONTINUE_SIGNAL=
15
+ - ENABLE_HISTORY=
16
+ - IMITATE_ACCESS_TOKEN=
17
+ volumes:
18
+ - ./chat.openai.com.har:/app/chat.openai.com.har
19
+ restart: unless-stopped
env/env.go ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ package env
2
+
3
+ import (
4
+ "github.com/joho/godotenv"
5
+ )
6
+
7
+ func init() {
8
+ godotenv.Load()
9
+ }
example/chatgpt.http ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### login
2
+ POST {{baseUrl}}/chatgpt/login
3
+ Content-Type: application/json
4
+
5
+ {
6
+ "username": "{{username}}",
7
+ "password": "{{password}}"
8
+ }
9
+
10
+ ### get conversations
11
+ GET {{baseUrl}}/chatgpt/backend-api/conversations?offset=0&limit=3&order=updated
12
+ Authorization: Bearer {{accessToken}}
13
+
14
+ ### get conversation
15
+ GET {{baseUrl}}/chatgpt/backend-api/conversation/id
16
+ Authorization: Bearer {{accessToken}}
17
+
18
+ ### create conversation
19
+ POST {{baseUrl}}/chatgpt/backend-api/conversation
20
+ Authorization: Bearer {{accessToken}}
21
+ Content-Type: application/json
22
+ Accept: text/event-stream
23
+
24
+ {
25
+ "action": "next",
26
+ "messages": [
27
+ {
28
+ "id": "{{$guid}}",
29
+ "author": {
30
+ "role": "user"
31
+ },
32
+ "content": {
33
+ "content_type": "text",
34
+ "parts": [
35
+ "hello"
36
+ ]
37
+ },
38
+ "metadata": {}
39
+ }
40
+ ],
41
+ "model": "gpt-4",
42
+ "timezone_offset_min": -480,
43
+ "history_and_training_disabled": false
44
+ }
45
+
46
+ ### get models
47
+ GET {{baseUrl}}/chatgpt/backend-api/models?history_and_training_disabled=false
48
+ Authorization: Bearer {{accessToken}}
49
+
50
+ ### check account
51
+ GET {{baseUrl}}/chatgpt/backend-api/accounts/check
52
+ Authorization: Bearer {{accessToken}}
53
+
54
+ ### check account v4
55
+ GET {{baseUrl}}/chatgpt/backend-api/accounts/check/v4-2023-04-27
56
+ Authorization: Bearer {{accessToken}}
57
+
58
+ ### get settings beta features
59
+ GET {{baseUrl}}/chatgpt/backend-api/settings/beta_features
60
+ Authorization: Bearer {{accessToken}}
61
+
62
+ ### get conversation limit (no need to pass access token)
63
+ GET {{baseUrl}}/chatgpt/public-api/conversation_limit
64
+ Authorization: Bearer {{accessToken}}
65
+
66
+ ### get models with pandora enabled
67
+ GET {{baseUrl}}/api/models?history_and_training_disabled=false
68
+ Authorization: Bearer {{accessToken}}
69
+
70
+ ### share link to chat
71
+ POST {{baseUrl}}/chatgpt/backend-api/share/create
72
+ Authorization: Bearer {{accessToken}}
73
+ Content-Type: application/json
74
+
75
+ {
76
+ "current_node_id": "9020711b-3dcf-4705-82ac-46b5af30fc7b",
77
+ "conversation_id": "74c406dd-a2e8-477a-b420-90ed57a55bf9",
78
+ "is_anonymous": false
79
+ }
80
+
81
+ ### copy link
82
+ PATCH {{baseUrl}}/chatgpt/backend-api/share/{share_id}
83
+ Authorization: Bearer {{accessToken}}
84
+ Content-Type: application/json
85
+
86
+ {
87
+ "share_id": "49cd2432-d084-4ab7-8549-4ee18046812b",
88
+ "highlighted_message_id": null,
89
+ "title": "Summarize Request and Response 11122",
90
+ "is_public": false,
91
+ "is_visible": false,
92
+ "is_anonymous": true
93
+ }
94
+
95
+ ### continue shared conversation
96
+ POST {{baseUrl}}/chatgpt/backend-api/conversation
97
+ Authorization: Bearer {{accessToken}}
98
+ Content-Type: application/json
99
+
100
+ {
101
+ "action": "next",
102
+ "messages": [{
103
+ "id": "{{$guid}}",
104
+ "author": {
105
+ "role": "user"
106
+ },
107
+ "content": {
108
+ "content_type": "text",
109
+ "parts": [
110
+ "hello again"
111
+ ]
112
+ },
113
+ "metadata": {}
114
+ }],
115
+ "continue_from_shared_conversation_id": "this is the share_id",
116
+ "parent_message_id": "this is the current_node_id",
117
+ "model": "text-davinci-002-render-sha",
118
+ "timezone_offset_min": -480,
119
+ "history_and_training_disabled": false,
120
+ "arkose_token": null
121
+ }
122
+
123
+ ### get plugins
124
+ GET {{baseUrl}}/chatgpt/backend-api/aip/p?offset=0&limit=250&statuses=approved
125
+ Authorization: Bearer {{accessToken}}
126
+
127
+ ### get payment url
128
+ GET {{baseUrl}}/chatgpt/backend-api/payments/customer_portal
129
+ Authorization: Bearer {{accessToken}}
example/imitate.http ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### Create chat completion
2
+ POST {{baseUrl}}/imitate/v1/chat/completions
3
+ Content-Type: application/json
4
+ Authorization: Bearer {{accessToken}}
5
+
6
+ {
7
+ "model": "gpt-3.5-turbo",
8
+ "messages": [
9
+ {
10
+ "role": "system",
11
+ "content": "You are a helpful assistant."
12
+ },
13
+ {
14
+ "role": "user",
15
+ "content": "Hello!"
16
+ }
17
+ ],
18
+ "stream": true
19
+ }
example/ios.http ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### create conversation
2
+ POST https://ios.chat.openai.com/backend-api/conversation
3
+ Authorization: Bearer {{accessToken}}
4
+ Cookie: _devicecheck=user-xxx
5
+ Accept: text/event-stream
6
+ Content-Type: application/json
7
+
8
+ {
9
+ "action": "next",
10
+ "messages": [
11
+ {
12
+ "id": "{{$guid}}",
13
+ "author": {
14
+ "role": "user"
15
+ },
16
+ "content": {
17
+ "content_type": "text",
18
+ "parts": [
19
+ "hello"
20
+ ]
21
+ },
22
+ "metadata": {}
23
+ }
24
+ ],
25
+ "model": "gpt-4",
26
+ "timezone_offset_min": -480,
27
+ "history_and_training_disabled": false,
28
+ "supports_modapi": true
29
+ }
30
+
31
+ ### get conversations
32
+ GET https://ios.chat.openai.com/backend-api/conversations?offset=0&limit=3&order=updated
33
+ Authorization: Bearer {{accessToken}}
34
+ Cookie: _devicecheck=user-xxx
35
+
36
+ ### device check
37
+ POST https://ios.chat.openai.com/backend-api/devicecheck
38
+ Authorization: Bearer {{accessToken}}
39
+ Cookie: _preauth_devicecheck=xxx
40
+ Content-Type: application/json
41
+
42
+ {
43
+ "bundle_id": "com.openai.chat",
44
+ "device_token": "ad"
45
+ }
46
+
47
+ ### me
48
+ GET https://ios.chat.openai.com/backend-api/me?include_groups=true
49
+ Authorization: Bearer {{accessToken}}
50
+ Cookie: _devicecheck=user-xxx
example/platform.http ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### login
2
+ POST {{baseUrl}}/platform/login
3
+ Content-Type: application/json
4
+
5
+ {
6
+ "username": "{{username}}",
7
+ "password": "{{password}}"
8
+ }
9
+
10
+ ### get models
11
+ GET {{baseUrl}}/platform/v1/models
12
+ Authorization: Bearer {{apiKey}}
13
+
14
+ ### get model
15
+ GET {{baseUrl}}/platform/v1/models/gpt-3.5-turbo-16k-0613
16
+ Authorization: Bearer {{apiKey}}
17
+
18
+ ### Create chat completion
19
+ POST {{baseUrl}}/platform/v1/chat/completions
20
+ Content-Type: application/json
21
+ Authorization: Bearer {{apiKey}}
22
+
23
+ {
24
+ "model": "gpt-3.5-turbo",
25
+ "messages": [
26
+ {
27
+ "role": "system",
28
+ "content": "You are a helpful assistant."
29
+ },
30
+ {
31
+ "role": "user",
32
+ "content": "Hello!"
33
+ }
34
+ ],
35
+ "stream": true
36
+ }
37
+
38
+ ### Create completion
39
+ POST {{baseUrl}}/platform/v1/completions
40
+ Content-Type: application/json
41
+ Authorization: Bearer {{apiKey}}
42
+
43
+ {
44
+ "model": "text-davinci-003",
45
+ "prompt": "Say this is a test",
46
+ "max_tokens": 7,
47
+ "temperature": 0,
48
+ "stream": true
49
+ }
50
+
51
+ ### get user api_keys
52
+ GET {{baseUrl}}/platform/dashboard/user/api_keys
53
+ Authorization: Bearer {{apiKey}}
54
+
55
+ ### get billing credit_grants
56
+ GET {{baseUrl}}/platform/dashboard/billing/credit_grants
57
+ Authorization: Bearer {{apiKey}}
58
+
59
+ ### get billing subscription
60
+ GET {{baseUrl}}/platform/dashboard/billing/subscription
61
+ Authorization: Bearer {{apiKey}}
62
+
63
+ ### get billing usage
64
+ GET {{baseUrl}}/platform/dashboard/billing/usage?end_date=2023-07-01&start_date=2023-06-01
65
+ Authorization: Bearer {{apiKey}}
go.mod ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module github.com/linweiyuan/go-chatgpt-api
2
+
3
+ go 1.21
4
+
5
+ require (
6
+ github.com/PuerkitoBio/goquery v1.8.1
7
+ github.com/bogdanfinn/fhttp v0.5.24
8
+ github.com/bogdanfinn/tls-client v1.6.1
9
+ github.com/gin-gonic/gin v1.9.1
10
+ github.com/google/uuid v1.3.1
11
+ github.com/joho/godotenv v1.5.1
12
+ github.com/linweiyuan/go-logger v0.0.0-20230709142852-da1f090a7d4c
13
+ github.com/xqdoo00o/OpenAIAuth v0.0.0-20230928031215-356afd0d7a6b
14
+ github.com/xqdoo00o/funcaptcha v0.0.0-20230928030317-87dbaf7079cf
15
+ )
16
+
17
+ require (
18
+ github.com/andybalholm/brotli v1.0.5 // indirect
19
+ github.com/andybalholm/cascadia v1.3.2 // indirect
20
+ github.com/bogdanfinn/utls v1.5.16 // indirect
21
+ github.com/bytedance/sonic v1.9.1 // indirect
22
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
23
+ github.com/gabriel-vasile/mimetype v1.4.2 // indirect
24
+ github.com/gin-contrib/sse v0.1.0 // indirect
25
+ github.com/go-playground/locales v0.14.1 // indirect
26
+ github.com/go-playground/universal-translator v0.18.1 // indirect
27
+ github.com/go-playground/validator/v10 v10.14.0 // indirect
28
+ github.com/goccy/go-json v0.10.2 // indirect
29
+ github.com/json-iterator/go v1.1.12 // indirect
30
+ github.com/klauspost/compress v1.17.0 // indirect
31
+ github.com/klauspost/cpuid/v2 v2.2.4 // indirect
32
+ github.com/leodido/go-urn v1.2.4 // indirect
33
+ github.com/mattn/go-isatty v0.0.19 // indirect
34
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
35
+ github.com/modern-go/reflect2 v1.0.2 // indirect
36
+ github.com/pelletier/go-toml/v2 v2.0.8 // indirect
37
+ github.com/sirupsen/logrus v1.9.3 // indirect
38
+ github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 // indirect
39
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
40
+ github.com/ugorji/go/codec v1.2.11 // indirect
41
+ golang.org/x/arch v0.3.0 // indirect
42
+ golang.org/x/crypto v0.13.0 // indirect
43
+ golang.org/x/net v0.15.0 // indirect
44
+ golang.org/x/sys v0.12.0 // indirect
45
+ golang.org/x/text v0.13.0 // indirect
46
+ google.golang.org/protobuf v1.30.0 // indirect
47
+ gopkg.in/yaml.v3 v3.0.1 // indirect
48
+ )
go.sum ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
4
+ github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
5
+ github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
6
+ github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
7
+ github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
8
+ github.com/bogdanfinn/fhttp v0.5.24 h1:OlyBKjvJp6a3TotN3wuj4mQHHRbfK7QUMrzCPOZGhRc=
9
+ github.com/bogdanfinn/fhttp v0.5.24/go.mod h1:brqi5woc5eSCVHdKYBV8aZLbO7HGqpwyDLeXW+fT18I=
10
+ github.com/bogdanfinn/tls-client v1.6.1 h1:GTIqQssFoIvLaDf4btoYRzDhUzudLqYD4axvfUCXl3I=
11
+ github.com/bogdanfinn/tls-client v1.6.1/go.mod h1:FtwQ3DndVZ0xAOO704v4iNAgbHOcEc5kPk9tjICTNQ0=
12
+ github.com/bogdanfinn/utls v1.5.16 h1:NhhWkegEcYETBMj9nvgO4lwvc6NcLH+znrXzO3gnw4M=
13
+ github.com/bogdanfinn/utls v1.5.16/go.mod h1:mHeRCi69cUiEyVBkKONB1cAbLjRcZnlJbGzttmiuK4o=
14
+ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
15
+ github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
16
+ github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
17
+ github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
18
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
19
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
20
+ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
21
+ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
22
+ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
23
+ github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
24
+ github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
25
+ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
26
+ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
27
+ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
28
+ github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
29
+ github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
30
+ github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
31
+ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
32
+ github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
33
+ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
34
+ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
35
+ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
36
+ github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
37
+ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
38
+ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
39
+ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
40
+ github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
41
+ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
42
+ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
43
+ github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
44
+ github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
45
+ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
46
+ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
47
+ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
48
+ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
49
+ github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
50
+ github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
51
+ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
52
+ github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
53
+ github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
54
+ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
55
+ github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
56
+ github.com/linweiyuan/go-logger v0.0.0-20230709142852-da1f090a7d4c h1:KJqkWkepk+PGSCC3i+ANrLnthM/x9qyfG4uCTGg2B8E=
57
+ github.com/linweiyuan/go-logger v0.0.0-20230709142852-da1f090a7d4c/go.mod h1:U37nbW0kovzaMnrX5sBmxOwidA7P4ymM3/K64oaTQJc=
58
+ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
59
+ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
60
+ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
61
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
62
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
63
+ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
64
+ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
65
+ github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
66
+ github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
67
+ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
68
+ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
69
+ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
70
+ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
71
+ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
72
+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
73
+ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
74
+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
75
+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
76
+ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
77
+ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
78
+ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
79
+ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
80
+ github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
81
+ github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
82
+ github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 h1:YqAladjX7xpA6BM04leXMWAEjS0mTZ5kUU9KRBriQJc=
83
+ github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5/go.mod h1:2JjD2zLQYH5HO74y5+aE3remJQvl6q4Sn6aWA2wD1Ng=
84
+ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
85
+ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
86
+ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
87
+ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
88
+ github.com/xqdoo00o/OpenAIAuth v0.0.0-20230928031215-356afd0d7a6b h1:xQmc9iMdowpMKC1zctG7+sVAJjsn57f6JoDAPORAOKo=
89
+ github.com/xqdoo00o/OpenAIAuth v0.0.0-20230928031215-356afd0d7a6b/go.mod h1:DxzLlILrE7hGJbg5SSjG22kvd3ThXADknESpo0riCRI=
90
+ github.com/xqdoo00o/funcaptcha v0.0.0-20230928030317-87dbaf7079cf h1:hUf7xpllFrVRiGHfiSIp2m+wxdrGMFJgDuwivA+P1B8=
91
+ github.com/xqdoo00o/funcaptcha v0.0.0-20230928030317-87dbaf7079cf/go.mod h1:2frDREz1MeGS6sXyNSQGKJGd/0yCCydf5khEqyi4krI=
92
+ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
93
+ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
94
+ golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
95
+ golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
96
+ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
97
+ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
98
+ golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
99
+ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
100
+ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
101
+ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
102
+ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
103
+ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
104
+ golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
105
+ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
106
+ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
107
+ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
108
+ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
109
+ golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
110
+ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
111
+ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
112
+ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
113
+ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
114
+ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
115
+ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
116
+ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
117
+ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
118
+ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
119
+ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
120
+ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
121
+ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
122
+ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
123
+ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
124
+ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
125
+ golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
126
+ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
127
+ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
128
+ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
129
+ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
130
+ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
131
+ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
132
+ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
133
+ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
134
+ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
135
+ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
136
+ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
137
+ golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
138
+ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
139
+ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
140
+ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
141
+ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
142
+ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
143
+ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
144
+ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
145
+ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
146
+ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
147
+ google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
148
+ google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
149
+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
150
+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
151
+ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
152
+ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
153
+ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
154
+ rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
main.go ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package main
2
+
3
+ import (
4
+ "log"
5
+ "os"
6
+ "strings"
7
+
8
+ http "github.com/bogdanfinn/fhttp"
9
+ "github.com/gin-gonic/gin"
10
+
11
+ "github.com/linweiyuan/go-chatgpt-api/api"
12
+ "github.com/linweiyuan/go-chatgpt-api/api/chatgpt"
13
+ "github.com/linweiyuan/go-chatgpt-api/api/imitate"
14
+ "github.com/linweiyuan/go-chatgpt-api/api/platform"
15
+ _ "github.com/linweiyuan/go-chatgpt-api/env"
16
+ "github.com/linweiyuan/go-chatgpt-api/middleware"
17
+ )
18
+
19
+ func init() {
20
+ gin.ForceConsoleColor()
21
+ gin.SetMode(gin.ReleaseMode)
22
+ }
23
+
24
+ func main() {
25
+ router := gin.Default()
26
+
27
+ router.Use(middleware.CORS())
28
+ router.Use(middleware.Authorization())
29
+
30
+ setupChatGPTAPIs(router)
31
+ setupPlatformAPIs(router)
32
+ setupPandoraAPIs(router)
33
+ setupImitateAPIs(router)
34
+ router.NoRoute(api.Proxy)
35
+
36
+ router.GET("/", func(c *gin.Context) {
37
+ c.String(http.StatusOK, api.ReadyHint)
38
+ })
39
+
40
+ port := os.Getenv("PORT")
41
+ if port == "" {
42
+ port = "8080"
43
+ }
44
+ err := router.Run(":" + port)
45
+ if err != nil {
46
+ log.Fatal("failed to start server: " + err.Error())
47
+ }
48
+ }
49
+
50
+ func setupChatGPTAPIs(router *gin.Engine) {
51
+ chatgptGroup := router.Group("/chatgpt")
52
+ {
53
+ chatgptGroup.POST("/login", chatgpt.Login)
54
+ chatgptGroup.POST("/backend-api/login", chatgpt.Login) // add support for other projects
55
+
56
+ conversationGroup := chatgptGroup.Group("/backend-api/conversation")
57
+ {
58
+ conversationGroup.POST("", chatgpt.CreateConversation)
59
+ }
60
+ }
61
+ }
62
+
63
+ func setupPlatformAPIs(router *gin.Engine) {
64
+ platformGroup := router.Group("/platform")
65
+ {
66
+ platformGroup.POST("/login", platform.Login)
67
+ platformGroup.POST("/v1/login", platform.Login)
68
+
69
+ apiGroup := platformGroup.Group("/v1")
70
+ {
71
+ apiGroup.POST("/chat/completions", platform.CreateChatCompletions)
72
+ apiGroup.POST("/completions", platform.CreateCompletions)
73
+ }
74
+ }
75
+ }
76
+
77
+ func setupPandoraAPIs(router *gin.Engine) {
78
+ router.Any("/api/*path", func(c *gin.Context) {
79
+ c.Request.URL.Path = strings.ReplaceAll(c.Request.URL.Path, "/api", "/chatgpt/backend-api")
80
+ router.HandleContext(c)
81
+ })
82
+ }
83
+
84
+ func setupImitateAPIs(router *gin.Engine) {
85
+ imitateGroup := router.Group("/imitate")
86
+ {
87
+ imitateGroup.POST("/login", chatgpt.Login)
88
+
89
+ apiGroup := imitateGroup.Group("/v1")
90
+ {
91
+ apiGroup.POST("/chat/completions", imitate.CreateChatCompletions)
92
+ }
93
+ }
94
+ }
middleware/authorization.go ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package middleware
2
+
3
+ import (
4
+ "encoding/base64"
5
+ "encoding/json"
6
+ "fmt"
7
+ "net/http"
8
+ "os"
9
+ "strings"
10
+ "time"
11
+
12
+ "github.com/gin-gonic/gin"
13
+
14
+ "github.com/linweiyuan/go-chatgpt-api/api"
15
+ )
16
+
17
+ const (
18
+ emptyAccessTokenErrorMessage = "please provide a valid access token or api key in 'Authorization' header"
19
+ accessTokenHasExpiredErrorMessage = "the accessToken for account %s has expired"
20
+ )
21
+
22
+ type AccessToken struct {
23
+ HTTPSAPIOpenaiComProfile struct {
24
+ Email string `json:"email"`
25
+ EmailVerified bool `json:"email_verified"`
26
+ } `json:"https://api.openai.com/profile"`
27
+ HTTPSAPIOpenaiComAuth struct {
28
+ UserID string `json:"user_id"`
29
+ } `json:"https://api.openai.com/auth"`
30
+ Iss string `json:"iss"`
31
+ Sub string `json:"sub"`
32
+ Aud []string `json:"aud"`
33
+ Iat int `json:"iat"`
34
+ Exp int `json:"exp"`
35
+ Azp string `json:"azp"`
36
+ Scope string `json:"scope"`
37
+ }
38
+
39
+ func Authorization() gin.HandlerFunc {
40
+ return func(c *gin.Context) {
41
+ authorization := c.GetHeader(api.AuthorizationHeader)
42
+ if authorization == "" {
43
+ authorization = c.GetHeader(api.XAuthorizationHeader)
44
+ }
45
+
46
+ if authorization == "" {
47
+ if c.Request.URL.Path == "/" {
48
+ c.Header("Content-Type", "text/plain")
49
+ } else if strings.HasSuffix(c.Request.URL.Path, "/login") ||
50
+ strings.HasPrefix(c.Request.URL.Path, "/chatgpt/public-api") ||
51
+ (strings.HasPrefix(c.Request.URL.Path, "/imitate") && os.Getenv("IMITATE_ACCESS_TOKEN") != "") {
52
+ c.Header("Content-Type", "application/json")
53
+ } else if c.Request.URL.Path == "/favicon.ico" {
54
+ c.Abort()
55
+ return
56
+ } else {
57
+ c.AbortWithStatusJSON(http.StatusUnauthorized, api.ReturnMessage(emptyAccessTokenErrorMessage))
58
+ return
59
+ }
60
+
61
+ c.Next()
62
+ } else {
63
+ if expired := isExpired(c); expired {
64
+ c.AbortWithStatusJSON(http.StatusUnauthorized, api.ReturnMessage(fmt.Sprintf(accessTokenHasExpiredErrorMessage, c.GetString(api.EmailKey))))
65
+ return
66
+ }
67
+
68
+ c.Set(api.AuthorizationHeader, authorization)
69
+ }
70
+ }
71
+ }
72
+
73
+ func isExpired(c *gin.Context) bool {
74
+ accessToken := c.GetHeader(api.AuthorizationHeader)
75
+ split := strings.Split(accessToken, ".")
76
+ if len(split) == 3 {
77
+ rawDecodedText, _ := base64.RawStdEncoding.DecodeString(split[1])
78
+ var accessToken AccessToken
79
+ json.Unmarshal(rawDecodedText, &accessToken)
80
+
81
+ c.Set(api.EmailKey, accessToken.HTTPSAPIOpenaiComProfile.Email)
82
+
83
+ exp := int64(accessToken.Exp)
84
+ expTime := time.Unix(exp, 0)
85
+ now := time.Now()
86
+
87
+ return now.After(expTime)
88
+ }
89
+
90
+ // apiKey
91
+ return false
92
+ }
middleware/cors.go ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package middleware
2
+
3
+ import (
4
+ http "github.com/bogdanfinn/fhttp"
5
+ "github.com/gin-gonic/gin"
6
+ )
7
+
8
+ func CORS() gin.HandlerFunc {
9
+ return func(c *gin.Context) {
10
+ c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
11
+ c.Writer.Header().Set("Access-Control-Allow-Headers", "*")
12
+ c.Writer.Header().Set("Access-Control-Allow-Methods", "*")
13
+
14
+ if c.Request.Method == http.MethodOptions {
15
+ c.AbortWithStatus(http.StatusNoContent)
16
+ return
17
+ }
18
+
19
+ c.Next()
20
+ }
21
+ }
render.yaml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ services:
2
+ - type: web
3
+ name: go-chatgpt-api
4
+ runtime: go
5
+ plan: free
6
+ buildCommand: go build -ldflags="-w -s" -o go-chatgpt-api main.go
7
+ startCommand: ./go-chatgpt-api