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 +9 -0
- .gitignore +4 -185
- Dockerfile +12 -12
- FreeGpt35/FreeGpt35.go +0 -231
- FreeGpt35Pool/FreeGpt35Pool.go +0 -128
- LICENSE +674 -0
- ProofWork/ProofWork.go +0 -55
- ProxyPool/ProxyPool.go +0 -87
- README.md +478 -43
- RequestClient/RequestClient.go +0 -10
- RequestClient/TlsClient.go +0 -77
- common/common.go +0 -285
- config/config.go +0 -74
- configs/dev/service.yml +6 -0
- configs/dev/system.yml +14 -0
- constant/constant.go +0 -11
- doc/example-0.png +0 -0
- doc/example-1.png +0 -0
- doc/example-10.png +0 -0
- doc/example-11.png +0 -0
- doc/example-12.png +0 -0
- doc/example-2.png +0 -0
- doc/example-3.png +0 -0
- doc/example-5.png +0 -0
- doc/example-6.png +0 -0
- doc/example-9.png +0 -0
- env.template +0 -8
- go.mod +0 -50
- go.sum +0 -139
- libs.d.ts +0 -0
- main.go +0 -35
- package.json +50 -0
- public/welcome.html +10 -0
- queue/queue.go +0 -106
- release.bat +0 -56
- router/middleware.go +0 -63
- router/router.go +0 -25
- service/v1/tokens.go +0 -20
- service/v1/util.go +0 -65
- service/v1Chat/completions.go +0 -19
- service/v1Chat/gpt35Completions.go +0 -231
- service/v1Chat/reqModel/apiReq.go +0 -14
- service/v1Chat/reqModel/chatReq.go +0 -31
- service/v1Chat/respModel/apiRespJson.go +0 -54
- service/v1Chat/respModel/apiRespStream.go +0 -43
- service/v1Chat/respModel/chatResp.go +0 -44
- src/api/consts/exceptions.ts +11 -0
- src/api/controllers/chat.ts +1183 -0
- src/api/routes/chat.ts +37 -0
- src/api/routes/images.ts +39 -0
.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 |
-
|
| 2 |
-
|
| 3 |
-
|
| 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
|
| 2 |
-
|
| 3 |
-
ENV CGO_ENABLED=0
|
| 4 |
|
| 5 |
WORKDIR /app
|
| 6 |
|
| 7 |
-
COPY
|
| 8 |
-
RUN go mod download
|
| 9 |
|
| 10 |
-
|
| 11 |
-
RUN go build -o /app/free-gpt3.5-2api .
|
| 12 |
|
| 13 |
-
FROM alpine
|
| 14 |
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
-
|
| 18 |
|
| 19 |
-
EXPOSE
|
| 20 |
|
| 21 |
-
CMD [ "
|
|
|
|
| 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 |
-
#
|
| 2 |
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
-
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
```
|
| 8 |
-
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
| 10 |
```
|
| 11 |
|
| 12 |
-
|
| 13 |
|
|
|
|
|
|
|
| 14 |
```
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
| 18 |
```
|
| 19 |
|
| 20 |
-
|
| 21 |
|
| 22 |
-
|
|
|
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
```
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
```
|
| 27 |
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
```
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
```
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
```
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
"messages": [
|
| 41 |
{
|
| 42 |
"role": "user",
|
| 43 |
-
"content": "
|
| 44 |
}
|
| 45 |
],
|
|
|
|
| 46 |
"stream": false
|
| 47 |
-
}
|
| 48 |
```
|
| 49 |
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
-
|
|
|
|
|
|
|
| 53 |
|
| 54 |
```
|
| 55 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
-
|
| 70 |
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
```
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
```
|
| 76 |
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
```
|
| 80 |
-
|
| 81 |
```
|
| 82 |
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
```
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
```
|
| 88 |
|
| 89 |
-
###
|
| 90 |
|
| 91 |
-
|
| 92 |
|
| 93 |
-
|
| 94 |
|
|
|
|
|
|
| 1 |
+
# GLM AI Free 服务
|
| 2 |
|
| 3 |
+
[](LICENSE)
|
| 4 |
+

|
| 5 |
+

|
| 6 |
+

|
| 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 |
+

|
| 76 |
+
|
| 77 |
+
### 智能体对话Demo
|
| 78 |
+
|
| 79 |
+
对应智能体链接:[网抑云评论生成器](https://chatglm.cn/main/gdetail/65c046a531d3fcb034918abe)
|
| 80 |
+
|
| 81 |
+

|
| 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 |
+

|
| 92 |
+
|
| 93 |
+
### AI绘图Demo
|
| 94 |
+
|
| 95 |
+

|
| 96 |
+
|
| 97 |
+
### 联网搜索Demo
|
| 98 |
+
|
| 99 |
+

|
| 100 |
+
|
| 101 |
+
### 长文档解读Demo
|
| 102 |
+
|
| 103 |
+

|
| 104 |
+
|
| 105 |
+
### 代码调用Demo
|
| 106 |
+
|
| 107 |
+

|
| 108 |
+
|
| 109 |
+
### 图像解析Demo
|
| 110 |
+
|
| 111 |
+

|
| 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 |
+

|
| 120 |
+
|
| 121 |
+
### 智能体接入
|
| 122 |
+
|
| 123 |
+
打开智能体的聊天界面,地址栏的一串ID就是智能体的ID,复制下来备用,这个值将用作调用时的 `model` 参数值。
|
| 124 |
+
|
| 125 |
+

|
| 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 |
+
[](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 |
+
? ``
|
| 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 |
+
? ``
|
| 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 |
+
};
|