dvc890 commited on
Commit
36a4021
·
1 Parent(s): 748556b

Upload 25 files

Browse files
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: yellow
5
- colorTo: gray
6
  sdk: docker
7
  pinned: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ ![](https://linweiyuan.github.io/about/mmqrcode.png)
441
+
442
+ ---
443
+
444
+ 微信赞赏码(经济条件允许的可以考虑支持下):
445
+
446
+ ![](https://linweiyuan.github.io/about/mm_reward_qrcode.png)
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
+ }