diff --git a/.chglog/CHANGELOG.tpl.md b/.chglog/CHANGELOG.tpl.md new file mode 100644 index 0000000000000000000000000000000000000000..6375290f61ffdb9c45e35f41374b4a2b6ecca373 --- /dev/null +++ b/.chglog/CHANGELOG.tpl.md @@ -0,0 +1,42 @@ +{{ range .Versions }} + +## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }} +{{ range .CommitGroups -}} +### {{ .Title }} +{{ range .Commits -}} +- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} +{{ end }} +{{ end -}} + +{{- if .RevertCommits -}} +### Reverts +{{ range .RevertCommits -}} +- {{ .Revert.Header }} +{{ end }} +{{ end -}} + +{{- if .MergeCommits -}} +### Pull Requests +{{ range .MergeCommits -}} +- {{ .Header }} +{{ end }} +{{ end -}} + +{{- if .NoteGroups -}} +{{ range .NoteGroups -}} +### {{ .Title }} +{{ range .Notes }} +{{ .Body }} +{{ end }} +{{ end -}} +{{ end -}} +{{ end -}} + +{{- if .Versions }} +[Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD +{{ range .Versions -}} +{{ if .Tag.Previous -}} +[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }} +{{ end -}} +{{ end -}} +{{ end -}} \ No newline at end of file diff --git a/.chglog/config.yml b/.chglog/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..0bc4b1fc36c7f2e9331c0bc9d35dfa1d03ec269c --- /dev/null +++ b/.chglog/config.yml @@ -0,0 +1,28 @@ +style: github +template: CHANGELOG.tpl.md +info: + title: CHANGELOG + repository_url: https://github.com/aurorax-neo/free-gpt3.5-2api +options: + commits: + # filters: + # Type: + # - feat + # - fix + # - perf + # - refactor + commit_groups: + # title_maps: + # feat: Features + # fix: Bug Fixes + # perf: Performance Improvements + # refactor: Code Refactoring + header: + pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$" + pattern_maps: + - Type + - Scope + - Subject + notes: + keywords: + - BREAKING CHANGE \ No newline at end of file diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 9345012e258b899cb78ba1e88801a34fc5a2367e..0000000000000000000000000000000000000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.html linguist-language=Go diff --git a/.github/workflows/Auto Release.yml b/.github/workflows/Auto Release.yml new file mode 100644 index 0000000000000000000000000000000000000000..9c10051b3f89830b978c59bac4059f0cf57cde79 --- /dev/null +++ b/.github/workflows/Auto Release.yml @@ -0,0 +1,89 @@ +name: Auto Release + +on: + push: + tags: + - 'release-v*' + +env: + APP_NAME: free-gpt3.5-2api + +jobs: + release: + runs-on: ubuntu-latest + steps: + + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 'stable' + check-latest: true + + - name: Build Binary + run: | + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o ${{ env.APP_NAME }} -a -ldflags '-s -w -extldflags "-static"' . && rm -rf artifact && mkdir -p artifact && cp ${{ env.APP_NAME }} artifact/${{ env.APP_NAME }} && cd artifact && tar -czvf ../${{ env.APP_NAME }}-linux-amd64.tar.gz * && cd .. + GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -o ${{ env.APP_NAME }} -a -ldflags '-s -w -extldflags "-static"' . && rm -rf artifact && mkdir -p artifact && cp ${{ env.APP_NAME }} artifact/${{ env.APP_NAME }} && cd artifact && tar -czvf ../${{ env.APP_NAME }}-darwin-amd64.tar.gz * && cd .. + GOOS=freebsd GOARCH=amd64 CGO_ENABLED=0 go build -o ${{ env.APP_NAME }} -a -ldflags '-s -w -extldflags "-static"' . && rm -rf artifact && mkdir -p artifact && cp ${{ env.APP_NAME }} artifact/${{ env.APP_NAME }} && cd artifact && tar -czvf ../${{ env.APP_NAME }}-freebsd-amd64.tar.gz * && cd .. + GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o ${{ env.APP_NAME }} -a -ldflags '-s -w -extldflags "-static"' . && rm -rf artifact && mkdir -p artifact && cp ${{ env.APP_NAME }} artifact/${{ env.APP_NAME }}.exe && cd artifact && tar -czvf ../${{ env.APP_NAME }}-windows-amd64.tar.gz * && cd .. + + - name: Upload Artifact + uses: actions/upload-artifact@main + with: + name: ${{ env.APP_NAME }}-pre-built.zip + path: | + ${{ env.APP_NAME }}-windows-amd64.tar.gz + ${{ env.APP_NAME }}-linux-amd64.tar.gz + ${{ env.APP_NAME }}-darwin-amd64.tar.gz + ${{ env.APP_NAME }}-freebsd-amd64.tar.gz + + - name: Get Release Name + shell: bash + id: grn + run: echo "tag=$(echo $GITHUB_REF | sed 's|refs/tags/||')" >> $GITHUB_OUTPUT + + - name: Generate Changelog + id: changelog + run: | + export PATH=$PATH:$HOME/go/bin + go install github.com/git-chglog/git-chglog/cmd/git-chglog@latest + git-chglog -o CHANGELOG.md ${{ steps.grn.outputs.tag }} + + - name: Release + id: release + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') + env: + # GITHUB_TOKEN + GITHUB_TOKEN: ${{ secrets.GB_TOKEN }} + with: + # tag_name: 使用 tag + tag_name: ${{ github.ref }} + # release_name: 使用 tag + name: ${{ steps.grn.outputs.tag }} + # body: 使用 changelog + body_path: CHANGELOG.md + # 草稿 + draft: false + # 预发布 + prerelease: false + # 自动生成 release notes + generate_release_notes: true + # 上传文件 + files: | + ${{ env.APP_NAME }}-linux-amd64.tar.gz + ${{ env.APP_NAME }}-windows-amd64.tar.gz + ${{ env.APP_NAME }}-darwin-amd64.tar.gz + ${{ env.APP_NAME }}-freebsd-amd64.tar.gz + + - name: Delete Workflow Runs + uses: Mattraks/delete-workflow-runs@v2 + with: + token: ${{ secrets.GB_TOKEN }} + repository: ${{ github.repository }} + retain_days: 1 + keep_minimum_runs: 8 \ No newline at end of file diff --git a/.github/workflows/Build Docker Image.yml b/.github/workflows/Build Docker Image.yml new file mode 100644 index 0000000000000000000000000000000000000000..a40045ca687da43a4d01c0edfb51d511ab009a09 --- /dev/null +++ b/.github/workflows/Build Docker Image.yml @@ -0,0 +1,67 @@ +name: Build Docker Image + +on: + push: + tags: + - 'release-v*' + workflow_dispatch: + +env: + GHCR_REPO: ghcr.io/aurorax-neo/free-gpt3.5-2api + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get Docker Image Tag + shell: bash + id: get_docker_image_tag + run: echo "tag=$(echo $GITHUB_REF | sed 's|refs/tags/release-v||')" >> $GITHUB_OUTPUT + + - name: Set Up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set Up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login To GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GB_TOKEN }} + + - name: Cache Docker Layers + uses: actions/cache@v4 + with: + path: /tmp/.buildx-cache + key: "${{ runner.os }}-buildx-${{ github.sha }}" + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Build Docker Image And Push To GHCR + uses: docker/build-push-action@v5 + with: + push: true + context: . + platforms: linux/amd64,linux/arm64 + file: Dockerfile + tags: | + ${{ env.GHCR_REPO }}:latest + ${{ env.GHCR_REPO }}:${{ steps.get_docker_image_tag.outputs.tag }} + ${{ env.GHCR_REPO }}:${{ github.sha }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache,mode=max + + - name: Delete Workflow Runs + uses: Mattraks/delete-workflow-runs@v2 + with: + token: ${{ secrets.GB_TOKEN }} + repository: ${{ github.repository }} + retain_days: 1 + keep_minimum_runs: 8 \ No newline at end of file diff --git a/.gitignore b/.gitignore index a9d06f8848e799707645633d5b2783de52397cf5..bc9ddb2fb07ed1babe95a6299513d7bbbed3f3c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,185 @@ -tools/authenticator/100-ACCOUNTS_COMPILED.txt -tools/authenticator/accounts.txt -tools/authenticator/proxies.txt -tools/authenticator/authenticated_accounts.txt -tools/authenticator/access_tokens.txt -*.txt -aurora -chatgpttoapi -tools/authenticator/.proxies.txt.swp -.env -*.har -.idea/ +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Go template +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +### GoLand template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + /logs/ +/.env /target/ -/bin/ +/CHANGELOG.md diff --git a/Dockerfile b/Dockerfile index 64e0b01281731f117aa519d4c78bc1f8ca5169f8..b6ae84c81107f21fb62e5bbe9960f52492324b16 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,30 +1,21 @@ -# 使用 Go 1.21 官方镜像作为构建环境 FROM golang:1.21 AS builder -# 禁用 CGO ENV CGO_ENABLED=0 -# 设置工作目录 WORKDIR /app -# 复制 go.mod 和 go.sum 并下载依赖 COPY go.mod go.sum ./ RUN go mod download -# 复制源代码并构建应用 COPY . . -RUN go build -ldflags "-s -w" -o /app/duck2api . +RUN go build -o /app/free-gpt3.5-2api . -# 使用 Alpine Linux 作为最终镜像 FROM alpine:latest -# 设置工作目录 WORKDIR /app -# 从构建阶段复制编译好的应用和资源 -COPY --from=builder /app/duck2api /app/duck2api +COPY --from=builder /app/free-gpt3.5-2api /app/free-gpt3.5-2api -# 暴露端口 -EXPOSE 8080 +EXPOSE 3040 -CMD ["/app/duck2api"] +CMD [ "./free-gpt3.5-2api" ] diff --git a/FreeGpt35/FreeGpt35.go b/FreeGpt35/FreeGpt35.go new file mode 100644 index 0000000000000000000000000000000000000000..790720dd1ffa41c102ee3ee470974eba0480f2b4 --- /dev/null +++ b/FreeGpt35/FreeGpt35.go @@ -0,0 +1,231 @@ +package FreeGpt35 + +import ( + "encoding/json" + "fmt" + ProofWork2 "free-gpt3.5-2api/ProofWork" + "free-gpt3.5-2api/ProxyPool" + "free-gpt3.5-2api/RequestClient" + "free-gpt3.5-2api/common" + "free-gpt3.5-2api/config" + "free-gpt3.5-2api/constant" + "github.com/aurorax-neo/go-logger" + fhttp "github.com/bogdanfinn/fhttp" + "github.com/google/uuid" + "io" +) + +var ( + BaseUrl = config.BaseUrl + ChatUrl = BaseUrl + "/backend-anon/conversation" + AuthUrl = BaseUrl + "/backend-anon/sentinel/chat-requirements" + OfficialBaseURLS = []string{"https://chat.openai.com", "https://chatgpt.com"} +) + +// NewFreeAuthType 定义一个枚举类型 +type NewFreeAuthType int + +const ( + NewFreeAuthNormal NewFreeAuthType = 0 //正常获取 + NewFreeAuthRefresh NewFreeAuthType = 1 // 刷新获取 +) + +type FreeGpt35 struct { + RequestClient RequestClient.RequestClient + Proxy *ProxyPool.Proxy + MaxUseCount int + ExpiresAt int64 + FreeAuth *freeAuth + Ua string + Cookies []*fhttp.Cookie +} + +type freeAuth struct { + OaiDeviceId string `json:"-"` + Persona string `json:"persona"` + Arkose arkose `json:"arkose"` + Turnstile turnstile `json:"turnstile"` + ProofWork ProofWork2.ProofWork `json:"proofofwork"` + Token string `json:"token"` +} + +type arkose struct { + Required bool `json:"required"` + Dx string `json:"dx"` +} + +type turnstile struct { + Required bool `json:"required"` +} + +// NewFreeGpt35 创建 FreeGpt35 实例 0 无论网络是否被标记限制都获取 1 在网络未标记时才能获取 +func NewFreeGpt35(newType NewFreeAuthType, maxUseCount int, expiresAt int64) *FreeGpt35 { + // 创建 FreeGpt35 实例 + freeGpt35 := &FreeGpt35{ + MaxUseCount: maxUseCount, + ExpiresAt: expiresAt, + FreeAuth: &freeAuth{}, + } + // 获取请求客户端 + err := freeGpt35.newRequestClient() + if err != nil { + logger.Logger.Debug(err.Error()) + return nil + } + // 获取并设置代理 + err = freeGpt35.getProxy(newType) + if err != nil { + logger.Logger.Debug(err.Error()) + return nil + } + // 获取cookies + if common.IsStrInArray(BaseUrl, OfficialBaseURLS) { + err = freeGpt35.getCookies() + if err != nil { + logger.Logger.Debug(err.Error()) + return nil + } + } + // 获取 FreeAuth + err = freeGpt35.newFreeAuth(newType) + if err != nil { + logger.Logger.Debug(err.Error()) + return nil + } + return freeGpt35 +} + +func (FG *FreeGpt35) NewRequest(method, url string, body io.Reader) (*fhttp.Request, error) { + request, err := RequestClient.NewRequest(method, url, body) + if err != nil { + return nil, err + } + request.Header.Set("accept", "*/*") + request.Header.Set("accept-language", "zh-CN,zh;q=0.9,zh-Hans;q=0.8,en;q=0.7") + for _, cookie := range FG.Cookies { + request.AddCookie(cookie) + } + request.Header.Set("oai-language", "en-US") + request.Header.Set("origin", common.GetOrigin(url)) + request.Header.Set("referer", common.GetOrigin(url)) + request.Header.Set("sec-ch-ua", `"Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"`) + request.Header.Set("sec-ch-ua-mobile", "?0") + request.Header.Set("sec-ch-ua-platform", `"Windows"`) + request.Header.Set("sec-fetch-dest", "empty") + request.Header.Set("sec-fetch-mode", "cors") + request.Header.Set("sec-fetch-site", "same-origin") + request.Header.Set("user-agent", FG.Ua) + return request, nil +} + +func (FG *FreeGpt35) newRequestClient() error { + // 请求客户端 + FG.RequestClient = RequestClient.NewTlsClient(300, constant.ClientProfile) + if FG.RequestClient == nil { + errStr := fmt.Sprint("RequestClient is nil") + logger.Logger.Debug(errStr) + return fmt.Errorf(errStr) + } + return nil +} + +func (FG *FreeGpt35) getProxy(newFreeAuthType NewFreeAuthType) error { + // 获取代理池 + ProxyPoolInstance := ProxyPool.GetProxyPoolInstance() + // 获取代理 + FG.Proxy = ProxyPoolInstance.GetProxy() + // 判断代理是否可用 + if FG.Proxy.CanUseAt > common.GetTimestampSecond(0) && newFreeAuthType == NewFreeAuthRefresh { + errStr := fmt.Sprint(FG.Proxy.Link, ": Proxy restricted, Reuse at ", FG.Proxy.CanUseAt) + return fmt.Errorf(errStr) + } + // Ua + FG.Ua = FG.Proxy.Ua + // 补全cookies + FG.Cookies = append(FG.Cookies, FG.Proxy.Cookies...) + // 设置代理 + err := FG.RequestClient.SetProxy(FG.Proxy.Link.String()) + if err != nil { + errStr := fmt.Sprint("SetProxy Error: ", err) + logger.Logger.Debug(errStr) + } + return nil +} + +func (FG *FreeGpt35) getCookies() error { + // 获取cookies + request, err := FG.NewRequest("GET", fmt.Sprint(BaseUrl, "/?oai-dm=1"), nil) + if err != nil { + return err + } + // 设置请求头 + 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") + // 发送 GET 请求 + response, err := FG.RequestClient.Do(request) + if err != nil { + return err + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(response.Body) + if response.StatusCode != 200 { + return fmt.Errorf("StatusCode: %d", response.StatusCode) + } + // 获取cookies + cookies := response.Cookies() + for i, cookie := range cookies { + if cookie.Name == "oai-did" { + FG.FreeAuth.OaiDeviceId = cookie.Value + cookies = append(cookies[:i], cookies[i+1:]...) + } + if cookie.Name == "__Secure-next-auth.callback-url" { + cookie.Value = BaseUrl + } + } + // 设置cookies + FG.Cookies = append(FG.Cookies, cookies...) + return nil +} + +func (FG *FreeGpt35) newFreeAuth(newFreeAuthType NewFreeAuthType) error { + // 生成新的设备 ID + if FG.FreeAuth.OaiDeviceId == "" { + FG.FreeAuth.OaiDeviceId = uuid.New().String() + } + // 创建请求 + request, err := FG.NewRequest("POST", AuthUrl, nil) + if err != nil { + return err + } + // 设置请求头 + request.Header.Set("Content-Type", "application/json") + request.Header.Set("oai-device-id", FG.FreeAuth.OaiDeviceId) + // 发送 POST 请求 + response, err := FG.RequestClient.Do(request) + if err != nil { + return err + } + if response.StatusCode != 200 { + logger.Logger.Debug(fmt.Sprint("newFreeAuth: StatusCode: ", response.StatusCode)) + if (response.StatusCode == 429 || response.StatusCode == 403) && newFreeAuthType == NewFreeAuthRefresh { + FG.Proxy.CanUseAt = common.GetTimestampSecond(300) + logger.Logger.Debug(fmt.Sprint("newFreeAuth: Proxy(", FG.Proxy.Link, ")restricted, Reuse at ", FG.Proxy.CanUseAt)) + } + return fmt.Errorf("StatusCode: %d", response.StatusCode) + } else if newFreeAuthType == 0 { + // 成功后更新代理的可用时间 + FG.Proxy.CanUseAt = common.GetTimestampSecond(0) + logger.Logger.Debug(fmt.Sprint("newFreeAuth: Proxy(", FG.Proxy.Link, ")Reuse at ", FG.Proxy.CanUseAt)) + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(response.Body) + if err := json.NewDecoder(response.Body).Decode(&FG.FreeAuth); err != nil { + return err + } + // ProofWork + if FG.FreeAuth.ProofWork.Required { + FG.FreeAuth.ProofWork.Ospt = ProofWork2.CalcProofToken(FG.FreeAuth.ProofWork.Seed, FG.FreeAuth.ProofWork.Difficulty, request.Header.Get("User-Agent")) + } + return nil +} diff --git a/FreeGpt35Pool/FreeGpt35Pool.go b/FreeGpt35Pool/FreeGpt35Pool.go new file mode 100644 index 0000000000000000000000000000000000000000..8d5c27d271f406e33fc9ec6dbcd2d002f9b1b9f7 --- /dev/null +++ b/FreeGpt35Pool/FreeGpt35Pool.go @@ -0,0 +1,128 @@ +package FreeGpt35Pool + +import ( + "fmt" + "free-gpt3.5-2api/FreeGpt35" + "free-gpt3.5-2api/common" + "free-gpt3.5-2api/config" + "free-gpt3.5-2api/queue" + "github.com/aurorax-neo/go-logger" + "sync" + "time" +) + +var ( + instance *FreeGpt35Pool + once sync.Once +) + +type FreeGpt35Pool struct { + queue *queue.Queue + capacity int // 队列容量 +} + +func newFreeGpt35Pool(capacity int) *FreeGpt35Pool { + return &FreeGpt35Pool{ + queue: queue.New(), + capacity: capacity, + } +} + +func GetFreeGpt35PoolInstance() *FreeGpt35Pool { + once.Do(func() { + logger.Logger.Info(fmt.Sprint("Init FreeGpt35Pool...")) + // 初始化 FreeGpt35Pool + instance = newFreeGpt35Pool(config.PoolMaxCount) + // 定时刷新 FreeGpt35Pool + instance.refreshFreeGpt35Pool(time.Millisecond * 256) + // + logger.Logger.Info(fmt.Sprint("Init FreeGpt35Pool Success", ", PoolMaxCount: ", config.PoolMaxCount, ", AuthExpirationDate: ", config.AuthED)) + }) + return instance +} + +func (G *FreeGpt35Pool) refreshFreeGpt35Pool(sleep time.Duration) { + // 检测 FreeGpt35Pool 是否已满 + common.AsyncLoopTask(sleep, func() { + // 判断 FreeGpt35Pool 是否已满 + if G.IsFull() { + return + } + // 获取新 FreeGpt35 实例 + gpt35 := FreeGpt35.NewFreeGpt35(FreeGpt35.NewFreeAuthRefresh, 1, common.GetTimestampSecond(config.AuthED)) + // 判断 FreeGpt35 实例是否有效 + if G.isLiveGpt35(gpt35) { + // 入队新 FreeGpt35 实例 + G.AddFreeGpt35(gpt35) + } + }) + // 检测并移除无效 FreeGpt35 实例 + common.AsyncLoopTask(sleep, func() { + // 遍历队列中的所有元素 + G.queue.Traverse(func(n *queue.Node) { + // 判断是否为无效 FreeGpt35 实例 + if !G.isLiveGpt35(n.Value.(*FreeGpt35.FreeGpt35)) { + // 移除无效 FreeGpt35 实例 + G.queue.Remove(n) + } + }) + }) +} + +func (G *FreeGpt35Pool) isLiveGpt35(gpt35 *FreeGpt35.FreeGpt35) bool { + //判断是否为空 + if gpt35 == nil || + gpt35.MaxUseCount <= 0 || //无可用次数 + gpt35.ExpiresAt <= common.GetTimestampSecond(0) { + return false + } + return true +} + +func (G *FreeGpt35Pool) GetFreeGpt35(retry int) *FreeGpt35.FreeGpt35 { + // 获取 FreeGpt35 实例 + n := G.queue.Peek() + if n != nil { + gpt35 := n.Value.(*FreeGpt35.FreeGpt35) + if G.isLiveGpt35(gpt35) { //有缓存 + // 深拷贝 + gpt35_ := common.DeepCopyStruct(gpt35).(*FreeGpt35.FreeGpt35) + // 减少 FreeGpt35 实例的最大使用次数 + gpt35.MaxUseCount-- + // 判断 FreeGpt35 实例是否有效 无效则移除 + if !G.isLiveGpt35(gpt35) { + G.queue.Dequeue() + } + return gpt35_ + } else if retry > 0 { + time.Sleep(time.Millisecond * 128) + return G.GetFreeGpt35(retry - 1) + } + } + // 缓存内无可用 FreeGpt35 实例,返回新 FreeGpt35 实例 + return FreeGpt35.NewFreeGpt35(FreeGpt35.NewFreeAuthNormal, 1, common.GetTimestampSecond(config.AuthED)) +} + +// GetSize 获取队列当前元素个数 +func (G *FreeGpt35Pool) GetSize() int { + return G.queue.Len() +} + +// GetCapacity 获取队列容量 +func (G *FreeGpt35Pool) GetCapacity() int { + return G.capacity +} + +// IsFull 检查队列是否已满 +func (G *FreeGpt35Pool) IsFull() bool { + return G.GetSize() == G.capacity +} + +// AddFreeGpt35 入队 +func (G *FreeGpt35Pool) AddFreeGpt35(v *FreeGpt35.FreeGpt35) bool { + if G.IsFull() || v == nil { + return false + } + G.queue.Enqueue(v) + return true +} diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d322e6dbf9a9471b0de438b2a539c4c9f4752a55..0000000000000000000000000000000000000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 aurora-develop - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Procfile b/Procfile deleted file mode 100644 index ae76712ac18a1eac1744a2e4bd74b805e45d9286..0000000000000000000000000000000000000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: aurora \ No newline at end of file diff --git a/ProofWork/ProofWork.go b/ProofWork/ProofWork.go new file mode 100644 index 0000000000000000000000000000000000000000..12d5cc363e1630f22cc3b3825b3abc8ea23b03e8 --- /dev/null +++ b/ProofWork/ProofWork.go @@ -0,0 +1,55 @@ +package ProofWork + +import ( + "encoding/base64" + "encoding/hex" + "encoding/json" + "golang.org/x/crypto/sha3" + "math/rand" + "time" +) + +var ( + numberCollisions = 100000 + cores = []int{8, 12, 16, 24} + screens = []int{3000, 4000, 6000} + timeLayout = "Mon Jan 2 2006 15:04:05" +) + +type ProofWork struct { + Difficulty string `json:"difficulty,omitempty"` + Required bool `json:"required"` + Seed string `json:"seed,omitempty"` + Ospt string `json:"-"` +} + +func getParseTime() string { + now := time.Now() + return now.Format(timeLayout) + " GMT" + now.Format("-0700 MST (MST)") +} + +func getConfig(userAgent string) []interface{} { + rand.New(rand.NewSource(time.Now().UnixNano())) + core := cores[rand.Intn(4)] + rand.New(rand.NewSource(time.Now().UnixNano())) + screen := screens[rand.Intn(3)] + return []interface{}{core + screen, getParseTime(), int64(4294705152), 0, userAgent} + +} + +func CalcProofToken(seed string, diff string, userAgent string) string { + config := getConfig(userAgent) + hasher := sha3.New512() + for i := 0; i < numberCollisions; i++ { + config[3] = i + jsonStr, _ := json.Marshal(config) + base := base64.StdEncoding.EncodeToString(jsonStr) + hasher.Write([]byte(seed + base)) + hash := hasher.Sum(nil) + hasher.Reset() + if hex.EncodeToString(hash[:len(diff)]) <= diff { + return "gAAAAAB" + base + } + } + return "gAAAAABwQ8Lk5FbGpA2NcR9dShT6gYjU7VxZ4D" + base64.StdEncoding.EncodeToString([]byte(`"`+seed+`"`)) +} diff --git a/ProxyPool/ProxyPool.go b/ProxyPool/ProxyPool.go new file mode 100644 index 0000000000000000000000000000000000000000..c512968654b22c9e069714ad4caca8640664149f --- /dev/null +++ b/ProxyPool/ProxyPool.go @@ -0,0 +1,87 @@ +package ProxyPool + +import ( + "fmt" + "free-gpt3.5-2api/common" + "free-gpt3.5-2api/config" + "free-gpt3.5-2api/constant" + "github.com/aurorax-neo/go-logger" + fhttp "github.com/bogdanfinn/fhttp" + "net/url" + "sync" + "time" +) + +var ( + Instance *ProxyPool + Once sync.Once +) + +type ProxyPool struct { + Proxies []*Proxy + Index int +} + +type Proxy struct { + Link *url.URL + CanUseAt int64 + Ua string + Cookies []*fhttp.Cookie +} + +func GetProxyPoolInstance() *ProxyPool { + Once.Do(func() { + logger.Logger.Info(fmt.Sprint("Init ProxyPool...")) + // 初始化 ProxyPool + Instance = NewProxyPool(nil) + // 遍历配置文件中的代理 添加到代理池 + for _, px := range config.Proxy { + proxy := NewProxy(px, common.GetTimestampSecond(0), constant.Ua) + _ = proxy.getCookies() + Instance.AddProxy(proxy) + } + //定时刷新代理cookies + common.AsyncLoopTask(1*time.Minute, func() { + for _, proxy := range Instance.Proxies { + _ = proxy.getCookies() + } + }) + logger.Logger.Info(fmt.Sprint("Init ProxyPool Success")) + }) + return Instance +} + +func NewProxyPool(proxies []*Proxy) *ProxyPool { + proxy := NewProxy("", common.GetTimestampSecond(0), constant.Ua) + _ = proxy.getCookies() + return &ProxyPool{ + Proxies: append([]*Proxy{proxy}, proxies...), + Index: 0, + } +} + +func (PP *ProxyPool) GetProxy() *Proxy { + PP.Index = (PP.Index + 1) % len(PP.Proxies) + // 如果配置了代理 不会使用无代理 + if PP.Index == 0 && len(PP.Proxies) > 1 { + PP.Index = 1 + } + // 返回代理 + return PP.Proxies[PP.Index] +} + +func (PP *ProxyPool) AddProxy(proxy *Proxy) { + PP.Proxies = append(PP.Proxies, proxy) +} + +func NewProxy(link string, cannotUseTime int64, ua string) *Proxy { + return &Proxy{ + Link: common.ParseUrl(link), + CanUseAt: cannotUseTime, + Ua: ua, + } +} + +func (P *Proxy) getCookies() error { + return nil +} diff --git a/README.md b/README.md index e0990e7cae55755151cb993efaa4504877768e4c..ff4a049f8f28a69e6000db27ee70a158a7ad76d3 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,94 @@ -# duck2api +# [free-gpt3.5-2api](https://github.com/aurorax-neo/free-gpt3.5-2api) +## 接口 +#### /v1/tokens -# 交流群 -https://t.me/aurora_develop - -# Web端 - -访问http://你的服务器ip:8080/web - -![web使用](https://jsd.cdn.zzko.cn/gh/xiaozhou26/tuph@main/images/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202024-04-07%20111706.png) +``` +curl --location --request GET 'http://127.0.0.1:9846/v1/tokens' \ +--header 'Authorization: Bearer abc' +``` -## Deploy +返回示例说明:`count`为授权池中可用授权数,如果`count` 为 `0`请检查`ip`是否支持 `openai` +``` +{ + "count": 0 +} +``` -### Render部署 -[![Deploy](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy) +#### /v1/chat/completions -### 编译部署 +###### 支持返回stream和json -```bash -git clone https://github.com/aurora-develop/duck2api -cd duck2api -go build -o duck2api -chmod +x ./duck2api -./duck2api +``` +http://:/v1/chat/completions ``` -### Docker部署 -## Docker部署 -您需要安装Docker和Docker Compose。 +##### 示例 -```bash -docker run -d \ - --name duck2api \ - -p 8080:8080 \ - ghcr.io/aurora-develop/duck2api:latest ``` - -## Docker Compose部署 -创建一个新的目录,例如duck2api,并进入该目录: -```bash -mkdir duck2api -cd duck2api +curl http://127.0.0.1:9846 ``` -在此目录中下载库中的docker-compose.yml文件: -```bash -docker-compose up -d ``` - -## Usage - -```bash -curl --location 'http://你的服务器ip:8080/v1/chat/completions' \ +curl --location --request POST 'http://127.0.0.1:9846/v1/chat/completions' \ +--header 'Authorization: Bearer abc' \ --header 'Content-Type: application/json' \ ---data '{ - "model": "gpt-3.5-turbo", - "messages": [{"role": "user", "content": "Say this is a test!"}], - "stream": true - }' +--data-raw '{ + "model": "gpt-3.5-turbo", + "messages": [ + { + "role": "user", + "content": "西红柿炒钢丝球怎么做?" + } + ], + "stream": false +}' ``` -支持claude和gpt-3.5-turbo -## 高级设置 -默认情况不需要设置,除非你有需求 +## 配置 ### 环境变量 + +``` +LOG_LEVEL=info # debug, info, warn, error +LOG_PATH= # 日志文件路径,默认为空(不生成日志文件) +BIND=0.0.0.0 # 127.0.0.1 +PORT=3040 +PROXY= # http://127.0.0.1:7890,http://127.0.0.1:7890 已支持多个代理(英文 "," 分隔) +AUTHORIZATIONS= # abc,bac (英文 "," 分隔) +BASE_URL= # 默认:https://chat.openai.com +POOL_MAX_COUNT=64 # max number of connections to keep in the pool 默认:64 +AUTH_ED=600 # expiration time for the authorization in seconds 默认:600 ``` -Authorization=your_authorization 用户认证 key。 -TLS_CERT=path_to_your_tls_cert 存储TLS(传输层安全协议)证书的路径。 -TLS_KEY=path_to_your_tls_key 存储TLS(传输层安全协议)证书的路径。 -PROXY_URL=your_proxy_url 添加代理池来。 +###### 也可使用与程序同目录下 `.env` 文件配置上述字段 + + +### docker部署 + +##### 1 .创建文件夹 + +``` +mkdir -p $PWD/free-gpt3.5-2api ``` -## 鸣谢 +##### 2.拉取镜像启动 -感谢各位大佬的pr支持,感谢。 +``` +docker run -itd --name=free-gpt3.5-2api -p 9846:3040 ghcr.io/aurorax-neo/free-gpt3.5-2api +``` +##### 3.更新容器 -## 参考项目 +``` +docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR free-gpt3.5-2api --debug +``` +### Koyeb部署 -https://github.com/xqdoo00o/ChatGPT-to-API +###### 注意:`Regions`请选择支持`openai`免登的区域!!! -## License +[![Deploy to Koyeb](https://www.koyeb.com/static/images/deploy/button.svg)](https://app.koyeb.com/deploy?type=docker&name=free-gpt3-5-2api®ion=par&ports=3040;http;/&image=ghcr.io/aurorax-neo/free-gpt3.5-2api) -MIT License diff --git a/RequestClient/RequestClient.go b/RequestClient/RequestClient.go new file mode 100644 index 0000000000000000000000000000000000000000..6038a6bdc16c62396a2bc434b36caaeaeec37e16 --- /dev/null +++ b/RequestClient/RequestClient.go @@ -0,0 +1,10 @@ +package RequestClient + +import ( + fhttp "github.com/bogdanfinn/fhttp" +) + +type RequestClient interface { + Do(req *fhttp.Request) (*fhttp.Response, error) + SetProxy(link string) error +} diff --git a/RequestClient/TlsClient.go b/RequestClient/TlsClient.go new file mode 100644 index 0000000000000000000000000000000000000000..c9662ced147072107af5a0804b9bf9f68238d605 --- /dev/null +++ b/RequestClient/TlsClient.go @@ -0,0 +1,77 @@ +package RequestClient + +import ( + fhttp "github.com/bogdanfinn/fhttp" + tlsClient "github.com/bogdanfinn/tls-client" + "github.com/bogdanfinn/tls-client/profiles" + "io" + "math/rand" + "time" +) + +type TlsClient struct { + client tlsClient.HttpClient +} + +func NewTlsClient(timeoutSeconds int, clientProfile profiles.ClientProfile) *TlsClient { + jar := tlsClient.NewCookieJar() + options := []tlsClient.HttpClientOption{ + tlsClient.WithTimeoutSeconds(timeoutSeconds), + tlsClient.WithClientProfile(clientProfile), + tlsClient.WithNotFollowRedirects(), + tlsClient.WithCookieJar(jar), + } + client, err := tlsClient.NewHttpClient(tlsClient.NewNoopLogger(), options...) + if err != nil { + return nil + } + return &TlsClient{ + client: client, + } +} + +func RandomClientProfile() profiles.ClientProfile { + // 初始化随机数生成器 + seed := time.Now().UnixNano() + rng := rand.New(rand.NewSource(seed)) + clientProfiles := []profiles.ClientProfile{ + profiles.Firefox_102, + profiles.Safari_15_6_1, + profiles.Safari_16_0, + profiles.Chrome_110, + profiles.Okhttp4Android13, + profiles.CloudflareCustom, + profiles.Firefox_117, + } + // 随机选择一个 + randomIndex := rng.Intn(len(clientProfiles)) + return clientProfiles[randomIndex] +} + +func NewRequest(method, url string, body io.Reader) (*fhttp.Request, error) { + request, err := fhttp.NewRequest(method, url, body) + if err != nil { + return nil, err + } + return request, nil + +} + +func (T *TlsClient) Do(req *fhttp.Request) (*fhttp.Response, error) { + response, err := T.client.Do(req) + if err != nil { + return nil, err + } + return response, nil +} + +func (T *TlsClient) SetProxy(link string) error { + if link == "" { + return nil + } + err := T.client.SetProxy(link) + if err != nil { + return err + } + return nil +} diff --git a/VERSION b/VERSION deleted file mode 100644 index 227cea215648b1af34a87c9acf5b707fe02d2072..0000000000000000000000000000000000000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -2.0.0 diff --git a/api/router.go b/api/router.go deleted file mode 100644 index 481e8ebd3bbfbd70bd3f450d8673256007d8b1f6..0000000000000000000000000000000000000000 --- a/api/router.go +++ /dev/null @@ -1,18 +0,0 @@ -package api - -import ( - "aurora/initialize" - "github.com/gin-gonic/gin" - "net/http" -) - -var router *gin.Engine - -func init() { - // 初始化gin - router = initialize.RegisterRouter() -} - -func Listen(w http.ResponseWriter, r *http.Request) { - router.ServeHTTP(w, r) -} diff --git a/build.sh b/build.sh deleted file mode 100644 index 3ccae9aeeb4d087bac5c84a7b51689d09966544e..0000000000000000000000000000000000000000 --- a/build.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -export GOPROXY=https://goproxy.io - -go get - -export CGO_ENABLED=0 -PKG=aurora - -targets=( - "windows/amd64" - "linux/amd64" - "darwin/amd64" - "windows/386" - "linux/386" - "darwin/386" - "linux/arm" - "linux/arm64" - "linux/s390x" -) - -upxPath=$(command -v upx) - -for target in "${targets[@]}"; do - GOOS=${target%/*} - GOARCH=${target#*/} - outputDir="bin/${GOOS}_${GOARCH}" - outputFile="${outputDir}/${PKG}" - archiveName="${PKG}-${GOOS}-${GOARCH}.tar.gz" - mkdir -p $(dirname ${outputFile}) - GOOS=$GOOS GOARCH=$GOARCH go build -ldflags="-s -w -extldflags '-static'" -o ${outputFile} *.go - if [ -n "$upxPath" ]; then - $upxPath -9 ${outputFile} - fi - # Archive the binary - if [ "$GOOS" = "windows" ]; then - zip -j "${outputDir}/${PKG}-${GOOS}-${GOARCH}.zip" "${outputFile}" - else - tar -C "${outputDir}" -czf "${outputDir}/${archiveName}" "${PKG}" - fi -done diff --git a/common/common.go b/common/common.go new file mode 100644 index 0000000000000000000000000000000000000000..429b192fe274742fad1febe24bf9a4d3178497ef --- /dev/null +++ b/common/common.go @@ -0,0 +1,285 @@ +package common + +import ( + "bytes" + "encoding/json" + "fmt" + fhttp "github.com/bogdanfinn/fhttp" + "github.com/bogdanfinn/fhttp/httputil" + "github.com/gin-gonic/gin" + jsoniter "github.com/json-iterator/go" + "math/rand" + "net/url" + "os" + "path/filepath" + "reflect" + "strings" + "time" +) + +func ErrorResponse(c *gin.Context, code int, msg interface{}, err interface{}) { + c.AbortWithStatusJSON(code, gin.H{ + "detail": struct { + Code int `json:"code"` + Msg interface{} `json:"msg"` + Error interface{} `json:"error"` + }{ + Code: code, + Msg: msg, + Error: err, + }, + }) + return +} + +// GetTimestampSecond 获取当前时间戳 + 指定 秒 +func GetTimestampSecond(second int) int64 { + return time.Now().Add(time.Second * time.Duration(second)).Unix() +} + +func ParseUrl(link string) *url.URL { + if link == "" { + return &url.URL{} + } + u, err := url.Parse(link) + if err != nil { + return &url.URL{} + } + return u +} + +func GetOrigin(link string) string { + u := ParseUrl(link) + if u == nil { + return "" + } + return u.Scheme + "://" + u.Host +} + +func Struct2BytesBuffer(v interface{}) (*bytes.Buffer, error) { + data := new(bytes.Buffer) + err := json.NewEncoder(data).Encode(v) + if err != nil { + return nil, err + } + return data, nil +} + +func Struct2Bytes(v interface{}) ([]byte, error) { + // 创建一个jsonIter的Encoder + configCompatibleWithStandardLibrary := jsoniter.ConfigCompatibleWithStandardLibrary + // 将结构体转换为JSON文本并保持顺序 + bytes_, err := configCompatibleWithStandardLibrary.Marshal(v) + if err != nil { + return nil, err + } + return bytes_, nil +} + +func SplitAndAddBearer(authTokens string) []string { + var authTokenList []string + for _, v := range strings.Split(authTokens, ",") { + authTokenList = append(authTokenList, "Bearer "+v) + } + return authTokenList +} + +func GetRand() rand.Rand { + // 初始化随机数生成器 + seed := time.Now().UnixNano() + rng := rand.New(rand.NewSource(seed)) + return *rng +} + +func RandomLanguage() string { + // 初始化随机数生成器 + rng := GetRand() + // 语言列表 + 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"} + // 随机选择一个语言 + randomIndex := rng.Intn(len(languages)) + return languages[randomIndex] +} + +// GetAbsPathAndGenerate 获取绝对路径并生成文件或文件夹 +func GetAbsPathAndGenerate(path string, isFilePath bool, content string) string { + // 获取绝对路径 + path = GetAbsPath(path) + if isFilePath { + // 判断文件是否存在 + if isExist := fileIsExistAndCreat(path, content); isExist { + return path + } + } else { + // 判断文件夹是否存在 + if isExist := dirIsExistAndMkdir(path, false); isExist { + return path + } + } + return path +} + +// GetAbsPath 获取绝对路径 +func GetAbsPath(path string) string { + if !filepath.IsAbs(path) { + absPath, err := filepath.Abs(path) + if err != nil { + return "" + } + return absPath + } + return path +} + +func dirIsExistAndMkdir(dirPath string, isFile bool) bool { + // 判断路径是否存在 + _, err := os.Stat(dirPath) + dir := dirPath + if err != nil { + if isFile { + dir = filepath.Dir(dirPath) + } + // 创建路径 + err := os.MkdirAll(dir, os.ModePerm) + if err != nil { + return false + } + } + return true +} + +func fileIsExistAndCreat(filePath string, content string) bool { + //判断文件是否存在 + _, err := os.Stat(filePath) + if err != nil { + // 判断文件夹是否存在 + if isExist := dirIsExistAndMkdir(filePath, true); !isExist { + return false + } + // 创建文件 + _, err := os.Create(filePath) + if err != nil { + return false + } + if content != "" { + // 写入content + file, _ := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) + _, _ = file.Write([]byte(content)) + defer func(file *os.File) { + _ = file.Close() + }(file) + } + } + return true +} + +// AsyncTimingTask 定时任务 参数含函数 +func AsyncTimingTask(nanosecond time.Duration, fun func()) { + go func() { + timerChan := time.After(nanosecond) + // 使用for循环阻塞等待定时器的信号 + for { + // 通过select语句监听定时器通道和其他事件 + select { + case <-timerChan: + fun() + // 重新设置定时器,以便下一次执行 + timerChan = time.After(nanosecond) + } + time.Sleep(time.Millisecond * 100) + } + }() +} + +// AsyncLoopTask AsyncTimingTask 定时任务 参数含函数 +func AsyncLoopTask(sleep time.Duration, fun func()) { + go func() { + for { + fun() + time.Sleep(sleep) + } + }() +} + +// DeepCopyStruct 深拷贝函数 +func DeepCopyStruct(src interface{}) interface{} { + // 获取源对象的类型信息 + srcType := reflect.TypeOf(src) + // 创建目标对象 + dst := reflect.New(srcType).Elem() + + // 深拷贝过程 + deepCopyValue(reflect.ValueOf(src), dst) + + return dst.Interface() +} + +// 递归进行深拷贝 +func deepCopyValue(src, dst reflect.Value) { + switch src.Kind() { + case reflect.Ptr: + if src.IsNil() { + dst.Set(src) + return + } + // 递归处理指针指向的内容 + newDst := reflect.New(src.Elem().Type()) + deepCopyValue(src.Elem(), newDst.Elem()) + dst.Set(newDst) + case reflect.Struct: + for i := 0; i < src.NumField(); i++ { + // 递归处理结构体的字段 + deepCopyValue(src.Field(i), dst.Field(i)) + } + default: + // 检查目标值是否支持设置 + if dst.CanSet() { + // 处理基本类型和数组、切片、映射等 + dst.Set(src) + } + } +} + +func RandomHexadecimalString() string { + rng := GetRand() + const charset = "0123456789abcdef" + const length = 16 // The length of the string you want to generate + b := make([]byte, length) + for i := range b { + b[i] = charset[rng.Intn(len(charset))] + } + return string(b) +} + +// OutRequest 打印请求. +func OutRequest(req *fhttp.Request) { + dump, err := httputil.DumpRequestOut(req, true) + if err != nil { + fmt.Println("Error dumping request:", err) + } else { + fmt.Println(string(dump)) + } +} + +// OutResponse 打印响应. +func OutResponse(res *fhttp.Response) { + dump, err := httputil.DumpResponse(res, true) + if err != nil { + fmt.Println("Error dumping response:", err) + } else { + fmt.Println(string(dump)) + } +} + +func IsStrInArray(str string, strS []string) bool { + // 如果 strS 为空,直接返回 true + if len(strS) == 0 { + return true + } + for _, v := range strS { + if v == str { + return true + } + } + return false +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000000000000000000000000000000000000..d55b16169cf5eaeca9bd42e25361b3f28e63d68b --- /dev/null +++ b/config/config.go @@ -0,0 +1,74 @@ +package config + +import ( + "free-gpt3.5-2api/common" + "github.com/joho/godotenv" + "os" + "strconv" + "strings" +) + +var ( + Bind string + Port string + Proxy []string + AUTHORIZATIONS []string + BaseUrl string + PoolMaxCount int + AuthED int +) + +func init() { + _ = godotenv.Load() + // Bind + Bind = os.Getenv("BIND") + if Bind == "" { + Bind = "0.0.0.0" + } + // PORT + Port = os.Getenv("PORT") + if Port == "" { + Port = "3040" + } + // PROXY + proxy := os.Getenv("PROXY") + if proxy != "" { + Proxy = strings.Split(proxy, ",") + } + // AUTH_TOKEN + authorizations := os.Getenv("AUTHORIZATIONS") + if authorizations == "" { + AUTHORIZATIONS = []string{} + } else { + //以,分割 AUTH_TOKEN 并且为每个AUTH_TOKEN前面加上Bearer + AUTHORIZATIONS = common.SplitAndAddBearer(authorizations) + } + // BASE_URL + BaseUrl = os.Getenv("BASE_URL") + if BaseUrl == "" { + BaseUrl = "https://chatgpt.com" + } else { + BaseUrl = strings.TrimRight(BaseUrl, "/") + } + // POOL_MAX_COUNT + poolMaxCount := os.Getenv("POOL_MAX_COUNT") + var err error + if poolMaxCount == "" { + PoolMaxCount = 64 + } else { + PoolMaxCount, err = strconv.Atoi(poolMaxCount) + if err != nil { + PoolMaxCount = 64 + } + } + // AUTH_ED + authED := os.Getenv("AUTH_ED") + if authED == "" { + AuthED = 600 + } else { + AuthED, err = strconv.Atoi(authED) + if err != nil { + AuthED = 600 + } + } +} diff --git a/constant/constant.go b/constant/constant.go new file mode 100644 index 0000000000000000000000000000000000000000..c48f636394f9d0dc75d351565950584dde8a0e7c --- /dev/null +++ b/constant/constant.go @@ -0,0 +1,11 @@ +package constant + +import "github.com/bogdanfinn/tls-client/profiles" + +var ( + ClientProfile = profiles.Safari_15_6_1 +) + +const ( + Ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" +) diff --git a/conversion/requests/duckgo/convert.go b/conversion/requests/duckgo/convert.go deleted file mode 100644 index 65768fa07570bb24870c79c3cc52528b3b08670f..0000000000000000000000000000000000000000 --- a/conversion/requests/duckgo/convert.go +++ /dev/null @@ -1,30 +0,0 @@ -package duckgo - -import ( - duckgotypes "aurora/typings/duckgo" - officialtypes "aurora/typings/official" - "strings" -) - -func ConvertAPIRequest(api_request officialtypes.APIRequest) duckgotypes.ApiRequest { - // 默认模型3.5 - duckgo_request := duckgotypes.NewApiRequest("gpt-3.5-turbo-0125") - // 检查并更新模型为 claude- 开头的情况 - if strings.HasPrefix(strings.ToLower(api_request.Model), "claude") { - duckgo_request.Model = "claude-3-haiku-20240307" - } - content := buildContent(&api_request) - duckgo_request.AddMessage("user", content) - return duckgo_request -} - -func buildContent(api_request *officialtypes.APIRequest) string { - var content strings.Builder - for _, apiMessage := range api_request.Messages { - role := apiMessage.Role - if role == "user" || role == "system" || role == "assistant" { - content.WriteString(role + ":" + apiMessage.Content + ";\r\n") - } - } - return content.String() -} diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 397ea764b629fc6facd5371f1b146793a1525d76..0000000000000000000000000000000000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: '3' - -services: - app: - image: ghcr.io/aurora-develop/duck2api:latest - container_name: duck2api - restart: unless-stopped - ports: - - '8080:8080' diff --git a/env.template b/env.template index a2e8c296a49f33756ea10b0317d212977d861d2c..44eaccf392a457fcd400c55e53f6a020f7bbd8b9 100644 --- a/env.template +++ b/env.template @@ -1,8 +1,8 @@ -SERVER_HOST=0.0.0.0 -SERVER_PORT=8080 -FREE_ACCOUNTS=true -FREE_ACCOUNTS_NUM=1024 -Authorization= -TLS_CERT= -TLS_KEY= -PROXY_URL= +LOG_LEVEL=info # debug, info, warn, error +BIND=127.0.0.1 # +PORT=8080 +PROXY= +AUTHORIZATIONS= +POOL_MAX_COUNT=5 # max number of connections to keep in the pool +AUTH_ED=180 # expiration time for the authorization in seconds +AUTH_USE_COUNT=5 # number of times an authorization can be used \ No newline at end of file diff --git a/go.mod b/go.mod index 5953626748dfe7dba946891b2489d710b3efa99a..34f61c1cbf861598141a55311139e5669a90391b 100644 --- a/go.mod +++ b/go.mod @@ -1,56 +1,50 @@ -module aurora +module free-gpt3.5-2api go 1.21 require ( - github.com/EDDYCJY/fake-useragent v0.2.0 - github.com/acheong08/endless v0.0.0-20230615162514-90545c7793fd - github.com/bogdanfinn/fhttp v0.5.27 + github.com/aurorax-neo/go-logger v0.0.0-20240421094709-1eb4bda786d5 + github.com/bogdanfinn/fhttp v0.5.28 github.com/bogdanfinn/tls-client v1.7.2 github.com/gin-gonic/gin v1.9.1 - github.com/go-resty/resty/v2 v2.12.0 github.com/google/uuid v1.6.0 - github.com/gorilla/websocket v1.5.1 github.com/joho/godotenv v1.5.1 - github.com/pkoukk/tiktoken-go v0.1.6 - github.com/xqdoo00o/funcaptcha v0.0.0-20240403090732-1b604d808f6c + github.com/json-iterator/go v1.1.12 + github.com/launchdarkly/eventsource v1.7.1 + golang.org/x/crypto v0.21.0 ) require ( - github.com/PuerkitoBio/goquery v1.9.1 // indirect github.com/andybalholm/brotli v1.0.5 // indirect - github.com/andybalholm/cascadia v1.3.2 // indirect github.com/bogdanfinn/utls v1.6.1 // indirect - github.com/bytedance/sonic v1.10.1 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.0 // indirect - github.com/cloudflare/circl v1.3.6 // indirect - github.com/dlclark/regexp2 v1.10.0 // indirect + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/cloudflare/circl v1.3.7 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.15.4 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.0 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect - github.com/kr/text v0.2.0 // indirect + github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect - github.com/quic-go/quic-go v0.37.4 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/quic-go/quic-go v0.42.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect - golang.org/x/arch v0.5.0 // indirect - golang.org/x/crypto v0.21.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect golang.org/x/net v0.22.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 11946d433b86d704d95ba8c95d51976ad462765a..47f6cdf813f5959f59c70c9a5ac9c6b5dd7fe86c 100644 --- a/go.sum +++ b/go.sum @@ -1,37 +1,24 @@ -github.com/EDDYCJY/fake-useragent v0.2.0 h1:Jcnkk2bgXmDpX0z+ELlUErTkoLb/mxFBNd2YdcpvJBs= -github.com/EDDYCJY/fake-useragent v0.2.0/go.mod h1:5wn3zzlDxhKW6NYknushqinPcAqZcAPHy8lLczCdJdc= -github.com/PuerkitoBio/goquery v1.9.1 h1:mTL6XjbJTZdpfL+Gwl5U2h1l9yEkJjhmlTeV9VPW7UI= -github.com/PuerkitoBio/goquery v1.9.1/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY= -github.com/acheong08/endless v0.0.0-20230615162514-90545c7793fd h1:oIpfrRhD7Jus41dotbK+SQjWSFRnf1cLZUYCZpF/o/4= -github.com/acheong08/endless v0.0.0-20230615162514-90545c7793fd/go.mod h1:0yO7neMeJLvKk/B/fq5votDY8rByrOPDubpvU+6saKo= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= -github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= -github.com/bogdanfinn/fhttp v0.5.27 h1:+glR3k8v5nxfUSk7+J3M246zEQ2yadhS0vLq1utK71A= -github.com/bogdanfinn/fhttp v0.5.27/go.mod h1:oJiYPG3jQTKzk/VFmogH8jxjH5yiv2rrOH48Xso2lrE= +github.com/aurorax-neo/go-logger v0.0.0-20240421094709-1eb4bda786d5 h1:L1ei0BPLvE/ld4KAh4bKVAn5tDYOdJz0SuxlbuzfKzQ= +github.com/aurorax-neo/go-logger v0.0.0-20240421094709-1eb4bda786d5/go.mod h1:BJsRG1ECcXTHwiz2zaMYxFkeXh+MpQVs6nWYphLT244= +github.com/bogdanfinn/fhttp v0.5.28 h1:G6thT8s8v6z1IuvXMUsX9QKy3ZHseTQTzxuIhSiaaAw= +github.com/bogdanfinn/fhttp v0.5.28/go.mod h1:oJiYPG3jQTKzk/VFmogH8jxjH5yiv2rrOH48Xso2lrE= github.com/bogdanfinn/tls-client v1.7.2 h1:vpL5qBYUfT9ueygEf1yLfymrXyUEZQatL25amfqGV8M= github.com/bogdanfinn/tls-client v1.7.2/go.mod h1:pOGa2euqTbEkGNqE5idx5jKKfs9ytlyn3fwEw8RSP+g= github.com/bogdanfinn/utls v1.6.1 h1:dKDYAcXEyFFJ3GaWaN89DEyjyRraD1qb4osdEK89ass= github.com/bogdanfinn/utls v1.6.1/go.mod h1:VXIbRZaiY/wHZc6Hu+DZ4O2CgTzjhjCg/Ou3V4r/39Y= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc= -github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= -github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= -github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg= -github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= -github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -46,16 +33,12 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.15.4 h1:zMXza4EpOdooxPel5xDqXEdXG5r+WggpvnAKMsalBjs= -github.com/go-playground/validator/v10 v10.15.4/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/go-resty/resty/v2 v2.12.0 h1:rsVL8P90LFvkUYq/V5BTVe203WfRIU4gvcf+yfzJzGA= -github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -63,22 +46,23 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= -github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/launchdarkly/eventsource v1.7.1 h1:StoRQeiPyrcQIXjlQ7b5jWMzHW4p+GGczN2r2oBhujg= +github.com/launchdarkly/eventsource v1.7.1/go.mod h1:LHxSeb4OnqznNZxCSXbFghxS/CjIQfzHovNoAqbO/Wk= +github.com/launchdarkly/go-test-helpers/v2 v2.2.0 h1:L3kGILP/6ewikhzhdNkHy1b5y4zs50LueWenVF0sBbs= +github.com/launchdarkly/go-test-helpers/v2 v2.2.0/go.mod h1:L7+th5govYp5oKU9iN7To5PgznBuIjBPn+ejqKR0avw= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= @@ -92,102 +76,64 @@ github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAcUsw= -github.com/pkoukk/tiktoken-go v0.1.6/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/quic-go/quic-go v0.37.4 h1:ke8B73yMCWGq9MfrCCAw0Uzdm7GaViC3i39dsIdDlH4= -github.com/quic-go/quic-go v0.37.4/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU= +github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM= +github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 h1:YqAladjX7xpA6BM04leXMWAEjS0mTZ5kUU9KRBriQJc= github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5/go.mod h1:2JjD2zLQYH5HO74y5+aE3remJQvl6q4Sn6aWA2wD1Ng= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/xqdoo00o/funcaptcha v0.0.0-20240403090732-1b604d808f6c h1:nj17XsSTwprsZUDXLldOUZmqz7VlHsLCeXXFOE6Q+Mk= -github.com/xqdoo00o/funcaptcha v0.0.0-20240403090732-1b604d808f6c/go.mod h1:7aCyoW5MHDUsoooMVLqKe0F7W9HMPUvDG3bXqw++8XA= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= -golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/httpclient/Iaurorahttpclient.go b/httpclient/Iaurorahttpclient.go deleted file mode 100644 index 91f3beb800e1a44ad4fc8075bd587032f4e85c5b..0000000000000000000000000000000000000000 --- a/httpclient/Iaurorahttpclient.go +++ /dev/null @@ -1,28 +0,0 @@ -package httpclient - -import ( - "io" - "net/http" -) - -type AuroraHttpClient interface { - Request(method HttpMethod, url string, headers AuroraHeaders, cookies []*http.Cookie, body io.Reader) (*http.Response, error) - SetProxy(url string) error -} - -type HttpMethod string - -const ( - GET HttpMethod = "GET" - POST HttpMethod = "POST" - PUT HttpMethod = "PUT" - HEAD HttpMethod = "HEAD" - DELETE HttpMethod = "DELETE" - OPTIONS HttpMethod = "OPTIONS" -) - -type AuroraHeaders map[string]string - -func (a AuroraHeaders) Set(key, value string) { - a[key] = value -} diff --git a/httpclient/bogdanfinn/tls_client.go b/httpclient/bogdanfinn/tls_client.go deleted file mode 100644 index 7f8e53b887306b0107db6564a33a1d8b0f8b7e01..0000000000000000000000000000000000000000 --- a/httpclient/bogdanfinn/tls_client.go +++ /dev/null @@ -1,101 +0,0 @@ -package bogdanfinn - -import ( - "aurora/httpclient" - "io" - "net/http" - - fhttp "github.com/bogdanfinn/fhttp" - tls_client "github.com/bogdanfinn/tls-client" - "github.com/bogdanfinn/tls-client/profiles" -) - -type TlsClient struct { - Client tls_client.HttpClient - ReqBefore handler -} - -type handler func(r *fhttp.Request) error - -func NewStdClient() *TlsClient { - client, _ := tls_client.NewHttpClient(tls_client.NewNoopLogger(), []tls_client.HttpClientOption{ - tls_client.WithCookieJar(tls_client.NewCookieJar()), - tls_client.WithTimeoutSeconds(600), - tls_client.WithClientProfile(profiles.Safari_15_6_1), - }...) - - stdClient := &TlsClient{Client: client} - return stdClient -} - -func convertResponse(resp *fhttp.Response) *http.Response { - response := &http.Response{ - Status: resp.Status, - StatusCode: resp.StatusCode, - Proto: resp.Proto, - ProtoMajor: resp.ProtoMajor, - ProtoMinor: resp.ProtoMinor, - Header: http.Header(resp.Header), - Body: resp.Body, - ContentLength: resp.ContentLength, - TransferEncoding: resp.TransferEncoding, - Close: resp.Close, - Uncompressed: resp.Uncompressed, - Trailer: http.Header(resp.Trailer), - } - return response -} - -func (t *TlsClient) handleHeaders(req *fhttp.Request, headers httpclient.AuroraHeaders) { - if headers == nil { - return - } - for k, v := range headers { - req.Header.Set(k, v) - } -} - -func (t *TlsClient) handleCookies(req *fhttp.Request, cookies []*http.Cookie) { - if cookies == nil { - return - } - for _, c := range cookies { - req.AddCookie(&fhttp.Cookie{ - Name: c.Name, - Value: c.Value, - Path: c.Path, - Domain: c.Domain, - Expires: c.Expires, - RawExpires: c.RawExpires, - MaxAge: c.MaxAge, - Secure: c.Secure, - HttpOnly: c.HttpOnly, - SameSite: fhttp.SameSite(c.SameSite), - Raw: c.Raw, - Unparsed: c.Unparsed, - }) - } -} - -func (t *TlsClient) Request(method httpclient.HttpMethod, url string, headers httpclient.AuroraHeaders, cookies []*http.Cookie, body io.Reader) (*http.Response, error) { - req, err := fhttp.NewRequest(string(method), url, body) - if err != nil { - return nil, err - } - t.handleHeaders(req, headers) - t.handleCookies(req, cookies) - if t.ReqBefore != nil { - if err := t.ReqBefore(req); err != nil { - return nil, err - } - } - do, err := t.Client.Do(req) - if err != nil { - return nil, err - } - return convertResponse(do), nil -} - -func (t *TlsClient) SetProxy(url string) error { - return t.Client.SetProxy(url) -} diff --git a/httpclient/bogdanfinn/tls_client_test.go b/httpclient/bogdanfinn/tls_client_test.go deleted file mode 100644 index 50794f0a48cfab9d4db89eb7d16ab03c94f8b19d..0000000000000000000000000000000000000000 --- a/httpclient/bogdanfinn/tls_client_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package bogdanfinn - -import ( - "aurora/httpclient" - "fmt" - "io" - "net/http" - "os" - "strings" - "testing" - - "github.com/joho/godotenv" -) - -var BaseURL string - -func init() { - _ = godotenv.Load(".env") - BaseURL = os.Getenv("BASE_URL") - if BaseURL == "" { - BaseURL = "https://chat.openai.com/backend-anon" - } -} -func TestTlsClient_Request(t *testing.T) { - client := NewStdClient() - userAgent := "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" - proxy := "http://127.0.0.1:7990" - client.SetProxy(proxy) - - apiUrl := BaseURL + "/sentinel/chat-requirements" - payload := strings.NewReader(`{"conversation_mode_kind":"primary_assistant"}`) - header := make(httpclient.AuroraHeaders) - header.Set("Content-Type", "application/json") - header.Set("User-Agent", userAgent) - header.Set("Accept", "*/*") - header.Set("oai-language", "en-US") - header.Set("origin", "https://chat.openai.com") - header.Set("referer", "https://chat.openai.com/") - header.Set("oai-device-id", "c83b24f0-5a9e-4c43-8915-3f67d4332609") - response, err := client.Request(http.MethodPost, apiUrl, header, nil, payload) - if err != nil { - return - } - defer response.Body.Close() - fmt.Println(response.StatusCode) - if response.StatusCode != 200 { - fmt.Println("Error: ", response.StatusCode) - } -} - -func TestChatGPTModel(t *testing.T) { - client := NewStdClient() - userAgent := "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" - proxy := "http://127.0.0.1:7990" - client.SetProxy(proxy) - apiUrl := "https://chat.openai.com/backend-anon/models" - - header := make(httpclient.AuroraHeaders) - header.Set("Content-Type", "application/json") - header.Set("User-Agent", userAgent) - header.Set("Accept", "*/*") - header.Set("oai-language", "en-US") - header.Set("origin", "https://chat.openai.com") - header.Set("referer", "https://chat.openai.com/") - header.Set("oai-device-id", "c83b24f0-5a9e-4c43-8915-3f67d4332609") - response, err := client.Request(http.MethodGet, apiUrl, header, nil, nil) - if err != nil { - return - } - defer response.Body.Close() - fmt.Println(response.StatusCode) - if response.StatusCode != 200 { - fmt.Println("Error: ", response.StatusCode) - body, _ := io.ReadAll(response.Body) - fmt.Println(string(body)) - return - } - - type EnginesData struct { - Models []struct { - Slug string `json:"slug"` - MaxTokens int `json:"max_tokens"` - Title string `json:"title"` - Description string `json:"description"` - Tags []string `json:"tags"` - Capabilities struct { - } `json:"capabilities,omitempty"` - ProductFeatures struct { - } `json:"product_features,omitempty"` - } `json:"models"` - Categories []struct { - Category string `json:"category"` - HumanCategoryName string `json:"human_category_name"` - SubscriptionLevel string `json:"subscription_level"` - DefaultModel string `json:"default_model"` - CodeInterpreterModel string `json:"code_interpreter_model,omitempty"` - PluginsModel string `json:"plugins_model"` - } `json:"categories"` - } - -} diff --git a/httpclient/resty/resty_client.go b/httpclient/resty/resty_client.go deleted file mode 100644 index 36b0cd485055e158781801f568925a1e5edfe7e3..0000000000000000000000000000000000000000 --- a/httpclient/resty/resty_client.go +++ /dev/null @@ -1,72 +0,0 @@ -package resty - -import ( - "aurora/util" - "crypto/tls" - browser "github.com/EDDYCJY/fake-useragent" - "github.com/go-resty/resty/v2" - "net/http" - "time" -) - -type RestyClient struct { - Client *resty.Client -} - -func NewStdClient() *RestyClient { - client := &RestyClient{ - Client: resty.NewWithClient(&http.Client{ - Transport: &http.Transport{ - // 禁用长连接 - DisableKeepAlives: true, - // 配置TLS设置,跳过证书验证 - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }, - }), - } - client.Client.SetBaseURL("https://chat.openai.com") - client.Client.SetRetryCount(3) - client.Client.SetRetryWaitTime(5 * time.Second) - client.Client.SetRetryMaxWaitTime(20 * time.Second) - - client.Client.SetTimeout(600 * time.Second) - client.Client.SetHeader("user-agent", browser.Random()). - SetHeader("accept", "*/*"). - SetHeader("accept-language", "en-US,en;q=0.9"). - SetHeader("cache-control", "no-cache"). - SetHeader("content-type", "application/json"). - SetHeader("oai-language", util.RandomLanguage()). - SetHeader("pragma", "no-cache"). - SetHeader("sec-ch-ua", `"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"`). - SetHeader("sec-ch-ua-mobile", "?0"). - SetHeader("sec-ch-ua-platform", "Windows"). - SetHeader("sec-fetch-dest", "empty"). - SetHeader("sec-fetch-mode", "cors"). - SetHeader("sec-fetch-site", "same-origin") - return client -} - -//func (c *RestyClient) Request(method string, url string, headers map[string]string, cookies []*http.Cookie, body io.Reader) (*http.Response, error) { -//} - -//func (c *RestyClient) Post(url string, headers map[string]string, cookies []*http.Cookie, body io.Reader) (*http.Response, error) { -//} -// -//func (c *RestyClient) Get(url string, headers map[string]string, cookies []*http.Cookie, body io.Reader) (*http.Response, error) { -//} -// -//func (c *RestyClient) Head(url string, headers map[string]string, cookies []*http.Cookie, body io.Reader) (*http.Response, error) { -//} -// -//func (c *RestyClient) Options(url string, headers map[string]string, cookies []*http.Cookie, body io.Reader) (*http.Response, error) { -//} -// -//func (c *RestyClient) Put(url string, headers map[string]string, cookies []*http.Cookie, body io.Reader) (*http.Response, error) { -//} -// -//func (c *RestyClient) Delete(url string, headers map[string]string, cookies []*http.Cookie, body io.Reader) (*http.Response, error) { -//} -// -//func (c *RestyClient) SetProxy(url string) error {} diff --git a/initialize/handlers.go b/initialize/handlers.go deleted file mode 100644 index c49b97d0cb1d86cdcdc75b1de681cc0d8ec78775..0000000000000000000000000000000000000000 --- a/initialize/handlers.go +++ /dev/null @@ -1,110 +0,0 @@ -package initialize - -import ( - duckgoConvert "aurora/conversion/requests/duckgo" - "aurora/httpclient/bogdanfinn" - "aurora/internal/duckgo" - "aurora/internal/proxys" - officialtypes "aurora/typings/official" - - "github.com/gin-gonic/gin" -) - -type Handler struct { - proxy *proxys.IProxy -} - -func NewHandle(proxy *proxys.IProxy) *Handler { - return &Handler{proxy: proxy} -} - -func optionsHandler(c *gin.Context) { - // Set headers for CORS - c.Header("Access-Control-Allow-Origin", "*") - c.Header("Access-Control-Allow-Methods", "POST") - c.Header("Access-Control-Allow-Headers", "*") - c.JSON(200, gin.H{ - "message": "pong", - }) -} - -func (h *Handler) duckduckgo(c *gin.Context) { - var original_request officialtypes.APIRequest - err := c.BindJSON(&original_request) - if err != nil { - c.JSON(400, gin.H{"error": gin.H{ - "message": "Request must be proper JSON", - "type": "invalid_request_error", - "param": nil, - "code": err.Error(), - }}) - return - } - proxyUrl := h.proxy.GetProxyIP() - client := bogdanfinn.NewStdClient() - token, err := duckgo.InitXVQD(client, proxyUrl) - if err != nil { - c.JSON(500, gin.H{ - "error": err.Error(), - }) - return - } - - translated_request := duckgoConvert.ConvertAPIRequest(original_request) - response, err := duckgo.POSTconversation(client, translated_request, token, proxyUrl) - if err != nil { - c.JSON(500, gin.H{ - "error": "request conversion error", - }) - return - } - - defer response.Body.Close() - if duckgo.Handle_request_error(c, response) { - return - } - var response_part string - response_part = duckgo.Handler(c, response, translated_request, original_request.Stream) - if c.Writer.Status() != 200 { - return - } - if !original_request.Stream { - c.JSON(200, officialtypes.NewChatCompletionWithModel(response_part, translated_request.Model)) - } else { - c.String(200, "data: [DONE]\n\n") - } -} - -func (h *Handler) engines(c *gin.Context) { - type ResData struct { - ID string `json:"id"` - Object string `json:"object"` - Created int `json:"created"` - OwnedBy string `json:"owned_by"` - } - - type JSONData struct { - Object string `json:"object"` - Data []ResData `json:"data"` - } - - modelS := JSONData{ - Object: "list", - } - var resModelList []ResData - - resModelList = append(resModelList, ResData{ - ID: "gpt-3.5-turbo-0125", - Object: "model", - Created: 1685474247, - OwnedBy: "duckgo", - }) - resModelList = append(resModelList, ResData{ - ID: "claude-3-haiku-20240307", - Object: "model", - Created: 1685474247, - OwnedBy: "duckgo", - }) - modelS.Data = resModelList - c.JSON(200, modelS) -} diff --git a/initialize/proxy.go b/initialize/proxy.go deleted file mode 100644 index f343a0916512400df78004cf00a27f7743d5b1f1..0000000000000000000000000000000000000000 --- a/initialize/proxy.go +++ /dev/null @@ -1,48 +0,0 @@ -package initialize - -import ( - "aurora/internal/proxys" - "bufio" - "log/slog" - "net/url" - "os" -) - -func checkProxy() *proxys.IProxy { - var proxies []string - proxyUrl := os.Getenv("PROXY_URL") - if proxyUrl != "" { - proxies = append(proxies, proxyUrl) - } - - if _, err := os.Stat("proxies.txt"); err == nil { - file, _ := os.Open("proxies.txt") - defer file.Close() - scanner := bufio.NewScanner(file) - for scanner.Scan() { - proxy := scanner.Text() - parsedURL, err := url.Parse(proxy) - if err != nil { - slog.Warn("proxy url is invalid", "url", proxy, "err", err) - continue - } - - // 如果缺少端口信息,不是完整的代理链接 - if parsedURL.Port() != "" { - proxies = append(proxies, proxy) - } else { - continue - } - } - } - - if len(proxies) == 0 { - proxy := os.Getenv("http_proxy") - if proxy != "" { - proxies = append(proxies, proxy) - } - } - - proxyIP := proxys.NewIProxyIP(proxies) - return &proxyIP -} diff --git a/initialize/router.go b/initialize/router.go deleted file mode 100644 index 3ecae947b2b5f8fc06eeb014f477dfde37b4219f..0000000000000000000000000000000000000000 --- a/initialize/router.go +++ /dev/null @@ -1,35 +0,0 @@ -package initialize - -import ( - "aurora/middlewares" - - "github.com/gin-gonic/gin" -) - -func RegisterRouter() *gin.Engine { - handler := NewHandle( - checkProxy(), - ) - - router := gin.Default() - router.Use(middlewares.Cors) - - router.GET("/", func(c *gin.Context) { - c.JSON(200, gin.H{ - "message": "Hello, world!", - }) - }) - - router.GET("/ping", func(c *gin.Context) { - c.JSON(200, gin.H{ - "message": "pong", - }) - }) - - router.OPTIONS("/v1/chat/completions", optionsHandler) - router.OPTIONS("/v1/chat/models", optionsHandler) - authGroup := router.Group("").Use(middlewares.Authorization) - authGroup.POST("/v1/chat/completions", handler.duckduckgo) - authGroup.GET("/v1/models", handler.engines) - return router -} diff --git a/internal/duckgo/request.go b/internal/duckgo/request.go deleted file mode 100644 index 19cfdc7af3cc89ee10909ec3981a2fea7fbf157c..0000000000000000000000000000000000000000 --- a/internal/duckgo/request.go +++ /dev/null @@ -1,188 +0,0 @@ -package duckgo - -import ( - "aurora/httpclient" - duckgotypes "aurora/typings/duckgo" - officialtypes "aurora/typings/official" - "bufio" - "bytes" - "encoding/json" - "errors" - "github.com/gin-gonic/gin" - "io" - "net/http" - "strings" - "sync" - "time" -) - -var ( - Token *XqdgToken - UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" -) - -type XqdgToken struct { - Token string `json:"token"` - M sync.Mutex `json:"-"` - ExpireAt time.Time `json:"expire"` -} - -func InitXVQD(client httpclient.AuroraHttpClient, proxyUrl string) (string, error) { - if Token == nil { - Token = &XqdgToken{ - Token: "", - M: sync.Mutex{}, - } - } - Token.M.Lock() - defer Token.M.Unlock() - if Token.Token == "" || Token.ExpireAt.Before(time.Now()) { - status, err := postStatus(client, proxyUrl) - if err != nil { - return "", err - } - defer status.Body.Close() - token := status.Header.Get("x-vqd-4") - if token == "" { - return "", errors.New("no x-vqd-4 token") - } - Token.Token = token - Token.ExpireAt = time.Now().Add(time.Minute * 5) - } - - return Token.Token, nil -} - -func postStatus(client httpclient.AuroraHttpClient, proxyUrl string) (*http.Response, error) { - if proxyUrl != "" { - client.SetProxy(proxyUrl) - } - header := createHeader() - header.Set("accept", "*/*") - header.Set("x-vqd-accept", "1") - response, err := client.Request(httpclient.GET, "https://duckduckgo.com/duckchat/v1/status", header, nil, nil) - if err != nil { - return nil, err - } - return response, nil -} - -func POSTconversation(client httpclient.AuroraHttpClient, request duckgotypes.ApiRequest, token string, proxyUrl string) (*http.Response, error) { - if proxyUrl != "" { - client.SetProxy(proxyUrl) - } - body_json, err := json.Marshal(request) - if err != nil { - return &http.Response{}, err - } - header := createHeader() - header.Set("accept", "text/event-stream") - header.Set("x-vqd-4", token) - response, err := client.Request(httpclient.POST, "https://duckduckgo.com/duckchat/v1/chat", header, nil, bytes.NewBuffer(body_json)) - if err != nil { - return nil, err - } - return response, nil -} - -func Handle_request_error(c *gin.Context, response *http.Response) bool { - if response.StatusCode != 200 { - // Try read response body as JSON - var error_response map[string]interface{} - err := json.NewDecoder(response.Body).Decode(&error_response) - if err != nil { - // Read response body - body, _ := io.ReadAll(response.Body) - c.JSON(response.StatusCode, gin.H{"error": gin.H{ - "message": "Unknown error", - "type": "internal_server_error", - "param": nil, - "code": "500", - "details": string(body), - }}) - return true - } - c.JSON(response.StatusCode, gin.H{"error": gin.H{ - "message": error_response["detail"], - "type": response.Status, - "param": nil, - "code": "error", - }}) - return true - } - return false -} - -func createHeader() httpclient.AuroraHeaders { - header := make(httpclient.AuroraHeaders) - header.Set("accept-language", "zh-CN,zh;q=0.9") - header.Set("content-type", "application/json") - header.Set("origin", "https://duckduckgo.com") - header.Set("referer", "https://duckduckgo.com/") - header.Set("sec-ch-ua", `"Chromium";v="120", "Google Chrome";v="120", "Not-A.Brand";v="99"`) - header.Set("sec-ch-ua-mobile", "?0") - header.Set("sec-ch-ua-platform", `"Windows"`) - header.Set("user-agent", UA) - return header -} - -func Handler(c *gin.Context, response *http.Response, oldRequest duckgotypes.ApiRequest, stream bool) string { - reader := bufio.NewReader(response.Body) - if stream { - // Response content type is text/event-stream - c.Header("Content-Type", "text/event-stream") - } else { - // Response content type is application/json - c.Header("Content-Type", "application/json") - } - - var previousText strings.Builder - for { - line, err := reader.ReadString('\n') - if err != nil { - if err == io.EOF { - break - } - return "" - } - if len(line) < 6 { - continue - } - line = line[6:] - if !strings.HasPrefix(line, "[DONE]") { - var originalResponse duckgotypes.ApiResponse - err = json.Unmarshal([]byte(line), &originalResponse) - if err != nil { - continue - } - if originalResponse.Action != "success" { - c.JSON(500, gin.H{"error": "Error"}) - return "" - } - responseString := "" - if originalResponse.Message != "" { - previousText.WriteString(originalResponse.Message) - translatedResponse := officialtypes.NewChatCompletionChunkWithModel(originalResponse.Message, originalResponse.Model) - responseString = "data: " + translatedResponse.String() + "\n\n" - } - - if responseString == "" { - continue - } - - if stream { - _, err = c.Writer.WriteString(responseString) - if err != nil { - return "" - } - c.Writer.Flush() - } - } else { - if stream { - final_line := officialtypes.StopChunkWithModel("stop", oldRequest.Model) - c.Writer.WriteString("data: " + final_line.String() + "\n\n") - } - } - } - return previousText.String() -} diff --git a/internal/proxys/proxys.go b/internal/proxys/proxys.go deleted file mode 100644 index 8ee40d222f0509c892ee53ff649c6cb06b3bebde..0000000000000000000000000000000000000000 --- a/internal/proxys/proxys.go +++ /dev/null @@ -1,35 +0,0 @@ -package proxys - -import "sync" - -type IProxy struct { - ips []string - lock sync.Mutex -} - -func NewIProxyIP(ips []string) IProxy { - return IProxy{ - ips: ips, - } -} - -func (p *IProxy) GetIPS() int { - return len(p.ips) -} - -func (p *IProxy) GetProxyIP() string { - if p == nil { - return "" - } - - p.lock.Lock() - defer p.lock.Unlock() - - if len(p.ips) == 0 { - return "" - } - - proxyIp := p.ips[0] - p.ips = append(p.ips[1:], proxyIp) - return proxyIp -} diff --git a/main.go b/main.go index 1edf89a1192b4f228140831fd176b5b251c444e8..38b09a0413cb0bde2eef30dede5e502476aeb161 100644 --- a/main.go +++ b/main.go @@ -1,50 +1,35 @@ package main import ( - "aurora/initialize" - "embed" - "io/fs" - "log" - "net/http" - "os" - + "fmt" + "free-gpt3.5-2api/FreeGpt35Pool" + "free-gpt3.5-2api/ProxyPool" + "free-gpt3.5-2api/config" + "free-gpt3.5-2api/router" + "github.com/aurorax-neo/go-logger" "github.com/gin-gonic/gin" - - "github.com/acheong08/endless" - "github.com/joho/godotenv" ) -//go:embed web/* -var staticFiles embed.FS +func Init() { + ProxyPool.GetProxyPoolInstance() + FreeGpt35Pool.GetFreeGpt35PoolInstance() +} func main() { + // Init + Init() + // Initialize HTTP server gin.SetMode(gin.ReleaseMode) - router := initialize.RegisterRouter() - subFS, err := fs.Sub(staticFiles, "web") - if err != nil { - log.Fatal(err) - } - router.StaticFS("/web", http.FS(subFS)) - - _ = godotenv.Load(".env") - host := os.Getenv("SERVER_HOST") - port := os.Getenv("SERVER_PORT") - tlsCert := os.Getenv("TLS_CERT") - tlsKey := os.Getenv("TLS_KEY") - - if host == "" { - host = "0.0.0.0" - } - if port == "" { - port = os.Getenv("PORT") - if port == "" { - port = "8080" - } - } - - if tlsCert != "" && tlsKey != "" { - _ = endless.ListenAndServeTLS(host+":"+port, tlsCert, tlsKey, router) - } else { - _ = endless.ListenAndServe(host+":"+port, router) + server := gin.New() + server.Use(gin.Recovery()) + // 设置路由 + router.SetRouter(server) + // 提示服务启动 + host := config.Bind + if config.Bind == "0.0.0.0" { + host = "127.0.0.1" } + logger.Logger.Info(fmt.Sprint("Server started on http://", host, ":", config.Port)) + // 启动 HTTP 服务器 + _ = server.Run(fmt.Sprint(config.Bind, ":", config.Port)) } diff --git a/middlewares/auth.go b/middlewares/auth.go deleted file mode 100644 index f67e5198e5fdf9043a79520910d839569e1516f4..0000000000000000000000000000000000000000 --- a/middlewares/auth.go +++ /dev/null @@ -1,31 +0,0 @@ -package middlewares - -import ( - "github.com/gin-gonic/gin" - "os" - "strings" -) - -func Authorization(c *gin.Context) { - customer_key := os.Getenv("Authorization") - if customer_key != "" { - authHeader := c.GetHeader("Authorization") - if authHeader == "" { - c.JSON(401, gin.H{"error": "Unauthorized"}) - c.Abort() - return - } - tokenParts := strings.Split(strings.Replace(authHeader, "Bearer ", "", 1)," ") - customAccessToken := tokenParts[0] - if customer_key != customAccessToken { - c.JSON(401, gin.H{"error": "Unauthorized"}) - c.Abort() - return - } - if len(tokenParts) > 1 { - openaiAccessToken := tokenParts[1] - c.Request.Header.Set("Authorization", "Bearer " + openaiAccessToken) - } - } - c.Next() -} diff --git a/middlewares/cors.go b/middlewares/cors.go deleted file mode 100644 index 8818637675f48b98abf5211570750767f5ad1232..0000000000000000000000000000000000000000 --- a/middlewares/cors.go +++ /dev/null @@ -1,10 +0,0 @@ -package middlewares - -import "github.com/gin-gonic/gin" - -func Cors(c *gin.Context) { - c.Header("Access-Control-Allow-Origin", "*") - c.Header("Access-Control-Allow-Methods", "*") - c.Header("Access-Control-Allow-Headers", "*") - c.Next() -} diff --git a/queue/queue.go b/queue/queue.go new file mode 100644 index 0000000000000000000000000000000000000000..3278488734b16abfdd1ae43c0f33bd2a0cbdbf4d --- /dev/null +++ b/queue/queue.go @@ -0,0 +1,106 @@ +package queue + +type ( + Queue struct { + start, end *Node + length int + } + Node struct { + Value interface{} + next *Node + } +) + +// New 新建一个队列 +func New() *Queue { + return &Queue{nil, nil, 0} +} + +// Dequeue 出队 +func (Q *Queue) Dequeue() *Node { + if Q.length == 0 { + return nil + } + n := Q.start + if Q.length == 1 { + Q.start = nil + Q.end = nil + } else { + Q.start = Q.start.next + } + Q.length-- + return n +} + +// Enqueue 入队 +func (Q *Queue) Enqueue(value interface{}) { + n := &Node{value, nil} + if Q.length == 0 { + Q.start = n + Q.end = n + } else { + Q.end.next = n + Q.end = n + } + Q.length++ +} + +// Len 获取队列长度 +func (Q *Queue) Len() int { + return Q.length +} + +// Peek 返回队列的第一个元素 +func (Q *Queue) Peek() *Node { + if Q.length == 0 { + return nil + } + return Q.start +} + +// Remove 移除指定节点 +func (Q *Queue) Remove(n *Node) { + if Q.length == 0 || n == nil { + return + } + + // 如果移除的是队列的第一个元素 + if n == Q.start { + Q.start = Q.start.next + if Q.start == nil { + // 如果移除后队列为空,则end也应该设置为nil + Q.end = nil + } + Q.length-- + return + } + + // 找到n的前一个节点 + prevNode := Q.start + for prevNode != nil && prevNode.next != n { + prevNode = prevNode.next + } + + if prevNode == nil { + // 没有找到n的前一个节点(n不在队列中) + return + } + + // 移除节点n + prevNode.next = n.next + // 如果移除的是最后一个元素,更新end指针 + if n.next == nil { + Q.end = prevNode + } + Q.length-- +} + +// Traverse 遍历队列 +func (Q *Queue) Traverse(cb func(n *Node)) { + if Q.length == 0 { + return + } + for n := Q.start; n != nil; n = n.next { + cb(n) + } +} diff --git a/release.bat b/release.bat index b7b5ac8dfe996ac4c5f7d1fc8e58bea8c205515b..dc1200192f647440b9a60db8438b2cbfc5d08565 100644 --- a/release.bat +++ b/release.bat @@ -5,10 +5,10 @@ REM 指定编码为 UTF-8 chcp 65001 REM 设置要生成的可执行文件的名称 -set OUTPUT_NAME=aurora +set OUTPUT_NAME=free-gpt3.5-2api REM 设置 Go 源文件的名称 -SET GOFILE=aurora +SET GOFILE=main.go REM 设置输出目录 SET OUTPUTDIR=target diff --git a/render.yaml b/render.yaml deleted file mode 100644 index 2593b666e39d9a6893b8cea0b279112aeffeda92..0000000000000000000000000000000000000000 --- a/render.yaml +++ /dev/null @@ -1,7 +0,0 @@ -services: - - type: web - name: duck2api - env: docker - dockerfilePath: ./Dockerfile - plan: free - diff --git a/router/middleware.go b/router/middleware.go new file mode 100644 index 0000000000000000000000000000000000000000..93c0e4e8b710f9ba03b605b12560a2df6c47b862 --- /dev/null +++ b/router/middleware.go @@ -0,0 +1,63 @@ +package router + +import ( + "fmt" + "free-gpt3.5-2api/common" + "free-gpt3.5-2api/config" + "github.com/aurorax-neo/go-logger" + "github.com/gin-gonic/gin" +) + +// Ping 测试接口 +func Ping(c *gin.Context) { + c.JSON(200, gin.H{ + "message": "pong", + }) +} + +// V1Cors 跨域中间件 +func V1Cors(c *gin.Context) { + // 允许跨域 + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept") + c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + // 如果是OPTIONS请求,直接返回 + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + c.Next() +} + +// V1Request 请求中间件 +func V1Request(c *gin.Context) { + // 打印请求摘要 方法 url ip - user-agent 格式化输出 + infoStr := fmt.Sprint(" -> ", c.Request.Method, " ", c.Request.URL.String(), " - ", c.ClientIP(), " - ", c.Request.Header.Get("User-Agent")) + logger.Logger.Info(infoStr) + c.Next() +} + +// V1Auth 验证v1 api 的token +func V1Auth(c *gin.Context) { + authToken := c.Request.Header.Get("Authorization") + if authToken == "" && len(config.AUTHORIZATIONS) > 0 { + 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) + return + } + // 判断 authToken 是否在 config.CONFIG.AUTHORIZATIONS 列表 + if !common.IsStrInArray(authToken, config.AUTHORIZATIONS) { + common.ErrorResponse(c, 401, "Incorrect API key provided: sk-4yNZz***************************************6mjw.", nil) + return + } + c.Next() +} + +// V1Response 响应中间件 +func V1Response(c *gin.Context) { + c.Next() + // 打印响应摘要 方法 url 状态码 + infoStr := fmt.Sprint(" <- ", c.Request.Method, " ", c.Request.URL.String(), " - ", c.Writer.Status()) + logger.Logger.Info(infoStr) +} diff --git a/router/router.go b/router/router.go new file mode 100644 index 0000000000000000000000000000000000000000..9aff77691a9921828c63e6140e28ea0cc2f06dde --- /dev/null +++ b/router/router.go @@ -0,0 +1,25 @@ +package router + +import ( + v1 "free-gpt3.5-2api/service/v1" + "free-gpt3.5-2api/service/v1Chat" + "github.com/gin-gonic/gin" + "net/http" +) + +func SetRouter(router *gin.Engine) { + router.GET("/", Index) + router.GET("/ping", Ping) + v1Router := router.Group("/v1") + v1Router.Use(V1Cors) + v1Router.Use(V1Request) + v1Router.Use(V1Response) + v1Router.Use(V1Auth) + v1Router.GET("/tokens", v1.Tokens) + v1Router.OPTIONS("/chat/completions", nil) + v1Router.POST("/chat/completions", v1Chat.Completions) +} + +func Index(c *gin.Context) { + c.String(http.StatusOK, "Hello,This is free-gpt3.5-2api.") +} diff --git a/service/v1/tokens.go b/service/v1/tokens.go new file mode 100644 index 0000000000000000000000000000000000000000..b576c184401eabe468ea04dee1de63a4c8698423 --- /dev/null +++ b/service/v1/tokens.go @@ -0,0 +1,20 @@ +package v1 + +import ( + "fmt" + "free-gpt3.5-2api/FreeGpt35Pool" + "github.com/aurorax-neo/go-logger" + "github.com/gin-gonic/gin" +) + +type TokensResp struct { + Count int `json:"count"` +} + +func Tokens(c *gin.Context) { + resp := &TokensResp{ + Count: FreeGpt35Pool.GetFreeGpt35PoolInstance().GetSize(), + } + logger.Logger.Info(fmt.Sprint("FreeGpt35Pool Tokens: ", resp.Count)) + c.JSON(200, resp) +} diff --git a/service/v1/util.go b/service/v1/util.go new file mode 100644 index 0000000000000000000000000000000000000000..358e84e457c7137e344a27af7f96a697563475e7 --- /dev/null +++ b/service/v1/util.go @@ -0,0 +1,65 @@ +package v1 + +import ( + "free-gpt3.5-2api/service/v1Chat/reqModel" + "github.com/google/uuid" + "math/rand" +) + +func MappingModel(model string) string { + var modelMapping = map[string]string{ + "gpt-3.5-turbo": "text-davinci-002-render-sha", + "gpt-3.5-turbo-16k": "text-davinci-002-render-sha", + "gpt-3.5-turbo-16k-0613": "text-davinci-002-render-sha", + "gpt-3.5-turbo-0301": "text-davinci-002-render-sha", + "gpt-3.5-turbo-0613": "text-davinci-002-render-sha", + "gpt-3.5-turbo-1106": "text-davinci-002-render-sha", + } + if model == "" { + return "text-davinci-002-render-sha" + } + if v, ok := modelMapping[model]; ok { + return v + } + return "text-davinci-002-render-sha" +} + +func GenerateID(length int) string { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + id := "chatcmpl-" + for i := 0; i < length; i++ { + id += string(charset[rand.Intn(len(charset))]) + } + return id +} + +func ApiReq2ChatReq35(apiReq *reqModel.ApiReq) (chatReq *reqModel.ChatReq35) { + messages := make([]reqModel.ChatMessages, 0) + for _, apiMessage := range apiReq.Messages { + chatMessage := reqModel.ChatMessages{ + Author: reqModel.ChatAuthor{ + Role: apiMessage.Role, + }, + Content: reqModel.ChatContent{ + ContentType: "text", + Parts: []string{apiMessage.Content}, + }, + } + messages = append(messages, chatMessage) + } + + chatReq = &reqModel.ChatReq35{ + Action: "next", + Messages: messages, + ParentMessageId: uuid.New().String(), + Model: MappingModel(apiReq.Model), + TimeZoneOffsetMin: -180, + Suggestions: make([]string, 0), + HistoryAndTrainingDisabled: true, + ConversationMode: reqModel.ChatConversationMode{ + Kind: "primary_assistant", + }, + WebsocketRequestId: uuid.New().String(), + } + return chatReq +} diff --git a/service/v1Chat/completions.go b/service/v1Chat/completions.go new file mode 100644 index 0000000000000000000000000000000000000000..434834a6513424ed52d5632b0532c0ab47ad87fb --- /dev/null +++ b/service/v1Chat/completions.go @@ -0,0 +1,19 @@ +package v1Chat + +import ( + "free-gpt3.5-2api/common" + "free-gpt3.5-2api/service/v1Chat/reqModel" + "github.com/gin-gonic/gin" + "net/http" +) + +func Completions(c *gin.Context) { + // 从请求中获取参数 + apiReq := &reqModel.ApiReq{} + err := c.BindJSON(apiReq) + if err != nil { + common.ErrorResponse(c, http.StatusBadRequest, "Invalid parameter", nil) + return + } + Gpt35Completions(c, apiReq) +} diff --git a/service/v1Chat/gpt35Completions.go b/service/v1Chat/gpt35Completions.go new file mode 100644 index 0000000000000000000000000000000000000000..650cc0b5d37cc6cd8498ceb1dc2106acf250971e --- /dev/null +++ b/service/v1Chat/gpt35Completions.go @@ -0,0 +1,231 @@ +package v1Chat + +import ( + "encoding/json" + "fmt" + "free-gpt3.5-2api/FreeGpt35" + "free-gpt3.5-2api/FreeGpt35Pool" + "free-gpt3.5-2api/common" + "free-gpt3.5-2api/service/v1" + "free-gpt3.5-2api/service/v1Chat/reqModel" + "free-gpt3.5-2api/service/v1Chat/respModel" + "github.com/aurorax-neo/go-logger" + fhttp "github.com/bogdanfinn/fhttp" + "github.com/gin-gonic/gin" + "github.com/launchdarkly/eventsource" + "io" + "net/http" + "strings" +) + +func Gpt35Completions(c *gin.Context, apiReq *reqModel.ApiReq) { + // 获取 FreeGpt35 实例 + ChatGpt35 := FreeGpt35Pool.GetFreeGpt35PoolInstance().GetFreeGpt35(3) + if ChatGpt35 == nil { + errStr := "please restart the program、change the IP address、use a proxy to try again." + logger.Logger.Error(errStr) + common.ErrorResponse(c, http.StatusUnauthorized, errStr, nil) + return + } + // 转换请求 + ChatReq35 := v1.ApiReq2ChatReq35(apiReq) + // 请求参数 + body, err := common.Struct2BytesBuffer(ChatReq35) + if err != nil { + logger.Logger.Error(err.Error()) + common.ErrorResponse(c, http.StatusInternalServerError, "", err) + return + + } + // 生成请求 + request, err := ChatGpt35.NewRequest(fhttp.MethodPost, FreeGpt35.ChatUrl, body) + if err != nil || request == nil { + errStr := "Request is nil or error" + logger.Logger.Error("Request is nil or error") + common.ErrorResponse(c, http.StatusInternalServerError, errStr, err) + return + } + // 设置请求头 + request.Header.Set("Content-Type", "application/json") + request.Header.Set("oai-device-id", ChatGpt35.FreeAuth.OaiDeviceId) + request.Header.Set("openai-sentinel-chat-requirements-token", ChatGpt35.FreeAuth.Token) + if ChatGpt35.FreeAuth.ProofWork.Required { + request.Header.Set("Openai-Sentinel-Proof-Token", ChatGpt35.FreeAuth.ProofWork.Ospt) + } + // 发送请求 + response, err := ChatGpt35.RequestClient.Do(request) + if err != nil { + errStr := "RequestClient Do error" + logger.Logger.Error(fmt.Sprint(errStr, " ", err)) + common.ErrorResponse(c, http.StatusInternalServerError, errStr, err) + return + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(response.Body) + if response.StatusCode != http.StatusOK { + errStr := "Request error" + logger.Logger.Error(fmt.Sprint(errStr, " ", response.StatusCode)) + common.ErrorResponse(c, response.StatusCode, errStr, nil) + return + } + // 流式返回 + if apiReq.Stream { + __CompletionsStream(c, apiReq, response) + } else { // 非流式回应 + __CompletionsNoStream(c, apiReq, response) + } +} + +func __CompletionsStream(c *gin.Context, apiReq *reqModel.ApiReq, resp *fhttp.Response) { + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + messageTemp := "" + decoder := eventsource.NewDecoder(resp.Body) + // 响应id + id := v1.GenerateID(29) + handlingSigns := false + for { + event, err := decoder.Decode() + if err != nil { + logger.Logger.Error(err.Error()) + common.ErrorResponse(c, http.StatusInternalServerError, "", err) + break + } + name := event.Event() + data := event.Data() + // 空白数据不处理 + if data == "" { + continue + } + // 结束标志 + if data == "[DONE]" { + // 生成响应 stream + apiRespStream := respModel.NewApiRespStream(id, apiReq.Model, "", "stop") + // 生成响应 bytes + bytes, err := common.Struct2Bytes(apiRespStream) + if err != nil { + logger.Logger.Error(err.Error()) + continue + } + // 发送响应 + c.SSEvent(name, fmt.Sprint(" ", string(bytes))) + // 结束 + c.SSEvent(name, " [DONE]") + return + } + chatResp35 := &respModel.ChatResp35{} + err = json.Unmarshal([]byte(data), chatResp35) + if chatResp35.Error != nil && !handlingSigns { + logger.Logger.Error(fmt.Sprint(chatResp35.Error)) + common.ErrorResponse(c, http.StatusInternalServerError, "", chatResp35.Error) + return + } + // 脏数据不处理 + if err != nil { + continue + } + // 被block + if contentIsBlocked(chatResp35) { + // 返回响应 + common.ErrorResponse(c, http.StatusBadRequest, "content is blocked.", "") + return + } + // 仅处理assistant的消息 + if chatResp35.Message.Author.Role == "assistant" && (chatResp35.Message.Status == "in_progress" || handlingSigns) { + // handlingSigns 置为 true + handlingSigns = true + // 仅处理第一个part + parts := chatResp35.Message.Content.Parts[0] + // 去除重复数据 + content := strings.Replace(parts, messageTemp, "", 1) + messageTemp = parts + // 空白数据不处理 + if content == "" { + continue + } + // 生成响应 stream + apiRespStream := respModel.NewApiRespStream(id, apiReq.Model, content, "") + // 生成响应 bytes + bytes, err := common.Struct2Bytes(apiRespStream) + if err != nil { + logger.Logger.Error(err.Error()) + continue + } + // 发送响应 + c.SSEvent(name, fmt.Sprint(" ", string(bytes))) + // 继续 + continue + } + } +} + +func __CompletionsNoStream(c *gin.Context, apiReq *reqModel.ApiReq, resp *fhttp.Response) { + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + content := "" + decoder := eventsource.NewDecoder(resp.Body) + handlingSigns := false + for { + event, err := decoder.Decode() + if err != nil { + logger.Logger.Error(err.Error()) + common.ErrorResponse(c, http.StatusInternalServerError, "", err) + return + } + data := event.Data() + // 空白数据不处理 + if data == "" { + continue + } + // 结束标志 + if data == "[DONE]" { + apiRespObj := respModel.NewApiRespJson(v1.GenerateID(29), apiReq.Model, content) + // 返回响应 + c.JSON(http.StatusOK, apiRespObj) + return + } + chatResp35 := &respModel.ChatResp35{} + err = json.Unmarshal([]byte(data), chatResp35) + if chatResp35.Error != nil && !handlingSigns { + logger.Logger.Error(fmt.Sprint(chatResp35.Error)) + common.ErrorResponse(c, http.StatusInternalServerError, "", chatResp35.Error) + return + } + // 被block + if contentIsBlocked(chatResp35) { + // 返回响应 + common.ErrorResponse(c, http.StatusBadRequest, "content is blocked.", "") + return + } + // 脏数据不处理 + if err != nil { + continue + } + // 仅处理assistant的消息 + if chatResp35.Message.Author.Role == "assistant" && (chatResp35.Message.Status == "in_progress" || handlingSigns) { + // handlingSigns 置为 true + handlingSigns = true + // 如果不包含上一次的数据则不处理 + if !strings.Contains(chatResp35.Message.Content.Parts[0], content) { + continue + } + // 仅处理第一个part + content = chatResp35.Message.Content.Parts[0] + // 空白数据不处理 + if content == "" { + continue + } + continue + } + } +} + +func contentIsBlocked(chatResp35 *respModel.ChatResp35) bool { + if !chatResp35.IsCompletion && chatResp35.ModerationResponse.Blocked { + return true + } + return false +} diff --git a/service/v1Chat/reqModel/apiReq.go b/service/v1Chat/reqModel/apiReq.go new file mode 100644 index 0000000000000000000000000000000000000000..3363f3638603fb56147f7fabeddab70d372912e4 --- /dev/null +++ b/service/v1Chat/reqModel/apiReq.go @@ -0,0 +1,14 @@ +package reqModel + +type ApiReq struct { + Messages []ApiMessage `json:"messages"` + Model string `json:"model"` + Stream bool `json:"stream"` + PluginIds []string `json:"plugin_ids"` + NewMessages string `json:"-"` +} + +type ApiMessage struct { + Role string `json:"role"` + Content string `json:"content"` +} diff --git a/service/v1Chat/reqModel/chatReq.go b/service/v1Chat/reqModel/chatReq.go new file mode 100644 index 0000000000000000000000000000000000000000..41e7c34df62d734d8dd76c0f1b43ab21548fdf55 --- /dev/null +++ b/service/v1Chat/reqModel/chatReq.go @@ -0,0 +1,31 @@ +package reqModel + +type ChatAuthor struct { + Role string `json:"role"` +} + +type ChatContent struct { + ContentType string `json:"content_type"` + Parts []string `json:"parts"` +} + +type ChatMessages struct { + Author ChatAuthor `json:"author"` + Content ChatContent `json:"content"` +} + +type ChatConversationMode struct { + Kind string `json:"kind"` +} + +type ChatReq35 struct { + Action string `json:"action"` + Messages []ChatMessages `json:"messages"` + ParentMessageId string `json:"parent_message_id"` + Model string `json:"model"` + TimeZoneOffsetMin int `json:"timezone_offset_min"` + Suggestions []string `json:"suggestions"` + HistoryAndTrainingDisabled bool `json:"history_and_training_disabled"` + ConversationMode ChatConversationMode `json:"conversation_mode"` + WebsocketRequestId string `json:"websocket_request_id"` +} diff --git a/service/v1Chat/respModel/apiRespJson.go b/service/v1Chat/respModel/apiRespJson.go new file mode 100644 index 0000000000000000000000000000000000000000..e6ca66afb59e7cd923ffb5885cb0a37274564992 --- /dev/null +++ b/service/v1Chat/respModel/apiRespJson.go @@ -0,0 +1,54 @@ +package respModel + +import "time" + +type ApiRespJson struct { + ID string `json:"id"` + Object string `json:"object"` + Created int64 `json:"created"` + Model string `json:"model"` + Usage ApiRespJsonUsage `json:"usage"` + Choices []ApiRespJsonChoice `json:"choices"` +} + +type ApiRespJsonMessage struct { + Role string `json:"role"` + Content string `json:"content"` +} + +type ApiRespJsonChoice struct { + Message ApiRespJsonMessage `json:"message"` + FinishReason string `json:"finish_reason"` + Index int `json:"index"` +} + +type ApiRespJsonUsage struct { + PromptTokens int `json:"prompt_tokens"` + CompletionTokens int `json:"completion_tokens"` + TotalTokens int `json:"total_tokens"` +} + +func NewApiRespJson(id string, model string, content string) *ApiRespJson { + apiRespObj := &ApiRespJson{ + ID: id, + Created: time.Now().Unix(), + Object: "chat.completion", + Model: model, + Usage: ApiRespJsonUsage{ + PromptTokens: 0, + CompletionTokens: 0, + TotalTokens: 0, + }, + Choices: []ApiRespJsonChoice{ + { + Message: ApiRespJsonMessage{ + Role: "assistant", + Content: content, + }, + FinishReason: "stop", + Index: 0, + }, + }, + } + return apiRespObj +} \ No newline at end of file diff --git a/service/v1Chat/respModel/apiRespStream.go b/service/v1Chat/respModel/apiRespStream.go new file mode 100644 index 0000000000000000000000000000000000000000..fad990741116fbedf3c76d9b990b04453db35808 --- /dev/null +++ b/service/v1Chat/respModel/apiRespStream.go @@ -0,0 +1,43 @@ +package respModel + +import "time" + +// ApiRespStream represents the JSON structure +type ApiRespStream struct { + ID string `json:"id"` + Object string `json:"object"` + Created int64 `json:"created"` + Model string `json:"model"` + Choices []ApiStreamChoice `json:"choices"` +} + +// ApiStreamChoice represents the nested "choices" object in the JSON +type ApiStreamChoice struct { + Delta ApiStreamDelta `json:"delta"` + Index int `json:"index"` + FinishReason string `json:"finish_reason"` +} + +// ApiStreamDelta represents the nested "delta" object in the JSON +type ApiStreamDelta struct { + Content string `json:"content"` +} + +func NewApiRespStream(id string, model string, content string, finishReason string) *ApiRespStream { + // 生成响应 model + apiRespStream := &ApiRespStream{ + ID: id, + Created: time.Now().Unix(), + Object: "chat.completion.chunk", + Model: model, + Choices: []ApiStreamChoice{ + { + Delta: ApiStreamDelta{ + Content: content, + }, + FinishReason: finishReason, + }, + }, + } + return apiRespStream +} diff --git a/service/v1Chat/respModel/chatResp.go b/service/v1Chat/respModel/chatResp.go new file mode 100644 index 0000000000000000000000000000000000000000..b54ccd01e79b3ba87968e7db9b46ea2142ff5ff5 --- /dev/null +++ b/service/v1Chat/respModel/chatResp.go @@ -0,0 +1,44 @@ +package respModel + +type ChatResp35 struct { + Message struct { + Id string `json:"id"` + Author struct { + Role string `json:"role"` + Name interface{} `json:"name"` + Metadata struct { + } `json:"metadata"` + } `json:"author"` + CreateTime float64 `json:"create_time"` + UpdateTime interface{} `json:"update_time"` + Content struct { + ContentType string `json:"content_type"` + Parts []string `json:"parts"` + } `json:"content"` + Status string `json:"status"` + EndTurn interface{} `json:"end_turn"` + Weight float64 `json:"weight"` + Metadata struct { + Citations []interface{} `json:"citations"` + GizmoId interface{} `json:"gizmo_id"` + MessageType string `json:"message_type"` + ModelSlug string `json:"model_slug"` + DefaultModelSlug string `json:"default_model_slug"` + Pad string `json:"pad"` + ParentId string `json:"parent_id"` + } `json:"metadata"` + Recipient string `json:"recipient"` + } `json:"message"` + ConversationId string `json:"conversation_id"` + Error interface{} `json:"error"` + // 审核 + Type string `json:"type"` + MessageId string `json:"message_id"` + IsCompletion bool `json:"is_completion"` + ModerationResponse struct { + Flagged bool `json:"flagged"` + Disclaimers []interface{} `json:"disclaimers"` + Blocked bool `json:"blocked"` + ModerationId string `json:"moderation_id"` + } `json:"moderation_response"` +} diff --git a/typings/duckgo/request.go b/typings/duckgo/request.go deleted file mode 100644 index b901d1c7f37ff3ee588c989ce66232e55a79fa25..0000000000000000000000000000000000000000 --- a/typings/duckgo/request.go +++ /dev/null @@ -1,23 +0,0 @@ -package duckgo - -type ApiRequest struct { - Model string `json:"model"` - Messages []messages `json:"messages"` -} -type messages struct { - Role string `json:"role"` - Content string `json:"content"` -} - -func (a *ApiRequest) AddMessage(role string, content string) { - a.Messages = append(a.Messages, messages{ - Role: role, - Content: content, - }) -} - -func NewApiRequest(model string) ApiRequest { - return ApiRequest{ - Model: model, - } -} diff --git a/typings/duckgo/response.go b/typings/duckgo/response.go deleted file mode 100644 index 010bea4db4aa17f374ecde33455f85c3548f4953..0000000000000000000000000000000000000000 --- a/typings/duckgo/response.go +++ /dev/null @@ -1,9 +0,0 @@ -package duckgo - -type ApiResponse struct { - Message string `json:"message"` - Created int `json:"created"` - Id string `json:"id"` - Action string `json:"action"` - Model string `json:"model"` -} diff --git a/typings/official/request.go b/typings/official/request.go deleted file mode 100644 index 43ee3ea74b825ae54d5e33b1b886ba723ed25bd1..0000000000000000000000000000000000000000 --- a/typings/official/request.go +++ /dev/null @@ -1,21 +0,0 @@ -package official - -type APIRequest struct { - Messages []api_message `json:"messages"` - Stream bool `json:"stream"` - Model string `json:"model"` - PluginIDs []string `json:"plugin_ids"` -} - -type api_message struct { - Role string `json:"role"` - Content string `json:"content"` -} - -type OpenAISessionToken struct { - SessionToken string `json:"session_token"` -} - -type OpenAIRefreshToken struct { - RefreshToken string `json:"refresh_token"` -} diff --git a/typings/official/response.go b/typings/official/response.go deleted file mode 100644 index 67624c2b42ec5ac326c68e858711936d4c625ded..0000000000000000000000000000000000000000 --- a/typings/official/response.go +++ /dev/null @@ -1,162 +0,0 @@ -package official - -import "encoding/json" - -type ChatCompletionChunk struct { - ID string `json:"id"` - Object string `json:"object"` - Created int64 `json:"created"` - Model string `json:"model"` - Choices []Choices `json:"choices"` -} - -func (chunk *ChatCompletionChunk) String() string { - resp, _ := json.Marshal(chunk) - return string(resp) -} - -type Choices struct { - Delta Delta `json:"delta"` - Index int `json:"index"` - FinishReason interface{} `json:"finish_reason"` -} - -type Delta struct { - Content string `json:"content,omitempty"` - Role string `json:"role,omitempty"` -} - -func NewChatCompletionChunk(text string) ChatCompletionChunk { - return ChatCompletionChunk{ - ID: "chatcmpl-QXlha2FBbmROaXhpZUFyZUF3ZXNvbWUK", - Object: "chat.completion.chunk", - Created: 0, - Model: "gpt-3.5-turbo-0301", - Choices: []Choices{ - { - Index: 0, - Delta: Delta{ - Content: text, - }, - FinishReason: nil, - }, - }, - } -} - -func NewChatCompletionChunkWithModel(text string, model string) ChatCompletionChunk { - return ChatCompletionChunk{ - ID: "chatcmpl-QXlha2FBbmROaXhpZUFyZUF3ZXNvbWUK", - Object: "chat.completion.chunk", - Created: 0, - Model: model, - Choices: []Choices{ - { - Index: 0, - Delta: Delta{ - Content: text, - }, - FinishReason: nil, - }, - }, - } -} - -func StopChunkWithModel(reason string, model string) ChatCompletionChunk { - return ChatCompletionChunk{ - ID: "chatcmpl-QXlha2FBbmROaXhpZUFyZUF3ZXNvbWUK", - Object: "chat.completion.chunk", - Created: 0, - Model: model, - Choices: []Choices{ - { - Index: 0, - FinishReason: reason, - }, - }, - } -} - -func StopChunk(reason string) ChatCompletionChunk { - return ChatCompletionChunk{ - ID: "chatcmpl-QXlha2FBbmROaXhpZUFyZUF3ZXNvbWUK", - Object: "chat.completion.chunk", - Created: 0, - Model: "gpt-3.5-turbo-0125", - Choices: []Choices{ - { - Index: 0, - FinishReason: reason, - }, - }, - } -} - -type ChatCompletion struct { - ID string `json:"id"` - Object string `json:"object"` - Created int64 `json:"created"` - Model string `json:"model"` - Usage usage `json:"usage"` - Choices []Choice `json:"choices"` -} -type Msg struct { - Role string `json:"role"` - Content string `json:"content"` -} -type Choice struct { - Index int `json:"index"` - Message Msg `json:"message"` - FinishReason interface{} `json:"finish_reason"` -} -type usage struct { - PromptTokens int `json:"prompt_tokens"` - CompletionTokens int `json:"completion_tokens"` - TotalTokens int `json:"total_tokens"` -} - -func NewChatCompletionWithModel(text string, model string) ChatCompletion { - return ChatCompletion{ - ID: "chatcmpl-QXlha2FBbmROaXhpZUFyZUF3ZXNvbWUK", - Object: "chat.completion", - Created: int64(0), - Model: model, - Usage: usage{ - PromptTokens: 0, - CompletionTokens: 0, - TotalTokens: 0, - }, - Choices: []Choice{ - { - Message: Msg{ - Content: text, - Role: "assistant", - }, - Index: 0, - }, - }, - } -} - -func NewChatCompletion(full_test string, input_tokens, output_tokens int) ChatCompletion { - return ChatCompletion{ - ID: "chatcmpl-QXlha2FBbmROaXhpZUFyZUF3ZXNvbWUK", - Object: "chat.completion", - Created: int64(0), - Model: "gpt-3.5-turbo-0125", - Usage: usage{ - PromptTokens: input_tokens, - CompletionTokens: output_tokens, - TotalTokens: input_tokens + output_tokens, - }, - Choices: []Choice{ - { - Message: Msg{ - Content: full_test, - Role: "assistant", - }, - Index: 0, - }, - }, - } -} diff --git a/typings/typings.go b/typings/typings.go deleted file mode 100644 index 044a5ead907fcea53649191a71cc78b58ff62cf0..0000000000000000000000000000000000000000 --- a/typings/typings.go +++ /dev/null @@ -1,10 +0,0 @@ -package typings - -type GenericResponseLine struct { - Line string `json:"line"` - Error string `json:"error"` -} - -type StringStruct struct { - Text string `json:"text"` -} diff --git a/util/util.go b/util/util.go deleted file mode 100644 index 367b56307c3f8bce7a1ab66c0672520ffad98307..0000000000000000000000000000000000000000 --- a/util/util.go +++ /dev/null @@ -1,39 +0,0 @@ -package util - -import ( - "github.com/pkoukk/tiktoken-go" - "log/slog" - "math/rand" - "time" -) - -func RandomLanguage() string { - // 初始化随机数生成器 - rand.Seed(time.Now().UnixNano()) - // 语言列表 - 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"} - // 随机选择一个语言 - randomIndex := rand.Intn(len(languages)) - return languages[randomIndex] -} - -func RandomHexadecimalString() string { - rand.Seed(time.Now().UnixNano()) - const charset = "0123456789abcdef" - const length = 16 // The length of the string you want to generate - b := make([]byte, length) - for i := range b { - b[i] = charset[rand.Intn(len(charset))] - } - return string(b) -} -func CountToken(input string) int { - encoding := "gpt-3.5-turbo" - tkm, err := tiktoken.EncodingForModel(encoding) - if err != nil { - slog.Warn("tiktoken.EncodingForModel error:", err) - return 0 - } - token := tkm.Encode(input, nil, nil) - return len(token) -} diff --git a/util/utils_test.go b/util/utils_test.go deleted file mode 100644 index be2a0d9a7a515492d65893fa5c0de29a110d550a..0000000000000000000000000000000000000000 --- a/util/utils_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package util - -import ( - "fmt" - "testing" -) - -func TestRandomHexadecimalString(t *testing.T) { - var str = RandomHexadecimalString() - fmt.Println(str) -} diff --git a/vercel.json b/vercel.json deleted file mode 100644 index 15103b0d4c6b439a6568160fcbc0eb03ec1bf9cc..0000000000000000000000000000000000000000 --- a/vercel.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "routes": [ - { - "src": "/.*", - "dest": "/api/router.go" - } - ] -} diff --git a/web/avatar.png b/web/avatar.png deleted file mode 100644 index 0d65fe7c5d9b508416b34ac8a08e50e5616a46cc..0000000000000000000000000000000000000000 Binary files a/web/avatar.png and /dev/null differ diff --git a/web/icon.png b/web/icon.png deleted file mode 100644 index 5730de9c25af4ab0082e4f81f222da76bd1ab9a9..0000000000000000000000000000000000000000 Binary files a/web/icon.png and /dev/null differ diff --git a/web/index.html b/web/index.html deleted file mode 100644 index 80e5d4fafd726d9779a5b9df4bfa602b9d11eb9e..0000000000000000000000000000000000000000 --- a/web/index.html +++ /dev/null @@ -1,7005 +0,0 @@ - - - - - - - - - - - - - - - - - ChatGPT - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
ChatGPT
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
ChatGPT
-
- - -
- -
-
-
-
- -
-
-
-
- - -
- - -
-
-
-
-
-
- - - -
-
-
-
-
- - - -
-
- - - -
-
- - - -
-
-
-
-
-
-
-
- - - -
-
- - - -
-
- - - -
-
-
- -
-
-
-
- - - -
-
- - - -
-
-
-
-
-
- - -
-
-
-
-
- -
-
-
- - -
-
-
-
- - - - - - - - - - - - - - - - - - -