Alcex commited on
Commit
6389914
·
1 Parent(s): 8e108cf
This view is limited to 50 files because it contains too many changes.   See raw diff
.dockerignore ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ logs
2
+ dist
3
+ doc
4
+ node_modules
5
+ .vscode
6
+ .git
7
+ .gitignore
8
+ README.md
9
+ *.tar.gz
.gitignore CHANGED
@@ -1,185 +1,4 @@
1
- ### JetBrains template
2
- # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
3
- # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
4
-
5
- # User-specific stuff
6
- .idea/**/workspace.xml
7
- .idea/**/tasks.xml
8
- .idea/**/usage.statistics.xml
9
- .idea/**/dictionaries
10
- .idea/**/shelf
11
-
12
- # AWS User-specific
13
- .idea/**/aws.xml
14
-
15
- # Generated files
16
- .idea/**/contentModel.xml
17
-
18
- # Sensitive or high-churn files
19
- .idea/**/dataSources/
20
- .idea/**/dataSources.ids
21
- .idea/**/dataSources.local.xml
22
- .idea/**/sqlDataSources.xml
23
- .idea/**/dynamic.xml
24
- .idea/**/uiDesigner.xml
25
- .idea/**/dbnavigator.xml
26
-
27
- # Gradle
28
- .idea/**/gradle.xml
29
- .idea/**/libraries
30
-
31
- # Gradle and Maven with auto-import
32
- # When using Gradle or Maven with auto-import, you should exclude module files,
33
- # since they will be recreated, and may cause churn. Uncomment if using
34
- # auto-import.
35
- # .idea/artifacts
36
- # .idea/compiler.xml
37
- # .idea/jarRepositories.xml
38
- # .idea/modules.xml
39
- # .idea/*.iml
40
- # .idea/modules
41
- # *.iml
42
- # *.ipr
43
-
44
- # CMake
45
- cmake-build-*/
46
-
47
- # Mongo Explorer plugin
48
- .idea/**/mongoSettings.xml
49
-
50
- # File-based project format
51
- *.iws
52
-
53
- # IntelliJ
54
- out/
55
-
56
- # mpeltonen/sbt-idea plugin
57
- .idea_modules/
58
-
59
- # JIRA plugin
60
- atlassian-ide-plugin.xml
61
-
62
- # Cursive Clojure plugin
63
- .idea/replstate.xml
64
-
65
- # SonarLint plugin
66
- .idea/sonarlint/
67
-
68
- # Crashlytics plugin (for Android Studio and IntelliJ)
69
- com_crashlytics_export_strings.xml
70
- crashlytics.properties
71
- crashlytics-build.properties
72
- fabric.properties
73
-
74
- # Editor-based Rest Client
75
- .idea/httpRequests
76
-
77
- # Android studio 3.1+ serialized cache file
78
- .idea/caches/build_file_checksums.ser
79
-
80
- ### Go template
81
- # If you prefer the allow list template instead of the deny list, see community template:
82
- # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
83
- #
84
- # Binaries for programs and plugins
85
- *.exe
86
- *.exe~
87
- *.dll
88
- *.so
89
- *.dylib
90
-
91
- # Test binary, built with `go test -c`
92
- *.test
93
-
94
- # Output of the go coverage tool, specifically when used with LiteIDE
95
- *.out
96
-
97
- # Dependency directories (remove the comment below to include it)
98
- # vendor/
99
-
100
- # Go workspace file
101
- go.work
102
-
103
- ### GoLand template
104
- # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
105
- # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
106
-
107
- # User-specific stuff
108
- .idea/**/workspace.xml
109
- .idea/**/tasks.xml
110
- .idea/**/usage.statistics.xml
111
- .idea/**/dictionaries
112
- .idea/**/shelf
113
-
114
- # AWS User-specific
115
- .idea/**/aws.xml
116
-
117
- # Generated files
118
- .idea/**/contentModel.xml
119
-
120
- # Sensitive or high-churn files
121
- .idea/**/dataSources/
122
- .idea/**/dataSources.ids
123
- .idea/**/dataSources.local.xml
124
- .idea/**/sqlDataSources.xml
125
- .idea/**/dynamic.xml
126
- .idea/**/uiDesigner.xml
127
- .idea/**/dbnavigator.xml
128
-
129
- # Gradle
130
- .idea/**/gradle.xml
131
- .idea/**/libraries
132
-
133
- # Gradle and Maven with auto-import
134
- # When using Gradle or Maven with auto-import, you should exclude module files,
135
- # since they will be recreated, and may cause churn. Uncomment if using
136
- # auto-import.
137
- # .idea/artifacts
138
- # .idea/compiler.xml
139
- # .idea/jarRepositories.xml
140
- # .idea/modules.xml
141
- # .idea/*.iml
142
- # .idea/modules
143
- # *.iml
144
- # *.ipr
145
-
146
- # CMake
147
- cmake-build-*/
148
-
149
- # Mongo Explorer plugin
150
- .idea/**/mongoSettings.xml
151
-
152
- # File-based project format
153
- *.iws
154
-
155
- # IntelliJ
156
- out/
157
-
158
- # mpeltonen/sbt-idea plugin
159
- .idea_modules/
160
-
161
- # JIRA plugin
162
- atlassian-ide-plugin.xml
163
-
164
- # Cursive Clojure plugin
165
- .idea/replstate.xml
166
-
167
- # SonarLint plugin
168
- .idea/sonarlint/
169
-
170
- # Crashlytics plugin (for Android Studio and IntelliJ)
171
- com_crashlytics_export_strings.xml
172
- crashlytics.properties
173
- crashlytics-build.properties
174
- fabric.properties
175
-
176
- # Editor-based Rest Client
177
- .idea/httpRequests
178
-
179
- # Android studio 3.1+ serialized cache file
180
- .idea/caches/build_file_checksums.ser
181
-
182
- /logs/
183
- /.env
184
- /target/
185
- /CHANGELOG.md
 
1
+ dist/
2
+ node_modules/
3
+ logs/
4
+ .vercel
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Dockerfile CHANGED
@@ -1,21 +1,21 @@
1
- FROM golang:1.21 AS builder
2
-
3
- ENV CGO_ENABLED=0
4
 
5
  WORKDIR /app
6
 
7
- COPY go.mod go.sum ./
8
- RUN go mod download
9
 
10
- COPY . .
11
- RUN go build -o /app/free-gpt3.5-2api .
12
 
13
- FROM alpine:latest
14
 
15
- WORKDIR /app
 
 
 
 
16
 
17
- COPY --from=builder /app/free-gpt3.5-2api /app/free-gpt3.5-2api
18
 
19
- EXPOSE 3040
20
 
21
- CMD [ "./free-gpt3.5-2api" ]
 
1
+ FROM node:lts AS BUILD_IMAGE
 
 
2
 
3
  WORKDIR /app
4
 
5
+ COPY . /app
 
6
 
7
+ RUN yarn install --registry https://registry.npmmirror.com/ && yarn run build
 
8
 
9
+ FROM node:lts-alpine
10
 
11
+ COPY --from=BUILD_IMAGE /app/configs /app/configs
12
+ COPY --from=BUILD_IMAGE /app/package.json /app/package.json
13
+ COPY --from=BUILD_IMAGE /app/dist /app/dist
14
+ COPY --from=BUILD_IMAGE /app/public /app/public
15
+ COPY --from=BUILD_IMAGE /app/node_modules /app/node_modules
16
 
17
+ WORKDIR /app
18
 
19
+ EXPOSE 8000
20
 
21
+ CMD ["npm", "start"]
FreeGpt35/FreeGpt35.go DELETED
@@ -1,231 +0,0 @@
1
- package FreeGpt35
2
-
3
- import (
4
- "encoding/json"
5
- "fmt"
6
- ProofWork2 "free-gpt3.5-2api/ProofWork"
7
- "free-gpt3.5-2api/ProxyPool"
8
- "free-gpt3.5-2api/RequestClient"
9
- "free-gpt3.5-2api/common"
10
- "free-gpt3.5-2api/config"
11
- "free-gpt3.5-2api/constant"
12
- "github.com/aurorax-neo/go-logger"
13
- fhttp "github.com/bogdanfinn/fhttp"
14
- "github.com/google/uuid"
15
- "io"
16
- )
17
-
18
- var (
19
- BaseUrl = config.BaseUrl
20
- ChatUrl = BaseUrl + "/backend-anon/conversation"
21
- AuthUrl = BaseUrl + "/backend-anon/sentinel/chat-requirements"
22
- OfficialBaseURLS = []string{"https://chat.openai.com", "https://chatgpt.com"}
23
- )
24
-
25
- // NewFreeAuthType 定义一个枚举类型
26
- type NewFreeAuthType int
27
-
28
- const (
29
- NewFreeAuthNormal NewFreeAuthType = 0 //正常获取
30
- NewFreeAuthRefresh NewFreeAuthType = 1 // 刷新获取
31
- )
32
-
33
- type FreeGpt35 struct {
34
- RequestClient RequestClient.RequestClient
35
- Proxy *ProxyPool.Proxy
36
- MaxUseCount int
37
- ExpiresAt int64
38
- FreeAuth *freeAuth
39
- Ua string
40
- Cookies []*fhttp.Cookie
41
- }
42
-
43
- type freeAuth struct {
44
- OaiDeviceId string `json:"-"`
45
- Persona string `json:"persona"`
46
- Arkose arkose `json:"arkose"`
47
- Turnstile turnstile `json:"turnstile"`
48
- ProofWork ProofWork2.ProofWork `json:"proofofwork"`
49
- Token string `json:"token"`
50
- }
51
-
52
- type arkose struct {
53
- Required bool `json:"required"`
54
- Dx string `json:"dx"`
55
- }
56
-
57
- type turnstile struct {
58
- Required bool `json:"required"`
59
- }
60
-
61
- // NewFreeGpt35 创建 FreeGpt35 实例 0 无论网络是否被标记限制都获取 1 在网络未标记时才能获取
62
- func NewFreeGpt35(newType NewFreeAuthType, maxUseCount int, expiresAt int64) *FreeGpt35 {
63
- // 创建 FreeGpt35 实例
64
- freeGpt35 := &FreeGpt35{
65
- MaxUseCount: maxUseCount,
66
- ExpiresAt: expiresAt,
67
- FreeAuth: &freeAuth{},
68
- }
69
- // 获取请求客户端
70
- err := freeGpt35.newRequestClient()
71
- if err != nil {
72
- logger.Logger.Debug(err.Error())
73
- return nil
74
- }
75
- // 获取并设置代理
76
- err = freeGpt35.getProxy(newType)
77
- if err != nil {
78
- logger.Logger.Debug(err.Error())
79
- return nil
80
- }
81
- // 获取cookies
82
- if common.IsStrInArray(BaseUrl, OfficialBaseURLS) {
83
- err = freeGpt35.getCookies()
84
- if err != nil {
85
- logger.Logger.Debug(err.Error())
86
- return nil
87
- }
88
- }
89
- // 获取 FreeAuth
90
- err = freeGpt35.newFreeAuth(newType)
91
- if err != nil {
92
- logger.Logger.Debug(err.Error())
93
- return nil
94
- }
95
- return freeGpt35
96
- }
97
-
98
- func (FG *FreeGpt35) NewRequest(method, url string, body io.Reader) (*fhttp.Request, error) {
99
- request, err := RequestClient.NewRequest(method, url, body)
100
- if err != nil {
101
- return nil, err
102
- }
103
- request.Header.Set("accept", "*/*")
104
- request.Header.Set("accept-language", "zh-CN,zh;q=0.9,zh-Hans;q=0.8,en;q=0.7")
105
- for _, cookie := range FG.Cookies {
106
- request.AddCookie(cookie)
107
- }
108
- request.Header.Set("oai-language", "en-US")
109
- request.Header.Set("origin", common.GetOrigin(url))
110
- request.Header.Set("referer", common.GetOrigin(url))
111
- request.Header.Set("sec-ch-ua", `"Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"`)
112
- request.Header.Set("sec-ch-ua-mobile", "?0")
113
- request.Header.Set("sec-ch-ua-platform", `"Windows"`)
114
- request.Header.Set("sec-fetch-dest", "empty")
115
- request.Header.Set("sec-fetch-mode", "cors")
116
- request.Header.Set("sec-fetch-site", "same-origin")
117
- request.Header.Set("user-agent", FG.Ua)
118
- return request, nil
119
- }
120
-
121
- func (FG *FreeGpt35) newRequestClient() error {
122
- // 请求客户端
123
- FG.RequestClient = RequestClient.NewTlsClient(300, constant.ClientProfile)
124
- if FG.RequestClient == nil {
125
- errStr := fmt.Sprint("RequestClient is nil")
126
- logger.Logger.Debug(errStr)
127
- return fmt.Errorf(errStr)
128
- }
129
- return nil
130
- }
131
-
132
- func (FG *FreeGpt35) getProxy(newFreeAuthType NewFreeAuthType) error {
133
- // 获取代理池
134
- ProxyPoolInstance := ProxyPool.GetProxyPoolInstance()
135
- // 获取代理
136
- FG.Proxy = ProxyPoolInstance.GetProxy()
137
- // 判断代理是否可用
138
- if FG.Proxy.CanUseAt > common.GetTimestampSecond(0) && newFreeAuthType == NewFreeAuthRefresh {
139
- errStr := fmt.Sprint(FG.Proxy.Link, ": Proxy restricted, Reuse at ", FG.Proxy.CanUseAt)
140
- return fmt.Errorf(errStr)
141
- }
142
- // Ua
143
- FG.Ua = FG.Proxy.Ua
144
- // 补全cookies
145
- FG.Cookies = append(FG.Cookies, FG.Proxy.Cookies...)
146
- // 设置代理
147
- err := FG.RequestClient.SetProxy(FG.Proxy.Link.String())
148
- if err != nil {
149
- errStr := fmt.Sprint("SetProxy Error: ", err)
150
- logger.Logger.Debug(errStr)
151
- }
152
- return nil
153
- }
154
-
155
- func (FG *FreeGpt35) getCookies() error {
156
- // 获取cookies
157
- request, err := FG.NewRequest("GET", fmt.Sprint(BaseUrl, "/?oai-dm=1"), nil)
158
- if err != nil {
159
- return err
160
- }
161
- // 设置请求头
162
- request.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
163
- // 发送 GET 请求
164
- response, err := FG.RequestClient.Do(request)
165
- if err != nil {
166
- return err
167
- }
168
- defer func(Body io.ReadCloser) {
169
- _ = Body.Close()
170
- }(response.Body)
171
- if response.StatusCode != 200 {
172
- return fmt.Errorf("StatusCode: %d", response.StatusCode)
173
- }
174
- // 获取cookies
175
- cookies := response.Cookies()
176
- for i, cookie := range cookies {
177
- if cookie.Name == "oai-did" {
178
- FG.FreeAuth.OaiDeviceId = cookie.Value
179
- cookies = append(cookies[:i], cookies[i+1:]...)
180
- }
181
- if cookie.Name == "__Secure-next-auth.callback-url" {
182
- cookie.Value = BaseUrl
183
- }
184
- }
185
- // 设置cookies
186
- FG.Cookies = append(FG.Cookies, cookies...)
187
- return nil
188
- }
189
-
190
- func (FG *FreeGpt35) newFreeAuth(newFreeAuthType NewFreeAuthType) error {
191
- // 生成新的设备 ID
192
- if FG.FreeAuth.OaiDeviceId == "" {
193
- FG.FreeAuth.OaiDeviceId = uuid.New().String()
194
- }
195
- // 创建请求
196
- request, err := FG.NewRequest("POST", AuthUrl, nil)
197
- if err != nil {
198
- return err
199
- }
200
- // 设置请求头
201
- request.Header.Set("Content-Type", "application/json")
202
- request.Header.Set("oai-device-id", FG.FreeAuth.OaiDeviceId)
203
- // 发送 POST 请求
204
- response, err := FG.RequestClient.Do(request)
205
- if err != nil {
206
- return err
207
- }
208
- if response.StatusCode != 200 {
209
- logger.Logger.Debug(fmt.Sprint("newFreeAuth: StatusCode: ", response.StatusCode))
210
- if (response.StatusCode == 429 || response.StatusCode == 403) && newFreeAuthType == NewFreeAuthRefresh {
211
- FG.Proxy.CanUseAt = common.GetTimestampSecond(300)
212
- logger.Logger.Debug(fmt.Sprint("newFreeAuth: Proxy(", FG.Proxy.Link, ")restricted, Reuse at ", FG.Proxy.CanUseAt))
213
- }
214
- return fmt.Errorf("StatusCode: %d", response.StatusCode)
215
- } else if newFreeAuthType == 0 {
216
- // 成功后更新代理的可用时间
217
- FG.Proxy.CanUseAt = common.GetTimestampSecond(0)
218
- logger.Logger.Debug(fmt.Sprint("newFreeAuth: Proxy(", FG.Proxy.Link, ")Reuse at ", FG.Proxy.CanUseAt))
219
- }
220
- defer func(Body io.ReadCloser) {
221
- _ = Body.Close()
222
- }(response.Body)
223
- if err := json.NewDecoder(response.Body).Decode(&FG.FreeAuth); err != nil {
224
- return err
225
- }
226
- // ProofWork
227
- if FG.FreeAuth.ProofWork.Required {
228
- FG.FreeAuth.ProofWork.Ospt = ProofWork2.CalcProofToken(FG.FreeAuth.ProofWork.Seed, FG.FreeAuth.ProofWork.Difficulty, request.Header.Get("User-Agent"))
229
- }
230
- return nil
231
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
FreeGpt35Pool/FreeGpt35Pool.go DELETED
@@ -1,128 +0,0 @@
1
- package FreeGpt35Pool
2
-
3
- import (
4
- "fmt"
5
- "free-gpt3.5-2api/FreeGpt35"
6
- "free-gpt3.5-2api/common"
7
- "free-gpt3.5-2api/config"
8
- "free-gpt3.5-2api/queue"
9
- "github.com/aurorax-neo/go-logger"
10
- "sync"
11
- "time"
12
- )
13
-
14
- var (
15
- instance *FreeGpt35Pool
16
- once sync.Once
17
- )
18
-
19
- type FreeGpt35Pool struct {
20
- queue *queue.Queue
21
- capacity int // 队列容量
22
- }
23
-
24
- func newFreeGpt35Pool(capacity int) *FreeGpt35Pool {
25
- return &FreeGpt35Pool{
26
- queue: queue.New(),
27
- capacity: capacity,
28
- }
29
- }
30
-
31
- func GetFreeGpt35PoolInstance() *FreeGpt35Pool {
32
- once.Do(func() {
33
- logger.Logger.Info(fmt.Sprint("Init FreeGpt35Pool..."))
34
- // 初始化 FreeGpt35Pool
35
- instance = newFreeGpt35Pool(config.PoolMaxCount)
36
- // 定时刷新 FreeGpt35Pool
37
- instance.refreshFreeGpt35Pool(time.Millisecond * 256)
38
- //
39
- logger.Logger.Info(fmt.Sprint("Init FreeGpt35Pool Success", ", PoolMaxCount: ", config.PoolMaxCount, ", AuthExpirationDate: ", config.AuthED))
40
- })
41
- return instance
42
- }
43
-
44
- func (G *FreeGpt35Pool) refreshFreeGpt35Pool(sleep time.Duration) {
45
- // 检测 FreeGpt35Pool 是否已满
46
- common.AsyncLoopTask(sleep, func() {
47
- // 判断 FreeGpt35Pool 是否已满
48
- if G.IsFull() {
49
- return
50
- }
51
- // 获取新 FreeGpt35 实例
52
- gpt35 := FreeGpt35.NewFreeGpt35(FreeGpt35.NewFreeAuthRefresh, 1, common.GetTimestampSecond(config.AuthED))
53
- // 判断 FreeGpt35 实例是否有效
54
- if G.isLiveGpt35(gpt35) {
55
- // 入队新 FreeGpt35 实例
56
- G.AddFreeGpt35(gpt35)
57
- }
58
- })
59
- // 检测并移除无效 FreeGpt35 实例
60
- common.AsyncLoopTask(sleep, func() {
61
- // 遍历队列中的所有元素
62
- G.queue.Traverse(func(n *queue.Node) {
63
- // 判断是否为无效 FreeGpt35 实例
64
- if !G.isLiveGpt35(n.Value.(*FreeGpt35.FreeGpt35)) {
65
- // 移除无效 FreeGpt35 实例
66
- G.queue.Remove(n)
67
- }
68
- })
69
- })
70
- }
71
-
72
- func (G *FreeGpt35Pool) isLiveGpt35(gpt35 *FreeGpt35.FreeGpt35) bool {
73
- //判断是否为空
74
- if gpt35 == nil ||
75
- gpt35.MaxUseCount <= 0 || //无可用次数
76
- gpt35.ExpiresAt <= common.GetTimestampSecond(0) {
77
- return false
78
- }
79
- return true
80
- }
81
-
82
- func (G *FreeGpt35Pool) GetFreeGpt35(retry int) *FreeGpt35.FreeGpt35 {
83
- // 获取 FreeGpt35 实例
84
- n := G.queue.Peek()
85
- if n != nil {
86
- gpt35 := n.Value.(*FreeGpt35.FreeGpt35)
87
- if G.isLiveGpt35(gpt35) { //有缓存
88
- // 深拷贝
89
- gpt35_ := common.DeepCopyStruct(gpt35).(*FreeGpt35.FreeGpt35)
90
- // 减少 FreeGpt35 实例的最大使用次数
91
- gpt35.MaxUseCount--
92
- // 判断 FreeGpt35 实例是否有效 无效则移除
93
- if !G.isLiveGpt35(gpt35) {
94
- G.queue.Dequeue()
95
- }
96
- return gpt35_
97
- } else if retry > 0 {
98
- time.Sleep(time.Millisecond * 128)
99
- return G.GetFreeGpt35(retry - 1)
100
- }
101
- }
102
- // 缓存内无可用 FreeGpt35 实例,返回新 FreeGpt35 实例
103
- return FreeGpt35.NewFreeGpt35(FreeGpt35.NewFreeAuthNormal, 1, common.GetTimestampSecond(config.AuthED))
104
- }
105
-
106
- // GetSize 获取队列当前元素个数
107
- func (G *FreeGpt35Pool) GetSize() int {
108
- return G.queue.Len()
109
- }
110
-
111
- // GetCapacity 获取队列容量
112
- func (G *FreeGpt35Pool) GetCapacity() int {
113
- return G.capacity
114
- }
115
-
116
- // IsFull 检查队列是否已满
117
- func (G *FreeGpt35Pool) IsFull() bool {
118
- return G.GetSize() == G.capacity
119
- }
120
-
121
- // AddFreeGpt35 入队
122
- func (G *FreeGpt35Pool) AddFreeGpt35(v *FreeGpt35.FreeGpt35) bool {
123
- if G.IsFull() || v == nil {
124
- return false
125
- }
126
- G.queue.Enqueue(v)
127
- return true
128
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
LICENSE ADDED
@@ -0,0 +1,674 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+ Preamble
9
+
10
+ The GNU General Public License is a free, copyleft license for
11
+ software and other kinds of works.
12
+
13
+ The licenses for most software and other practical works are designed
14
+ to take away your freedom to share and change the works. By contrast,
15
+ the GNU General Public License is intended to guarantee your freedom to
16
+ share and change all versions of a program--to make sure it remains free
17
+ software for all its users. We, the Free Software Foundation, use the
18
+ GNU General Public License for most of our software; it applies also to
19
+ any other work released this way by its authors. You can apply it to
20
+ your programs, too.
21
+
22
+ When we speak of free software, we are referring to freedom, not
23
+ price. Our General Public Licenses are designed to make sure that you
24
+ have the freedom to distribute copies of free software (and charge for
25
+ them if you wish), that you receive source code or can get it if you
26
+ want it, that you can change the software or use pieces of it in new
27
+ free programs, and that you know you can do these things.
28
+
29
+ To protect your rights, we need to prevent others from denying you
30
+ these rights or asking you to surrender the rights. Therefore, you have
31
+ certain responsibilities if you distribute copies of the software, or if
32
+ you modify it: responsibilities to respect the freedom of others.
33
+
34
+ For example, if you distribute copies of such a program, whether
35
+ gratis or for a fee, you must pass on to the recipients the same
36
+ freedoms that you received. You must make sure that they, too, receive
37
+ or can get the source code. And you must show them these terms so they
38
+ know their rights.
39
+
40
+ Developers that use the GNU GPL protect your rights with two steps:
41
+ (1) assert copyright on the software, and (2) offer you this License
42
+ giving you legal permission to copy, distribute and/or modify it.
43
+
44
+ For the developers' and authors' protection, the GPL clearly explains
45
+ that there is no warranty for this free software. For both users' and
46
+ authors' sake, the GPL requires that modified versions be marked as
47
+ changed, so that their problems will not be attributed erroneously to
48
+ authors of previous versions.
49
+
50
+ Some devices are designed to deny users access to install or run
51
+ modified versions of the software inside them, although the manufacturer
52
+ can do so. This is fundamentally incompatible with the aim of
53
+ protecting users' freedom to change the software. The systematic
54
+ pattern of such abuse occurs in the area of products for individuals to
55
+ use, which is precisely where it is most unacceptable. Therefore, we
56
+ have designed this version of the GPL to prohibit the practice for those
57
+ products. If such problems arise substantially in other domains, we
58
+ stand ready to extend this provision to those domains in future versions
59
+ of the GPL, as needed to protect the freedom of users.
60
+
61
+ Finally, every program is threatened constantly by software patents.
62
+ States should not allow patents to restrict development and use of
63
+ software on general-purpose computers, but in those that do, we wish to
64
+ avoid the special danger that patents applied to a free program could
65
+ make it effectively proprietary. To prevent this, the GPL assures that
66
+ patents cannot be used to render the program non-free.
67
+
68
+ The precise terms and conditions for copying, distribution and
69
+ modification follow.
70
+
71
+ TERMS AND CONDITIONS
72
+
73
+ 0. Definitions.
74
+
75
+ "This License" refers to version 3 of the GNU General Public License.
76
+
77
+ "Copyright" also means copyright-like laws that apply to other kinds of
78
+ works, such as semiconductor masks.
79
+
80
+ "The Program" refers to any copyrightable work licensed under this
81
+ License. Each licensee is addressed as "you". "Licensees" and
82
+ "recipients" may be individuals or organizations.
83
+
84
+ To "modify" a work means to copy from or adapt all or part of the work
85
+ in a fashion requiring copyright permission, other than the making of an
86
+ exact copy. The resulting work is called a "modified version" of the
87
+ earlier work or a work "based on" the earlier work.
88
+
89
+ A "covered work" means either the unmodified Program or a work based
90
+ on the Program.
91
+
92
+ To "propagate" a work means to do anything with it that, without
93
+ permission, would make you directly or secondarily liable for
94
+ infringement under applicable copyright law, except executing it on a
95
+ computer or modifying a private copy. Propagation includes copying,
96
+ distribution (with or without modification), making available to the
97
+ public, and in some countries other activities as well.
98
+
99
+ To "convey" a work means any kind of propagation that enables other
100
+ parties to make or receive copies. Mere interaction with a user through
101
+ a computer network, with no transfer of a copy, is not conveying.
102
+
103
+ An interactive user interface displays "Appropriate Legal Notices"
104
+ to the extent that it includes a convenient and prominently visible
105
+ feature that (1) displays an appropriate copyright notice, and (2)
106
+ tells the user that there is no warranty for the work (except to the
107
+ extent that warranties are provided), that licensees may convey the
108
+ work under this License, and how to view a copy of this License. If
109
+ the interface presents a list of user commands or options, such as a
110
+ menu, a prominent item in the list meets this criterion.
111
+
112
+ 1. Source Code.
113
+
114
+ The "source code" for a work means the preferred form of the work
115
+ for making modifications to it. "Object code" means any non-source
116
+ form of a work.
117
+
118
+ A "Standard Interface" means an interface that either is an official
119
+ standard defined by a recognized standards body, or, in the case of
120
+ interfaces specified for a particular programming language, one that
121
+ is widely used among developers working in that language.
122
+
123
+ The "System Libraries" of an executable work include anything, other
124
+ than the work as a whole, that (a) is included in the normal form of
125
+ packaging a Major Component, but which is not part of that Major
126
+ Component, and (b) serves only to enable use of the work with that
127
+ Major Component, or to implement a Standard Interface for which an
128
+ implementation is available to the public in source code form. A
129
+ "Major Component", in this context, means a major essential component
130
+ (kernel, window system, and so on) of the specific operating system
131
+ (if any) on which the executable work runs, or a compiler used to
132
+ produce the work, or an object code interpreter used to run it.
133
+
134
+ The "Corresponding Source" for a work in object code form means all
135
+ the source code needed to generate, install, and (for an executable
136
+ work) run the object code and to modify the work, including scripts to
137
+ control those activities. However, it does not include the work's
138
+ System Libraries, or general-purpose tools or generally available free
139
+ programs which are used unmodified in performing those activities but
140
+ which are not part of the work. For example, Corresponding Source
141
+ includes interface definition files associated with source files for
142
+ the work, and the source code for shared libraries and dynamically
143
+ linked subprograms that the work is specifically designed to require,
144
+ such as by intimate data communication or control flow between those
145
+ subprograms and other parts of the work.
146
+
147
+ The Corresponding Source need not include anything that users
148
+ can regenerate automatically from other parts of the Corresponding
149
+ Source.
150
+
151
+ The Corresponding Source for a work in source code form is that
152
+ same work.
153
+
154
+ 2. Basic Permissions.
155
+
156
+ All rights granted under this License are granted for the term of
157
+ copyright on the Program, and are irrevocable provided the stated
158
+ conditions are met. This License explicitly affirms your unlimited
159
+ permission to run the unmodified Program. The output from running a
160
+ covered work is covered by this License only if the output, given its
161
+ content, constitutes a covered work. This License acknowledges your
162
+ rights of fair use or other equivalent, as provided by copyright law.
163
+
164
+ You may make, run and propagate covered works that you do not
165
+ convey, without conditions so long as your license otherwise remains
166
+ in force. You may convey covered works to others for the sole purpose
167
+ of having them make modifications exclusively for you, or provide you
168
+ with facilities for running those works, provided that you comply with
169
+ the terms of this License in conveying all material for which you do
170
+ not control copyright. Those thus making or running the covered works
171
+ for you must do so exclusively on your behalf, under your direction
172
+ and control, on terms that prohibit them from making any copies of
173
+ your copyrighted material outside their relationship with you.
174
+
175
+ Conveying under any other circumstances is permitted solely under
176
+ the conditions stated below. Sublicensing is not allowed; section 10
177
+ makes it unnecessary.
178
+
179
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180
+
181
+ No covered work shall be deemed part of an effective technological
182
+ measure under any applicable law fulfilling obligations under article
183
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184
+ similar laws prohibiting or restricting circumvention of such
185
+ measures.
186
+
187
+ When you convey a covered work, you waive any legal power to forbid
188
+ circumvention of technological measures to the extent such circumvention
189
+ is effected by exercising rights under this License with respect to
190
+ the covered work, and you disclaim any intention to limit operation or
191
+ modification of the work as a means of enforcing, against the work's
192
+ users, your or third parties' legal rights to forbid circumvention of
193
+ technological measures.
194
+
195
+ 4. Conveying Verbatim Copies.
196
+
197
+ You may convey verbatim copies of the Program's source code as you
198
+ receive it, in any medium, provided that you conspicuously and
199
+ appropriately publish on each copy an appropriate copyright notice;
200
+ keep intact all notices stating that this License and any
201
+ non-permissive terms added in accord with section 7 apply to the code;
202
+ keep intact all notices of the absence of any warranty; and give all
203
+ recipients a copy of this License along with the Program.
204
+
205
+ You may charge any price or no price for each copy that you convey,
206
+ and you may offer support or warranty protection for a fee.
207
+
208
+ 5. Conveying Modified Source Versions.
209
+
210
+ You may convey a work based on the Program, or the modifications to
211
+ produce it from the Program, in the form of source code under the
212
+ terms of section 4, provided that you also meet all of these conditions:
213
+
214
+ a) The work must carry prominent notices stating that you modified
215
+ it, and giving a relevant date.
216
+
217
+ b) The work must carry prominent notices stating that it is
218
+ released under this License and any conditions added under section
219
+ 7. This requirement modifies the requirement in section 4 to
220
+ "keep intact all notices".
221
+
222
+ c) You must license the entire work, as a whole, under this
223
+ License to anyone who comes into possession of a copy. This
224
+ License will therefore apply, along with any applicable section 7
225
+ additional terms, to the whole of the work, and all its parts,
226
+ regardless of how they are packaged. This License gives no
227
+ permission to license the work in any other way, but it does not
228
+ invalidate such permission if you have separately received it.
229
+
230
+ d) If the work has interactive user interfaces, each must display
231
+ Appropriate Legal Notices; however, if the Program has interactive
232
+ interfaces that do not display Appropriate Legal Notices, your
233
+ work need not make them do so.
234
+
235
+ A compilation of a covered work with other separate and independent
236
+ works, which are not by their nature extensions of the covered work,
237
+ and which are not combined with it such as to form a larger program,
238
+ in or on a volume of a storage or distribution medium, is called an
239
+ "aggregate" if the compilation and its resulting copyright are not
240
+ used to limit the access or legal rights of the compilation's users
241
+ beyond what the individual works permit. Inclusion of a covered work
242
+ in an aggregate does not cause this License to apply to the other
243
+ parts of the aggregate.
244
+
245
+ 6. Conveying Non-Source Forms.
246
+
247
+ You may convey a covered work in object code form under the terms
248
+ of sections 4 and 5, provided that you also convey the
249
+ machine-readable Corresponding Source under the terms of this License,
250
+ in one of these ways:
251
+
252
+ a) Convey the object code in, or embodied in, a physical product
253
+ (including a physical distribution medium), accompanied by the
254
+ Corresponding Source fixed on a durable physical medium
255
+ customarily used for software interchange.
256
+
257
+ b) Convey the object code in, or embodied in, a physical product
258
+ (including a physical distribution medium), accompanied by a
259
+ written offer, valid for at least three years and valid for as
260
+ long as you offer spare parts or customer support for that product
261
+ model, to give anyone who possesses the object code either (1) a
262
+ copy of the Corresponding Source for all the software in the
263
+ product that is covered by this License, on a durable physical
264
+ medium customarily used for software interchange, for a price no
265
+ more than your reasonable cost of physically performing this
266
+ conveying of source, or (2) access to copy the
267
+ Corresponding Source from a network server at no charge.
268
+
269
+ c) Convey individual copies of the object code with a copy of the
270
+ written offer to provide the Corresponding Source. This
271
+ alternative is allowed only occasionally and noncommercially, and
272
+ only if you received the object code with such an offer, in accord
273
+ with subsection 6b.
274
+
275
+ d) Convey the object code by offering access from a designated
276
+ place (gratis or for a charge), and offer equivalent access to the
277
+ Corresponding Source in the same way through the same place at no
278
+ further charge. You need not require recipients to copy the
279
+ Corresponding Source along with the object code. If the place to
280
+ copy the object code is a network server, the Corresponding Source
281
+ may be on a different server (operated by you or a third party)
282
+ that supports equivalent copying facilities, provided you maintain
283
+ clear directions next to the object code saying where to find the
284
+ Corresponding Source. Regardless of what server hosts the
285
+ Corresponding Source, you remain obligated to ensure that it is
286
+ available for as long as needed to satisfy these requirements.
287
+
288
+ e) Convey the object code using peer-to-peer transmission, provided
289
+ you inform other peers where the object code and Corresponding
290
+ Source of the work are being offered to the general public at no
291
+ charge under subsection 6d.
292
+
293
+ A separable portion of the object code, whose source code is excluded
294
+ from the Corresponding Source as a System Library, need not be
295
+ included in conveying the object code work.
296
+
297
+ A "User Product" is either (1) a "consumer product", which means any
298
+ tangible personal property which is normally used for personal, family,
299
+ or household purposes, or (2) anything designed or sold for incorporation
300
+ into a dwelling. In determining whether a product is a consumer product,
301
+ doubtful cases shall be resolved in favor of coverage. For a particular
302
+ product received by a particular user, "normally used" refers to a
303
+ typical or common use of that class of product, regardless of the status
304
+ of the particular user or of the way in which the particular user
305
+ actually uses, or expects or is expected to use, the product. A product
306
+ is a consumer product regardless of whether the product has substantial
307
+ commercial, industrial or non-consumer uses, unless such uses represent
308
+ the only significant mode of use of the product.
309
+
310
+ "Installation Information" for a User Product means any methods,
311
+ procedures, authorization keys, or other information required to install
312
+ and execute modified versions of a covered work in that User Product from
313
+ a modified version of its Corresponding Source. The information must
314
+ suffice to ensure that the continued functioning of the modified object
315
+ code is in no case prevented or interfered with solely because
316
+ modification has been made.
317
+
318
+ If you convey an object code work under this section in, or with, or
319
+ specifically for use in, a User Product, and the conveying occurs as
320
+ part of a transaction in which the right of possession and use of the
321
+ User Product is transferred to the recipient in perpetuity or for a
322
+ fixed term (regardless of how the transaction is characterized), the
323
+ Corresponding Source conveyed under this section must be accompanied
324
+ by the Installation Information. But this requirement does not apply
325
+ if neither you nor any third party retains the ability to install
326
+ modified object code on the User Product (for example, the work has
327
+ been installed in ROM).
328
+
329
+ The requirement to provide Installation Information does not include a
330
+ requirement to continue to provide support service, warranty, or updates
331
+ for a work that has been modified or installed by the recipient, or for
332
+ the User Product in which it has been modified or installed. Access to a
333
+ network may be denied when the modification itself materially and
334
+ adversely affects the operation of the network or violates the rules and
335
+ protocols for communication across the network.
336
+
337
+ Corresponding Source conveyed, and Installation Information provided,
338
+ in accord with this section must be in a format that is publicly
339
+ documented (and with an implementation available to the public in
340
+ source code form), and must require no special password or key for
341
+ unpacking, reading or copying.
342
+
343
+ 7. Additional Terms.
344
+
345
+ "Additional permissions" are terms that supplement the terms of this
346
+ License by making exceptions from one or more of its conditions.
347
+ Additional permissions that are applicable to the entire Program shall
348
+ be treated as though they were included in this License, to the extent
349
+ that they are valid under applicable law. If additional permissions
350
+ apply only to part of the Program, that part may be used separately
351
+ under those permissions, but the entire Program remains governed by
352
+ this License without regard to the additional permissions.
353
+
354
+ When you convey a copy of a covered work, you may at your option
355
+ remove any additional permissions from that copy, or from any part of
356
+ it. (Additional permissions may be written to require their own
357
+ removal in certain cases when you modify the work.) You may place
358
+ additional permissions on material, added by you to a covered work,
359
+ for which you have or can give appropriate copyright permission.
360
+
361
+ Notwithstanding any other provision of this License, for material you
362
+ add to a covered work, you may (if authorized by the copyright holders of
363
+ that material) supplement the terms of this License with terms:
364
+
365
+ a) Disclaiming warranty or limiting liability differently from the
366
+ terms of sections 15 and 16 of this License; or
367
+
368
+ b) Requiring preservation of specified reasonable legal notices or
369
+ author attributions in that material or in the Appropriate Legal
370
+ Notices displayed by works containing it; or
371
+
372
+ c) Prohibiting misrepresentation of the origin of that material, or
373
+ requiring that modified versions of such material be marked in
374
+ reasonable ways as different from the original version; or
375
+
376
+ d) Limiting the use for publicity purposes of names of licensors or
377
+ authors of the material; or
378
+
379
+ e) Declining to grant rights under trademark law for use of some
380
+ trade names, trademarks, or service marks; or
381
+
382
+ f) Requiring indemnification of licensors and authors of that
383
+ material by anyone who conveys the material (or modified versions of
384
+ it) with contractual assumptions of liability to the recipient, for
385
+ any liability that these contractual assumptions directly impose on
386
+ those licensors and authors.
387
+
388
+ All other non-permissive additional terms are considered "further
389
+ restrictions" within the meaning of section 10. If the Program as you
390
+ received it, or any part of it, contains a notice stating that it is
391
+ governed by this License along with a term that is a further
392
+ restriction, you may remove that term. If a license document contains
393
+ a further restriction but permits relicensing or conveying under this
394
+ License, you may add to a covered work material governed by the terms
395
+ of that license document, provided that the further restriction does
396
+ not survive such relicensing or conveying.
397
+
398
+ If you add terms to a covered work in accord with this section, you
399
+ must place, in the relevant source files, a statement of the
400
+ additional terms that apply to those files, or a notice indicating
401
+ where to find the applicable terms.
402
+
403
+ Additional terms, permissive or non-permissive, may be stated in the
404
+ form of a separately written license, or stated as exceptions;
405
+ the above requirements apply either way.
406
+
407
+ 8. Termination.
408
+
409
+ You may not propagate or modify a covered work except as expressly
410
+ provided under this License. Any attempt otherwise to propagate or
411
+ modify it is void, and will automatically terminate your rights under
412
+ this License (including any patent licenses granted under the third
413
+ paragraph of section 11).
414
+
415
+ However, if you cease all violation of this License, then your
416
+ license from a particular copyright holder is reinstated (a)
417
+ provisionally, unless and until the copyright holder explicitly and
418
+ finally terminates your license, and (b) permanently, if the copyright
419
+ holder fails to notify you of the violation by some reasonable means
420
+ prior to 60 days after the cessation.
421
+
422
+ Moreover, your license from a particular copyright holder is
423
+ reinstated permanently if the copyright holder notifies you of the
424
+ violation by some reasonable means, this is the first time you have
425
+ received notice of violation of this License (for any work) from that
426
+ copyright holder, and you cure the violation prior to 30 days after
427
+ your receipt of the notice.
428
+
429
+ Termination of your rights under this section does not terminate the
430
+ licenses of parties who have received copies or rights from you under
431
+ this License. If your rights have been terminated and not permanently
432
+ reinstated, you do not qualify to receive new licenses for the same
433
+ material under section 10.
434
+
435
+ 9. Acceptance Not Required for Having Copies.
436
+
437
+ You are not required to accept this License in order to receive or
438
+ run a copy of the Program. Ancillary propagation of a covered work
439
+ occurring solely as a consequence of using peer-to-peer transmission
440
+ to receive a copy likewise does not require acceptance. However,
441
+ nothing other than this License grants you permission to propagate or
442
+ modify any covered work. These actions infringe copyright if you do
443
+ not accept this License. Therefore, by modifying or propagating a
444
+ covered work, you indicate your acceptance of this License to do so.
445
+
446
+ 10. Automatic Licensing of Downstream Recipients.
447
+
448
+ Each time you convey a covered work, the recipient automatically
449
+ receives a license from the original licensors, to run, modify and
450
+ propagate that work, subject to this License. You are not responsible
451
+ for enforcing compliance by third parties with this License.
452
+
453
+ An "entity transaction" is a transaction transferring control of an
454
+ organization, or substantially all assets of one, or subdividing an
455
+ organization, or merging organizations. If propagation of a covered
456
+ work results from an entity transaction, each party to that
457
+ transaction who receives a copy of the work also receives whatever
458
+ licenses to the work the party's predecessor in interest had or could
459
+ give under the previous paragraph, plus a right to possession of the
460
+ Corresponding Source of the work from the predecessor in interest, if
461
+ the predecessor has it or can get it with reasonable efforts.
462
+
463
+ You may not impose any further restrictions on the exercise of the
464
+ rights granted or affirmed under this License. For example, you may
465
+ not impose a license fee, royalty, or other charge for exercise of
466
+ rights granted under this License, and you may not initiate litigation
467
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
468
+ any patent claim is infringed by making, using, selling, offering for
469
+ sale, or importing the Program or any portion of it.
470
+
471
+ 11. Patents.
472
+
473
+ A "contributor" is a copyright holder who authorizes use under this
474
+ License of the Program or a work on which the Program is based. The
475
+ work thus licensed is called the contributor's "contributor version".
476
+
477
+ A contributor's "essential patent claims" are all patent claims
478
+ owned or controlled by the contributor, whether already acquired or
479
+ hereafter acquired, that would be infringed by some manner, permitted
480
+ by this License, of making, using, or selling its contributor version,
481
+ but do not include claims that would be infringed only as a
482
+ consequence of further modification of the contributor version. For
483
+ purposes of this definition, "control" includes the right to grant
484
+ patent sublicenses in a manner consistent with the requirements of
485
+ this License.
486
+
487
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
488
+ patent license under the contributor's essential patent claims, to
489
+ make, use, sell, offer for sale, import and otherwise run, modify and
490
+ propagate the contents of its contributor version.
491
+
492
+ In the following three paragraphs, a "patent license" is any express
493
+ agreement or commitment, however denominated, not to enforce a patent
494
+ (such as an express permission to practice a patent or covenant not to
495
+ sue for patent infringement). To "grant" such a patent license to a
496
+ party means to make such an agreement or commitment not to enforce a
497
+ patent against the party.
498
+
499
+ If you convey a covered work, knowingly relying on a patent license,
500
+ and the Corresponding Source of the work is not available for anyone
501
+ to copy, free of charge and under the terms of this License, through a
502
+ publicly available network server or other readily accessible means,
503
+ then you must either (1) cause the Corresponding Source to be so
504
+ available, or (2) arrange to deprive yourself of the benefit of the
505
+ patent license for this particular work, or (3) arrange, in a manner
506
+ consistent with the requirements of this License, to extend the patent
507
+ license to downstream recipients. "Knowingly relying" means you have
508
+ actual knowledge that, but for the patent license, your conveying the
509
+ covered work in a country, or your recipient's use of the covered work
510
+ in a country, would infringe one or more identifiable patents in that
511
+ country that you have reason to believe are valid.
512
+
513
+ If, pursuant to or in connection with a single transaction or
514
+ arrangement, you convey, or propagate by procuring conveyance of, a
515
+ covered work, and grant a patent license to some of the parties
516
+ receiving the covered work authorizing them to use, propagate, modify
517
+ or convey a specific copy of the covered work, then the patent license
518
+ you grant is automatically extended to all recipients of the covered
519
+ work and works based on it.
520
+
521
+ A patent license is "discriminatory" if it does not include within
522
+ the scope of its coverage, prohibits the exercise of, or is
523
+ conditioned on the non-exercise of one or more of the rights that are
524
+ specifically granted under this License. You may not convey a covered
525
+ work if you are a party to an arrangement with a third party that is
526
+ in the business of distributing software, under which you make payment
527
+ to the third party based on the extent of your activity of conveying
528
+ the work, and under which the third party grants, to any of the
529
+ parties who would receive the covered work from you, a discriminatory
530
+ patent license (a) in connection with copies of the covered work
531
+ conveyed by you (or copies made from those copies), or (b) primarily
532
+ for and in connection with specific products or compilations that
533
+ contain the covered work, unless you entered into that arrangement,
534
+ or that patent license was granted, prior to 28 March 2007.
535
+
536
+ Nothing in this License shall be construed as excluding or limiting
537
+ any implied license or other defenses to infringement that may
538
+ otherwise be available to you under applicable patent law.
539
+
540
+ 12. No Surrender of Others' Freedom.
541
+
542
+ If conditions are imposed on you (whether by court order, agreement or
543
+ otherwise) that contradict the conditions of this License, they do not
544
+ excuse you from the conditions of this License. If you cannot convey a
545
+ covered work so as to satisfy simultaneously your obligations under this
546
+ License and any other pertinent obligations, then as a consequence you may
547
+ not convey it at all. For example, if you agree to terms that obligate you
548
+ to collect a royalty for further conveying from those to whom you convey
549
+ the Program, the only way you could satisfy both those terms and this
550
+ License would be to refrain entirely from conveying the Program.
551
+
552
+ 13. Use with the GNU Affero General Public License.
553
+
554
+ Notwithstanding any other provision of this License, you have
555
+ permission to link or combine any covered work with a work licensed
556
+ under version 3 of the GNU Affero General Public License into a single
557
+ combined work, and to convey the resulting work. The terms of this
558
+ License will continue to apply to the part which is the covered work,
559
+ but the special requirements of the GNU Affero General Public License,
560
+ section 13, concerning interaction through a network will apply to the
561
+ combination as such.
562
+
563
+ 14. Revised Versions of this License.
564
+
565
+ The Free Software Foundation may publish revised and/or new versions of
566
+ the GNU General Public License from time to time. Such new versions will
567
+ be similar in spirit to the present version, but may differ in detail to
568
+ address new problems or concerns.
569
+
570
+ Each version is given a distinguishing version number. If the
571
+ Program specifies that a certain numbered version of the GNU General
572
+ Public License "or any later version" applies to it, you have the
573
+ option of following the terms and conditions either of that numbered
574
+ version or of any later version published by the Free Software
575
+ Foundation. If the Program does not specify a version number of the
576
+ GNU General Public License, you may choose any version ever published
577
+ by the Free Software Foundation.
578
+
579
+ If the Program specifies that a proxy can decide which future
580
+ versions of the GNU General Public License can be used, that proxy's
581
+ public statement of acceptance of a version permanently authorizes you
582
+ to choose that version for the Program.
583
+
584
+ Later license versions may give you additional or different
585
+ permissions. However, no additional obligations are imposed on any
586
+ author or copyright holder as a result of your choosing to follow a
587
+ later version.
588
+
589
+ 15. Disclaimer of Warranty.
590
+
591
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599
+
600
+ 16. Limitation of Liability.
601
+
602
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610
+ SUCH DAMAGES.
611
+
612
+ 17. Interpretation of Sections 15 and 16.
613
+
614
+ If the disclaimer of warranty and limitation of liability provided
615
+ above cannot be given local legal effect according to their terms,
616
+ reviewing courts shall apply local law that most closely approximates
617
+ an absolute waiver of all civil liability in connection with the
618
+ Program, unless a warranty or assumption of liability accompanies a
619
+ copy of the Program in return for a fee.
620
+
621
+ END OF TERMS AND CONDITIONS
622
+
623
+ How to Apply These Terms to Your New Programs
624
+
625
+ If you develop a new program, and you want it to be of the greatest
626
+ possible use to the public, the best way to achieve this is to make it
627
+ free software which everyone can redistribute and change under these terms.
628
+
629
+ To do so, attach the following notices to the program. It is safest
630
+ to attach them to the start of each source file to most effectively
631
+ state the exclusion of warranty; and each file should have at least
632
+ the "copyright" line and a pointer to where the full notice is found.
633
+
634
+ <one line to give the program's name and a brief idea of what it does.>
635
+ Copyright (C) <year> <name of author>
636
+
637
+ This program is free software: you can redistribute it and/or modify
638
+ it under the terms of the GNU General Public License as published by
639
+ the Free Software Foundation, either version 3 of the License, or
640
+ (at your option) any later version.
641
+
642
+ This program is distributed in the hope that it will be useful,
643
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
644
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645
+ GNU General Public License for more details.
646
+
647
+ You should have received a copy of the GNU General Public License
648
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
649
+
650
+ Also add information on how to contact you by electronic and paper mail.
651
+
652
+ If the program does terminal interaction, make it output a short
653
+ notice like this when it starts in an interactive mode:
654
+
655
+ <program> Copyright (C) <year> <name of author>
656
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657
+ This is free software, and you are welcome to redistribute it
658
+ under certain conditions; type `show c' for details.
659
+
660
+ The hypothetical commands `show w' and `show c' should show the appropriate
661
+ parts of the General Public License. Of course, your program's commands
662
+ might be different; for a GUI interface, you would use an "about box".
663
+
664
+ You should also get your employer (if you work as a programmer) or school,
665
+ if any, to sign a "copyright disclaimer" for the program, if necessary.
666
+ For more information on this, and how to apply and follow the GNU GPL, see
667
+ <https://www.gnu.org/licenses/>.
668
+
669
+ The GNU General Public License does not permit incorporating your program
670
+ into proprietary programs. If your program is a subroutine library, you
671
+ may consider it more useful to permit linking proprietary applications with
672
+ the library. If this is what you want to do, use the GNU Lesser General
673
+ Public License instead of this License. But first, please read
674
+ <https://www.gnu.org/licenses/why-not-lgpl.html>.
ProofWork/ProofWork.go DELETED
@@ -1,55 +0,0 @@
1
- package ProofWork
2
-
3
- import (
4
- "encoding/base64"
5
- "encoding/hex"
6
- "encoding/json"
7
- "golang.org/x/crypto/sha3"
8
- "math/rand"
9
- "time"
10
- )
11
-
12
- var (
13
- numberCollisions = 100000
14
- cores = []int{8, 12, 16, 24}
15
- screens = []int{3000, 4000, 6000}
16
- timeLayout = "Mon Jan 2 2006 15:04:05"
17
- )
18
-
19
- type ProofWork struct {
20
- Difficulty string `json:"difficulty,omitempty"`
21
- Required bool `json:"required"`
22
- Seed string `json:"seed,omitempty"`
23
- Ospt string `json:"-"`
24
- }
25
-
26
- func getParseTime() string {
27
- now := time.Now()
28
- return now.Format(timeLayout) + " GMT" + now.Format("-0700 MST (MST)")
29
- }
30
-
31
- func getConfig(userAgent string) []interface{} {
32
- rand.New(rand.NewSource(time.Now().UnixNano()))
33
- core := cores[rand.Intn(4)]
34
- rand.New(rand.NewSource(time.Now().UnixNano()))
35
- screen := screens[rand.Intn(3)]
36
- return []interface{}{core + screen, getParseTime(), int64(4294705152), 0, userAgent}
37
-
38
- }
39
-
40
- func CalcProofToken(seed string, diff string, userAgent string) string {
41
- config := getConfig(userAgent)
42
- hasher := sha3.New512()
43
- for i := 0; i < numberCollisions; i++ {
44
- config[3] = i
45
- jsonStr, _ := json.Marshal(config)
46
- base := base64.StdEncoding.EncodeToString(jsonStr)
47
- hasher.Write([]byte(seed + base))
48
- hash := hasher.Sum(nil)
49
- hasher.Reset()
50
- if hex.EncodeToString(hash[:len(diff)]) <= diff {
51
- return "gAAAAAB" + base
52
- }
53
- }
54
- return "gAAAAABwQ8Lk5FbGpA2NcR9dShT6gYjU7VxZ4D" + base64.StdEncoding.EncodeToString([]byte(`"`+seed+`"`))
55
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ProxyPool/ProxyPool.go DELETED
@@ -1,87 +0,0 @@
1
- package ProxyPool
2
-
3
- import (
4
- "fmt"
5
- "free-gpt3.5-2api/common"
6
- "free-gpt3.5-2api/config"
7
- "free-gpt3.5-2api/constant"
8
- "github.com/aurorax-neo/go-logger"
9
- fhttp "github.com/bogdanfinn/fhttp"
10
- "net/url"
11
- "sync"
12
- "time"
13
- )
14
-
15
- var (
16
- Instance *ProxyPool
17
- Once sync.Once
18
- )
19
-
20
- type ProxyPool struct {
21
- Proxies []*Proxy
22
- Index int
23
- }
24
-
25
- type Proxy struct {
26
- Link *url.URL
27
- CanUseAt int64
28
- Ua string
29
- Cookies []*fhttp.Cookie
30
- }
31
-
32
- func GetProxyPoolInstance() *ProxyPool {
33
- Once.Do(func() {
34
- logger.Logger.Info(fmt.Sprint("Init ProxyPool..."))
35
- // 初始化 ProxyPool
36
- Instance = NewProxyPool(nil)
37
- // 遍历配置文件中的代理 添加到代理池
38
- for _, px := range config.Proxy {
39
- proxy := NewProxy(px, common.GetTimestampSecond(0), constant.Ua)
40
- _ = proxy.getCookies()
41
- Instance.AddProxy(proxy)
42
- }
43
- //定时刷新代理cookies
44
- common.AsyncLoopTask(1*time.Minute, func() {
45
- for _, proxy := range Instance.Proxies {
46
- _ = proxy.getCookies()
47
- }
48
- })
49
- logger.Logger.Info(fmt.Sprint("Init ProxyPool Success"))
50
- })
51
- return Instance
52
- }
53
-
54
- func NewProxyPool(proxies []*Proxy) *ProxyPool {
55
- proxy := NewProxy("", common.GetTimestampSecond(0), constant.Ua)
56
- _ = proxy.getCookies()
57
- return &ProxyPool{
58
- Proxies: append([]*Proxy{proxy}, proxies...),
59
- Index: 0,
60
- }
61
- }
62
-
63
- func (PP *ProxyPool) GetProxy() *Proxy {
64
- PP.Index = (PP.Index + 1) % len(PP.Proxies)
65
- // 如果配置了代理 不会使用无代理
66
- if PP.Index == 0 && len(PP.Proxies) > 1 {
67
- PP.Index = 1
68
- }
69
- // 返回代理
70
- return PP.Proxies[PP.Index]
71
- }
72
-
73
- func (PP *ProxyPool) AddProxy(proxy *Proxy) {
74
- PP.Proxies = append(PP.Proxies, proxy)
75
- }
76
-
77
- func NewProxy(link string, cannotUseTime int64, ua string) *Proxy {
78
- return &Proxy{
79
- Link: common.ParseUrl(link),
80
- CanUseAt: cannotUseTime,
81
- Ua: ua,
82
- }
83
- }
84
-
85
- func (P *Proxy) getCookies() error {
86
- return nil
87
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -1,94 +1,529 @@
1
- # [free-gpt3.5-2api](https://github.com/aurorax-neo/free-gpt3.5-2api)
2
 
3
- ## 接口
 
 
 
4
 
5
- #### /v1/tokens
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  ```
8
- curl --location --request GET 'http://127.0.0.1:9846/v1/tokens' \
9
- --header 'Authorization: Bearer abc'
 
 
 
10
  ```
11
 
12
- 返回示例说明:`count`为授权池中可用授权数,如果`count` 为 `0`请检查`ip`是否支持 `openai`
13
 
 
 
14
  ```
15
- {
16
- "count": 0
17
- }
 
 
18
  ```
19
 
20
- #### /v1/chat/completions
21
 
22
- ###### 支持返回stream和json
 
23
 
 
 
 
 
 
 
 
 
 
24
  ```
25
- http://<ip>:<port>/v1/chat/completions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  ```
27
 
28
- ##### 示例
 
 
 
 
29
 
 
 
 
 
30
  ```
31
- curl http://127.0.0.1:9846
 
 
 
 
32
  ```
33
 
 
 
 
 
34
  ```
35
- curl --location --request POST 'http://127.0.0.1:9846/v1/chat/completions' \
36
- --header 'Authorization: Bearer abc' \
37
- --header 'Content-Type: application/json' \
38
- --data-raw '{
39
- "model": "gpt-3.5-turbo",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  "messages": [
41
  {
42
  "role": "user",
43
- "content": "西红柿炒钢丝球怎么做?"
44
  }
45
  ],
 
46
  "stream": false
47
- }'
48
  ```
49
 
50
- ## 配置
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
- ### 环境变量
 
 
53
 
54
  ```
55
- LOG_LEVEL=info # debug, info, warn, error
56
- LOG_PATH= # 日志文件路径,默认为空(不生成日志文件)
57
- BIND=0.0.0.0 # 127.0.0.1
58
- PORT=3040
59
- PROXY= # http://127.0.0.1:7890,http://127.0.0.1:7890 已支持多个代理(英文 "," 分隔)
60
- AUTHORIZATIONS= # abc,bac (英文 "," 分隔)
61
- BASE_URL= # 默认:https://chat.openai.com
62
- POOL_MAX_COUNT=64 # max number of connections to keep in the pool 默认:64
63
- AUTH_ED=600 # expiration time for the authorization in seconds 默认:600
64
  ```
65
 
66
- ###### 也可使用与程序同目录下 `.env` 文件配置上述字段
 
 
 
 
 
 
 
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
- ### docker部署
70
 
71
- ##### 1 .创建文件夹
 
 
 
 
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  ```
74
- mkdir -p $PWD/free-gpt3.5-2api
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  ```
76
 
77
- ##### 2.拉取镜像启动
 
 
 
 
 
 
 
 
78
 
79
  ```
80
- docker run -itd --name=free-gpt3.5-2api -p 9846:3040 ghcr.io/aurorax-neo/free-gpt3.5-2api
81
  ```
82
 
83
- ##### 3.更新容器
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  ```
86
- docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR free-gpt3.5-2api --debug
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  ```
88
 
89
- ### Koyeb部署
90
 
91
- ###### 注意:`Regions`请选择支持`openai`免登的区域!!!
92
 
93
- [![Deploy to Koyeb](https://www.koyeb.com/static/images/deploy/button.svg)](https://app.koyeb.com/deploy?type=docker&name=free-gpt3-5-2api&region=par&ports=3040;http;/&image=ghcr.io/aurorax-neo/free-gpt3.5-2api)
94
 
 
 
1
+ # GLM AI Free 服务
2
 
3
+ [![](https://img.shields.io/github/license/llm-red-team/glm-free-api.svg)](LICENSE)
4
+ ![](https://img.shields.io/github/stars/llm-red-team/glm-free-api.svg)
5
+ ![](https://img.shields.io/github/forks/llm-red-team/glm-free-api.svg)
6
+ ![](https://img.shields.io/docker/pulls/vinlic/glm-free-api.svg)
7
 
8
+ 支持高速流式输出、支持多轮对话、支持智能体对话、支持AI绘图、支持联网搜索、支持长文档解读、支持图像解析,零配置部署,多路token支持,自动清理会话痕迹。
9
 
10
+ 与ChatGPT接口完全兼容。
11
+
12
+ 还有以下七个free-api欢迎关注:
13
+
14
+ Moonshot AI(Kimi.ai)接口转API [kimi-free-api](https://github.com/LLM-Red-Team/kimi-free-api)
15
+
16
+ 阶跃星辰 (跃问StepChat) 接口转API [step-free-api](https://github.com/LLM-Red-Team/step-free-api)
17
+
18
+ 阿里通义 (Qwen) 接口转API [qwen-free-api](https://github.com/LLM-Red-Team/qwen-free-api)
19
+
20
+ 秘塔AI (Metaso) 接口转API [metaso-free-api](https://github.com/LLM-Red-Team/metaso-free-api)
21
+
22
+ 讯飞星火(Spark)接口转API [spark-free-api](https://github.com/LLM-Red-Team/spark-free-api)
23
+
24
+ MiniMax(海螺AI)接口转API [hailuo-free-api](https://github.com/LLM-Red-Team/hailuo-free-api)
25
+
26
+ 聆心智能 (Emohaa) 接口转API [emohaa-free-api](https://github.com/LLM-Red-Team/emohaa-free-api)
27
+
28
+ ## 目录
29
+
30
+ * [免责声明](#免责声明)
31
+ * [在线体验](#在线体验)
32
+ * [效果示例](#效果示例)
33
+ * [接入准备](#接入准备)
34
+ * [智能体接入](#智能体接入)
35
+ * [多账号接入](#多账号接入)
36
+ * [Docker部署](#Docker部署)
37
+ * [Docker-compose部署](#Docker-compose部署)
38
+ * [Render部署](#Render部署)
39
+ * [Vercel部署](#Vercel部署)
40
+ * [原生部署](#原生部署)
41
+ * [推荐使用客户端](#推荐使用客户端)
42
+ * [接口列表](#接口列表)
43
+ * [对话补全](#对话补全)
44
+ * [AI绘图](#AI绘图)
45
+ * [文档解读](#文档解读)
46
+ * [图像解析](#图像解析)
47
+ * [refresh_token存活检测](#refresh_token存活检测)
48
+ * [注意事项](#注意事项)
49
+ * [Nginx反代优化](#Nginx反代优化)
50
+ * [Token统计](#Token统计)
51
+ * [Star History](#star-history)
52
+
53
+ ## 免责声明
54
+
55
+ **逆向API是不稳定的,建议前往智谱AI官方 https://open.bigmodel.cn/ 付费使用API,避免封禁的风险。**
56
+
57
+ **本组织和个人不接受任何资金捐助和交易,此项目是纯粹研究交流学习性质!**
58
+
59
+ **仅限自用,禁止对外提供服务或商用,避免对官方造成服务压力,否则风险自担!**
60
+
61
+ **仅限自用,禁止对外提供服务或商用,避免对官方造成服务压力,否则风险自担!**
62
+
63
+ **仅限自用,禁止对外提供服务或商用,避免对官方造成服务压力,否则风险自担!**
64
+
65
+ ## 在线体验
66
+
67
+ 此链接仅临时测试功能,只有一路并发,如果遇到异常请稍后重试,建议自行部署使用。
68
+
69
+ https://udify.app/chat/Pe89TtaX3rKXM8NS
70
+
71
+ ## 效果示例
72
+
73
+ ### 验明正身Demo
74
+
75
+ ![验明正身](./doc/example-1.png)
76
+
77
+ ### 智能体对话Demo
78
+
79
+ 对应智能体链接:[网抑云评论生成器](https://chatglm.cn/main/gdetail/65c046a531d3fcb034918abe)
80
+
81
+ ![智能体对话](./doc/example-9.png)
82
+
83
+ ### 结合Dify工作流Demo
84
+
85
+ 体验地址:https://udify.app/chat/m46YgeVLNzFh4zRs
86
+
87
+ <img width="390" alt="image" src="https://github.com/LLM-Red-Team/glm-free-api/assets/20235341/4773b9f6-b1ca-460c-b3a7-c56bdb1f0659">
88
+
89
+ ### 多轮对话Demo
90
+
91
+ ![多轮对话](./doc/example-6.png)
92
+
93
+ ### AI绘图Demo
94
+
95
+ ![AI绘图](./doc/example-10.png)
96
+
97
+ ### 联网搜索Demo
98
+
99
+ ![联网搜索](./doc/example-2.png)
100
+
101
+ ### 长文档解读Demo
102
+
103
+ ![长文档解读](./doc/example-5.png)
104
+
105
+ ### 代码调用Demo
106
+
107
+ ![代码调用](./doc/example-12.png)
108
+
109
+ ### 图像解析Demo
110
+
111
+ ![图像解析](./doc/example-3.png)
112
+
113
+ ## 接入准备
114
+
115
+ 从 [智谱清言](https://chatglm.cn/) 获取refresh_token
116
+
117
+ 进入智谱清言随便发起一个对话,然后F12打开开发者工具,从Application > Cookies中找到`chatglm_refresh_token`的值,这将作为Authorization的Bearer Token值:`Authorization: Bearer TOKEN`
118
+
119
+ ![example0](./doc/example-0.png)
120
+
121
+ ### 智能体接入
122
+
123
+ 打开智能体的聊天界面,地址栏的一串ID就是智能体的ID,复制下来备用,这个值将用作调用时的 `model` 参数值。
124
+
125
+ ![example11](./doc/example-11.png)
126
+
127
+ ### 多账号接入
128
+
129
+ 目前似乎限制同个账号同时只能有*一路*输出,你可以通过提供多个账号的chatglm_refresh_token并使用`,`拼接提供:
130
+
131
+ `Authorization: Bearer TOKEN1,TOKEN2,TOKEN3`
132
+
133
+ 每次请求服务会从中挑选一个。
134
+
135
+ ## Docker部署
136
+
137
+ 请准备一台具有公网IP的服务器并将8000端口开放。
138
+
139
+ 拉取镜像并启动服务
140
+
141
+ ```shell
142
+ docker run -it -d --init --name glm-free-api -p 8000:8000 -e TZ=Asia/Shanghai vinlic/glm-free-api:latest
143
  ```
144
+
145
+ 查看服务实时日志
146
+
147
+ ```shell
148
+ docker logs -f glm-free-api
149
  ```
150
 
151
+ 重启服务
152
 
153
+ ```shell
154
+ docker restart glm-free-api
155
  ```
156
+
157
+ 停止服务
158
+
159
+ ```shell
160
+ docker stop glm-free-api
161
  ```
162
 
163
+ ### Docker-compose部署
164
 
165
+ ```yaml
166
+ version: '3'
167
 
168
+ services:
169
+ glm-free-api:
170
+ container_name: glm-free-api
171
+ image: vinlic/glm-free-api:latest
172
+ restart: always
173
+ ports:
174
+ - "8000:8000"
175
+ environment:
176
+ - TZ=Asia/Shanghai
177
  ```
178
+
179
+ ### Render部署
180
+
181
+ **注意:部分部署区域可能无法连接glm,如容器日志出现请求超时或无法连接,请切换其他区域部署!**
182
+ **注意:免费账户的容器实例将在一段时间不活动时自动停止运行,这会导致下次请求时遇到50秒或更长的延迟,建议查看[Render容器保活](https://github.com/LLM-Red-Team/free-api-hub/#Render%E5%AE%B9%E5%99%A8%E4%BF%9D%E6%B4%BB)**
183
+
184
+ 1. fork本项目到你的github账号下。
185
+
186
+ 2. 访问 [Render](https://dashboard.render.com/) 并登录你的github账号。
187
+
188
+ 3. 构建你的 Web Service(New+ -> Build and deploy from a Git repository -> Connect你fork的项目 -> 选择部署区域 -> 选择实例类型为Free -> Create Web Service)。
189
+
190
+ 4. 等待构建完成后,复制分配的域名并拼接URL访问即可。
191
+
192
+ ### Vercel部署
193
+
194
+ **注意:Vercel免费账户的请求响应超时时间为10秒,但接口响应通常较久,可能会遇到Vercel返回的504超时错误!**
195
+
196
+ 请先确保安装了Node.js环境。
197
+
198
+ ```shell
199
+ npm i -g vercel --registry http://registry.npmmirror.com
200
+ vercel login
201
+ git clone https://github.com/LLM-Red-Team/glm-free-api
202
+ cd glm-free-api
203
+ vercel --prod
204
  ```
205
 
206
+ ## 原生部署
207
+
208
+ 请准备一台具有公网IP的服务器并将8000端口开放。
209
+
210
+ 请先安装好Node.js环境并且配置好环境变量,确认node命令可用。
211
 
212
+ 安装依赖
213
+
214
+ ```shell
215
+ npm i
216
  ```
217
+
218
+ 安装PM2进行进程守护
219
+
220
+ ```shell
221
+ npm i -g pm2
222
  ```
223
 
224
+ 编译构建,看到dist目录就是构建完成
225
+
226
+ ```shell
227
+ npm run build
228
  ```
229
+
230
+ 启动服务
231
+
232
+ ```shell
233
+ pm2 start dist/index.js --name "glm-free-api"
234
+ ```
235
+
236
+ 查看服务实时日志
237
+
238
+ ```shell
239
+ pm2 logs glm-free-api
240
+ ```
241
+
242
+ 重启服务
243
+
244
+ ```shell
245
+ pm2 reload glm-free-api
246
+ ```
247
+
248
+ 停止服务
249
+
250
+ ```shell
251
+ pm2 stop glm-free-api
252
+ ```
253
+
254
+ ## 推荐使用客户端
255
+
256
+ 使用以下二次开发客户端接入free-api系列项目更快更简单,支持文档/图像上传!
257
+
258
+ 由 [Clivia](https://github.com/Yanyutin753/lobe-chat) 二次开发的LobeChat [https://github.com/Yanyutin753/lobe-chat](https://github.com/Yanyutin753/lobe-chat)
259
+
260
+ 由 [时光@](https://github.com/SuYxh) 二次开发的ChatGPT Web [https://github.com/SuYxh/chatgpt-web-sea](https://github.com/SuYxh/chatgpt-web-sea)
261
+
262
+ ## 接口列表
263
+
264
+ 目前支持与openai兼容的 `/v1/chat/completions` 接口,可自行使用与openai或其他兼容的客户端接入接口,或者使用 [dify](https://dify.ai/) 等线上服务接入使用。
265
+
266
+ ### 对话补全
267
+
268
+ 对话补全接口,与openai的 [chat-completions-api](https://platform.openai.com/docs/guides/text-generation/chat-completions-api) 兼容。
269
+
270
+ **POST /v1/chat/completions**
271
+
272
+ header 需要设置 Authorization 头部:
273
+
274
+ ```
275
+ Authorization: Bearer [refresh_token]
276
+ ```
277
+
278
+ 请求数据:
279
+ ```json
280
+ {
281
+ // 如果使用智能体请填写智能体ID到此处,否则可以乱填
282
+ "model": "glm4",
283
+ // 目前多轮对话基于消息合并实现,某些场景可能导致能力下降且受单轮最大token数限制
284
+ // 如果您想获得原生的多轮对话体验,可以传入首轮消息获得的id,来接续上下文
285
+ // "conversation_id": "65f6c28546bae1f0fbb532de",
286
  "messages": [
287
  {
288
  "role": "user",
289
+ "content": "你叫什么?"
290
  }
291
  ],
292
+ // 如果使用SSE流请设置为true,默认false
293
  "stream": false
294
+ }
295
  ```
296
 
297
+ 响应数据:
298
+ ```json
299
+ {
300
+ // 如果想获得原生多轮对话体验,此id,你可以传入到下一轮对话的conversation_id来接续上下文
301
+ "id": "65f6c28546bae1f0fbb532de",
302
+ "model": "glm4",
303
+ "object": "chat.completion",
304
+ "choices": [
305
+ {
306
+ "index": 0,
307
+ "message": {
308
+ "role": "assistant",
309
+ "content": "我叫智谱清言,是基于智谱 AI 公司于 2023 年训练的 ChatGLM 开发的。我的任务是针对用户的问题和要求提供适当的答复和支持。"
310
+ },
311
+ "finish_reason": "stop"
312
+ }
313
+ ],
314
+ "usage": {
315
+ "prompt_tokens": 1,
316
+ "completion_tokens": 1,
317
+ "total_tokens": 2
318
+ },
319
+ "created": 1710152062
320
+ }
321
+ ```
322
+
323
+ ### AI绘图
324
+
325
+ 对话补全接口,与openai的 [images-create-api](https://platform.openai.com/docs/api-reference/images/create) 兼容。
326
 
327
+ **POST /v1/images/generations**
328
+
329
+ header 需要设置 Authorization 头部:
330
 
331
  ```
332
+ Authorization: Bearer [refresh_token]
 
 
 
 
 
 
 
 
333
  ```
334
 
335
+ 请求数据:
336
+ ```json
337
+ {
338
+ // 如果使用智能体请填写智能体ID到此处,否则可以乱填
339
+ "model": "cogview-3",
340
+ "prompt": "一只可爱的猫"
341
+ }
342
+ ```
343
 
344
+ 响应数据:
345
+ ```json
346
+ {
347
+ "created": 1711507449,
348
+ "data": [
349
+ {
350
+ "url": "https://sfile.chatglm.cn/testpath/5e56234b-34ae-593c-ba4e-3f7ba77b5768_0.png"
351
+ }
352
+ ]
353
+ }
354
+ ```
355
+
356
+ ### 文档解读
357
+
358
+ 提供一个可访问的文件URL或者BASE64_URL进行解析。
359
 
360
+ **POST /v1/chat/completions**
361
 
362
+ header 需要设置 Authorization 头部:
363
+
364
+ ```
365
+ Authorization: Bearer [refresh_token]
366
+ ```
367
 
368
+ 请求数据:
369
+ ```json
370
+ {
371
+ // 如果使用智能体请填写智能体ID到此处,否则可以乱填
372
+ "model": "glm4",
373
+ "messages": [
374
+ {
375
+ "role": "user",
376
+ "content": [
377
+ {
378
+ "type": "file",
379
+ "file_url": {
380
+ "url": "https://mj101-1317487292.cos.ap-shanghai.myqcloud.com/ai/test.pdf"
381
+ }
382
+ },
383
+ {
384
+ "type": "text",
385
+ "text": "文档里说了什么?"
386
+ }
387
+ ]
388
+ }
389
+ ],
390
+ // 如果使用SSE流请设置为true,默认false
391
+ "stream": false
392
+ }
393
  ```
394
+
395
+ 响应数据:
396
+ ```json
397
+ {
398
+ "id": "cnmuo7mcp7f9hjcmihn0",
399
+ "model": "glm4",
400
+ "object": "chat.completion",
401
+ "choices": [
402
+ {
403
+ "index": 0,
404
+ "message": {
405
+ "role": "assistant",
406
+ "content": "根据文档内容,我总结如下:\n\n这是一份关于希腊罗马时期的魔法咒语和仪式的文本,包含几个魔法仪式:\n\n1. 一个涉及面包、仪式场所和特定咒语的仪式,用于使某人爱上你。\n\n2. 一个针对女神赫卡忒的召唤仪式,用来折磨某人直到她自愿来到你身边。\n\n3. 一个通过念诵爱神阿芙罗狄蒂的秘密名字,连续七天进行仪式,来赢得一个美丽女子的心。\n\n4. 一个通过燃烧没药并念诵咒语,让一个女子对你产生强烈欲望的仪式。\n\n这些仪式都带有魔法和迷信色彩,使用各种咒语和象征性行为来影响人的感情和意愿。"
407
+ },
408
+ "finish_reason": "stop"
409
+ }
410
+ ],
411
+ "usage": {
412
+ "prompt_tokens": 1,
413
+ "completion_tokens": 1,
414
+ "total_tokens": 2
415
+ },
416
+ "created": 100920
417
+ }
418
  ```
419
 
420
+ ### 图像解析
421
+
422
+ 提供一个可访问的图像URL或者BASE64_URL进行解析。
423
+
424
+ 此格式兼容 [gpt-4-vision-preview](https://platform.openai.com/docs/guides/vision) API格式,您也可以用这个格式传送文档进行解析。
425
+
426
+ **POST /v1/chat/completions**
427
+
428
+ header 需要设置 Authorization 头部:
429
 
430
  ```
431
+ Authorization: Bearer [refresh_token]
432
  ```
433
 
434
+ 请求数据:
435
+ ```json
436
+ {
437
+ "model": "65c046a531d3fcb034918abe",
438
+ "messages": [
439
+ {
440
+ "role": "user",
441
+ "content": [
442
+ {
443
+ "type": "image_url",
444
+ "image_url": {
445
+ "url": "http://1255881664.vod2.myqcloud.com/6a0cd388vodbj1255881664/7b97ce1d3270835009240537095/uSfDwh6ZpB0A.png"
446
+ }
447
+ },
448
+ {
449
+ "type": "text",
450
+ "text": "图像描述了什么?"
451
+ }
452
+ ]
453
+ }
454
+ ],
455
+ "stream": false
456
+ }
457
+ ```
458
 
459
+ 响应数据:
460
+ ```json
461
+ {
462
+ "id": "65f6c28546bae1f0fbb532de",
463
+ "model": "glm",
464
+ "object": "chat.completion",
465
+ "choices": [
466
+ {
467
+ "index": 0,
468
+ "message": {
469
+ "role": "assistant",
470
+ "content": "图片中展示的是一个蓝色背景下的logo,具体地,左边是一个由多个蓝色的圆点组成的圆形图案,右边是“智谱·AI”四个字,字体颜色为蓝色。"
471
+ },
472
+ "finish_reason": "stop"
473
+ }
474
+ ],
475
+ "usage": {
476
+ "prompt_tokens": 1,
477
+ "completion_tokens": 1,
478
+ "total_tokens": 2
479
+ },
480
+ "created": 1710670469
481
+ }
482
  ```
483
+
484
+ ### refresh_token存活检测
485
+
486
+ 检测refresh_token是否存活,如果存活live未true,否则为false,请不要频繁(小于10分钟)调用此接口。
487
+
488
+ **POST /token/check**
489
+
490
+ 请求数据:
491
+ ```json
492
+ {
493
+ "token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9..."
494
+ }
495
+ ```
496
+
497
+ 响应数据:
498
+ ```json
499
+ {
500
+ "live": true
501
+ }
502
+ ```
503
+
504
+ ## 注意事项
505
+
506
+ ### Nginx反代优化
507
+
508
+ 如果您正在使用Nginx反向代理glm-free-api,请添加以下配置项优化流的输出效果,优化体验感。
509
+
510
+ ```nginx
511
+ # 关闭代理缓冲。当设置为off时,Nginx会立即将客户端请求发送到后端服务器,并立即将从后端服务器接收到的响应发送回客户端。
512
+ proxy_buffering off;
513
+ # 启用分块传输编码。分块传输编码允许服务器为动态生成的内容分块发送数据,而不需要预先知道内容的大小。
514
+ chunked_transfer_encoding on;
515
+ # 开启TCP_NOPUSH,这告诉Nginx在数据包发送到客户端之前,尽可能地发送数据。这通常在sendfile使用时配合使用,可以提高网络效率。
516
+ tcp_nopush on;
517
+ # 开启TCP_NODELAY,这告诉Nginx不延迟发送数据,立即发送小数据包。在某些情况下,这可以减少网络的延迟。
518
+ tcp_nodelay on;
519
+ # 设置保持连接的超时时间,这里设置为120秒。如果在这段时间内,客户端和服务器之间没有进一步的通信,连接将被关闭。
520
+ keepalive_timeout 120;
521
  ```
522
 
523
+ ### Token统计
524
 
525
+ 由于推理侧不在glm-free-api,因此token不可统计,将以固定数字返回。
526
 
527
+ ## Star History
528
 
529
+ [![Star History Chart](https://api.star-history.com/svg?repos=LLM-Red-Team/glm-free-api&type=Date)](https://star-history.com/#LLM-Red-Team/glm-free-api&Date)
RequestClient/RequestClient.go DELETED
@@ -1,10 +0,0 @@
1
- package RequestClient
2
-
3
- import (
4
- fhttp "github.com/bogdanfinn/fhttp"
5
- )
6
-
7
- type RequestClient interface {
8
- Do(req *fhttp.Request) (*fhttp.Response, error)
9
- SetProxy(link string) error
10
- }
 
 
 
 
 
 
 
 
 
 
 
RequestClient/TlsClient.go DELETED
@@ -1,77 +0,0 @@
1
- package RequestClient
2
-
3
- import (
4
- fhttp "github.com/bogdanfinn/fhttp"
5
- tlsClient "github.com/bogdanfinn/tls-client"
6
- "github.com/bogdanfinn/tls-client/profiles"
7
- "io"
8
- "math/rand"
9
- "time"
10
- )
11
-
12
- type TlsClient struct {
13
- client tlsClient.HttpClient
14
- }
15
-
16
- func NewTlsClient(timeoutSeconds int, clientProfile profiles.ClientProfile) *TlsClient {
17
- jar := tlsClient.NewCookieJar()
18
- options := []tlsClient.HttpClientOption{
19
- tlsClient.WithTimeoutSeconds(timeoutSeconds),
20
- tlsClient.WithClientProfile(clientProfile),
21
- tlsClient.WithNotFollowRedirects(),
22
- tlsClient.WithCookieJar(jar),
23
- }
24
- client, err := tlsClient.NewHttpClient(tlsClient.NewNoopLogger(), options...)
25
- if err != nil {
26
- return nil
27
- }
28
- return &TlsClient{
29
- client: client,
30
- }
31
- }
32
-
33
- func RandomClientProfile() profiles.ClientProfile {
34
- // 初始化随机数生成器
35
- seed := time.Now().UnixNano()
36
- rng := rand.New(rand.NewSource(seed))
37
- clientProfiles := []profiles.ClientProfile{
38
- profiles.Firefox_102,
39
- profiles.Safari_15_6_1,
40
- profiles.Safari_16_0,
41
- profiles.Chrome_110,
42
- profiles.Okhttp4Android13,
43
- profiles.CloudflareCustom,
44
- profiles.Firefox_117,
45
- }
46
- // 随机选择一个
47
- randomIndex := rng.Intn(len(clientProfiles))
48
- return clientProfiles[randomIndex]
49
- }
50
-
51
- func NewRequest(method, url string, body io.Reader) (*fhttp.Request, error) {
52
- request, err := fhttp.NewRequest(method, url, body)
53
- if err != nil {
54
- return nil, err
55
- }
56
- return request, nil
57
-
58
- }
59
-
60
- func (T *TlsClient) Do(req *fhttp.Request) (*fhttp.Response, error) {
61
- response, err := T.client.Do(req)
62
- if err != nil {
63
- return nil, err
64
- }
65
- return response, nil
66
- }
67
-
68
- func (T *TlsClient) SetProxy(link string) error {
69
- if link == "" {
70
- return nil
71
- }
72
- err := T.client.SetProxy(link)
73
- if err != nil {
74
- return err
75
- }
76
- return nil
77
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
common/common.go DELETED
@@ -1,285 +0,0 @@
1
- package common
2
-
3
- import (
4
- "bytes"
5
- "encoding/json"
6
- "fmt"
7
- fhttp "github.com/bogdanfinn/fhttp"
8
- "github.com/bogdanfinn/fhttp/httputil"
9
- "github.com/gin-gonic/gin"
10
- jsoniter "github.com/json-iterator/go"
11
- "math/rand"
12
- "net/url"
13
- "os"
14
- "path/filepath"
15
- "reflect"
16
- "strings"
17
- "time"
18
- )
19
-
20
- func ErrorResponse(c *gin.Context, code int, msg interface{}, err interface{}) {
21
- c.AbortWithStatusJSON(code, gin.H{
22
- "detail": struct {
23
- Code int `json:"code"`
24
- Msg interface{} `json:"msg"`
25
- Error interface{} `json:"error"`
26
- }{
27
- Code: code,
28
- Msg: msg,
29
- Error: err,
30
- },
31
- })
32
- return
33
- }
34
-
35
- // GetTimestampSecond 获取当前时间戳 + 指定 秒
36
- func GetTimestampSecond(second int) int64 {
37
- return time.Now().Add(time.Second * time.Duration(second)).Unix()
38
- }
39
-
40
- func ParseUrl(link string) *url.URL {
41
- if link == "" {
42
- return &url.URL{}
43
- }
44
- u, err := url.Parse(link)
45
- if err != nil {
46
- return &url.URL{}
47
- }
48
- return u
49
- }
50
-
51
- func GetOrigin(link string) string {
52
- u := ParseUrl(link)
53
- if u == nil {
54
- return ""
55
- }
56
- return u.Scheme + "://" + u.Host
57
- }
58
-
59
- func Struct2BytesBuffer(v interface{}) (*bytes.Buffer, error) {
60
- data := new(bytes.Buffer)
61
- err := json.NewEncoder(data).Encode(v)
62
- if err != nil {
63
- return nil, err
64
- }
65
- return data, nil
66
- }
67
-
68
- func Struct2Bytes(v interface{}) ([]byte, error) {
69
- // 创建一个jsonIter的Encoder
70
- configCompatibleWithStandardLibrary := jsoniter.ConfigCompatibleWithStandardLibrary
71
- // 将结构体转换为JSON文本并保持顺序
72
- bytes_, err := configCompatibleWithStandardLibrary.Marshal(v)
73
- if err != nil {
74
- return nil, err
75
- }
76
- return bytes_, nil
77
- }
78
-
79
- func SplitAndAddBearer(authTokens string) []string {
80
- var authTokenList []string
81
- for _, v := range strings.Split(authTokens, ",") {
82
- authTokenList = append(authTokenList, "Bearer "+v)
83
- }
84
- return authTokenList
85
- }
86
-
87
- func GetRand() rand.Rand {
88
- // 初始化随机数生成器
89
- seed := time.Now().UnixNano()
90
- rng := rand.New(rand.NewSource(seed))
91
- return *rng
92
- }
93
-
94
- func RandomLanguage() string {
95
- // 初始化随机数生成器
96
- rng := GetRand()
97
- // 语言列表
98
- languages := []string{"af", "am", "ar-sa", "as", "az-Latn", "be", "bg", "bn-BD", "bn-IN", "bs", "ca", "ca-ES-valencia", "cs", "cy", "da", "de", "de-de", "el", "en-GB", "en-US", "es", "es-ES", "es-US", "es-MX", "et", "eu", "fa", "fi", "fil-Latn", "fr", "fr-FR", "fr-CA", "ga", "gd-Latn", "gl", "gu", "ha-Latn", "he", "hi", "hr", "hu", "hy", "id", "ig-Latn", "is", "it", "it-it", "ja", "ka", "kk", "km", "kn", "ko", "kok", "ku-Arab", "ky-Cyrl", "lb", "lt", "lv", "mi-Latn", "mk", "ml", "mn-Cyrl", "mr", "ms", "mt", "nb", "ne", "nl", "nl-BE", "nn", "nso", "or", "pa", "pa-Arab", "pl", "prs-Arab", "pt-BR", "pt-PT", "qut-Latn", "quz", "ro", "ru", "rw", "sd-Arab", "si", "sk", "sl", "sq", "sr-Cyrl-BA", "sr-Cyrl-RS", "sr-Latn-RS", "sv", "sw", "ta", "te", "tg-Cyrl", "th", "ti", "tk-Latn", "tn", "tr", "tt-Cyrl", "ug-Arab", "uk", "ur", "uz-Latn", "vi", "wo", "xh", "yo-Latn", "zh-Hans", "zh-Hant", "zu"}
99
- // 随机选择一个语言
100
- randomIndex := rng.Intn(len(languages))
101
- return languages[randomIndex]
102
- }
103
-
104
- // GetAbsPathAndGenerate 获取绝对路径并生成文件或文件夹
105
- func GetAbsPathAndGenerate(path string, isFilePath bool, content string) string {
106
- // 获取绝对路径
107
- path = GetAbsPath(path)
108
- if isFilePath {
109
- // 判断文件是否存在
110
- if isExist := fileIsExistAndCreat(path, content); isExist {
111
- return path
112
- }
113
- } else {
114
- // 判断文件夹是否存在
115
- if isExist := dirIsExistAndMkdir(path, false); isExist {
116
- return path
117
- }
118
- }
119
- return path
120
- }
121
-
122
- // GetAbsPath 获取绝对路径
123
- func GetAbsPath(path string) string {
124
- if !filepath.IsAbs(path) {
125
- absPath, err := filepath.Abs(path)
126
- if err != nil {
127
- return ""
128
- }
129
- return absPath
130
- }
131
- return path
132
- }
133
-
134
- func dirIsExistAndMkdir(dirPath string, isFile bool) bool {
135
- // 判断路径是否存在
136
- _, err := os.Stat(dirPath)
137
- dir := dirPath
138
- if err != nil {
139
- if isFile {
140
- dir = filepath.Dir(dirPath)
141
- }
142
- // 创建路径
143
- err := os.MkdirAll(dir, os.ModePerm)
144
- if err != nil {
145
- return false
146
- }
147
- }
148
- return true
149
- }
150
-
151
- func fileIsExistAndCreat(filePath string, content string) bool {
152
- //判断文件是否存在
153
- _, err := os.Stat(filePath)
154
- if err != nil {
155
- // 判断文件夹是否存在
156
- if isExist := dirIsExistAndMkdir(filePath, true); !isExist {
157
- return false
158
- }
159
- // 创建文件
160
- _, err := os.Create(filePath)
161
- if err != nil {
162
- return false
163
- }
164
- if content != "" {
165
- // 写入content
166
- file, _ := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777)
167
- _, _ = file.Write([]byte(content))
168
- defer func(file *os.File) {
169
- _ = file.Close()
170
- }(file)
171
- }
172
- }
173
- return true
174
- }
175
-
176
- // AsyncTimingTask 定时任务 参数含函数
177
- func AsyncTimingTask(nanosecond time.Duration, fun func()) {
178
- go func() {
179
- timerChan := time.After(nanosecond)
180
- // 使用for循环阻塞等待定时器的信号
181
- for {
182
- // 通过select语句监听定时器通道和其他事件
183
- select {
184
- case <-timerChan:
185
- fun()
186
- // 重新设置定时器,以便下一次执行
187
- timerChan = time.After(nanosecond)
188
- }
189
- time.Sleep(time.Millisecond * 100)
190
- }
191
- }()
192
- }
193
-
194
- // AsyncLoopTask AsyncTimingTask 定时任务 参数含函数
195
- func AsyncLoopTask(sleep time.Duration, fun func()) {
196
- go func() {
197
- for {
198
- fun()
199
- time.Sleep(sleep)
200
- }
201
- }()
202
- }
203
-
204
- // DeepCopyStruct 深拷贝函数
205
- func DeepCopyStruct(src interface{}) interface{} {
206
- // 获取源对象的类型信息
207
- srcType := reflect.TypeOf(src)
208
- // 创建目标对象
209
- dst := reflect.New(srcType).Elem()
210
-
211
- // 深拷贝过程
212
- deepCopyValue(reflect.ValueOf(src), dst)
213
-
214
- return dst.Interface()
215
- }
216
-
217
- // 递归进行深拷贝
218
- func deepCopyValue(src, dst reflect.Value) {
219
- switch src.Kind() {
220
- case reflect.Ptr:
221
- if src.IsNil() {
222
- dst.Set(src)
223
- return
224
- }
225
- // 递归处理指针指向的内容
226
- newDst := reflect.New(src.Elem().Type())
227
- deepCopyValue(src.Elem(), newDst.Elem())
228
- dst.Set(newDst)
229
- case reflect.Struct:
230
- for i := 0; i < src.NumField(); i++ {
231
- // 递归处理结构体的字段
232
- deepCopyValue(src.Field(i), dst.Field(i))
233
- }
234
- default:
235
- // 检查目标值是否支持设置
236
- if dst.CanSet() {
237
- // 处理基本类型和数组、切片、映射等
238
- dst.Set(src)
239
- }
240
- }
241
- }
242
-
243
- func RandomHexadecimalString() string {
244
- rng := GetRand()
245
- const charset = "0123456789abcdef"
246
- const length = 16 // The length of the string you want to generate
247
- b := make([]byte, length)
248
- for i := range b {
249
- b[i] = charset[rng.Intn(len(charset))]
250
- }
251
- return string(b)
252
- }
253
-
254
- // OutRequest 打印请求.
255
- func OutRequest(req *fhttp.Request) {
256
- dump, err := httputil.DumpRequestOut(req, true)
257
- if err != nil {
258
- fmt.Println("Error dumping request:", err)
259
- } else {
260
- fmt.Println(string(dump))
261
- }
262
- }
263
-
264
- // OutResponse 打印响应.
265
- func OutResponse(res *fhttp.Response) {
266
- dump, err := httputil.DumpResponse(res, true)
267
- if err != nil {
268
- fmt.Println("Error dumping response:", err)
269
- } else {
270
- fmt.Println(string(dump))
271
- }
272
- }
273
-
274
- func IsStrInArray(str string, strS []string) bool {
275
- // 如果 strS 为空,直接返回 true
276
- if len(strS) == 0 {
277
- return true
278
- }
279
- for _, v := range strS {
280
- if v == str {
281
- return true
282
- }
283
- }
284
- return false
285
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
config/config.go DELETED
@@ -1,74 +0,0 @@
1
- package config
2
-
3
- import (
4
- "free-gpt3.5-2api/common"
5
- "github.com/joho/godotenv"
6
- "os"
7
- "strconv"
8
- "strings"
9
- )
10
-
11
- var (
12
- Bind string
13
- Port string
14
- Proxy []string
15
- AUTHORIZATIONS []string
16
- BaseUrl string
17
- PoolMaxCount int
18
- AuthED int
19
- )
20
-
21
- func init() {
22
- _ = godotenv.Load()
23
- // Bind
24
- Bind = os.Getenv("BIND")
25
- if Bind == "" {
26
- Bind = "0.0.0.0"
27
- }
28
- // PORT
29
- Port = os.Getenv("PORT")
30
- if Port == "" {
31
- Port = "3040"
32
- }
33
- // PROXY
34
- proxy := os.Getenv("PROXY")
35
- if proxy != "" {
36
- Proxy = strings.Split(proxy, ",")
37
- }
38
- // AUTH_TOKEN
39
- authorizations := os.Getenv("AUTHORIZATIONS")
40
- if authorizations == "" {
41
- AUTHORIZATIONS = []string{}
42
- } else {
43
- //以,分割 AUTH_TOKEN 并且为每个AUTH_TOKEN前面加上Bearer
44
- AUTHORIZATIONS = common.SplitAndAddBearer(authorizations)
45
- }
46
- // BASE_URL
47
- BaseUrl = os.Getenv("BASE_URL")
48
- if BaseUrl == "" {
49
- BaseUrl = "https://chatgpt.com"
50
- } else {
51
- BaseUrl = strings.TrimRight(BaseUrl, "/")
52
- }
53
- // POOL_MAX_COUNT
54
- poolMaxCount := os.Getenv("POOL_MAX_COUNT")
55
- var err error
56
- if poolMaxCount == "" {
57
- PoolMaxCount = 64
58
- } else {
59
- PoolMaxCount, err = strconv.Atoi(poolMaxCount)
60
- if err != nil {
61
- PoolMaxCount = 64
62
- }
63
- }
64
- // AUTH_ED
65
- authED := os.Getenv("AUTH_ED")
66
- if authED == "" {
67
- AuthED = 600
68
- } else {
69
- AuthED, err = strconv.Atoi(authED)
70
- if err != nil {
71
- AuthED = 600
72
- }
73
- }
74
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
configs/dev/service.yml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # 服务名称
2
+ name: glm-free-api
3
+ # 服务绑定主机地址
4
+ host: '0.0.0.0'
5
+ # 服务绑定端口
6
+ port: 8000
configs/dev/system.yml ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 是否开启请求日志
2
+ requestLog: true
3
+ # 临时目录路径
4
+ tmpDir: ./tmp
5
+ # 日志目录路径
6
+ logDir: ./logs
7
+ # 日志写入间隔(毫秒)
8
+ logWriteInterval: 200
9
+ # 日志文件有效期(毫秒)
10
+ logFileExpires: 2626560000
11
+ # 公共目录路径
12
+ publicDir: ./public
13
+ # 临时文件有效期(毫秒)
14
+ tmpFileExpires: 86400000
constant/constant.go DELETED
@@ -1,11 +0,0 @@
1
- package constant
2
-
3
- import "github.com/bogdanfinn/tls-client/profiles"
4
-
5
- var (
6
- ClientProfile = profiles.Safari_15_6_1
7
- )
8
-
9
- const (
10
- Ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
11
- )
 
 
 
 
 
 
 
 
 
 
 
 
doc/example-0.png ADDED
doc/example-1.png ADDED
doc/example-10.png ADDED
doc/example-11.png ADDED
doc/example-12.png ADDED
doc/example-2.png ADDED
doc/example-3.png ADDED
doc/example-5.png ADDED
doc/example-6.png ADDED
doc/example-9.png ADDED
env.template DELETED
@@ -1,8 +0,0 @@
1
- LOG_LEVEL=info # debug, info, warn, error
2
- BIND=127.0.0.1 #
3
- PORT=8080
4
- PROXY=
5
- AUTHORIZATIONS=
6
- POOL_MAX_COUNT=5 # max number of connections to keep in the pool
7
- AUTH_ED=180 # expiration time for the authorization in seconds
8
- AUTH_USE_COUNT=5 # number of times an authorization can be used
 
 
 
 
 
 
 
 
 
go.mod DELETED
@@ -1,50 +0,0 @@
1
- module free-gpt3.5-2api
2
-
3
- go 1.21
4
-
5
- require (
6
- github.com/aurorax-neo/go-logger v0.0.0-20240421094709-1eb4bda786d5
7
- github.com/bogdanfinn/fhttp v0.5.28
8
- github.com/bogdanfinn/tls-client v1.7.2
9
- github.com/gin-gonic/gin v1.9.1
10
- github.com/google/uuid v1.6.0
11
- github.com/joho/godotenv v1.5.1
12
- github.com/json-iterator/go v1.1.12
13
- github.com/launchdarkly/eventsource v1.7.1
14
- golang.org/x/crypto v0.21.0
15
- )
16
-
17
- require (
18
- github.com/andybalholm/brotli v1.0.5 // indirect
19
- github.com/bogdanfinn/utls v1.6.1 // indirect
20
- github.com/bytedance/sonic v1.9.1 // indirect
21
- github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
22
- github.com/cloudflare/circl v1.3.7 // 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/klauspost/compress v1.16.7 // indirect
30
- github.com/klauspost/cpuid/v2 v2.2.4 // indirect
31
- github.com/leodido/go-urn v1.2.4 // indirect
32
- github.com/mattn/go-isatty v0.0.19 // indirect
33
- github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
34
- github.com/modern-go/reflect2 v1.0.2 // indirect
35
- github.com/pelletier/go-toml/v2 v2.0.8 // indirect
36
- github.com/quic-go/quic-go v0.42.0 // indirect
37
- github.com/rogpeppe/go-internal v1.12.0 // 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
- go.uber.org/multierr v1.11.0 // indirect
42
- go.uber.org/zap v1.27.0 // indirect
43
- golang.org/x/arch v0.3.0 // indirect
44
- golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
45
- golang.org/x/net v0.22.0 // indirect
46
- golang.org/x/sys v0.18.0 // indirect
47
- golang.org/x/text v0.14.0 // indirect
48
- google.golang.org/protobuf v1.33.0 // indirect
49
- gopkg.in/yaml.v3 v3.0.1 // indirect
50
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
go.sum DELETED
@@ -1,139 +0,0 @@
1
- github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
2
- github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
3
- github.com/aurorax-neo/go-logger v0.0.0-20240421094709-1eb4bda786d5 h1:L1ei0BPLvE/ld4KAh4bKVAn5tDYOdJz0SuxlbuzfKzQ=
4
- github.com/aurorax-neo/go-logger v0.0.0-20240421094709-1eb4bda786d5/go.mod h1:BJsRG1ECcXTHwiz2zaMYxFkeXh+MpQVs6nWYphLT244=
5
- github.com/bogdanfinn/fhttp v0.5.28 h1:G6thT8s8v6z1IuvXMUsX9QKy3ZHseTQTzxuIhSiaaAw=
6
- github.com/bogdanfinn/fhttp v0.5.28/go.mod h1:oJiYPG3jQTKzk/VFmogH8jxjH5yiv2rrOH48Xso2lrE=
7
- github.com/bogdanfinn/tls-client v1.7.2 h1:vpL5qBYUfT9ueygEf1yLfymrXyUEZQatL25amfqGV8M=
8
- github.com/bogdanfinn/tls-client v1.7.2/go.mod h1:pOGa2euqTbEkGNqE5idx5jKKfs9ytlyn3fwEw8RSP+g=
9
- github.com/bogdanfinn/utls v1.6.1 h1:dKDYAcXEyFFJ3GaWaN89DEyjyRraD1qb4osdEK89ass=
10
- github.com/bogdanfinn/utls v1.6.1/go.mod h1:VXIbRZaiY/wHZc6Hu+DZ4O2CgTzjhjCg/Ou3V4r/39Y=
11
- github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
12
- github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
13
- github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
14
- github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
15
- github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
16
- github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
17
- github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
18
- github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
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-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
29
- github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
30
- github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
31
- github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
32
- github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
33
- github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
34
- github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
35
- github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
36
- github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
37
- github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
38
- github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
39
- github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
40
- github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
41
- github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
42
- github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
43
- github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
44
- github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
45
- github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
46
- github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
47
- github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
48
- github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
49
- github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
50
- github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
51
- github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
52
- github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
53
- github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
54
- github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
55
- github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
56
- github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
57
- github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
58
- github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
59
- github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
60
- github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
61
- github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
62
- github.com/launchdarkly/eventsource v1.7.1 h1:StoRQeiPyrcQIXjlQ7b5jWMzHW4p+GGczN2r2oBhujg=
63
- github.com/launchdarkly/eventsource v1.7.1/go.mod h1:LHxSeb4OnqznNZxCSXbFghxS/CjIQfzHovNoAqbO/Wk=
64
- github.com/launchdarkly/go-test-helpers/v2 v2.2.0 h1:L3kGILP/6ewikhzhdNkHy1b5y4zs50LueWenVF0sBbs=
65
- github.com/launchdarkly/go-test-helpers/v2 v2.2.0/go.mod h1:L7+th5govYp5oKU9iN7To5PgznBuIjBPn+ejqKR0avw=
66
- github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
67
- github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
68
- github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
69
- github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
70
- github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
71
- github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
72
- github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
73
- github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
74
- github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
75
- github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
76
- github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
77
- github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
78
- github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
79
- github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
80
- github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
81
- github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
82
- github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
83
- github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
84
- github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
85
- github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
86
- github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
87
- github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
88
- github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
89
- github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
90
- github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
91
- github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
92
- github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
93
- github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
94
- github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
95
- github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
96
- github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
97
- github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
98
- github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
99
- github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
100
- github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 h1:YqAladjX7xpA6BM04leXMWAEjS0mTZ5kUU9KRBriQJc=
101
- github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5/go.mod h1:2JjD2zLQYH5HO74y5+aE3remJQvl6q4Sn6aWA2wD1Ng=
102
- github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
103
- github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
104
- github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
105
- github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
106
- go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
107
- go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
108
- go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
109
- go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
110
- go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
111
- go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
112
- golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
113
- golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
114
- golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
115
- golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
116
- golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
117
- golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
118
- golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
119
- golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
120
- golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
121
- golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
122
- golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
123
- golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
124
- golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
125
- golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
126
- golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
127
- golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
128
- golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
129
- google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
130
- google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
131
- gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
132
- gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
133
- gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
134
- gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
135
- gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
136
- gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
137
- gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
138
- gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
139
- rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
libs.d.ts ADDED
File without changes
main.go DELETED
@@ -1,35 +0,0 @@
1
- package main
2
-
3
- import (
4
- "fmt"
5
- "free-gpt3.5-2api/FreeGpt35Pool"
6
- "free-gpt3.5-2api/ProxyPool"
7
- "free-gpt3.5-2api/config"
8
- "free-gpt3.5-2api/router"
9
- "github.com/aurorax-neo/go-logger"
10
- "github.com/gin-gonic/gin"
11
- )
12
-
13
- func Init() {
14
- ProxyPool.GetProxyPoolInstance()
15
- FreeGpt35Pool.GetFreeGpt35PoolInstance()
16
- }
17
-
18
- func main() {
19
- // Init
20
- Init()
21
- // Initialize HTTP server
22
- gin.SetMode(gin.ReleaseMode)
23
- server := gin.New()
24
- server.Use(gin.Recovery())
25
- // 设置路由
26
- router.SetRouter(server)
27
- // 提示服务启动
28
- host := config.Bind
29
- if config.Bind == "0.0.0.0" {
30
- host = "127.0.0.1"
31
- }
32
- logger.Logger.Info(fmt.Sprint("Server started on http://", host, ":", config.Port))
33
- // 启动 HTTP 服务器
34
- _ = server.Run(fmt.Sprint(config.Bind, ":", config.Port))
35
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
package.json ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "glm-free-api",
3
+ "version": "0.0.30",
4
+ "description": "GLM Free API Server",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.ts",
9
+ "directories": {
10
+ "dist": "dist"
11
+ },
12
+ "files": [
13
+ "dist/"
14
+ ],
15
+ "scripts": {
16
+ "dev": "tsup src/index.ts --format cjs,esm --sourcemap --dts --publicDir public --watch --onSuccess \"node dist/index.js\"",
17
+ "start": "node dist/index.js",
18
+ "build": "tsup src/index.ts --format cjs,esm --sourcemap --dts --clean --publicDir public"
19
+ },
20
+ "author": "Vinlic",
21
+ "license": "ISC",
22
+ "dependencies": {
23
+ "axios": "^1.6.7",
24
+ "colors": "^1.4.0",
25
+ "crc-32": "^1.2.2",
26
+ "cron": "^3.1.6",
27
+ "date-fns": "^3.3.1",
28
+ "eventsource-parser": "^1.1.2",
29
+ "form-data": "^4.0.0",
30
+ "fs-extra": "^11.2.0",
31
+ "koa": "^2.15.0",
32
+ "koa-body": "^5.0.0",
33
+ "koa-bodyparser": "^4.4.1",
34
+ "koa-range": "^0.3.0",
35
+ "koa-router": "^12.0.1",
36
+ "koa2-cors": "^2.0.6",
37
+ "lodash": "^4.17.21",
38
+ "mime": "^4.0.1",
39
+ "minimist": "^1.2.8",
40
+ "randomstring": "^1.3.0",
41
+ "uuid": "^9.0.1",
42
+ "yaml": "^2.3.4"
43
+ },
44
+ "devDependencies": {
45
+ "@types/lodash": "^4.14.202",
46
+ "@types/mime": "^3.0.4",
47
+ "tsup": "^8.0.2",
48
+ "typescript": "^5.3.3"
49
+ }
50
+ }
public/welcome.html ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8"/>
5
+ <title>🚀 服务已启动</title>
6
+ </head>
7
+ <body>
8
+ <p>glm-free-api已启动!<br>请通过LobeChat / NextChat / Dify等客户端或OpenAI SDK接入!</p>
9
+ </body>
10
+ </html>
queue/queue.go DELETED
@@ -1,106 +0,0 @@
1
- package queue
2
-
3
- type (
4
- Queue struct {
5
- start, end *Node
6
- length int
7
- }
8
- Node struct {
9
- Value interface{}
10
- next *Node
11
- }
12
- )
13
-
14
- // New 新建一个队列
15
- func New() *Queue {
16
- return &Queue{nil, nil, 0}
17
- }
18
-
19
- // Dequeue 出队
20
- func (Q *Queue) Dequeue() *Node {
21
- if Q.length == 0 {
22
- return nil
23
- }
24
- n := Q.start
25
- if Q.length == 1 {
26
- Q.start = nil
27
- Q.end = nil
28
- } else {
29
- Q.start = Q.start.next
30
- }
31
- Q.length--
32
- return n
33
- }
34
-
35
- // Enqueue 入队
36
- func (Q *Queue) Enqueue(value interface{}) {
37
- n := &Node{value, nil}
38
- if Q.length == 0 {
39
- Q.start = n
40
- Q.end = n
41
- } else {
42
- Q.end.next = n
43
- Q.end = n
44
- }
45
- Q.length++
46
- }
47
-
48
- // Len 获取队列长度
49
- func (Q *Queue) Len() int {
50
- return Q.length
51
- }
52
-
53
- // Peek 返回队列的第一个元素
54
- func (Q *Queue) Peek() *Node {
55
- if Q.length == 0 {
56
- return nil
57
- }
58
- return Q.start
59
- }
60
-
61
- // Remove 移除指定节点
62
- func (Q *Queue) Remove(n *Node) {
63
- if Q.length == 0 || n == nil {
64
- return
65
- }
66
-
67
- // 如果移除的是队列的第一个元素
68
- if n == Q.start {
69
- Q.start = Q.start.next
70
- if Q.start == nil {
71
- // 如果移除后队列为空,则end也应该设置为nil
72
- Q.end = nil
73
- }
74
- Q.length--
75
- return
76
- }
77
-
78
- // 找到n的前一个节点
79
- prevNode := Q.start
80
- for prevNode != nil && prevNode.next != n {
81
- prevNode = prevNode.next
82
- }
83
-
84
- if prevNode == nil {
85
- // 没有找到n的前一个节点(n不在队列中)
86
- return
87
- }
88
-
89
- // 移除节点n
90
- prevNode.next = n.next
91
- // 如果移除的是最后一个元素,更新end指针
92
- if n.next == nil {
93
- Q.end = prevNode
94
- }
95
- Q.length--
96
- }
97
-
98
- // Traverse 遍历队列
99
- func (Q *Queue) Traverse(cb func(n *Node)) {
100
- if Q.length == 0 {
101
- return
102
- }
103
- for n := Q.start; n != nil; n = n.next {
104
- cb(n)
105
- }
106
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
release.bat DELETED
@@ -1,56 +0,0 @@
1
- @echo off
2
- SETLOCAL
3
-
4
- REM 指定编码为 UTF-8
5
- chcp 65001
6
-
7
- REM 设置要生成的可执行文件的名称
8
- set OUTPUT_NAME=free-gpt3.5-2api
9
-
10
- REM 设置 Go 源文件的名称
11
- SET GOFILE=main.go
12
-
13
- REM 设置输出目录
14
- SET OUTPUTDIR=target
15
-
16
- REM 确保输出目录存在
17
- IF NOT EXIST %OUTPUTDIR% MKDIR %OUTPUTDIR%
18
-
19
- REM 编译为 Windows/amd64
20
- echo 开始编译 Windows/amd64
21
- SET GOOS=windows
22
- SET GOARCH=amd64
23
- go build -o %OUTPUTDIR%/%OUTPUT_NAME%_windows_amd64.exe %GOFILE%
24
- echo 编译完成 Windows/amd64
25
-
26
- REM 编译为 Windows/386
27
- echo 开始编译 Windows/386
28
- SET GOOS=windows
29
- SET GOARCH=386
30
- go build -o %OUTPUTDIR%/%OUTPUT_NAME%_windows_386.exe %GOFILE%
31
- echo 编译完成 Windows/386
32
-
33
- REM 编译为 Linux/amd64
34
- echo 开始编译 Linux/amd64
35
- SET GOOS=linux
36
- SET GOARCH=amd64
37
- go build -o %OUTPUTDIR%/%OUTPUT_NAME%_linux_amd64 %GOFILE%
38
- echo 编译完成 Linux/amd64
39
-
40
- REM 编译为 macOS/amd64
41
- echo 开始编译 macOS/amd64
42
- SET GOOS=darwin
43
- SET GOARCH=amd64
44
- go build -o %OUTPUTDIR%/%OUTPUT_NAME%_macos_amd64 %GOFILE%
45
- echo 编译完成 macOS/amd64
46
-
47
- REM 编译为 freebsd/amd64
48
- echo 开始编译 freebsd/amd64
49
- SET GOOS=freebsd
50
- SET GOARCH=amd64
51
- go build -o %OUTPUTDIR%/%OUTPUT_NAME%_freebsd_amd64 %GOFILE%
52
- echo 编译完成 freebsd/amd64
53
-
54
- REM 结束批处理脚本
55
- ENDLOCAL
56
- echo 编译完成!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
router/middleware.go DELETED
@@ -1,63 +0,0 @@
1
- package router
2
-
3
- import (
4
- "fmt"
5
- "free-gpt3.5-2api/common"
6
- "free-gpt3.5-2api/config"
7
- "github.com/aurorax-neo/go-logger"
8
- "github.com/gin-gonic/gin"
9
- )
10
-
11
- // Ping 测试接口
12
- func Ping(c *gin.Context) {
13
- c.JSON(200, gin.H{
14
- "message": "pong",
15
- })
16
- }
17
-
18
- // V1Cors 跨域中间件
19
- func V1Cors(c *gin.Context) {
20
- // 允许跨域
21
- c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
22
- c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
23
- c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
24
- c.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept")
25
- c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
26
- // 如果是OPTIONS请求,直接返回
27
- if c.Request.Method == "OPTIONS" {
28
- c.AbortWithStatus(204)
29
- return
30
- }
31
- c.Next()
32
- }
33
-
34
- // V1Request 请求中间件
35
- func V1Request(c *gin.Context) {
36
- // 打印请求摘要 方法 url ip - user-agent 格式化输出
37
- infoStr := fmt.Sprint(" -> ", c.Request.Method, " ", c.Request.URL.String(), " - ", c.ClientIP(), " - ", c.Request.Header.Get("User-Agent"))
38
- logger.Logger.Info(infoStr)
39
- c.Next()
40
- }
41
-
42
- // V1Auth 验证v1 api 的token
43
- func V1Auth(c *gin.Context) {
44
- authToken := c.Request.Header.Get("Authorization")
45
- if authToken == "" && len(config.AUTHORIZATIONS) > 0 {
46
- common.ErrorResponse(c, 401, "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY)", nil)
47
- return
48
- }
49
- // 判断 authToken 是否在 config.CONFIG.AUTHORIZATIONS 列表
50
- if !common.IsStrInArray(authToken, config.AUTHORIZATIONS) {
51
- common.ErrorResponse(c, 401, "Incorrect API key provided: sk-4yNZz***************************************6mjw.", nil)
52
- return
53
- }
54
- c.Next()
55
- }
56
-
57
- // V1Response 响应中间件
58
- func V1Response(c *gin.Context) {
59
- c.Next()
60
- // 打印响应摘要 方法 url 状态码
61
- infoStr := fmt.Sprint(" <- ", c.Request.Method, " ", c.Request.URL.String(), " - ", c.Writer.Status())
62
- logger.Logger.Info(infoStr)
63
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
router/router.go DELETED
@@ -1,25 +0,0 @@
1
- package router
2
-
3
- import (
4
- v1 "free-gpt3.5-2api/service/v1"
5
- "free-gpt3.5-2api/service/v1Chat"
6
- "github.com/gin-gonic/gin"
7
- "net/http"
8
- )
9
-
10
- func SetRouter(router *gin.Engine) {
11
- router.GET("/", Index)
12
- router.GET("/ping", Ping)
13
- v1Router := router.Group("/v1")
14
- v1Router.Use(V1Cors)
15
- v1Router.Use(V1Request)
16
- v1Router.Use(V1Response)
17
- v1Router.Use(V1Auth)
18
- v1Router.GET("/tokens", v1.Tokens)
19
- v1Router.OPTIONS("/chat/completions", nil)
20
- v1Router.POST("/chat/completions", v1Chat.Completions)
21
- }
22
-
23
- func Index(c *gin.Context) {
24
- c.String(http.StatusOK, "Hello,This is free-gpt3.5-2api.")
25
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
service/v1/tokens.go DELETED
@@ -1,20 +0,0 @@
1
- package v1
2
-
3
- import (
4
- "fmt"
5
- "free-gpt3.5-2api/FreeGpt35Pool"
6
- "github.com/aurorax-neo/go-logger"
7
- "github.com/gin-gonic/gin"
8
- )
9
-
10
- type TokensResp struct {
11
- Count int `json:"count"`
12
- }
13
-
14
- func Tokens(c *gin.Context) {
15
- resp := &TokensResp{
16
- Count: FreeGpt35Pool.GetFreeGpt35PoolInstance().GetSize(),
17
- }
18
- logger.Logger.Info(fmt.Sprint("FreeGpt35Pool Tokens: ", resp.Count))
19
- c.JSON(200, resp)
20
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
service/v1/util.go DELETED
@@ -1,65 +0,0 @@
1
- package v1
2
-
3
- import (
4
- "free-gpt3.5-2api/service/v1Chat/reqModel"
5
- "github.com/google/uuid"
6
- "math/rand"
7
- )
8
-
9
- func MappingModel(model string) string {
10
- var modelMapping = map[string]string{
11
- "gpt-3.5-turbo": "text-davinci-002-render-sha",
12
- "gpt-3.5-turbo-16k": "text-davinci-002-render-sha",
13
- "gpt-3.5-turbo-16k-0613": "text-davinci-002-render-sha",
14
- "gpt-3.5-turbo-0301": "text-davinci-002-render-sha",
15
- "gpt-3.5-turbo-0613": "text-davinci-002-render-sha",
16
- "gpt-3.5-turbo-1106": "text-davinci-002-render-sha",
17
- }
18
- if model == "" {
19
- return "text-davinci-002-render-sha"
20
- }
21
- if v, ok := modelMapping[model]; ok {
22
- return v
23
- }
24
- return "text-davinci-002-render-sha"
25
- }
26
-
27
- func GenerateID(length int) string {
28
- const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
29
- id := "chatcmpl-"
30
- for i := 0; i < length; i++ {
31
- id += string(charset[rand.Intn(len(charset))])
32
- }
33
- return id
34
- }
35
-
36
- func ApiReq2ChatReq35(apiReq *reqModel.ApiReq) (chatReq *reqModel.ChatReq35) {
37
- messages := make([]reqModel.ChatMessages, 0)
38
- for _, apiMessage := range apiReq.Messages {
39
- chatMessage := reqModel.ChatMessages{
40
- Author: reqModel.ChatAuthor{
41
- Role: apiMessage.Role,
42
- },
43
- Content: reqModel.ChatContent{
44
- ContentType: "text",
45
- Parts: []string{apiMessage.Content},
46
- },
47
- }
48
- messages = append(messages, chatMessage)
49
- }
50
-
51
- chatReq = &reqModel.ChatReq35{
52
- Action: "next",
53
- Messages: messages,
54
- ParentMessageId: uuid.New().String(),
55
- Model: MappingModel(apiReq.Model),
56
- TimeZoneOffsetMin: -180,
57
- Suggestions: make([]string, 0),
58
- HistoryAndTrainingDisabled: true,
59
- ConversationMode: reqModel.ChatConversationMode{
60
- Kind: "primary_assistant",
61
- },
62
- WebsocketRequestId: uuid.New().String(),
63
- }
64
- return chatReq
65
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
service/v1Chat/completions.go DELETED
@@ -1,19 +0,0 @@
1
- package v1Chat
2
-
3
- import (
4
- "free-gpt3.5-2api/common"
5
- "free-gpt3.5-2api/service/v1Chat/reqModel"
6
- "github.com/gin-gonic/gin"
7
- "net/http"
8
- )
9
-
10
- func Completions(c *gin.Context) {
11
- // 从请求中获取参数
12
- apiReq := &reqModel.ApiReq{}
13
- err := c.BindJSON(apiReq)
14
- if err != nil {
15
- common.ErrorResponse(c, http.StatusBadRequest, "Invalid parameter", nil)
16
- return
17
- }
18
- Gpt35Completions(c, apiReq)
19
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
service/v1Chat/gpt35Completions.go DELETED
@@ -1,231 +0,0 @@
1
- package v1Chat
2
-
3
- import (
4
- "encoding/json"
5
- "fmt"
6
- "free-gpt3.5-2api/FreeGpt35"
7
- "free-gpt3.5-2api/FreeGpt35Pool"
8
- "free-gpt3.5-2api/common"
9
- "free-gpt3.5-2api/service/v1"
10
- "free-gpt3.5-2api/service/v1Chat/reqModel"
11
- "free-gpt3.5-2api/service/v1Chat/respModel"
12
- "github.com/aurorax-neo/go-logger"
13
- fhttp "github.com/bogdanfinn/fhttp"
14
- "github.com/gin-gonic/gin"
15
- "github.com/launchdarkly/eventsource"
16
- "io"
17
- "net/http"
18
- "strings"
19
- )
20
-
21
- func Gpt35Completions(c *gin.Context, apiReq *reqModel.ApiReq) {
22
- // 获取 FreeGpt35 实例
23
- ChatGpt35 := FreeGpt35Pool.GetFreeGpt35PoolInstance().GetFreeGpt35(3)
24
- if ChatGpt35 == nil {
25
- errStr := "please restart the program、change the IP address、use a proxy to try again."
26
- logger.Logger.Error(errStr)
27
- common.ErrorResponse(c, http.StatusUnauthorized, errStr, nil)
28
- return
29
- }
30
- // 转换请求
31
- ChatReq35 := v1.ApiReq2ChatReq35(apiReq)
32
- // 请求参数
33
- body, err := common.Struct2BytesBuffer(ChatReq35)
34
- if err != nil {
35
- logger.Logger.Error(err.Error())
36
- common.ErrorResponse(c, http.StatusInternalServerError, "", err)
37
- return
38
-
39
- }
40
- // 生成请求
41
- request, err := ChatGpt35.NewRequest(fhttp.MethodPost, FreeGpt35.ChatUrl, body)
42
- if err != nil || request == nil {
43
- errStr := "Request is nil or error"
44
- logger.Logger.Error("Request is nil or error")
45
- common.ErrorResponse(c, http.StatusInternalServerError, errStr, err)
46
- return
47
- }
48
- // 设置请求头
49
- request.Header.Set("Content-Type", "application/json")
50
- request.Header.Set("oai-device-id", ChatGpt35.FreeAuth.OaiDeviceId)
51
- request.Header.Set("openai-sentinel-chat-requirements-token", ChatGpt35.FreeAuth.Token)
52
- if ChatGpt35.FreeAuth.ProofWork.Required {
53
- request.Header.Set("Openai-Sentinel-Proof-Token", ChatGpt35.FreeAuth.ProofWork.Ospt)
54
- }
55
- // 发送请求
56
- response, err := ChatGpt35.RequestClient.Do(request)
57
- if err != nil {
58
- errStr := "RequestClient Do error"
59
- logger.Logger.Error(fmt.Sprint(errStr, " ", err))
60
- common.ErrorResponse(c, http.StatusInternalServerError, errStr, err)
61
- return
62
- }
63
- defer func(Body io.ReadCloser) {
64
- _ = Body.Close()
65
- }(response.Body)
66
- if response.StatusCode != http.StatusOK {
67
- errStr := "Request error"
68
- logger.Logger.Error(fmt.Sprint(errStr, " ", response.StatusCode))
69
- common.ErrorResponse(c, response.StatusCode, errStr, nil)
70
- return
71
- }
72
- // 流式返回
73
- if apiReq.Stream {
74
- __CompletionsStream(c, apiReq, response)
75
- } else { // 非流式回应
76
- __CompletionsNoStream(c, apiReq, response)
77
- }
78
- }
79
-
80
- func __CompletionsStream(c *gin.Context, apiReq *reqModel.ApiReq, resp *fhttp.Response) {
81
- defer func(Body io.ReadCloser) {
82
- _ = Body.Close()
83
- }(resp.Body)
84
- messageTemp := ""
85
- decoder := eventsource.NewDecoder(resp.Body)
86
- // 响应id
87
- id := v1.GenerateID(29)
88
- handlingSigns := false
89
- for {
90
- event, err := decoder.Decode()
91
- if err != nil {
92
- logger.Logger.Error(err.Error())
93
- common.ErrorResponse(c, http.StatusInternalServerError, "", err)
94
- break
95
- }
96
- name := event.Event()
97
- data := event.Data()
98
- // 空白数据不处理
99
- if data == "" {
100
- continue
101
- }
102
- // 结束标志
103
- if data == "[DONE]" {
104
- // 生成响应 stream
105
- apiRespStream := respModel.NewApiRespStream(id, apiReq.Model, "", "stop")
106
- // 生成响应 bytes
107
- bytes, err := common.Struct2Bytes(apiRespStream)
108
- if err != nil {
109
- logger.Logger.Error(err.Error())
110
- continue
111
- }
112
- // 发送响应
113
- c.SSEvent(name, fmt.Sprint(" ", string(bytes)))
114
- // 结束
115
- c.SSEvent(name, " [DONE]")
116
- return
117
- }
118
- chatResp35 := &respModel.ChatResp35{}
119
- err = json.Unmarshal([]byte(data), chatResp35)
120
- if chatResp35.Error != nil && !handlingSigns {
121
- logger.Logger.Error(fmt.Sprint(chatResp35.Error))
122
- common.ErrorResponse(c, http.StatusInternalServerError, "", chatResp35.Error)
123
- return
124
- }
125
- // 脏数据不处理
126
- if err != nil {
127
- continue
128
- }
129
- // 被block
130
- if contentIsBlocked(chatResp35) {
131
- // 返回响应
132
- common.ErrorResponse(c, http.StatusBadRequest, "content is blocked.", "")
133
- return
134
- }
135
- // 仅处理assistant的消息
136
- if chatResp35.Message.Author.Role == "assistant" && (chatResp35.Message.Status == "in_progress" || handlingSigns) {
137
- // handlingSigns 置为 true
138
- handlingSigns = true
139
- // 仅处理第一个part
140
- parts := chatResp35.Message.Content.Parts[0]
141
- // 去除重复数据
142
- content := strings.Replace(parts, messageTemp, "", 1)
143
- messageTemp = parts
144
- // 空白数据不处理
145
- if content == "" {
146
- continue
147
- }
148
- // 生成响应 stream
149
- apiRespStream := respModel.NewApiRespStream(id, apiReq.Model, content, "")
150
- // 生成响应 bytes
151
- bytes, err := common.Struct2Bytes(apiRespStream)
152
- if err != nil {
153
- logger.Logger.Error(err.Error())
154
- continue
155
- }
156
- // 发送响应
157
- c.SSEvent(name, fmt.Sprint(" ", string(bytes)))
158
- // 继续
159
- continue
160
- }
161
- }
162
- }
163
-
164
- func __CompletionsNoStream(c *gin.Context, apiReq *reqModel.ApiReq, resp *fhttp.Response) {
165
- defer func(Body io.ReadCloser) {
166
- _ = Body.Close()
167
- }(resp.Body)
168
- content := ""
169
- decoder := eventsource.NewDecoder(resp.Body)
170
- handlingSigns := false
171
- for {
172
- event, err := decoder.Decode()
173
- if err != nil {
174
- logger.Logger.Error(err.Error())
175
- common.ErrorResponse(c, http.StatusInternalServerError, "", err)
176
- return
177
- }
178
- data := event.Data()
179
- // 空白数据不处理
180
- if data == "" {
181
- continue
182
- }
183
- // 结束标志
184
- if data == "[DONE]" {
185
- apiRespObj := respModel.NewApiRespJson(v1.GenerateID(29), apiReq.Model, content)
186
- // 返回响应
187
- c.JSON(http.StatusOK, apiRespObj)
188
- return
189
- }
190
- chatResp35 := &respModel.ChatResp35{}
191
- err = json.Unmarshal([]byte(data), chatResp35)
192
- if chatResp35.Error != nil && !handlingSigns {
193
- logger.Logger.Error(fmt.Sprint(chatResp35.Error))
194
- common.ErrorResponse(c, http.StatusInternalServerError, "", chatResp35.Error)
195
- return
196
- }
197
- // 被block
198
- if contentIsBlocked(chatResp35) {
199
- // 返回响应
200
- common.ErrorResponse(c, http.StatusBadRequest, "content is blocked.", "")
201
- return
202
- }
203
- // 脏数据不处理
204
- if err != nil {
205
- continue
206
- }
207
- // 仅处理assistant的消息
208
- if chatResp35.Message.Author.Role == "assistant" && (chatResp35.Message.Status == "in_progress" || handlingSigns) {
209
- // handlingSigns 置为 true
210
- handlingSigns = true
211
- // 如果不包含上一次的数据则不处理
212
- if !strings.Contains(chatResp35.Message.Content.Parts[0], content) {
213
- continue
214
- }
215
- // 仅处理第一个part
216
- content = chatResp35.Message.Content.Parts[0]
217
- // 空白数据不处理
218
- if content == "" {
219
- continue
220
- }
221
- continue
222
- }
223
- }
224
- }
225
-
226
- func contentIsBlocked(chatResp35 *respModel.ChatResp35) bool {
227
- if !chatResp35.IsCompletion && chatResp35.ModerationResponse.Blocked {
228
- return true
229
- }
230
- return false
231
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
service/v1Chat/reqModel/apiReq.go DELETED
@@ -1,14 +0,0 @@
1
- package reqModel
2
-
3
- type ApiReq struct {
4
- Messages []ApiMessage `json:"messages"`
5
- Model string `json:"model"`
6
- Stream bool `json:"stream"`
7
- PluginIds []string `json:"plugin_ids"`
8
- NewMessages string `json:"-"`
9
- }
10
-
11
- type ApiMessage struct {
12
- Role string `json:"role"`
13
- Content string `json:"content"`
14
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
service/v1Chat/reqModel/chatReq.go DELETED
@@ -1,31 +0,0 @@
1
- package reqModel
2
-
3
- type ChatAuthor struct {
4
- Role string `json:"role"`
5
- }
6
-
7
- type ChatContent struct {
8
- ContentType string `json:"content_type"`
9
- Parts []string `json:"parts"`
10
- }
11
-
12
- type ChatMessages struct {
13
- Author ChatAuthor `json:"author"`
14
- Content ChatContent `json:"content"`
15
- }
16
-
17
- type ChatConversationMode struct {
18
- Kind string `json:"kind"`
19
- }
20
-
21
- type ChatReq35 struct {
22
- Action string `json:"action"`
23
- Messages []ChatMessages `json:"messages"`
24
- ParentMessageId string `json:"parent_message_id"`
25
- Model string `json:"model"`
26
- TimeZoneOffsetMin int `json:"timezone_offset_min"`
27
- Suggestions []string `json:"suggestions"`
28
- HistoryAndTrainingDisabled bool `json:"history_and_training_disabled"`
29
- ConversationMode ChatConversationMode `json:"conversation_mode"`
30
- WebsocketRequestId string `json:"websocket_request_id"`
31
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
service/v1Chat/respModel/apiRespJson.go DELETED
@@ -1,54 +0,0 @@
1
- package respModel
2
-
3
- import "time"
4
-
5
- type ApiRespJson struct {
6
- ID string `json:"id"`
7
- Object string `json:"object"`
8
- Created int64 `json:"created"`
9
- Model string `json:"model"`
10
- Usage ApiRespJsonUsage `json:"usage"`
11
- Choices []ApiRespJsonChoice `json:"choices"`
12
- }
13
-
14
- type ApiRespJsonMessage struct {
15
- Role string `json:"role"`
16
- Content string `json:"content"`
17
- }
18
-
19
- type ApiRespJsonChoice struct {
20
- Message ApiRespJsonMessage `json:"message"`
21
- FinishReason string `json:"finish_reason"`
22
- Index int `json:"index"`
23
- }
24
-
25
- type ApiRespJsonUsage struct {
26
- PromptTokens int `json:"prompt_tokens"`
27
- CompletionTokens int `json:"completion_tokens"`
28
- TotalTokens int `json:"total_tokens"`
29
- }
30
-
31
- func NewApiRespJson(id string, model string, content string) *ApiRespJson {
32
- apiRespObj := &ApiRespJson{
33
- ID: id,
34
- Created: time.Now().Unix(),
35
- Object: "chat.completion",
36
- Model: model,
37
- Usage: ApiRespJsonUsage{
38
- PromptTokens: 0,
39
- CompletionTokens: 0,
40
- TotalTokens: 0,
41
- },
42
- Choices: []ApiRespJsonChoice{
43
- {
44
- Message: ApiRespJsonMessage{
45
- Role: "assistant",
46
- Content: content,
47
- },
48
- FinishReason: "stop",
49
- Index: 0,
50
- },
51
- },
52
- }
53
- return apiRespObj
54
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
service/v1Chat/respModel/apiRespStream.go DELETED
@@ -1,43 +0,0 @@
1
- package respModel
2
-
3
- import "time"
4
-
5
- // ApiRespStream represents the JSON structure
6
- type ApiRespStream struct {
7
- ID string `json:"id"`
8
- Object string `json:"object"`
9
- Created int64 `json:"created"`
10
- Model string `json:"model"`
11
- Choices []ApiStreamChoice `json:"choices"`
12
- }
13
-
14
- // ApiStreamChoice represents the nested "choices" object in the JSON
15
- type ApiStreamChoice struct {
16
- Delta ApiStreamDelta `json:"delta"`
17
- Index int `json:"index"`
18
- FinishReason string `json:"finish_reason"`
19
- }
20
-
21
- // ApiStreamDelta represents the nested "delta" object in the JSON
22
- type ApiStreamDelta struct {
23
- Content string `json:"content"`
24
- }
25
-
26
- func NewApiRespStream(id string, model string, content string, finishReason string) *ApiRespStream {
27
- // 生成响应 model
28
- apiRespStream := &ApiRespStream{
29
- ID: id,
30
- Created: time.Now().Unix(),
31
- Object: "chat.completion.chunk",
32
- Model: model,
33
- Choices: []ApiStreamChoice{
34
- {
35
- Delta: ApiStreamDelta{
36
- Content: content,
37
- },
38
- FinishReason: finishReason,
39
- },
40
- },
41
- }
42
- return apiRespStream
43
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
service/v1Chat/respModel/chatResp.go DELETED
@@ -1,44 +0,0 @@
1
- package respModel
2
-
3
- type ChatResp35 struct {
4
- Message struct {
5
- Id string `json:"id"`
6
- Author struct {
7
- Role string `json:"role"`
8
- Name interface{} `json:"name"`
9
- Metadata struct {
10
- } `json:"metadata"`
11
- } `json:"author"`
12
- CreateTime float64 `json:"create_time"`
13
- UpdateTime interface{} `json:"update_time"`
14
- Content struct {
15
- ContentType string `json:"content_type"`
16
- Parts []string `json:"parts"`
17
- } `json:"content"`
18
- Status string `json:"status"`
19
- EndTurn interface{} `json:"end_turn"`
20
- Weight float64 `json:"weight"`
21
- Metadata struct {
22
- Citations []interface{} `json:"citations"`
23
- GizmoId interface{} `json:"gizmo_id"`
24
- MessageType string `json:"message_type"`
25
- ModelSlug string `json:"model_slug"`
26
- DefaultModelSlug string `json:"default_model_slug"`
27
- Pad string `json:"pad"`
28
- ParentId string `json:"parent_id"`
29
- } `json:"metadata"`
30
- Recipient string `json:"recipient"`
31
- } `json:"message"`
32
- ConversationId string `json:"conversation_id"`
33
- Error interface{} `json:"error"`
34
- // 审核
35
- Type string `json:"type"`
36
- MessageId string `json:"message_id"`
37
- IsCompletion bool `json:"is_completion"`
38
- ModerationResponse struct {
39
- Flagged bool `json:"flagged"`
40
- Disclaimers []interface{} `json:"disclaimers"`
41
- Blocked bool `json:"blocked"`
42
- ModerationId string `json:"moderation_id"`
43
- } `json:"moderation_response"`
44
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/api/consts/exceptions.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default {
2
+ API_TEST: [-9999, 'API异常错误'],
3
+ API_REQUEST_PARAMS_INVALID: [-2000, '请求参数非法'],
4
+ API_REQUEST_FAILED: [-2001, '请求失败'],
5
+ API_TOKEN_EXPIRES: [-2002, 'Token已失效'],
6
+ API_FILE_URL_INVALID: [-2003, '远程文件URL非法'],
7
+ API_FILE_EXECEEDS_SIZE: [-2004, '远程文件超出大小'],
8
+ API_CHAT_STREAM_PUSHING: [-2005, '已有对话流正在输出'],
9
+ API_CONTENT_FILTERED: [-2006, '内容由于合规问题已被阻止生成'],
10
+ API_IMAGE_GENERATION_FAILED: [-2007, '图像生成失败']
11
+ }
src/api/controllers/chat.ts ADDED
@@ -0,0 +1,1183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { PassThrough } from "stream";
2
+ import path from "path";
3
+ import _ from "lodash";
4
+ import mime from "mime";
5
+ import FormData from "form-data";
6
+ import axios, { AxiosResponse } from "axios";
7
+
8
+ import APIException from "@/lib/exceptions/APIException.ts";
9
+ import EX from "@/api/consts/exceptions.ts";
10
+ import { createParser } from "eventsource-parser";
11
+ import logger from "@/lib/logger.ts";
12
+ import util from "@/lib/util.ts";
13
+
14
+ // 模型名称
15
+ const MODEL_NAME = "glm";
16
+ // 默认的智能体ID,GLM4
17
+ const DEFAULT_ASSISTANT_ID = "65940acff94777010aa6b796";
18
+ // access_token有效期
19
+ const ACCESS_TOKEN_EXPIRES = 3600;
20
+ // 最大重试次数
21
+ const MAX_RETRY_COUNT = 3;
22
+ // 重试延迟
23
+ const RETRY_DELAY = 5000;
24
+ // 伪装headers
25
+ const FAKE_HEADERS = {
26
+ Accept: "*/*",
27
+ "App-Name": "chatglm",
28
+ Platform: "pc",
29
+ Origin: "https://chatglm.cn",
30
+ "Sec-Ch-Ua":
31
+ '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
32
+ "Sec-Ch-Ua-Mobile": "?0",
33
+ "Sec-Ch-Ua-Platform": '"Windows"',
34
+ "User-Agent":
35
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
36
+ Version: "0.0.1",
37
+ };
38
+ // 文件最大大小
39
+ const FILE_MAX_SIZE = 100 * 1024 * 1024;
40
+ // access_token映射
41
+ const accessTokenMap = new Map();
42
+ // access_token请求队列映射
43
+ const accessTokenRequestQueueMap: Record<string, Function[]> = {};
44
+
45
+ /**
46
+ * 请求access_token
47
+ *
48
+ * 使用refresh_token去刷新获得access_token
49
+ *
50
+ * @param refreshToken 用于刷新access_token的refresh_token
51
+ */
52
+ async function requestToken(refreshToken: string) {
53
+ if (accessTokenRequestQueueMap[refreshToken])
54
+ return new Promise((resolve) =>
55
+ accessTokenRequestQueueMap[refreshToken].push(resolve)
56
+ );
57
+ accessTokenRequestQueueMap[refreshToken] = [];
58
+ logger.info(`Refresh token: ${refreshToken}`);
59
+ const result = await (async () => {
60
+ const result = await axios.post(
61
+ "https://chatglm.cn/chatglm/backend-api/v1/user/refresh",
62
+ {},
63
+ {
64
+ headers: {
65
+ Authorization: `Bearer ${refreshToken}`,
66
+ Referer: "https://chatglm.cn/main/alltoolsdetail",
67
+ "X-Device-Id": util.uuid(false),
68
+ "X-Request-Id": util.uuid(false),
69
+ ...FAKE_HEADERS,
70
+ },
71
+ timeout: 15000,
72
+ validateStatus: () => true,
73
+ }
74
+ );
75
+ const { result: _result } = checkResult(result, refreshToken);
76
+ const { accessToken } = _result;
77
+ return {
78
+ accessToken,
79
+ refreshToken,
80
+ refreshTime: util.unixTimestamp() + ACCESS_TOKEN_EXPIRES,
81
+ };
82
+ })()
83
+ .then((result) => {
84
+ if (accessTokenRequestQueueMap[refreshToken]) {
85
+ accessTokenRequestQueueMap[refreshToken].forEach((resolve) =>
86
+ resolve(result)
87
+ );
88
+ delete accessTokenRequestQueueMap[refreshToken];
89
+ }
90
+ logger.success(`Refresh successful`);
91
+ return result;
92
+ })
93
+ .catch((err) => {
94
+ if (accessTokenRequestQueueMap[refreshToken]) {
95
+ accessTokenRequestQueueMap[refreshToken].forEach((resolve) =>
96
+ resolve(err)
97
+ );
98
+ delete accessTokenRequestQueueMap[refreshToken];
99
+ }
100
+ return err;
101
+ });
102
+ if (_.isError(result)) throw result;
103
+ return result;
104
+ }
105
+
106
+ /**
107
+ * 获取缓存中的access_token
108
+ *
109
+ * 避免短时间大量刷新token,未加锁,如果有并发要求还需加锁
110
+ *
111
+ * @param refreshToken 用于刷新access_token的refresh_token
112
+ */
113
+ async function acquireToken(refreshToken: string): Promise<string> {
114
+ let result = accessTokenMap.get(refreshToken);
115
+ if (!result) {
116
+ result = await requestToken(refreshToken);
117
+ accessTokenMap.set(refreshToken, result);
118
+ }
119
+ if (util.unixTimestamp() > result.refreshTime) {
120
+ result = await requestToken(refreshToken);
121
+ accessTokenMap.set(refreshToken, result);
122
+ }
123
+ return result.accessToken;
124
+ }
125
+
126
+ /**
127
+ * 移除会话
128
+ *
129
+ * 在对话流传输完毕后移除会话,避免创建的会话出现在用户的对话列表中
130
+ *
131
+ * @param refreshToken 用于刷新access_token的refresh_token
132
+ */
133
+ async function removeConversation(
134
+ convId: string,
135
+ refreshToken: string,
136
+ assistantId = DEFAULT_ASSISTANT_ID
137
+ ) {
138
+ const token = await acquireToken(refreshToken);
139
+
140
+ const result = await axios.post(
141
+ "https://chatglm.cn/chatglm/backend-api/assistant/conversation/delete",
142
+ {
143
+ assistant_id: assistantId,
144
+ conversation_id: convId,
145
+ },
146
+ {
147
+ headers: {
148
+ Authorization: `Bearer ${token}`,
149
+ Referer: `https://chatglm.cn/main/alltoolsdetail`,
150
+ "X-Device-Id": util.uuid(false),
151
+ "X-Request-Id": util.uuid(false),
152
+ ...FAKE_HEADERS,
153
+ },
154
+ timeout: 15000,
155
+ validateStatus: () => true,
156
+ }
157
+ );
158
+ checkResult(result, refreshToken);
159
+ }
160
+
161
+ /**
162
+ * 同步对话补全
163
+ *
164
+ * @param messages 参考gpt系列消息格式,多轮对话请完整提供上下文
165
+ * @param refreshToken 用于刷新access_token的refresh_token
166
+ * @param assistantId 智能体ID,默认使用GLM4原版
167
+ * @param retryCount 重试次数
168
+ */
169
+ async function createCompletion(
170
+ messages: any[],
171
+ refreshToken: string,
172
+ assistantId = DEFAULT_ASSISTANT_ID,
173
+ refConvId = '',
174
+ retryCount = 0
175
+ ) {
176
+ return (async () => {
177
+ logger.info(messages);
178
+
179
+ // 提取引用文件URL并上传获得引用的文件ID列表
180
+ const refFileUrls = extractRefFileUrls(messages);
181
+ const refs = refFileUrls.length
182
+ ? await Promise.all(
183
+ refFileUrls.map((fileUrl) => uploadFile(fileUrl, refreshToken))
184
+ )
185
+ : [];
186
+
187
+ // 如果引用对话ID不正确则重置引用
188
+ if (!/[0-9a-zA-Z]{24}/.test(refConvId))
189
+ refConvId = '';
190
+
191
+ // 请求流
192
+ const token = await acquireToken(refreshToken);
193
+ const result = await axios.post(
194
+ "https://chatglm.cn/chatglm/backend-api/assistant/stream",
195
+ {
196
+ assistant_id: assistantId,
197
+ conversation_id: refConvId,
198
+ messages: messagesPrepare(messages, refs, !!refConvId),
199
+ meta_data: {
200
+ channel: "",
201
+ draft_id: "",
202
+ input_question_type: "xxxx",
203
+ is_test: false,
204
+ },
205
+ },
206
+ {
207
+ headers: {
208
+ Authorization: `Bearer ${token}`,
209
+ Referer:
210
+ assistantId == DEFAULT_ASSISTANT_ID
211
+ ? "https://chatglm.cn/main/alltoolsdetail"
212
+ : `https://chatglm.cn/main/gdetail/${assistantId}`,
213
+ "X-Device-Id": util.uuid(false),
214
+ "X-Request-Id": util.uuid(false),
215
+ ...FAKE_HEADERS,
216
+ },
217
+ // 120秒超时
218
+ timeout: 120000,
219
+ validateStatus: () => true,
220
+ responseType: "stream",
221
+ }
222
+ );
223
+ if (result.headers["content-type"].indexOf("text/event-stream") == -1) {
224
+ result.data.on("data", buffer => logger.error(buffer.toString()));
225
+ throw new APIException(
226
+ EX.API_REQUEST_FAILED,
227
+ `Stream response Content-Type invalid: ${result.headers["content-type"]}`
228
+ );
229
+ }
230
+
231
+ const streamStartTime = util.timestamp();
232
+ // 接收流为输出文本
233
+ const answer = await receiveStream(result.data);
234
+ logger.success(
235
+ `Stream has completed transfer ${util.timestamp() - streamStartTime}ms`
236
+ );
237
+
238
+ // 异步移除会话
239
+ removeConversation(answer.id, refreshToken, assistantId).catch((err) =>
240
+ !refConvId && console.error(err)
241
+ );
242
+
243
+ return answer;
244
+ })().catch((err) => {
245
+ if (retryCount < MAX_RETRY_COUNT) {
246
+ logger.error(`Stream response error: ${err.stack}`);
247
+ logger.warn(`Try again after ${RETRY_DELAY / 1000}s...`);
248
+ return (async () => {
249
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
250
+ return createCompletion(
251
+ messages,
252
+ refreshToken,
253
+ assistantId,
254
+ refConvId,
255
+ retryCount + 1
256
+ );
257
+ })();
258
+ }
259
+ throw err;
260
+ });
261
+ }
262
+
263
+ /**
264
+ * 流式对话补全
265
+ *
266
+ * @param messages 参考gpt系列消息格式,多轮对话请完整提供上下文
267
+ * @param refreshToken 用于刷新access_token的refresh_token
268
+ * @param assistantId 智能体ID,默认使用GLM4原版
269
+ * @param retryCount 重试次数
270
+ */
271
+ async function createCompletionStream(
272
+ messages: any[],
273
+ refreshToken: string,
274
+ assistantId = DEFAULT_ASSISTANT_ID,
275
+ refConvId = '',
276
+ retryCount = 0
277
+ ) {
278
+ return (async () => {
279
+ logger.info(messages);
280
+
281
+ // 提取引用文件URL并上传获得引用的文件ID列表
282
+ const refFileUrls = extractRefFileUrls(messages);
283
+ const refs = refFileUrls.length
284
+ ? await Promise.all(
285
+ refFileUrls.map((fileUrl) => uploadFile(fileUrl, refreshToken))
286
+ )
287
+ : [];
288
+
289
+ // 如果引用对话ID不正确则重置引用
290
+ if (!/[0-9a-zA-Z]{24}/.test(refConvId))
291
+ refConvId = '';
292
+
293
+ // 请求流
294
+ const token = await acquireToken(refreshToken);
295
+ const result = await axios.post(
296
+ `https://chatglm.cn/chatglm/backend-api/assistant/stream`,
297
+ {
298
+ assistant_id: assistantId,
299
+ conversation_id: refConvId,
300
+ messages: messagesPrepare(messages, refs, !!refConvId),
301
+ meta_data: {
302
+ channel: "",
303
+ draft_id: "",
304
+ input_question_type: "xxxx",
305
+ is_test: false,
306
+ },
307
+ },
308
+ {
309
+ headers: {
310
+ Authorization: `Bearer ${token}`,
311
+ Referer:
312
+ assistantId == DEFAULT_ASSISTANT_ID
313
+ ? "https://chatglm.cn/main/alltoolsdetail"
314
+ : `https://chatglm.cn/main/gdetail/${assistantId}`,
315
+ "X-Device-Id": util.uuid(false),
316
+ "X-Request-Id": util.uuid(false),
317
+ ...FAKE_HEADERS,
318
+ },
319
+ // 120秒超时
320
+ timeout: 120000,
321
+ validateStatus: () => true,
322
+ responseType: "stream",
323
+ }
324
+ );
325
+
326
+ if (result.headers["content-type"].indexOf("text/event-stream") == -1) {
327
+ logger.error(
328
+ `Invalid response Content-Type:`,
329
+ result.headers["content-type"]
330
+ );
331
+ result.data.on("data", buffer => logger.error(buffer.toString()));
332
+ const transStream = new PassThrough();
333
+ transStream.end(
334
+ `data: ${JSON.stringify({
335
+ id: "",
336
+ model: MODEL_NAME,
337
+ object: "chat.completion.chunk",
338
+ choices: [
339
+ {
340
+ index: 0,
341
+ delta: {
342
+ role: "assistant",
343
+ content: "服务暂时不可用,第三方响应错误",
344
+ },
345
+ finish_reason: "stop",
346
+ },
347
+ ],
348
+ usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
349
+ created: util.unixTimestamp(),
350
+ })}\n\n`
351
+ );
352
+ return transStream;
353
+ }
354
+
355
+ const streamStartTime = util.timestamp();
356
+ // 创建转换流将消息格式转换为gpt兼容格式
357
+ return createTransStream(result.data, (convId: string) => {
358
+ logger.success(
359
+ `Stream has completed transfer ${util.timestamp() - streamStartTime}ms`
360
+ );
361
+ // 流传输结束后异步移除会话
362
+ removeConversation(convId, refreshToken, assistantId).catch((err) =>
363
+ !refConvId && console.error(err)
364
+ );
365
+ });
366
+ })().catch((err) => {
367
+ if (retryCount < MAX_RETRY_COUNT) {
368
+ logger.error(`Stream response error: ${err.stack}`);
369
+ logger.warn(`Try again after ${RETRY_DELAY / 1000}s...`);
370
+ return (async () => {
371
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
372
+ return createCompletionStream(
373
+ messages,
374
+ refreshToken,
375
+ assistantId,
376
+ refConvId,
377
+ retryCount + 1
378
+ );
379
+ })();
380
+ }
381
+ throw err;
382
+ });
383
+ }
384
+
385
+ async function generateImages(
386
+ model = "65a232c082ff90a2ad2f15e2",
387
+ prompt: string,
388
+ refreshToken: string,
389
+ retryCount = 0
390
+ ) {
391
+ return (async () => {
392
+ logger.info(prompt);
393
+ const messages = [
394
+ { role: "user", content: prompt.indexOf('画') == -1 ? `请画:${prompt}` : prompt },
395
+ ];
396
+ // 请求流
397
+ const token = await acquireToken(refreshToken);
398
+ const result = await axios.post(
399
+ "https://chatglm.cn/chatglm/backend-api/assistant/stream",
400
+ {
401
+ assistant_id: model,
402
+ conversation_id: "",
403
+ messages: messagesPrepare(messages, []),
404
+ meta_data: {
405
+ channel: "",
406
+ draft_id: "",
407
+ input_question_type: "xxxx",
408
+ is_test: false,
409
+ },
410
+ },
411
+ {
412
+ headers: {
413
+ Authorization: `Bearer ${token}`,
414
+ Referer: `https://chatglm.cn/main/gdetail/${model}`,
415
+ "X-Device-Id": util.uuid(false),
416
+ "X-Request-Id": util.uuid(false),
417
+ ...FAKE_HEADERS,
418
+ },
419
+ // 120秒超时
420
+ timeout: 120000,
421
+ validateStatus: () => true,
422
+ responseType: "stream",
423
+ }
424
+ );
425
+
426
+ if (result.headers["content-type"].indexOf("text/event-stream") == -1)
427
+ throw new APIException(
428
+ EX.API_REQUEST_FAILED,
429
+ `Stream response Content-Type invalid: ${result.headers["content-type"]}`
430
+ );
431
+
432
+ const streamStartTime = util.timestamp();
433
+ // 接收流为输出文本
434
+ const { convId, imageUrls } = await receiveImages(result.data);
435
+ logger.success(
436
+ `Stream has completed transfer ${util.timestamp() - streamStartTime}ms`
437
+ );
438
+
439
+ // 异步移除会话,如果消息不合规,此操作可能会抛出数据库错误异常,请忽略
440
+ removeConversation(convId, refreshToken, model).catch((err) =>
441
+ console.error(err)
442
+ );
443
+
444
+ if (imageUrls.length == 0)
445
+ throw new APIException(EX.API_IMAGE_GENERATION_FAILED);
446
+
447
+ return imageUrls;
448
+ })().catch((err) => {
449
+ if (retryCount < MAX_RETRY_COUNT) {
450
+ logger.error(`Stream response error: ${err.message}`);
451
+ logger.warn(`Try again after ${RETRY_DELAY / 1000}s...`);
452
+ return (async () => {
453
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
454
+ return generateImages(model, prompt, refreshToken, retryCount + 1);
455
+ })();
456
+ }
457
+ throw err;
458
+ });
459
+ }
460
+
461
+ /**
462
+ * 提取消息中引用的文件URL
463
+ *
464
+ * @param messages 参考gpt系列消息格式,多轮对话请完整提供上下文
465
+ */
466
+ function extractRefFileUrls(messages: any[]) {
467
+ const urls = [];
468
+ // 如果没有消息,则返回[]
469
+ if (!messages.length) {
470
+ return urls;
471
+ }
472
+ // 只获取最新的消息
473
+ const lastMessage = messages[messages.length - 1];
474
+ if (_.isArray(lastMessage.content)) {
475
+ lastMessage.content.forEach((v) => {
476
+ if (!_.isObject(v) || !["file", "image_url"].includes(v["type"])) return;
477
+ // glm-free-api支持格式
478
+ if (
479
+ v["type"] == "file" &&
480
+ _.isObject(v["file_url"]) &&
481
+ _.isString(v["file_url"]["url"])
482
+ )
483
+ urls.push(v["file_url"]["url"]);
484
+ // 兼容gpt-4-vision-preview API格式
485
+ else if (
486
+ v["type"] == "image_url" &&
487
+ _.isObject(v["image_url"]) &&
488
+ _.isString(v["image_url"]["url"])
489
+ )
490
+ urls.push(v["image_url"]["url"]);
491
+ });
492
+ }
493
+ logger.info("本次请求上��:" + urls.length + "个文件");
494
+ return urls;
495
+ }
496
+
497
+ /**
498
+ * 消息预处理
499
+ *
500
+ * 由于接口只取第一条消息,此处会将多条消息合并为一条,实现多轮对话效果
501
+ *
502
+ * @param messages 参考gpt系列消息格式,多轮对话请完整提供上下文
503
+ * @param refs 参考文件列表
504
+ * @param isRefConv 是否为引用会话
505
+ */
506
+ function messagesPrepare(messages: any[], refs: any[], isRefConv = false) {
507
+ let content;
508
+ if (isRefConv || messages.length < 2) {
509
+ content = messages.reduce((content, message) => {
510
+ if (_.isArray(message.content)) {
511
+ return (
512
+ message.content.reduce((_content, v) => {
513
+ if (!_.isObject(v) || v["type"] != "text") return _content;
514
+ return _content + (v["text"] || "") + "\n";
515
+ }, content)
516
+ );
517
+ }
518
+ return content + `${message.content}\n`;
519
+ }, "");
520
+ logger.info("\n透传内容:\n" + content);
521
+ }
522
+ else {
523
+ // 检查最新消息是否含有"type": "image_url"或"type": "file",如果有则注入消息
524
+ let latestMessage = messages[messages.length - 1];
525
+ let hasFileOrImage =
526
+ Array.isArray(latestMessage.content) &&
527
+ latestMessage.content.some(
528
+ (v) => typeof v === "object" && ["file", "image_url"].includes(v["type"])
529
+ );
530
+ if (hasFileOrImage) {
531
+ let newFileMessage = {
532
+ content: "关注用户最新发送文件和消息",
533
+ role: "system",
534
+ };
535
+ messages.splice(messages.length - 1, 0, newFileMessage);
536
+ logger.info("注入提升尾部文件注意力system prompt");
537
+ } else {
538
+ // 由于注入会导致设定污染,暂时注释
539
+ // let newTextMessage = {
540
+ // content: "关注用户最新的消息",
541
+ // role: "system",
542
+ // };
543
+ // messages.splice(messages.length - 1, 0, newTextMessage);
544
+ // logger.info("注入提升尾部消息注意力system prompt");
545
+ }
546
+ content = (
547
+ messages.reduce((content, message) => {
548
+ const role = message.role
549
+ .replace("system", "<|sytstem|>")
550
+ .replace("assistant", "<|assistant|>")
551
+ .replace("user", "<|user|>");
552
+ if (_.isArray(message.content)) {
553
+ return (
554
+ message.content.reduce((_content, v) => {
555
+ if (!_.isObject(v) || v["type"] != "text") return _content;
556
+ return _content + (`${role}\n` + v["text"] || "") + "\n";
557
+ }, content)
558
+ );
559
+ }
560
+ return (content += `${role}\n${message.content}\n`);
561
+ }, "") + "<|assistant|>\n"
562
+ )
563
+ // 移除MD图像URL避免幻觉
564
+ .replace(/\!\[.+\]\(.+\)/g, "")
565
+ // 移除临时路径避免在新会话引发幻觉
566
+ .replace(/\/mnt\/data\/.+/g, "");
567
+ logger.info("\n对话合并:\n" + content);
568
+ }
569
+
570
+ const fileRefs = refs.filter((ref) => !ref.width && !ref.height);
571
+ const imageRefs = refs
572
+ .filter((ref) => ref.width || ref.height)
573
+ .map((ref) => {
574
+ ref.image_url = ref.file_url;
575
+ return ref;
576
+ });
577
+ return [
578
+ {
579
+ role: "user",
580
+ content: [
581
+ { type: "text", text: content },
582
+ ...(fileRefs.length == 0
583
+ ? []
584
+ : [
585
+ {
586
+ type: "file",
587
+ file: fileRefs,
588
+ },
589
+ ]),
590
+ ...(imageRefs.length == 0
591
+ ? []
592
+ : [
593
+ {
594
+ type: "image",
595
+ image: imageRefs,
596
+ },
597
+ ]),
598
+ ],
599
+ },
600
+ ];
601
+ }
602
+
603
+ /**
604
+ * 预检查文件URL有效性
605
+ *
606
+ * @param fileUrl 文件URL
607
+ */
608
+ async function checkFileUrl(fileUrl: string) {
609
+ if (util.isBASE64Data(fileUrl)) return;
610
+ const result = await axios.head(fileUrl, {
611
+ timeout: 15000,
612
+ validateStatus: () => true,
613
+ });
614
+ if (result.status >= 400)
615
+ throw new APIException(
616
+ EX.API_FILE_URL_INVALID,
617
+ `File ${fileUrl} is not valid: [${result.status}] ${result.statusText}`
618
+ );
619
+ // 检查文件大小
620
+ if (result.headers && result.headers["content-length"]) {
621
+ const fileSize = parseInt(result.headers["content-length"], 10);
622
+ if (fileSize > FILE_MAX_SIZE)
623
+ throw new APIException(
624
+ EX.API_FILE_EXECEEDS_SIZE,
625
+ `File ${fileUrl} is not valid`
626
+ );
627
+ }
628
+ }
629
+
630
+ /**
631
+ * 上传文件
632
+ *
633
+ * @param fileUrl 文件URL
634
+ * @param refreshToken 用于刷新access_token的refresh_token
635
+ */
636
+ async function uploadFile(fileUrl: string, refreshToken: string) {
637
+ // 预检查远程文件URL可用性
638
+ await checkFileUrl(fileUrl);
639
+
640
+ let filename, fileData, mimeType;
641
+ // 如果是BASE64数据则直接转换为Buffer
642
+ if (util.isBASE64Data(fileUrl)) {
643
+ mimeType = util.extractBASE64DataFormat(fileUrl);
644
+ const ext = mime.getExtension(mimeType);
645
+ filename = `${util.uuid()}.${ext}`;
646
+ fileData = Buffer.from(util.removeBASE64DataHeader(fileUrl), "base64");
647
+ }
648
+ // 下载文件到内存,如果您的服务器内存很小,建议考虑改造为流直传到下一个接口上,避免停留占用内存
649
+ else {
650
+ filename = path.basename(fileUrl);
651
+ ({ data: fileData } = await axios.get(fileUrl, {
652
+ responseType: "arraybuffer",
653
+ // 100M限制
654
+ maxContentLength: FILE_MAX_SIZE,
655
+ // 60秒超时
656
+ timeout: 60000,
657
+ }));
658
+ }
659
+
660
+ // 获取文件的MIME类型
661
+ mimeType = mimeType || mime.getType(filename);
662
+
663
+ const formData = new FormData();
664
+ formData.append("file", fileData, {
665
+ filename,
666
+ contentType: mimeType,
667
+ });
668
+
669
+ // 上传文件到目标OSS
670
+ const token = await acquireToken(refreshToken);
671
+ let result = await axios.request({
672
+ method: "POST",
673
+ url: "https://chatglm.cn/chatglm/backend-api/assistant/file_upload",
674
+ data: formData,
675
+ // 100M限制
676
+ maxBodyLength: FILE_MAX_SIZE,
677
+ // 60秒超时
678
+ timeout: 60000,
679
+ headers: {
680
+ Authorization: `Bearer ${token}`,
681
+ Referer: `https://chatglm.cn/`,
682
+ ...FAKE_HEADERS,
683
+ ...formData.getHeaders(),
684
+ },
685
+ validateStatus: () => true,
686
+ });
687
+ const { result: uploadResult } = checkResult(result, refreshToken);
688
+
689
+ return uploadResult;
690
+ }
691
+
692
+ /**
693
+ * 检查请求结果
694
+ *
695
+ * @param result 结果
696
+ */
697
+ function checkResult(result: AxiosResponse, refreshToken: string) {
698
+ if (!result.data) return null;
699
+ const { code, status, message } = result.data;
700
+ if (!_.isFinite(code) && !_.isFinite(status)) return result.data;
701
+ if (code === 0 || status === 0) return result.data;
702
+ if (code == 401) accessTokenMap.delete(refreshToken);
703
+ throw new APIException(EX.API_REQUEST_FAILED, `[请求glm失败]: ${message}`);
704
+ }
705
+
706
+ /**
707
+ * 从流接收完整的消息内容
708
+ *
709
+ * @param stream 消息流
710
+ */
711
+ async function receiveStream(stream: any): Promise<any> {
712
+ return new Promise((resolve, reject) => {
713
+ // 消息初始化
714
+ const data = {
715
+ id: "",
716
+ model: MODEL_NAME,
717
+ object: "chat.completion",
718
+ choices: [
719
+ {
720
+ index: 0,
721
+ message: { role: "assistant", content: "" },
722
+ finish_reason: "stop",
723
+ },
724
+ ],
725
+ usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
726
+ created: util.unixTimestamp(),
727
+ };
728
+ let toolCall = false;
729
+ let codeGenerating = false;
730
+ let textChunkLength = 0;
731
+ let codeTemp = "";
732
+ let lastExecutionOutput = "";
733
+ let textOffset = 0;
734
+ let refContent = '';
735
+ const parser = createParser((event) => {
736
+ try {
737
+ if (event.type !== "event") return;
738
+ // 解析JSON
739
+ const result = _.attempt(() => JSON.parse(event.data));
740
+ if (_.isError(result))
741
+ throw new Error(`Stream response invalid: ${event.data}`);
742
+ if (!data.id && result.conversation_id)
743
+ data.id = result.conversation_id;
744
+ if (result.status != "finish") {
745
+ const text = result.parts.reduce((str, part) => {
746
+ const { status, content, meta_data } = part;
747
+ if (!_.isArray(content)) return str;
748
+ const partText = content.reduce((innerStr, value) => {
749
+ const {
750
+ status: partStatus,
751
+ type,
752
+ text,
753
+ image,
754
+ code,
755
+ content,
756
+ } = value;
757
+ if (partStatus == "init" && textChunkLength > 0) {
758
+ textOffset += textChunkLength + 1;
759
+ textChunkLength = 0;
760
+ innerStr += "\n";
761
+ }
762
+ if (type == "text") {
763
+ if (toolCall) {
764
+ innerStr += "\n";
765
+ textOffset++;
766
+ toolCall = false;
767
+ }
768
+ if (partStatus == "finish") textChunkLength = text.length;
769
+ return innerStr + text;
770
+ } else if (
771
+ type == "quote_result" &&
772
+ status == "finish" &&
773
+ meta_data &&
774
+ _.isArray(meta_data.metadata_list)
775
+ ) {
776
+ refContent = meta_data.metadata_list.reduce((meta, v) => {
777
+ return meta + `${v.title} - ${v.url}\n`;
778
+ }, refContent);
779
+ } else if (
780
+ type == "image" &&
781
+ _.isArray(image) &&
782
+ status == "finish"
783
+ ) {
784
+ const imageText =
785
+ image.reduce(
786
+ (imgs, v) =>
787
+ imgs +
788
+ (/^(http|https):\/\//.test(v.image_url)
789
+ ? `![图像](${v.image_url || ""})`
790
+ : ""),
791
+ ""
792
+ ) + "\n";
793
+ textOffset += imageText.length;
794
+ toolCall = true;
795
+ return innerStr + imageText;
796
+ } else if (type == "code" && partStatus == "init") {
797
+ let codeHead = "";
798
+ if (!codeGenerating) {
799
+ codeGenerating = true;
800
+ codeHead = "```python\n";
801
+ }
802
+ const chunk = code.substring(codeTemp.length, code.length);
803
+ codeTemp += chunk;
804
+ textOffset += codeHead.length + chunk.length;
805
+ return innerStr + codeHead + chunk;
806
+ } else if (
807
+ type == "code" &&
808
+ partStatus == "finish" &&
809
+ codeGenerating
810
+ ) {
811
+ const codeFooter = "\n```\n";
812
+ codeGenerating = false;
813
+ codeTemp = "";
814
+ textOffset += codeFooter.length;
815
+ return innerStr + codeFooter;
816
+ } else if (
817
+ type == "execution_output" &&
818
+ _.isString(content) &&
819
+ partStatus == "done" &&
820
+ lastExecutionOutput != content
821
+ ) {
822
+ lastExecutionOutput = content;
823
+ const _content = content.replace(/^\n/, "");
824
+ textOffset += _content.length + 1;
825
+ return innerStr + _content + "\n";
826
+ }
827
+ return innerStr;
828
+ }, "");
829
+ return str + partText;
830
+ }, "");
831
+ const chunk = text.substring(
832
+ data.choices[0].message.content.length - textOffset,
833
+ text.length
834
+ );
835
+ data.choices[0].message.content += chunk;
836
+ } else {
837
+ data.choices[0].message.content =
838
+ data.choices[0].message.content.replace(/【\d+†(来源|source)】/g, "") + (refContent ? `\n\n搜索结果来自:\n${refContent.replace(/\n$/, '')}` : '');
839
+ resolve(data);
840
+ }
841
+ } catch (err) {
842
+ logger.error(err);
843
+ reject(err);
844
+ }
845
+ });
846
+ // 将流数据喂给SSE转换器
847
+ stream.on("data", (buffer) => parser.feed(buffer.toString()));
848
+ stream.once("error", (err) => reject(err));
849
+ stream.once("close", () => resolve(data));
850
+ });
851
+ }
852
+
853
+ /**
854
+ * 创建转换流
855
+ *
856
+ * 将流格式转换为gpt兼容流格式
857
+ *
858
+ * @param stream 消息流
859
+ * @param endCallback 传输结束回调
860
+ */
861
+ function createTransStream(stream: any, endCallback?: Function) {
862
+ // 消息创建时间
863
+ const created = util.unixTimestamp();
864
+ // 创建转换流
865
+ const transStream = new PassThrough();
866
+ let content = "";
867
+ let toolCall = false;
868
+ let codeGenerating = false;
869
+ let textChunkLength = 0;
870
+ let codeTemp = "";
871
+ let lastExecutionOutput = "";
872
+ let textOffset = 0;
873
+ !transStream.closed &&
874
+ transStream.write(
875
+ `data: ${JSON.stringify({
876
+ id: "",
877
+ model: MODEL_NAME,
878
+ object: "chat.completion.chunk",
879
+ choices: [
880
+ {
881
+ index: 0,
882
+ delta: { role: "assistant", content: "" },
883
+ finish_reason: null,
884
+ },
885
+ ],
886
+ created,
887
+ })}\n\n`
888
+ );
889
+ const parser = createParser((event) => {
890
+ try {
891
+ if (event.type !== "event") return;
892
+ // 解析JSON
893
+ const result = _.attempt(() => JSON.parse(event.data));
894
+ if (_.isError(result))
895
+ throw new Error(`Stream response invalid: ${event.data}`);
896
+ if (result.status != "finish" && result.status != "intervene") {
897
+ const text = result.parts.reduce((str, part) => {
898
+ const { status, content, meta_data } = part;
899
+ if (!_.isArray(content)) return str;
900
+ const partText = content.reduce((innerStr, value) => {
901
+ const {
902
+ status: partStatus,
903
+ type,
904
+ text,
905
+ image,
906
+ code,
907
+ content,
908
+ } = value;
909
+ if (partStatus == "init" && textChunkLength > 0) {
910
+ textOffset += textChunkLength + 1;
911
+ textChunkLength = 0;
912
+ innerStr += "\n";
913
+ }
914
+ if (type == "text") {
915
+ if (toolCall) {
916
+ innerStr += "\n";
917
+ textOffset++;
918
+ toolCall = false;
919
+ }
920
+ if (partStatus == "finish") textChunkLength = text.length;
921
+ return innerStr + text;
922
+ } else if (
923
+ type == "quote_result" &&
924
+ status == "finish" &&
925
+ meta_data &&
926
+ _.isArray(meta_data.metadata_list)
927
+ ) {
928
+ const searchText =
929
+ meta_data.metadata_list.reduce(
930
+ (meta, v) => meta + `检索 ${v.title}(${v.url}) ...`,
931
+ ""
932
+ ) + "\n";
933
+ textOffset += searchText.length;
934
+ toolCall = true;
935
+ return innerStr + searchText;
936
+ } else if (
937
+ type == "image" &&
938
+ _.isArray(image) &&
939
+ status == "finish"
940
+ ) {
941
+ const imageText =
942
+ image.reduce(
943
+ (imgs, v) =>
944
+ imgs +
945
+ (/^(http|https):\/\//.test(v.image_url)
946
+ ? `![图像](${v.image_url || ""})`
947
+ : ""),
948
+ ""
949
+ ) + "\n";
950
+ textOffset += imageText.length;
951
+ toolCall = true;
952
+ return innerStr + imageText;
953
+ } else if (type == "code" && partStatus == "init") {
954
+ let codeHead = "";
955
+ if (!codeGenerating) {
956
+ codeGenerating = true;
957
+ codeHead = "```python\n";
958
+ }
959
+ const chunk = code.substring(codeTemp.length, code.length);
960
+ codeTemp += chunk;
961
+ textOffset += codeHead.length + chunk.length;
962
+ return innerStr + codeHead + chunk;
963
+ } else if (
964
+ type == "code" &&
965
+ partStatus == "finish" &&
966
+ codeGenerating
967
+ ) {
968
+ const codeFooter = "\n```\n";
969
+ codeGenerating = false;
970
+ codeTemp = "";
971
+ textOffset += codeFooter.length;
972
+ return innerStr + codeFooter;
973
+ } else if (
974
+ type == "execution_output" &&
975
+ _.isString(content) &&
976
+ partStatus == "done" &&
977
+ lastExecutionOutput != content
978
+ ) {
979
+ lastExecutionOutput = content;
980
+ textOffset += content.length + 1;
981
+ return innerStr + content + "\n";
982
+ }
983
+ return innerStr;
984
+ }, "");
985
+ return str + partText;
986
+ }, "");
987
+ const chunk = text.substring(content.length - textOffset, text.length);
988
+ if (chunk) {
989
+ content += chunk;
990
+ const data = `data: ${JSON.stringify({
991
+ id: result.conversation_id,
992
+ model: MODEL_NAME,
993
+ object: "chat.completion.chunk",
994
+ choices: [
995
+ { index: 0, delta: { content: chunk }, finish_reason: null },
996
+ ],
997
+ created,
998
+ })}\n\n`;
999
+ !transStream.closed && transStream.write(data);
1000
+ }
1001
+ } else {
1002
+ const data = `data: ${JSON.stringify({
1003
+ id: result.conversation_id,
1004
+ model: MODEL_NAME,
1005
+ object: "chat.completion.chunk",
1006
+ choices: [
1007
+ {
1008
+ index: 0,
1009
+ delta:
1010
+ result.status == "intervene" &&
1011
+ result.last_error &&
1012
+ result.last_error.intervene_text
1013
+ ? { content: `\n\n${result.last_error.intervene_text}` }
1014
+ : {},
1015
+ finish_reason: "stop",
1016
+ },
1017
+ ],
1018
+ usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
1019
+ created,
1020
+ })}\n\n`;
1021
+ !transStream.closed && transStream.write(data);
1022
+ !transStream.closed && transStream.end("data: [DONE]\n\n");
1023
+ content = "";
1024
+ endCallback && endCallback(result.conversation_id);
1025
+ }
1026
+ } catch (err) {
1027
+ logger.error(err);
1028
+ !transStream.closed && transStream.end("\n\n");
1029
+ }
1030
+ });
1031
+ // 将流数据喂给SSE转换器
1032
+ stream.on("data", (buffer) => parser.feed(buffer.toString()));
1033
+ stream.once(
1034
+ "error",
1035
+ () => !transStream.closed && transStream.end("data: [DONE]\n\n")
1036
+ );
1037
+ stream.once(
1038
+ "close",
1039
+ () => !transStream.closed && transStream.end("data: [DONE]\n\n")
1040
+ );
1041
+ return transStream;
1042
+ }
1043
+
1044
+ /**
1045
+ * 从流接收图像
1046
+ *
1047
+ * @param stream 消息流
1048
+ */
1049
+ async function receiveImages(
1050
+ stream: any
1051
+ ): Promise<{ convId: string; imageUrls: string[] }> {
1052
+ return new Promise((resolve, reject) => {
1053
+ let convId = "";
1054
+ const imageUrls = [];
1055
+ const parser = createParser((event) => {
1056
+ try {
1057
+ if (event.type !== "event") return;
1058
+ // 解析JSON
1059
+ const result = _.attempt(() => JSON.parse(event.data));
1060
+ if (_.isError(result))
1061
+ throw new Error(`Stream response invalid: ${event.data}`);
1062
+ if (!convId && result.conversation_id) convId = result.conversation_id;
1063
+ if (result.status == "intervene")
1064
+ throw new APIException(EX.API_CONTENT_FILTERED);
1065
+ if (result.status != "finish") {
1066
+ result.parts.forEach((part) => {
1067
+ const { content } = part;
1068
+ if (!_.isArray(content)) return;
1069
+ content.forEach((value) => {
1070
+ const { status: partStatus, type, image, text } = value;
1071
+ if (
1072
+ type == "image" &&
1073
+ _.isArray(image) &&
1074
+ partStatus == "finish"
1075
+ ) {
1076
+ image.forEach((value) => {
1077
+ if (
1078
+ !/^(http|https):\/\//.test(value.image_url) ||
1079
+ imageUrls.indexOf(value.image_url) != -1
1080
+ )
1081
+ return;
1082
+ imageUrls.push(value.image_url);
1083
+ });
1084
+ }
1085
+ if (
1086
+ type == "text" &&
1087
+ partStatus == "finish"
1088
+ ) {
1089
+ const urlPattern = /\((https?:\/\/\S+)\)/g;
1090
+ let match;
1091
+ while ((match = urlPattern.exec(text)) !== null) {
1092
+ const url = match[1];
1093
+ if (imageUrls.indexOf(url) == -1)
1094
+ imageUrls.push(url);
1095
+ }
1096
+ }
1097
+ });
1098
+ });
1099
+ }
1100
+ } catch (err) {
1101
+ logger.error(err);
1102
+ reject(err);
1103
+ }
1104
+ });
1105
+ // 将流数据喂给SSE转换器
1106
+ stream.on("data", (buffer) => parser.feed(buffer.toString()));
1107
+ stream.once("error", (err) => reject(err));
1108
+ stream.once("close", () =>
1109
+ resolve({
1110
+ convId,
1111
+ imageUrls,
1112
+ })
1113
+ );
1114
+ });
1115
+ }
1116
+
1117
+ /**
1118
+ * Token切分
1119
+ *
1120
+ * @param authorization 认证字符串
1121
+ */
1122
+ function tokenSplit(authorization: string) {
1123
+ return authorization.replace("Bearer ", "").split(",");
1124
+ }
1125
+
1126
+ /**
1127
+ * 备用生成cookie
1128
+ *
1129
+ * 暂时还不需要
1130
+ *
1131
+ * @param refreshToken
1132
+ * @param token
1133
+ */
1134
+ function generateCookie(refreshToken: string, token: string) {
1135
+ const timestamp = util.unixTimestamp();
1136
+ const gsTimestamp = timestamp - Math.round(Math.random() * 2592000);
1137
+ return {
1138
+ chatglm_refresh_token: refreshToken,
1139
+ // chatglm_user_id: '',
1140
+ _ga_PMD05MS2V9: `GS1.1.${gsTimestamp}.18.0.${gsTimestamp}.0.0.0`,
1141
+ chatglm_token: token,
1142
+ chatglm_token_expires: util.getDateString("yyyy-MM-dd HH:mm:ss"),
1143
+ abtestid: "a",
1144
+ // acw_tc: ''
1145
+ };
1146
+ }
1147
+
1148
+ /**
1149
+ * 获取Token存活状态
1150
+ */
1151
+ async function getTokenLiveStatus(refreshToken: string) {
1152
+ const result = await axios.post(
1153
+ "https://chatglm.cn/chatglm/backend-api/v1/user/refresh",
1154
+ {},
1155
+ {
1156
+ headers: {
1157
+ Authorization: `Bearer ${refreshToken}`,
1158
+ Referer: "https://chatglm.cn/main/alltoolsdetail",
1159
+ "X-Device-Id": util.uuid(false),
1160
+ "X-Request-Id": util.uuid(false),
1161
+ ...FAKE_HEADERS,
1162
+ },
1163
+ timeout: 15000,
1164
+ validateStatus: () => true,
1165
+ }
1166
+ );
1167
+ try {
1168
+ const { result: _result } = checkResult(result, refreshToken);
1169
+ const { accessToken } = _result;
1170
+ return !!accessToken;
1171
+ }
1172
+ catch (err) {
1173
+ return false;
1174
+ }
1175
+ }
1176
+
1177
+ export default {
1178
+ createCompletion,
1179
+ createCompletionStream,
1180
+ generateImages,
1181
+ getTokenLiveStatus,
1182
+ tokenSplit,
1183
+ };
src/api/routes/chat.ts ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import _ from 'lodash';
2
+
3
+ import Request from '@/lib/request/Request.ts';
4
+ import Response from '@/lib/response/Response.ts';
5
+ import chat from '@/api/controllers/chat.ts';
6
+ import logger from '@/lib/logger.ts';
7
+
8
+ export default {
9
+
10
+ prefix: '/v1/chat',
11
+
12
+ post: {
13
+
14
+ '/completions': async (request: Request) => {
15
+ request
16
+ .validate('body.conversation_id', v => _.isUndefined(v) || _.isString(v))
17
+ .validate('body.messages', _.isArray)
18
+ .validate('headers.authorization', _.isString)
19
+ // refresh_token切分
20
+ const tokens = chat.tokenSplit(request.headers.authorization);
21
+ // 随机挑选一个refresh_token
22
+ const token = _.sample(tokens);
23
+ const { model, conversation_id: convId, messages, stream } = request.body;
24
+ const assistantId = /^[a-z0-9]{24,}$/.test(model) ? model : undefined
25
+ if (stream) {
26
+ const stream = await chat.createCompletionStream(messages, token, assistantId, convId);
27
+ return new Response(stream, {
28
+ type: "text/event-stream"
29
+ });
30
+ }
31
+ else
32
+ return await chat.createCompletion(messages, token, assistantId, convId);
33
+ }
34
+
35
+ }
36
+
37
+ }
src/api/routes/images.ts ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import _ from "lodash";
2
+
3
+ import Request from "@/lib/request/Request.ts";
4
+ import chat from "@/api/controllers/chat.ts";
5
+ import util from "@/lib/util.ts";
6
+
7
+ export default {
8
+ prefix: "/v1/images",
9
+
10
+ post: {
11
+ "/generations": async (request: Request) => {
12
+ request
13
+ .validate("body.prompt", _.isString)
14
+ .validate("headers.authorization", _.isString);
15
+ // refresh_token切分
16
+ const tokens = chat.tokenSplit(request.headers.authorization);
17
+ // 随机挑选一个refresh_token
18
+ const token = _.sample(tokens);
19
+ const prompt = request.body.prompt;
20
+ const responseFormat = _.defaultTo(request.body.response_format, "url");
21
+ const assistantId = /^[a-z0-9]{24,}$/.test(request.body.model) ? request.body.model : undefined
22
+ const imageUrls = await chat.generateImages(assistantId, prompt, token);
23
+ let data = [];
24
+ if (responseFormat == "b64_json") {
25
+ data = (
26
+ await Promise.all(imageUrls.map((url) => util.fetchFileBASE64(url)))
27
+ ).map((b64) => ({ b64_json: b64 }));
28
+ } else {
29
+ data = imageUrls.map((url) => ({
30
+ url,
31
+ }));
32
+ }
33
+ return {
34
+ created: util.unixTimestamp(),
35
+ data,
36
+ };
37
+ },
38
+ },
39
+ };