├── pkg ├── support │ ├── file.go │ ├── t.go │ ├── http.go │ ├── ip.go │ └── s.go └── jscrypt │ └── crypt.go ├── internal ├── vars │ └── var.go ├── types │ ├── auth.go │ ├── coze.go │ ├── gemini.go │ ├── claude.go │ ├── bing.go │ └── openai.go ├── coze │ ├── discord │ │ ├── types.go │ │ ├── live_discord_bot.go │ │ ├── api_http.go │ │ └── discord.go │ └── api │ │ └── api.go ├── openai │ ├── api │ │ ├── all.go │ │ └── platform.go │ ├── cst │ │ └── c.go │ ├── auth │ │ ├── platform.go │ │ └── web.go │ ├── image │ │ └── image.go │ └── arkose │ │ ├── solve │ │ ├── solve.go │ │ └── token.go │ │ ├── har │ │ └── har.go │ │ └── funcaptcha │ │ └── funcaptcha.go ├── client │ └── client.go ├── config │ └── config.go ├── gemini │ └── gemini.go └── bing │ └── bing.go ├── .github └── workflows │ ├── go.yml │ └── docker-image.yml ├── Dockerfile ├── LICENSE ├── go.mod ├── cmd └── main.go ├── etc └── c.yaml ├── README.md └── go.sum /pkg/support/file.go: -------------------------------------------------------------------------------- 1 | package support 2 | 3 | import "os" 4 | 5 | func FileExists(filePath string) bool { 6 | if _, err := os.Stat(filePath); err != nil { 7 | return false 8 | } 9 | return true 10 | } 11 | -------------------------------------------------------------------------------- /pkg/support/t.go: -------------------------------------------------------------------------------- 1 | package support 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func TimeStamp() string { 9 | return fmt.Sprintf("%d", time.Now().UnixNano()/int64(time.Millisecond)) 10 | } 11 | 12 | func TimeString() string { 13 | now := time.Now() 14 | return fmt.Sprintf("%s%d", now.Format("20060102150405"), now.UnixNano()%1e9) 15 | } 16 | -------------------------------------------------------------------------------- /internal/vars/var.go: -------------------------------------------------------------------------------- 1 | package vars 2 | 3 | var ( 4 | AcceptAll = "*/*" 5 | AcceptStream = "text/event-stream" 6 | AcceptEncoding = "gzip, deflate, br, zstd" 7 | UserAgentOkHttp = "okhttp/4.10.0" 8 | ContentType = "application/x-www-form-urlencoded" 9 | ContentTypeJSON = "application/json" 10 | ContentTypeStream = "text/event-stream; charset=utf-8" 11 | UserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0" 12 | ) 13 | -------------------------------------------------------------------------------- /pkg/support/http.go: -------------------------------------------------------------------------------- 1 | package support 2 | 3 | import ( 4 | "encoding/base64" 5 | "strings" 6 | ) 7 | 8 | func EqURL(s string) bool { 9 | return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://") || strings.HasPrefix(s, "ftp://") 10 | } 11 | 12 | func EqImageBase64(s string) bool { 13 | if !strings.HasPrefix(s, "data:image/") || !strings.Contains(s, ";base64,") { 14 | return false 15 | } 16 | dataParts := strings.Split(s, ";base64,") 17 | if len(dataParts) != 2 { 18 | return false 19 | } 20 | base64Data := dataParts[1] 21 | _, err := base64.StdEncoding.DecodeString(base64Data) 22 | return err == nil 23 | } 24 | -------------------------------------------------------------------------------- /pkg/support/ip.go: -------------------------------------------------------------------------------- 1 | package support 2 | 3 | import ( 4 | "math/rand" 5 | "strconv" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | func GenerateUsRandIp() string { 11 | var ipBuilder strings.Builder 12 | ipBuilder.WriteString("13.") 13 | ipBuilder.WriteString(strconv.Itoa(RandIntn(104, 107))) 14 | ipBuilder.WriteString(".") 15 | ipBuilder.WriteString(strconv.Itoa(RandIntn(0, 255))) 16 | ipBuilder.WriteString(".") 17 | ipBuilder.WriteString(strconv.Itoa(RandIntn(0, 255))) 18 | return ipBuilder.String() 19 | } 20 | 21 | func RandIntn(min, max int) int { 22 | rand.Seed(time.Now().UnixNano()) 23 | return rand.Intn(max-min+1) + min 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.22.2' 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | 27 | - name: Test 28 | run: go test -v ./... 29 | -------------------------------------------------------------------------------- /internal/types/auth.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type OpenAiWebAuthTokenRequest struct { 4 | Email string `json:"email" binding:"required"` 5 | Password string `json:"password" binding:"required"` 6 | ArkoseToken string `json:"arkose_token,omitempty"` 7 | Reset bool `json:"reset,omitempty"` 8 | } 9 | 10 | type OpenAiCsrfTokenResponse struct { 11 | Token string `json:"csrfToken"` 12 | } 13 | 14 | type OpenAiWebAuthTokenResponse struct { 15 | AccessToken string `json:"accessToken"` 16 | AuthProvider string `json:"authProvider"` 17 | Expires string `json:"expires"` 18 | User map[string]any `json:"user"` 19 | } 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:bookworm 2 | WORKDIR /go/src/anp 3 | ADD . /go/src/anp 4 | ENV CGO_ENABLED=0 5 | RUN go build -ldflags "-s -w" -o main cmd/main.go 6 | RUN cd /go/src/anp \ 7 | && apt update \ 8 | && apt install xz-utils \ 9 | && wget https://github.com/upx/upx/releases/download/v4.2.4/upx-4.2.4-$(go env GOARCH)_linux.tar.xz \ 10 | && tar -xf upx-4.2.4-$(go env GOARCH)_linux.tar.xz \ 11 | && cp upx-4.2.4-$(go env GOARCH)_linux/upx upx \ 12 | && ./upx --best main 13 | FROM gcr.io/distroless/static-debian12 14 | MAINTAINER Tu 15 | WORKDIR /anp 16 | ADD . /anp/data/ssl 17 | ADD . /anp/data/hars 18 | ADD . /anp/data/pics 19 | ADD . /anp/data/cookies 20 | COPY --from=0 /go/src/anp/main /anp/ 21 | COPY --from=0 /go/src/anp/etc /anp/data/etc 22 | ENTRYPOINT ["/anp/main", "-c", "/anp/data/etc/c.yaml"] -------------------------------------------------------------------------------- /internal/coze/discord/types.go: -------------------------------------------------------------------------------- 1 | package discord 2 | 3 | type ChannelResp struct { 4 | Id string `json:"id" swaggertype:"string" description:"频道ID"` 5 | Name string `json:"name" swaggertype:"string" description:"频道名称"` 6 | } 7 | 8 | type ChannelStopChan struct { 9 | Id string `json:"id" ` 10 | IsNew bool `json:"IsNew"` 11 | } 12 | 13 | type ChannelReq struct { 14 | ParentId string `json:"parentId" swaggertype:"string" description:"父频道Id,为空时默认为创建父频道"` 15 | Type int `json:"type" swaggertype:"number" description:"类型:[0:文本频道,4:频道分类](其它枚举请查阅discord-api文档)"` 16 | Name string `json:"name" swaggertype:"string" description:"频道名称"` 17 | } 18 | 19 | type ReplyResp struct { 20 | Content string `json:"content" swaggertype:"string" description:"回复内容"` 21 | EmbedUrls []string `json:"embedUrls" swaggertype:"array,string" description:"嵌入网址"` 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 zatxm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker image 2 | 3 | on: 4 | push: 5 | tags: 6 | - '**' 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v4 14 | - name: Set up QEMU 15 | uses: docker/setup-qemu-action@v3 16 | - name: Set up Docker Buildx 17 | uses: docker/setup-buildx-action@v3 18 | 19 | - name: Get metadata for Docker 20 | id: metadata 21 | uses: docker/metadata-action@v5 22 | with: 23 | images: | 24 | zatxm120/aiproxy 25 | 26 | - name: Log in to Docker Hub 27 | uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a 28 | with: 29 | username: ${{ secrets.DOCKERHUB_USERNAME }} 30 | password: ${{ secrets.DOCKERHUB_TOKEN }} 31 | 32 | - name: Build and push image 33 | uses: docker/build-push-action@v5 34 | with: 35 | context: . 36 | file: Dockerfile 37 | platforms: linux/amd64,linux/arm64 38 | push: true 39 | tags: ${{ steps.metadata.outputs.tags }} 40 | -------------------------------------------------------------------------------- /internal/openai/api/all.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | http "github.com/bogdanfinn/fhttp" 5 | "github.com/zatxm/any-proxy/internal/bing" 6 | "github.com/zatxm/any-proxy/internal/claude" 7 | coze "github.com/zatxm/any-proxy/internal/coze/api" 8 | "github.com/zatxm/any-proxy/internal/gemini" 9 | "github.com/zatxm/any-proxy/internal/types" 10 | "github.com/zatxm/fhblade" 11 | ) 12 | 13 | // v1/chat/completions通用接口,目前只支持stream=true 14 | func DoChatCompletions() func(*fhblade.Context) error { 15 | return func(c *fhblade.Context) error { 16 | var p types.ChatCompletionRequest 17 | if err := c.ShouldBindJSON(&p); err != nil { 18 | return c.JSONAndStatus(http.StatusBadRequest, types.ErrorResponse{ 19 | Error: &types.CError{ 20 | Message: "params error", 21 | Type: "invalid_request_error", 22 | Code: "invalid_parameter", 23 | }, 24 | }) 25 | } 26 | switch p.Provider { 27 | case Provider: 28 | return DoChatCompletionsByWeb(c, p) 29 | case gemini.Provider: 30 | return gemini.DoChatCompletions(c, p) 31 | case bing.Provider: 32 | return bing.DoChatCompletions(c, p) 33 | case coze.Provider: 34 | return coze.DoChatCompletions(c, p) 35 | case claude.Provider: 36 | return claude.DoChatCompletions(c, p) 37 | default: 38 | return DoHttp(c, "/v1/chat/completions") 39 | } 40 | return nil 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /internal/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/zatxm/any-proxy/internal/config" 7 | "github.com/zatxm/fhblade" 8 | tlsClient "github.com/zatxm/tls-client" 9 | "github.com/zatxm/tls-client/profiles" 10 | "go.uber.org/zap" 11 | ) 12 | 13 | var ( 14 | defaultTimeoutSeconds = 600 15 | CPool = sync.Pool{ 16 | New: func() interface{} { 17 | c, err := tlsClient.NewHttpClient(tlsClient.NewNoopLogger(), []tlsClient.HttpClientOption{ 18 | tlsClient.WithTimeoutSeconds(defaultTimeoutSeconds), 19 | tlsClient.WithClientProfile(profiles.Okhttp4Android13), 20 | }...) 21 | if err != nil { 22 | fhblade.Log.Error("ClientPool error", zap.Error(err)) 23 | } 24 | proxyUrl := config.ProxyUrl() 25 | if proxyUrl != "" { 26 | c.SetProxy(proxyUrl) 27 | } 28 | return c 29 | }, 30 | } 31 | CcPool = sync.Pool{ 32 | New: func() interface{} { 33 | c, err := tlsClient.NewHttpClient(tlsClient.NewNoopLogger(), []tlsClient.HttpClientOption{ 34 | tlsClient.WithTimeoutSeconds(defaultTimeoutSeconds), 35 | tlsClient.WithCookieJar(tlsClient.NewCookieJar()), 36 | tlsClient.WithClientProfile(profiles.Okhttp4Android13), 37 | }...) 38 | if err != nil { 39 | fhblade.Log.Error("ClientWithCookiePool error", zap.Error(err)) 40 | } 41 | proxyUrl := config.ProxyUrl() 42 | if proxyUrl != "" { 43 | c.SetProxy(proxyUrl) 44 | } 45 | return c 46 | }, 47 | } 48 | ) 49 | -------------------------------------------------------------------------------- /pkg/support/s.go: -------------------------------------------------------------------------------- 1 | package support 2 | 3 | import ( 4 | crand "crypto/rand" 5 | "encoding/hex" 6 | "fmt" 7 | "math/rand" 8 | "net/url" 9 | "time" 10 | 11 | "github.com/zatxm/fhblade" 12 | "github.com/zatxm/fhblade/tools" 13 | ) 14 | 15 | func GenerateRandomString(length int) string { 16 | const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 17 | rand.Seed(time.Now().UnixNano()) 18 | result := make([]byte, length) 19 | for i := range result { 20 | result[i] = charset[rand.Intn(len(charset))] 21 | } 22 | return tools.BytesToString(result) 23 | } 24 | 25 | func StructToFormByJson(data interface{}) string { 26 | b, _ := fhblade.Json.Marshal(data) 27 | var bMap map[string]interface{} 28 | fhblade.Json.Unmarshal(b, &bMap) 29 | var form url.Values = url.Values{} 30 | for k := range bMap { 31 | form.Add(k, fmt.Sprintf("%v", bMap[k])) 32 | } 33 | return form.Encode() 34 | } 35 | 36 | func RandHex(len int) string { 37 | b := make([]byte, len) 38 | crand.Read(b) 39 | return hex.EncodeToString(b) 40 | } 41 | 42 | func ExplodeSlice(s string, lg int) []string { 43 | var data []string 44 | runeSlice := []rune(s) 45 | l := len(runeSlice) 46 | for i := 0; i < l; i += lg { 47 | // 检查是否达到或超过字符串末尾 48 | if i+lg > l { 49 | // 如果超过,直接从当前位置到字符串末尾的所有字符都添加到结果切片中 50 | data = append(data, string(runeSlice[i:l])) 51 | } else { 52 | // 否则,从当前位置到i+lg的子切片添加到结果切片中 53 | data = append(data, string(runeSlice[i:i+lg])) 54 | } 55 | } 56 | return data 57 | } 58 | -------------------------------------------------------------------------------- /internal/coze/discord/live_discord_bot.go: -------------------------------------------------------------------------------- 1 | package discord 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/zatxm/any-proxy/internal/config" 8 | "github.com/zatxm/fhblade" 9 | "go.uber.org/zap" 10 | ) 11 | 12 | type LiveDiscordBot interface { 13 | Close() 14 | } 15 | 16 | type liveDiscordBot struct { 17 | sync.Mutex 18 | quit chan struct{} 19 | } 20 | 21 | func NewLiveDiscordBot() LiveDiscordBot { 22 | l := &liveDiscordBot{} 23 | l.quit = make(chan struct{}) 24 | go l.run() 25 | return l 26 | } 27 | 28 | func (l *liveDiscordBot) run() { 29 | // 创建定时器,设置定时器的间隔为距离明天凌晨的时间间隔 30 | now := time.Now() 31 | durationUntilTomorrow := time.Until(time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())) 32 | t := time.NewTimer(durationUntilTomorrow) 33 | defer t.Stop() 34 | 35 | for { 36 | select { 37 | case <-t.C: 38 | l.Lock() 39 | 40 | botIds := config.V().Coze.Discord.CozeBot 41 | if len(botIds) > 0 { 42 | for k := range botIds { 43 | botId := botIds[k] 44 | _, err := SendMessage("Hi!", botId) 45 | if err != nil { 46 | fhblade.Log.Error("Active bot error", 47 | zap.Error(err), 48 | zap.String("botId", botId)) 49 | } else { 50 | fhblade.Log.Debug("Active bot success", zap.String("botId", botId)) 51 | } 52 | } 53 | } 54 | 55 | l.Unlock() 56 | t.Reset(24 * time.Hour) 57 | case <-l.quit: 58 | return 59 | } 60 | } 61 | } 62 | 63 | func (l *liveDiscordBot) Close() { 64 | close(l.quit) 65 | } 66 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zatxm/any-proxy 2 | 3 | go 1.22.2 4 | 5 | require ( 6 | github.com/bogdanfinn/fhttp v0.5.28 7 | github.com/bwmarrin/discordgo v0.28.1 8 | github.com/google/uuid v1.6.0 9 | github.com/gorilla/websocket v1.5.1 10 | github.com/h2non/filetype v1.1.3 11 | github.com/zatxm/fhblade v0.0.0-20240108032359-f02aa4f5523f 12 | github.com/zatxm/tls-client v0.0.0-20231223102741-4e348055c451 13 | go.uber.org/zap v1.27.0 14 | golang.org/x/crypto v0.23.0 15 | gopkg.in/yaml.v3 v3.0.1 16 | ) 17 | 18 | require ( 19 | github.com/andybalholm/brotli v1.0.5 // indirect 20 | github.com/bogdanfinn/utls v1.6.1 // indirect 21 | github.com/cloudflare/circl v1.3.6 // indirect 22 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 23 | github.com/go-playground/locales v0.14.1 // indirect 24 | github.com/go-playground/universal-translator v0.18.1 // indirect 25 | github.com/go-playground/validator/v10 v10.16.0 // indirect 26 | github.com/json-iterator/go v1.1.12 // indirect 27 | github.com/klauspost/compress v1.16.7 // indirect 28 | github.com/leodido/go-urn v1.2.4 // indirect 29 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 30 | github.com/modern-go/reflect2 v1.0.2 // indirect 31 | github.com/pkg/errors v0.9.1 // indirect 32 | github.com/quic-go/quic-go v0.37.4 // indirect 33 | github.com/rogpeppe/go-internal v1.12.0 // indirect 34 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 // indirect 35 | github.com/ugorji/go/codec v1.2.12 // indirect 36 | go.uber.org/multierr v1.10.0 // indirect 37 | golang.org/x/net v0.21.0 // indirect 38 | golang.org/x/sys v0.20.0 // indirect 39 | golang.org/x/text v0.15.0 // indirect 40 | google.golang.org/protobuf v1.32.0 // indirect 41 | ) 42 | -------------------------------------------------------------------------------- /internal/types/coze.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type CozeCompletionRequest struct { 4 | Conversation *CozeConversation `json:"conversation,omitempty"` 5 | // 作为上下文传递的聊天历史记录 6 | ChatHistory []*CozeApiChatMessage `json:"chat_history,omitempty"` 7 | } 8 | 9 | type CozeApiChatMessage struct { 10 | // 发送消息角色,user用户输入内容,assistant为Bot返回内容 11 | Role string `json:"role"` 12 | // 标识消息类型,主要用于区分role=assistant时Bot返回的消息 13 | Type string `json:"type"` 14 | // 消息内容 15 | Content string `json:"content"` 16 | // 消息内容类型,一般为text 17 | ContentType string `json:"content_type"` 18 | } 19 | 20 | type CozeConversation struct { 21 | Type string `json:"type,omitempty"` 22 | // 要进行会话聊天的Bot ID 23 | BotId string `json:"bot_id"` 24 | // 对话标识,需自己生成 25 | ConversationId string `json:"conversation_id,omitempty"` 26 | // 标识当前与Bot交互的用户,使用方自行维护此字段 27 | User string `json:"user"` 28 | } 29 | 30 | // Api请求 31 | // https://www.coze.com/open/docs/chat?_lang=zh 32 | type CozeApiChatRequest struct { 33 | // 要进行会话聊天的Bot ID 34 | BotId string `json:"bot_id"` 35 | // 对话标识,需自己生成 36 | ConversationId string `json:"conversation_id,omitempty"` 37 | // 标识当前与Bot交互的用户,使用方自行维护此字段 38 | User string `json:"user"` 39 | // 用户输入内容 40 | Query string `json:"query"` 41 | // 作为上下文传递的聊天历史记录 42 | ChatHistory []*CozeApiChatMessage `json:"chat_history,omitempty"` 43 | // 使用启用流式返回,目前本代码只处理true流式响应 44 | Stream bool `json:"stream"` 45 | // Bot中定义的变量信息,key是变量名,value是变量值 46 | CustomVariables map[string]interface{} `json:"custom_variables,omitempty"` 47 | } 48 | 49 | // Api返回,本代码只处理true流式响应 50 | type CozeApiChatResponse struct { 51 | // 数据包事件,不同event会返回不同字段 52 | // message消息内容,done正常结束标志,error错误结束标志 53 | Event string `json:"event"` 54 | // event为error时返回的错误信息 55 | ErrorInformation map[string]interface{} `json:"error_information,omitempty"` 56 | // 增量返回的消息内容 57 | Message *CozeApiChatMessage `json:"message"` 58 | // 当前message是否结束,false未结束,true结束 59 | // 结束表示一条完整的消息已经发送完成,不代表整个模型返回结束 60 | IsFinish bool `json:"is_finish"` 61 | // 同一个index的增量返回属于同一条消息 62 | Index int `json:"index"` 63 | // 会话ID 64 | ConversationId string `json:"conversation_id"` 65 | // 序号 66 | SeqId int `json:"seq_id"` 67 | // 状态码,0表示调用成功 68 | Code int `json:"code,omitempty"` 69 | // 状态信息 70 | Msg string `json:"msg,omitempty"` 71 | } 72 | -------------------------------------------------------------------------------- /internal/openai/cst/c.go: -------------------------------------------------------------------------------- 1 | package cst 2 | 3 | import ( 4 | "github.com/google/uuid" 5 | ) 6 | 7 | const ( 8 | OaiLanguage = "en-US" 9 | 10 | ChatHost = "chatgpt.com" 11 | ChatOriginUrl = "https://chatgpt.com" 12 | ChatRefererUrl = "https://chatgpt.com/" 13 | ChatCsrfUrl = "https://chatgpt.com/api/auth/csrf" 14 | ChatPromptLoginUrl = "https://chatgpt.com/api/auth/signin/login-web?prompt=login&screen_hint=login" 15 | Auth0OriginUrl = "https://auth0.openai.com" 16 | AuthRefererUrl = "https://auth.openai.com/" 17 | LoginUsernameUrl = "https://auth0.openai.com/u/login/identifier?state=" 18 | LoginPasswordUrl = "https://auth0.openai.com/u/login/password?state=" 19 | ChatAuthRedirectUri = "https://chatgpt.com/api/auth/callback/login-web" 20 | ChatAuthSessionUrl = "https://chatgpt.com/api/auth/session" 21 | OauthTokenUrl = "https://auth0.openai.com/oauth/token" 22 | OauthTokenRevokeUrl = "https://auth0.openai.com/oauth/revoke" 23 | 24 | PlatformAuthClientID = "DRivsnm2Mu42T3KOpqdtwB3NYviHYzwD" 25 | PlatformAuth0Client = "eyJuYW1lIjoiYXV0aDAtc3BhLWpzIiwidmVyc2lvbiI6IjEuMjEuMCJ9" 26 | PlatformAuthAudience = "https://api.openai.com/v1" 27 | PlatformAuthRedirectURL = "https://platform.openai.com/auth/callback" 28 | PlatformAuthScope = "openid email profile offline_access model.request model.read organization.read organization.write" 29 | PlatformAuthResponseType = "code" 30 | PlatformAuthGrantType = "authorization_code" 31 | PlatformAuth0Url = "https://auth0.openai.com/authorize?" 32 | PlatformAuth0LogoutUrl = "https://auth0.openai.com/v2/logout?returnTo=https%3A%2F%2Fplatform.openai.com%2Floggedout&client_id=DRivsnm2Mu42T3KOpqdtwB3NYviHYzwD&auth0Client=eyJuYW1lIjoiYXV0aDAtc3BhLWpzIiwidmVyc2lvbiI6IjEuMjEuMCJ9" 33 | DashboardLoginUrl = "https://api.openai.com/dashboard/onboarding/login" 34 | ) 35 | 36 | var ( 37 | OaiDeviceId = uuid.NewString() 38 | ChatAskMap = map[string]map[string]string{ 39 | "backend-anon": map[string]string{ 40 | "requirementsPath": "/backend-anon/sentinel/chat-requirements", 41 | "askPath": "/backend-anon/conversation", 42 | }, 43 | "backend-api": map[string]string{ 44 | "requirementsPath": "/backend-api/sentinel/chat-requirements", 45 | "askPath": "/backend-api/conversation", 46 | }, 47 | } 48 | ) 49 | -------------------------------------------------------------------------------- /internal/types/gemini.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type GeminiCompletionRequest struct { 4 | GeminiCompletionResponse 5 | } 6 | 7 | type GeminiCompletionResponse struct { 8 | Type string `json:"type,omitempty"` 9 | Index string `json:"index,omitempty"` 10 | } 11 | 12 | // 流式响应模型对话,省略部分不常用参数 13 | type StreamGenerateContent struct { 14 | // 自定义的model 15 | Model string `json:"model,omitempty"` 16 | // 必需,当前与模型对话的内容 17 | // 对于单轮查询此值为单个实例 18 | // 对于多轮查询,此字段为重复字段,包含对话记录和最新请求 19 | Contents []*GeminiContent `json:"contents"` 20 | // 可选,开发者集系统说明,目前仅支持文字 21 | SystemInstruction []*GeminiContent `json:"systemInstruction,omitempty"` 22 | // 可选,用于模型生成和输出的配置选项 23 | GenerationConfig *GenerationConfig `json:"generationConfig,omitempty"` 24 | } 25 | 26 | type GeminiContent struct { 27 | Parts []*GeminiPart `json:"parts"` 28 | Role string `json:"role"` 29 | } 30 | 31 | // Union field data can be only one of the following 32 | type GeminiPart struct { 33 | // 文本 34 | Text string `json:"text,omitempty"` 35 | // 原始媒体字节 36 | MimeType string `json:"mimeType,omitempty"` //image/png等 37 | Data string `json:"data,omitempty"` //媒体格式的原始字节,使用base64编码的字符串 38 | // 基于URI的数据 39 | UriMimeType string `json:"mimeType,omitempty"` //可选,源数据的IANA标准MIME类型 40 | FileUri string `json:"fileUri,omitempty"` //必需,URI值 41 | } 42 | 43 | type GenerationConfig struct { 44 | // 将停止生成输出的字符序列集(最多5个) 45 | // 如果指定,API将在第一次出现停止序列时停止 46 | // 该停止序列不会包含在响应中 47 | StopSequences []string `json:"stopSequences,omitempty"` 48 | // 生成的候选文本的输出响应MIME类型 49 | // 支持的mimetype:text/plain(默认)文本输出,application/json JSON响应 50 | ResponseMimeType string `json:"responseMimeType,omitempty"` 51 | // 要返回的已生成响应数 52 | // 目前此值只能设置为1或者未设置默认为1 53 | CandidateCount int `json:"candidateCount,omitempty"` 54 | // 候选内容中包含的词元数量上限 55 | // 默认值因模型而异,请参阅getModel函数返回的Model的Model.output_token_limit属性 56 | MaxOutputTokens int `json:"maxOutputTokens,omitempty"` 57 | // 控制输出的随机性 58 | // 默认值因模型而异,请参阅getModel函数返回的Model的Model.temperature属性 59 | // 值的范围为[0.0, 2.0] 60 | Temperature float64 `json:"temperature,omitempty"` 61 | // 采样时要考虑的词元的最大累积概率 62 | // 该模型使用Top-k和核采样的组合 63 | // 词元根据其分配的概率进行排序,因此只考虑可能性最大的词元 64 | // Top-k采样会直接限制要考虑的最大词元数量,而Nucleus采样则会根据累计概率限制词元数量 65 | // 默认值因模型而异,请参阅getModel函数返回的Model的Model.top_p属性 66 | TopP float64 `json:"topP,omitempty"` 67 | // 采样时要考虑的词元数量上限 68 | // 模型使用核采样或合并Top-k和核采样,Top-k采样考虑topK集合中概率最高的词元 69 | // 通过核采样运行的模型不允许TopK设置 70 | // 默认值因模型而异,请参阅getModel函数返回的Model的Model.top_k属性 71 | // Model中的topK字段为空表示模型未应用Top-k采样,不允许对请求设置topK 72 | TopK int `json:"topK,omitempty"` 73 | } 74 | -------------------------------------------------------------------------------- /internal/openai/api/platform.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "math/rand" 5 | "strings" 6 | "time" 7 | 8 | http "github.com/bogdanfinn/fhttp" 9 | "github.com/bogdanfinn/fhttp/httputil" 10 | "github.com/zatxm/any-proxy/internal/client" 11 | "github.com/zatxm/any-proxy/internal/config" 12 | "github.com/zatxm/any-proxy/internal/vars" 13 | "github.com/zatxm/fhblade" 14 | tlsClient "github.com/zatxm/tls-client" 15 | ) 16 | 17 | func DoPlatform(tag string) func(*fhblade.Context) error { 18 | return func(c *fhblade.Context) error { 19 | path := "/" + tag + "/" + c.Get("path") 20 | return DoHttp(c, path) 21 | } 22 | } 23 | 24 | func DoHttp(c *fhblade.Context, path string) error { 25 | gClient := client.CPool.Get().(tlsClient.HttpClient) 26 | defer client.CPool.Put(gClient) 27 | // 防止乱七八糟的header被拒,特别是开启https的cf域名从大陆访问 28 | accept := c.Request().Header("Accept") 29 | if accept == "" { 30 | accept = "*/*" 31 | } 32 | auth, index := parseAuth(c, "api", "") 33 | if index != "" { 34 | c.Response().SetHeader("x-auth-id", index) 35 | } 36 | c.Request().Req().Header = http.Header{ 37 | "Accept": {accept}, 38 | "Accept-Encoding": {vars.AcceptEncoding}, 39 | "User-Agent": {vars.UserAgentOkHttp}, 40 | "Content-Type": {vars.ContentTypeJSON}, 41 | "Authorization": {"Bearer " + auth}, 42 | } 43 | query := c.Request().RawQuery() 44 | goProxy := httputil.ReverseProxy{ 45 | Director: func(req *http.Request) { 46 | req.Host = "api.openai.com" 47 | req.URL.Host = "api.openai.com" 48 | req.URL.Scheme = "https" 49 | req.URL.Path = path 50 | req.URL.RawQuery = query 51 | }, 52 | Transport: gClient.TClient().Transport, 53 | } 54 | goProxy.ServeHTTP(c.Response().Rw(), c.Request().Req()) 55 | return nil 56 | } 57 | 58 | // tag: api和web两种 59 | func parseAuth(c *fhblade.Context, tag string, index string) (string, string) { 60 | auth := c.Request().Header("Authorization") 61 | if auth != "" { 62 | if strings.HasPrefix(auth, "Bearer ") { 63 | return strings.TrimPrefix(auth, "Bearer "), "" 64 | } 65 | return auth, "" 66 | } 67 | 68 | var keys []config.ApiKeyMap 69 | if tag == "web" { 70 | keys = config.V().Openai.WebSessions 71 | } else { 72 | keys = config.V().Openai.ApiKeys 73 | } 74 | l := len(keys) 75 | if l == 0 { 76 | return "", "" 77 | } 78 | 79 | hIndex := c.Request().Header("x-auth-id") 80 | if hIndex != "" { 81 | index = hIndex 82 | } 83 | if index != "" { 84 | for k := range keys { 85 | v := keys[k] 86 | if index == v.ID { 87 | return v.Val, index 88 | } 89 | } 90 | return "", "" 91 | } 92 | 93 | if l == 1 { 94 | v := keys[0] 95 | return v.Val, v.ID 96 | } 97 | 98 | rand.Seed(time.Now().UnixNano()) 99 | i := rand.Intn(l) 100 | v := keys[i] 101 | return v.Val, v.ID 102 | } 103 | -------------------------------------------------------------------------------- /internal/coze/discord/api_http.go: -------------------------------------------------------------------------------- 1 | package discord 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "math/rand" 7 | "strings" 8 | "time" 9 | 10 | http "github.com/bogdanfinn/fhttp" 11 | "github.com/zatxm/any-proxy/internal/client" 12 | "github.com/zatxm/any-proxy/internal/config" 13 | "github.com/zatxm/any-proxy/internal/vars" 14 | "github.com/zatxm/fhblade" 15 | tlsClient "github.com/zatxm/tls-client" 16 | "go.uber.org/zap" 17 | ) 18 | 19 | func SendMsg(content string) (string, error) { 20 | authentication := GetRandomAuthentication() 21 | if authentication == "" { 22 | return "", fmt.Errorf("No available user auth") 23 | } 24 | cozeCfg := config.V().Coze.Discord 25 | channelId := cozeCfg.ChannelId 26 | b, _ := fhblade.Json.Marshal(map[string]interface{}{ 27 | "content": content, 28 | }) 29 | goUrl := "https://discord.com/api/v9/channels/" + channelId + "/messages" 30 | req, err := http.NewRequest("POST", goUrl, bytes.NewBuffer(b)) 31 | if err != nil { 32 | fhblade.Log.Error("discord api http send msg request new err", zap.Error(err)) 33 | return "", err 34 | } 35 | 36 | guildId := cozeCfg.GuildId 37 | referUrl := "https://discord.com/channels/" + guildId + "/" + channelId 38 | req.Header.Set("Content-Type", "application/json") 39 | req.Header.Set("Authorization", authentication) 40 | req.Header.Set("Origin", "https://discord.com") 41 | req.Header.Set("Referer", referUrl) 42 | req.Header.Set("User-Agent", vars.UserAgent) 43 | 44 | // 请求 45 | gClient := client.CPool.Get().(tlsClient.HttpClient) 46 | proxyUrl := config.CozeProxyUrl() 47 | if proxyUrl != "" { 48 | gClient.SetProxy(proxyUrl) 49 | } 50 | resp, err := gClient.Do(req) 51 | client.CPool.Put(gClient) 52 | if err != nil { 53 | fhblade.Log.Error("discord api http send msg req err", zap.Error(err)) 54 | return "", err 55 | } 56 | defer resp.Body.Close() 57 | 58 | // 处理响应 59 | var result map[string]interface{} 60 | if err := fhblade.Json.NewDecoder(resp.Body).Decode(&result); err != nil { 61 | fhblade.Log.Error("discord api http send msg res err", zap.Error(err)) 62 | return "", err 63 | } 64 | id, ok := result["id"].(string) 65 | if !ok { 66 | if errMessage, ok := result["message"].(string); ok { 67 | if strings.Contains(errMessage, "401: Unauthorized") || 68 | strings.Contains(errMessage, "You need to verify your account in order to perform this action.") { 69 | fhblade.Log.Error("discord authentication expired", zap.String("authentication", authentication)) 70 | return "", fmt.Errorf("errCode: %v, message: %v", 401, "discord authentication expired or error") 71 | } 72 | } 73 | return "", fmt.Errorf("/api/v9/channels/%s/messages response err", channelId) 74 | } 75 | return id, nil 76 | } 77 | 78 | // 随机获取设置的authentication 79 | func GetRandomAuthentication() string { 80 | authentications := config.V().Coze.Discord.Auth 81 | l := len(authentications) 82 | if l == 0 { 83 | return "" 84 | } 85 | if l == 1 { 86 | return authentications[0] 87 | } 88 | 89 | rand.Seed(time.Now().UnixNano()) 90 | index := rand.Intn(l) 91 | return authentications[index] 92 | } 93 | -------------------------------------------------------------------------------- /internal/openai/auth/platform.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "strings" 5 | 6 | http "github.com/bogdanfinn/fhttp" 7 | "github.com/zatxm/any-proxy/internal/client" 8 | "github.com/zatxm/any-proxy/internal/openai/cst" 9 | "github.com/zatxm/any-proxy/internal/vars" 10 | "github.com/zatxm/fhblade" 11 | tlsClient "github.com/zatxm/tls-client" 12 | "go.uber.org/zap" 13 | ) 14 | 15 | type tokenRefreshParams struct { 16 | RefreshToken string `json:"refresh_token" binding:"required"` 17 | } 18 | 19 | // session 20 | func DoPlatformSession() func(*fhblade.Context) error { 21 | return func(c *fhblade.Context) error { 22 | req, _ := http.NewRequest(http.MethodPost, cst.DashboardLoginUrl, strings.NewReader("{}")) 23 | req.Header.Set("content-type", vars.ContentTypeJSON) 24 | req.Header.Set("user-agent", vars.UserAgentOkHttp) 25 | req.Header.Set("authorization", c.Request().Header("Authorization")) 26 | gClient := client.CPool.Get().(tlsClient.HttpClient) 27 | resp, err := gClient.Do(req) 28 | if err != nil { 29 | client.CPool.Put(gClient) 30 | fhblade.Log.Error("auth/session/platform req err", zap.Error(err)) 31 | return c.JSONAndStatus(http.StatusInternalServerError, fhblade.H{"errorMessage": err.Error()}) 32 | } 33 | defer resp.Body.Close() 34 | client.CPool.Put(gClient) 35 | return c.Reader(resp.Body) 36 | } 37 | } 38 | 39 | // token refresh 40 | func DoPlatformRefresh() func(*fhblade.Context) error { 41 | return func(c *fhblade.Context) error { 42 | var p tokenRefreshParams 43 | if err := c.ShouldBindJSON(&p); err != nil { 44 | return c.JSONAndStatus(http.StatusBadRequest, fhblade.H{"errorMessage": "params error"}) 45 | } 46 | 47 | jsonBytes, _ := fhblade.Json.MarshalToString(map[string]string{ 48 | "redirect_uri": cst.PlatformAuthRedirectURL, 49 | "grant_type": "refresh_token", 50 | "client_id": cst.PlatformAuthClientID, 51 | "refresh_token": p.RefreshToken, 52 | }) 53 | req, _ := http.NewRequest(http.MethodPost, cst.OauthTokenUrl, strings.NewReader(jsonBytes)) 54 | req.Header.Set("content-type", vars.ContentTypeJSON) 55 | req.Header.Set("user-agent", vars.UserAgentOkHttp) 56 | gClient := client.CPool.Get().(tlsClient.HttpClient) 57 | resp, err := gClient.Do(req) 58 | if err != nil { 59 | client.CPool.Put(gClient) 60 | fhblade.Log.Error("token/platform/refresh req err", zap.Error(err)) 61 | return c.JSONAndStatus(http.StatusInternalServerError, fhblade.H{"errorMessage": err.Error()}) 62 | } 63 | defer resp.Body.Close() 64 | client.CPool.Put(gClient) 65 | return c.Reader(resp.Body) 66 | } 67 | } 68 | 69 | // refresh token revoke 70 | func DoPlatformRevoke() func(*fhblade.Context) error { 71 | return func(c *fhblade.Context) error { 72 | var p tokenRefreshParams 73 | if err := c.ShouldBindJSON(&p); err != nil { 74 | return c.JSONAndStatus(http.StatusBadRequest, fhblade.H{"errorMessage": "params error"}) 75 | } 76 | 77 | jsonBytes, _ := fhblade.Json.MarshalToString(map[string]string{ 78 | "client_id": cst.PlatformAuthClientID, 79 | "token": p.RefreshToken, 80 | }) 81 | req, _ := http.NewRequest(http.MethodPost, cst.OauthTokenRevokeUrl, strings.NewReader(jsonBytes)) 82 | req.Header.Set("content-type", vars.ContentTypeJSON) 83 | req.Header.Set("user-agent", vars.UserAgentOkHttp) 84 | gClient := client.CPool.Get().(tlsClient.HttpClient) 85 | resp, err := gClient.Do(req) 86 | if err != nil { 87 | client.CPool.Put(gClient) 88 | fhblade.Log.Error("token/platform/revoke req err", zap.Error(err)) 89 | return c.JSONAndStatus(http.StatusInternalServerError, fhblade.H{"errorMessage": err.Error()}) 90 | } 91 | defer resp.Body.Close() 92 | client.CPool.Put(gClient) 93 | return c.Reader(resp.Body) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /internal/openai/image/image.go: -------------------------------------------------------------------------------- 1 | package image 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "net/url" 7 | "os" 8 | "strings" 9 | 10 | http "github.com/bogdanfinn/fhttp" 11 | "github.com/zatxm/any-proxy/internal/client" 12 | "github.com/zatxm/any-proxy/internal/config" 13 | "github.com/zatxm/any-proxy/internal/types" 14 | "github.com/zatxm/any-proxy/internal/vars" 15 | "github.com/zatxm/any-proxy/pkg/support" 16 | "github.com/zatxm/fhblade" 17 | tlsClient "github.com/zatxm/tls-client" 18 | "go.uber.org/zap" 19 | ) 20 | 21 | var ( 22 | OpenaiChatImageUrl = "https://files.oaiusercontent.com" 23 | ) 24 | 25 | // 获取openai web图片,url如下 26 | // // https://files.oaiusercontent.com/file-srFSQTo80fxxueB7j?se=2024-05-29T14%3A51%3A37Z&sp=r&sv=2023-11-03&sr=b&rscc=max-age%3D299%2C%20immutable&rscd=attachment%3B%20filename%3Da5c9bcf0-d82e-471c-828b-bfb1af64e75c&sig=d/9DHT2L%2B/nTAwSDa2kpPVlYvnK1RgTz2OKIueHrDwg%3D 27 | func Do() func(*fhblade.Context) error { 28 | return func(c *fhblade.Context) error { 29 | path := c.Get("path") 30 | if !strings.HasPrefix(path, "file-") { 31 | return c.JSONAndStatus(http.StatusBadRequest, types.ErrorResponse{ 32 | Error: &types.CError{ 33 | Message: "params error", 34 | Type: "invalid_request_error", 35 | Code: "request_err", 36 | }, 37 | }) 38 | } 39 | // 存在保存的图片直接返回 40 | fileName := strings.TrimPrefix(path, "file-") 41 | file := config.V().Openai.ImagePath + "/" + fileName 42 | if !support.FileExists(file) { 43 | // 通信、保存图片、返回 44 | imageUrl := OpenaiChatImageUrl + "/" + path + "?" + c.Request().RawQuery() 45 | _, err := Save(imageUrl) 46 | if err != nil { 47 | return c.JSONAndStatus(http.StatusBadRequest, types.ErrorResponse{ 48 | Error: &types.CError{ 49 | Message: err.Error(), 50 | Type: "invalid_request_error", 51 | Code: "request_err", 52 | }, 53 | }) 54 | } 55 | } 56 | 57 | return c.File(file) 58 | } 59 | } 60 | 61 | func Save(imageUrl string) (string, error) { 62 | u, err := url.Parse(imageUrl) 63 | if err != nil { 64 | fhblade.Log.Error("openai save image parse url err", 65 | zap.Error(err), 66 | zap.String("url", imageUrl)) 67 | return "", err 68 | } 69 | 70 | // 请求 71 | req, err := http.NewRequest(http.MethodGet, imageUrl, nil) 72 | if err != nil { 73 | fhblade.Log.Error("openai save image req err", 74 | zap.Error(err), 75 | zap.String("url", imageUrl)) 76 | return "", err 77 | } 78 | req.Header = http.Header{ 79 | "accept": {"image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"}, 80 | "accept-encoding": {vars.AcceptEncoding}, 81 | } 82 | gClient := client.CPool.Get().(tlsClient.HttpClient) 83 | resp, err := gClient.Do(req) 84 | if err != nil { 85 | client.CPool.Put(gClient) 86 | fhblade.Log.Error("openai save image req do err", 87 | zap.Error(err), 88 | zap.String("url", imageUrl)) 89 | return "", err 90 | } 91 | defer resp.Body.Close() 92 | if resp.StatusCode != http.StatusOK { 93 | fhblade.Log.Error("openai save image res status err", 94 | zap.Int("status", resp.StatusCode), 95 | zap.String("url", imageUrl)) 96 | return "", errors.New("request image error") 97 | } 98 | // 创建一个文件用于保存图片 99 | id := strings.TrimPrefix(u.Path, "/file-") 100 | fileName := config.V().Openai.ImagePath + "/" + id 101 | file, err := os.Create(fileName) 102 | if err != nil { 103 | fhblade.Log.Error("openai save image openfile err", 104 | zap.String("url", imageUrl)) 105 | return "", err 106 | } 107 | defer file.Close() 108 | _, err = io.Copy(file, resp.Body) 109 | if err != nil { 110 | fhblade.Log.Error("openai save image save err", 111 | zap.String("url", imageUrl)) 112 | return "", err 113 | } 114 | 115 | return id, nil 116 | } 117 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | 8 | http "github.com/bogdanfinn/fhttp" 9 | "github.com/zatxm/any-proxy/internal/bing" 10 | "github.com/zatxm/any-proxy/internal/claude" 11 | "github.com/zatxm/any-proxy/internal/config" 12 | "github.com/zatxm/any-proxy/internal/coze/discord" 13 | "github.com/zatxm/any-proxy/internal/gemini" 14 | oapi "github.com/zatxm/any-proxy/internal/openai/api" 15 | "github.com/zatxm/any-proxy/internal/openai/arkose/har" 16 | "github.com/zatxm/any-proxy/internal/openai/arkose/solve" 17 | "github.com/zatxm/any-proxy/internal/openai/auth" 18 | "github.com/zatxm/any-proxy/internal/openai/image" 19 | "github.com/zatxm/fhblade" 20 | ) 21 | 22 | func main() { 23 | // parse config 24 | var configFile string 25 | flag.StringVar(&configFile, "c", "", "where is config filepath") 26 | flag.Parse() 27 | if configFile == "" { 28 | fmt.Println("You must set config file use -c") 29 | return 30 | } 31 | cfg, err := config.Parse(configFile) 32 | if err != nil { 33 | fmt.Println(err) 34 | return 35 | } 36 | 37 | // parse har 38 | err = har.Parse() 39 | if err != nil { 40 | fmt.Println(err) 41 | } 42 | 43 | if cfg.Coze.Discord.Enable { 44 | ctx, cancel := context.WithCancel(context.Background()) 45 | defer cancel() 46 | 47 | go discord.Parse(ctx) 48 | } 49 | 50 | app := fhblade.New() 51 | 52 | // ping 53 | app.Get("/ping", func(c *fhblade.Context) error { 54 | return c.JSONAndStatus(http.StatusOK, fhblade.H{"ping": "ok"}) 55 | }) 56 | 57 | // all 58 | app.Post("/c/v1/chat/completions", oapi.DoChatCompletions()) 59 | 60 | // bing 61 | app.Get("/bing/conversation", bing.DoListConversation()) 62 | app.Post("/bing/conversation", bing.DoCreateConversation()) 63 | app.Delete("/bing/conversation", bing.DoDeleteConversation()) 64 | app.Post("/bing/message", bing.DoSendMessage()) 65 | 66 | // claude 67 | // 转发web操作,关键要有sessionKey 68 | app.Any("/claude/web/*path", claude.ProxyWeb()) 69 | app.Any("/claude/api/*path", claude.ProxyApi()) 70 | 71 | // google gemini 72 | app.Any("/gemini/*path", gemini.Do()) 73 | 74 | // web login token 75 | app.Post("/auth/token/web", auth.DoWeb()) 76 | 77 | // refresh platform token 78 | app.Post("/auth/token/platform/refresh", auth.DoPlatformRefresh()) 79 | 80 | // revoke platform token 81 | app.Post("/auth/token/platform/revoke", auth.DoPlatformRevoke()) 82 | 83 | // get arkose token 84 | app.Post("/arkose/token/:pk", solve.DoAkToken()) 85 | 86 | // arkose token image 87 | app.Post("/arkose/solve/:pk", solve.DoSolveToken()) 88 | 89 | // proxy /public-api/* 90 | app.Any("/public-api/*path", oapi.DoWeb("public-api")) 91 | 92 | // 免登录chat会话 93 | app.Post("/backend-anon/conversation", oapi.DoAnonOrigin()) 94 | app.Post("/backend-anon/web2api", func(c *fhblade.Context) error { 95 | return oapi.DoWebToApi(c, "backend-anon") 96 | }) 97 | 98 | // chatgpt web图片 99 | app.Get("/gptimage/*path", image.Do()) 100 | 101 | // middleware 102 | app.Use(func(next fhblade.Handler) fhblade.Handler { 103 | return func(c *fhblade.Context) error { 104 | // cors 105 | c.Response().SetHeader("Access-Control-Allow-Origin", "*") 106 | c.Response().SetHeader("Access-Control-Allow-Headers", "*") 107 | c.Response().SetHeader("Access-Control-Allow-Methods", "*") 108 | return next(c) 109 | } 110 | }) 111 | 112 | // platform session key 113 | app.Post("auth/session/platform", auth.DoPlatformSession()) 114 | 115 | // proxy /dashboard/* 116 | app.Any("/dashboard/*path", oapi.DoPlatform("dashboard")) 117 | 118 | // proxy /v1/* 119 | app.Any("/v1/*path", oapi.DoPlatform("v1")) 120 | 121 | // proxy /backend-api/* 122 | app.Any("/backend-api/*path", oapi.DoWeb("backend-api")) 123 | 124 | // run 125 | var runErr error 126 | if cfg.HttpsInfo.Enable { 127 | runErr = app.RunTLS(cfg.Port, cfg.HttpsInfo.PemFile, cfg.HttpsInfo.KeyFile) 128 | } else { 129 | runErr = app.Run(cfg.Port) 130 | } 131 | if runErr != nil { 132 | fmt.Println(runErr) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "io/ioutil" 5 | 6 | "gopkg.in/yaml.v3" 7 | ) 8 | 9 | var cfg *Config 10 | 11 | type Config struct { 12 | Port string `yaml:"port"` 13 | HttpsInfo httpsInfo `yaml:"https_info"` 14 | HarsPath string `yaml:"hars_path"` 15 | ProxyUrl string `yaml:"proxy_url"` 16 | Openai openai `yaml:"openai"` 17 | Gemini gemini `yaml:"google_gemini"` 18 | Arkose arkose `yaml:"arkose"` 19 | Bing bing `yaml:"bing"` 20 | Coze coze `yaml:"coze"` 21 | Claude claude `yaml:"claude"` 22 | } 23 | 24 | type httpsInfo struct { 25 | Enable bool `yaml:"enable"` 26 | PemFile string `yaml:"pem_file"` 27 | KeyFile string `yaml:"key_file"` 28 | } 29 | 30 | type openai struct { 31 | AuthProxyUrl string `yaml:"auth_proxy_url"` 32 | CookiePath string `yaml:"cookie_path"` 33 | ChatWebUrl string `yaml:"chat_web_url"` 34 | ApiKeys []ApiKeyMap `yaml:"api_keys"` 35 | ImagePath string `yaml:"image_path"` 36 | WebSessions []ApiKeyMap `yaml:"web_sessions"` 37 | } 38 | 39 | type gemini struct { 40 | ProxyUrl string `yaml:"proxy_url"` 41 | Model string `yaml:"model"` 42 | ApiKeys []geminiApiKey `yaml:"api_keys"` 43 | } 44 | 45 | type geminiApiKey struct { 46 | ID string `yaml:"id"` 47 | Val string `yaml:"val"` 48 | Version string `yaml:"version"` 49 | } 50 | 51 | type arkose struct { 52 | GameCoreVersion string `yaml:"game_core_version"` 53 | ClientArkoselabsUrl string `yaml:"client_arkoselabs_url"` 54 | PicSavePath string `yaml:"pic_save_path"` 55 | SolveApiUrl string `yaml:"solve_api_url"` 56 | } 57 | 58 | type bing struct { 59 | ProxyUrl string `yaml:"proxy_url"` 60 | } 61 | 62 | type coze struct { 63 | ProxyUrl string `yaml:"proxy_url"` 64 | Discord cozeDiscord `yaml:"discord"` 65 | ApiChat cozeApiChat `yaml:"api_chat"` 66 | } 67 | 68 | type cozeDiscord struct { 69 | Enable bool `yaml:"enable"` 70 | GuildId string `yaml:"guild_id"` 71 | ChannelId string `yaml:"channel_id"` 72 | ChatBotToken string `yaml:"chat_bot_token"` 73 | CozeBot []string `yaml:"coze_bot"` 74 | RequestOutTime int64 `yaml:"request_out_time"` 75 | RequestStreamOutTime int64 `yaml:"request_stream_out_time"` 76 | Auth []string `yaml:"auth"` 77 | } 78 | 79 | type cozeApiChat struct { 80 | AccessToken string `yaml:"access_token"` 81 | Bots []cozeApiBot `yaml:"bots"` 82 | } 83 | 84 | type cozeApiBot struct { 85 | BotId string `yaml:"bot_id"` 86 | User string `yaml:"user"` 87 | AccessToken string `yaml:"access_token"` 88 | } 89 | 90 | type claude struct { 91 | ProxyUrl string `yaml:"proxy_url"` 92 | ApiVersion string `yaml:"api_version"` 93 | WebSessions []ApiKeyMap `yaml:"web_sessions"` 94 | ApiKeys []ApiKeyMap `yaml:"api_keys"` 95 | } 96 | 97 | type ApiKeyMap struct { 98 | ID string `yaml:"id"` 99 | Val string `yaml:"val"` 100 | OrganizationId string `yaml:"organization_id,omitempty"` 101 | } 102 | 103 | func V() *Config { 104 | return cfg 105 | } 106 | 107 | func Parse(filename string) (*Config, error) { 108 | data, err := ioutil.ReadFile(filename) 109 | if err != nil { 110 | return nil, err 111 | } 112 | if err := yaml.Unmarshal(data, &cfg); err != nil { 113 | return nil, err 114 | } 115 | return cfg, nil 116 | } 117 | 118 | func ProxyUrl() string { 119 | return cfg.ProxyUrl 120 | } 121 | 122 | func OpenaiAuthProxyUrl() string { 123 | return cfg.Openai.AuthProxyUrl 124 | } 125 | 126 | func GeminiProxyUrl() string { 127 | return cfg.Gemini.ProxyUrl 128 | } 129 | 130 | func BingProxyUrl() string { 131 | return cfg.Bing.ProxyUrl 132 | } 133 | 134 | func ClaudeProxyUrl() string { 135 | return cfg.Claude.ProxyUrl 136 | } 137 | 138 | func CozeProxyUrl() string { 139 | return cfg.Coze.ProxyUrl 140 | } 141 | 142 | func OpenaiChatWebUrl() string { 143 | return cfg.Openai.ChatWebUrl 144 | } 145 | -------------------------------------------------------------------------------- /etc/c.yaml: -------------------------------------------------------------------------------- 1 | # 如果用于docker,目录固定为/anp/data 2 | # 绑定的端口 3 | port : :8999 4 | 5 | # 开启https信息 6 | https_info : 7 | # 是否开启https 8 | enable: false 9 | # 证书pem或crt文件目录 10 | pem_file: /anp/data/ssl/my.pem 11 | # 证书key文件目录 12 | key_file: /anp/data/ssl/my.key 13 | 14 | # har文件目录,强烈建议加上,为了获取arkose token 15 | hars_path: /anp/data/hars 16 | 17 | # 代理url 18 | # proxy_url: http://127.0.0.1:1081 19 | 20 | # openai设置 21 | openai: 22 | # 登录设置代理 23 | # auth_proxy_url: http://127.0.0.1:1081 24 | # web登录后放置cookie的文件夹 25 | cookie_path: /anp/data/cookies 26 | # openai web chat url,可以修改为任意代理地址 27 | # 不设置默认官方https://chatgpt.com容易出盾 28 | # 目前chat.openai.com还能用,建议设置成这个 29 | # 结尾不要加/ 30 | chat_web_url: https://chat.openai.com 31 | # 保存openai web图片路径,结尾不要加/ 32 | image_path: /anp/data/images 33 | # api密钥 34 | api_keys: 35 | - 36 | # 密钥标识,可通过头部传递x-auth-id识别 37 | id: 10001 38 | # 密钥 39 | val: sk-proj-HduBcfGGimFxxxxgohfUZCXKm 40 | # web chat token 41 | # 通过登录https://chatgpt.com/api/auth/session获取 42 | web_sessions: 43 | - 44 | # 密钥标识,可通过头部传递x-auth-id识别 45 | id: 10001 46 | # accessToken 47 | val: eyJhbGciOiJSUzxxxe5w50h7ls7rIf4onG59fIFCJAwsoyyvjq7KUrI3nI7lwA 48 | 49 | # 谷歌gemini接口 50 | # https://makersuite.google.com/app/apikey申请 51 | google_gemini: 52 | # 代理,没有取全局proxy_url 53 | # proxy_url: http://127.0.0.1:1081 54 | # 默认模型 55 | model: gemini-pro 56 | # 密钥 57 | api_keys: 58 | - 59 | # 密钥标识,可通过头部传递x-auth-id 60 | id: 10001 61 | # 密钥 62 | val: AIzaxxxxMuods 63 | # 版本 64 | version: v1beta 65 | 66 | # arkose设置 67 | arkose: 68 | # 版本 69 | game_core_version: 2.2.2 70 | # 客户端url 71 | client_arkoselabs_url: https://client-api.arkoselabs.com/v2/2.3.1/enforcement.db38df7eed55a4641d0eec2d11e1ff6a.html 72 | # 验证码保存目录,结尾以/结束 73 | pic_save_path: /anp/data/pics/ 74 | # 解决验证码通信url,可自主搭建处理接码平台 75 | # 优先用har获取,没有sup=1就需要解决验证码 76 | solve_api_url: http://127.0.0.1:9118/do 77 | 78 | # bing设置 79 | bing: 80 | # 部署国外vps不需要配置此代理,最好是干净IP否则会出验证码 81 | # 代理,没有取全局proxy_url 82 | # proxy_url: http://127.0.0.1:1081 83 | 84 | # 相关配置 85 | coze: 86 | # 代理,没有取全局proxy_url 87 | # proxy_url: http://127.0.0.1:1081 88 | # coze通过discord 89 | # 创建bot A,用于交互监听信息 90 | # 创建bot B、C...托管coze 91 | discord: 92 | # 是否开启coze discord 93 | enable: false 94 | # discord服务器ID 95 | guild_id: 1087xx7244 96 | # discord频道ID 97 | channel_id: 1087xx7685 98 | # bot A token 99 | chat_bot_token: MTIxxvJmQkKyI 100 | # 其他coze bot id 101 | coze_bot: 102 | - 12029xxx830 103 | # discord用户Authorization,支持多个随机取值 104 | # 用于发送信息 105 | auth: 106 | - ODk4NDxxxx2I3WLrAcIkg 107 | # 对话接口非流响应下的请求超时时间 108 | request_out_time: 300 109 | # 对话接口流响应下的每次流返回超时时间 110 | request_stream_out_time: 300 111 | # coze的api通信设置 112 | api_chat: 113 | # 通信token 114 | access_token: pat_tD0StYHdSTrHWxxrc3Gvx10x3OipnPxlGVsKbumr1voy 115 | bots: 116 | - 117 | # bot机器ID 118 | bot_id: 7317282xx21134853 119 | # 标识当前与Bot交互的用户 120 | user: 1000000001 121 | # 通信token,没有取全局access_token 122 | access_token: 123 | - 124 | bot_id: 731284xx535 125 | user: 1000000002 126 | 127 | 128 | # claude配置 129 | claude: 130 | # 代理,没有取全局proxy_url 131 | # proxy_url: http://127.0.0.1:1081 132 | # 接口版本 133 | api_version: 2023-06-01 134 | # web chat cookie里面的sessionKey值 135 | web_sessions: 136 | - 137 | # 自定义cookie标识,可通过头部传递x-auth-id或请求传递index 138 | id: 10001 139 | # cookie值 140 | val: sk-ant-sid01-anIGofTkl4RhcJYxxxVDWvwk6hB3CZigfH2Cw-ZbwI3AAA 141 | # 组织ID,可以不用设置 142 | organization_id: 143 | # api密钥 144 | api_keys: 145 | - 146 | # 密钥标识,可通过头部传递x-auth-id 147 | id: 10001 148 | # 密钥 149 | val: sk-ant-api03-xxxxpuVofdKzcHRrjg-XNvdMFsY43Ov1w-Q0MZqQAA 150 | -------------------------------------------------------------------------------- /pkg/jscrypt/crypt.go: -------------------------------------------------------------------------------- 1 | package jscrypt 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/md5" 8 | "encoding/base64" 9 | "encoding/hex" 10 | "errors" 11 | "hash" 12 | "math/rand" 13 | "strconv" 14 | 15 | "github.com/zatxm/fhblade" 16 | ) 17 | 18 | type CryptoJsData struct { 19 | Ct string `json:"ct"` 20 | Iv string `json:"iv"` 21 | S string `json:"s"` 22 | } 23 | 24 | func GenerateBw(bt int64) string { 25 | return strconv.FormatInt(bt-(bt%21600), 10) 26 | } 27 | 28 | func GenerateN(t int64) string { 29 | timestamp := strconv.FormatInt(t, 10) 30 | return base64.StdEncoding.EncodeToString([]byte(timestamp)) 31 | } 32 | 33 | // 解密 34 | func Decrypt(data string, password string) (string, error) { 35 | encBytes, err := base64.StdEncoding.DecodeString(data) 36 | if err != nil { 37 | return "", err 38 | } 39 | var encData CryptoJsData 40 | err = fhblade.Json.Unmarshal(encBytes, &encData) 41 | if err != nil { 42 | return "", err 43 | } 44 | cipherBytes, err := base64.StdEncoding.DecodeString(encData.Ct) 45 | if err != nil { 46 | return "", err 47 | } 48 | salt, err := hex.DecodeString(encData.S) 49 | if err != nil { 50 | return "", err 51 | } 52 | dstBytes := make([]byte, len(cipherBytes)) 53 | key, _, err := DefaultEvpKDF([]byte(password), salt) 54 | iv, _ := hex.DecodeString(encData.Iv) 55 | if err != nil { 56 | return "", err 57 | } 58 | block, err := aes.NewCipher(key) 59 | if err != nil { 60 | return "", err 61 | } 62 | mode := cipher.NewCBCDecrypter(block, iv) 63 | mode.CryptBlocks(dstBytes, cipherBytes) 64 | result := PKCS5UnPadding(dstBytes) 65 | return string(result), nil 66 | } 67 | 68 | // 加密 69 | func Encrypt(data string, password string) (string, error) { 70 | salt := make([]byte, 8) 71 | _, err := rand.Read(salt) 72 | if err != nil { 73 | return "", err 74 | } 75 | key, iv, err := DefaultEvpKDF([]byte(password), salt) 76 | if err != nil { 77 | return "", err 78 | } 79 | block, err := aes.NewCipher(key) 80 | if err != nil { 81 | return "", err 82 | } 83 | mode := cipher.NewCBCEncrypter(block, iv) 84 | cipherBytes := PKCS5Padding([]byte(data), aes.BlockSize) 85 | mode.CryptBlocks(cipherBytes, cipherBytes) 86 | md5Hash := md5.New() 87 | salted := "" 88 | var dx []byte 89 | for i := 0; i < 3; i++ { 90 | md5Hash.Write(dx) 91 | md5Hash.Write([]byte(password)) 92 | md5Hash.Write(salt) 93 | dx = md5Hash.Sum(nil) 94 | md5Hash.Reset() 95 | salted += hex.EncodeToString(dx) 96 | } 97 | cipherText := base64.StdEncoding.EncodeToString(cipherBytes) 98 | encData := &CryptoJsData{ 99 | Ct: cipherText, 100 | Iv: salted[64 : 64+32], 101 | S: hex.EncodeToString(salt), 102 | } 103 | encDataJson, err := fhblade.Json.Marshal(encData) 104 | if err != nil { 105 | return "", err 106 | } 107 | return base64.StdEncoding.EncodeToString(encDataJson), nil 108 | } 109 | 110 | func PKCS5UnPadding(src []byte) []byte { 111 | length := len(src) 112 | unpadding := int(src[length-1]) 113 | return src[:(length - unpadding)] 114 | } 115 | 116 | func PKCS5Padding(src []byte, blockSize int) []byte { 117 | padding := blockSize - len(src)%blockSize 118 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 119 | return append(src, padtext...) 120 | } 121 | 122 | func DefaultEvpKDF(password []byte, salt []byte) (key []byte, iv []byte, err error) { 123 | keySize := 256 / 32 124 | ivSize := 128 / 32 125 | derivedKeyBytes, err := EvpKDF(password, salt, keySize+ivSize, 1, "md5") 126 | if err != nil { 127 | return []byte{}, []byte{}, err 128 | } 129 | return derivedKeyBytes[:keySize*4], derivedKeyBytes[keySize*4:], nil 130 | } 131 | 132 | func EvpKDF(password []byte, salt []byte, keySize int, iterations int, hashAlgorithm string) ([]byte, error) { 133 | var block []byte 134 | var hasher hash.Hash 135 | derivedKeyBytes := make([]byte, 0) 136 | switch hashAlgorithm { 137 | case "md5": 138 | hasher = md5.New() 139 | default: 140 | return []byte{}, errors.New("not implement hasher algorithm") 141 | } 142 | for len(derivedKeyBytes) < keySize*4 { 143 | if len(block) > 0 { 144 | hasher.Write(block) 145 | } 146 | hasher.Write(password) 147 | hasher.Write(salt) 148 | block = hasher.Sum([]byte{}) 149 | hasher.Reset() 150 | 151 | for i := 1; i < iterations; i++ { 152 | hasher.Write(block) 153 | block = hasher.Sum([]byte{}) 154 | hasher.Reset() 155 | } 156 | derivedKeyBytes = append(derivedKeyBytes, block...) 157 | } 158 | return derivedKeyBytes[:keySize*4], nil 159 | } 160 | -------------------------------------------------------------------------------- /internal/openai/arkose/solve/solve.go: -------------------------------------------------------------------------------- 1 | package solve 2 | 3 | import ( 4 | "encoding/base64" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "runtime" 9 | "strings" 10 | 11 | http "github.com/bogdanfinn/fhttp" 12 | "github.com/zatxm/any-proxy/internal/client" 13 | "github.com/zatxm/any-proxy/internal/config" 14 | "github.com/zatxm/any-proxy/internal/openai/arkose/funcaptcha" 15 | "github.com/zatxm/any-proxy/internal/vars" 16 | "github.com/zatxm/any-proxy/pkg/support" 17 | "github.com/zatxm/fhblade" 18 | "github.com/zatxm/fhblade/tools" 19 | tlsClient "github.com/zatxm/tls-client" 20 | "go.uber.org/zap" 21 | ) 22 | 23 | func DoToken(pk, dx string) (string, error) { 24 | arkoseToken, err := GetTokenByPk(pk, dx) 25 | if err == nil { 26 | return arkoseToken, nil 27 | } 28 | if err != nil && arkoseToken == "" { 29 | return "", err 30 | } 31 | fields := strings.Split(arkoseToken, "|") 32 | sessionToken := fields[0] 33 | sid := strings.Split(fields[1], "=")[1] 34 | hSession := strings.Replace(arkoseToken, "|", "&", -1) 35 | s := funcaptcha.NewArkoseSession(sid, sessionToken, hSession) 36 | cfg := config.V() 37 | s.Loged("", 0, "Site URL", cfg.Arkose.ClientArkoselabsUrl) 38 | 39 | // 生成验证图片 40 | ArkoseApiBreaker, err := s.GoArkoseChallenge(false) 41 | if err != nil { 42 | return "", err 43 | } 44 | fhblade.Log.Debug("session.ConciseChallenge", zap.Any("Data", s.ConciseChallenge)) 45 | fhblade.Log.Debug("Downloading challenge") 46 | imgs, err := downloadArkoseChallengeImg(s.ConciseChallenge.URLs) 47 | if err != nil { 48 | return "", err 49 | } 50 | 51 | // 解决验证码 52 | answerIndexs, err := solveServiceDo(imgs) 53 | if err != nil { 54 | fhblade.Log.Error("service solve error", zap.Error(err)) 55 | return "", err 56 | } 57 | 58 | err = s.SubmitAnswer(answerIndexs, false, ArkoseApiBreaker) 59 | if err != nil { 60 | fhblade.Log.Error("go submit answer end error", zap.Error(err)) 61 | ArkosePicPath := cfg.Arkose.PicSavePath 62 | // 保存图片 63 | for k := range imgs { 64 | filename := fmt.Sprintf("%s/image%s.jpg", ArkosePicPath, support.TimeStamp()) 65 | os.WriteFile(filename, tools.StringToBytes(imgs[k]), 0644) 66 | } 67 | return "", err 68 | } 69 | 70 | return arkoseToken, nil 71 | } 72 | 73 | // 解决验证码,自己编写接码平台等 74 | func solveServiceDo(imgs []string) ([]int, error) { 75 | solveApiUrl := config.V().Arkose.SolveApiUrl 76 | l := len(imgs) 77 | rChan := make(chan map[string]interface{}, l) 78 | defer close(rChan) 79 | var doRes []map[string]interface{} 80 | for k := range imgs { 81 | go func(tag int, b string, rc chan map[string]interface{}) { 82 | jsonBytes, _ := fhblade.Json.MarshalToString(map[string]string{ 83 | "question": "3d_rollball_objects", 84 | "image": b, 85 | }) 86 | req, _ := http.NewRequest(http.MethodPost, solveApiUrl, strings.NewReader(jsonBytes)) 87 | req.Header.Set("Content-Type", vars.ContentTypeJSON) 88 | gClient := client.CPool.Get().(tlsClient.HttpClient) 89 | resp, err := gClient.Do(req) 90 | var rMap interface{} 91 | if err != nil { 92 | client.CPool.Put(gClient) 93 | fhblade.Log.Error("solve challenge req err", zap.Error(err)) 94 | rMap = err 95 | } else { 96 | defer resp.Body.Close() 97 | client.CPool.Put(gClient) 98 | err := fhblade.Json.NewDecoder(resp.Body).Decode(&rMap) 99 | if err != nil { 100 | fhblade.Log.Error("solve challenge res err", zap.Error(err)) 101 | rMap = err 102 | } 103 | } 104 | runtime.Gosched() 105 | rc <- map[string]interface{}{"tag": tag, "val": rMap} 106 | }(k, imgs[k], rChan) 107 | } 108 | for i := 0; i < l; i++ { 109 | t := <-rChan 110 | doRes = append(doRes, t) 111 | } 112 | // fmt.Println(doRes) 113 | answerIndexs := make([]int, l) 114 | for k := range doRes { 115 | t := doRes[k] 116 | if _, ok := t["val"].(map[string]interface{})["index"]; !ok { 117 | return nil, errors.New("solve challenge err") 118 | } 119 | answerIndexs[t["tag"].(int)] = int(t["val"].(map[string]interface{})["index"].(float64)) 120 | } 121 | return answerIndexs, nil 122 | } 123 | 124 | func downloadArkoseChallengeImg(urls []string) ([]string, error) { 125 | var imgs []string = make([]string, len(urls)) 126 | for k := range urls { 127 | gUrl := urls[k] 128 | req, _ := http.NewRequest(http.MethodGet, gUrl, nil) 129 | req.Header = funcaptcha.ArkoseHeaders 130 | gClient := client.CPool.Get().(tlsClient.HttpClient) 131 | resp, err := gClient.Do(req) 132 | if err != nil { 133 | client.CPool.Put(gClient) 134 | fhblade.Log.Error("downloading challenge err", zap.Error(err)) 135 | return nil, err 136 | } 137 | defer resp.Body.Close() 138 | client.CPool.Put(gClient) 139 | if resp.StatusCode != 200 { 140 | return nil, fmt.Errorf("Downloading challenge status code %d", resp.StatusCode) 141 | } 142 | b, _ := tools.ReadAll(resp.Body) 143 | imgs[k] = base64.StdEncoding.EncodeToString(b) 144 | } 145 | return imgs, nil 146 | } 147 | -------------------------------------------------------------------------------- /internal/openai/arkose/solve/token.go: -------------------------------------------------------------------------------- 1 | package solve 2 | 3 | import ( 4 | "errors" 5 | "math/rand" 6 | "net/url" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | "time" 11 | 12 | http "github.com/bogdanfinn/fhttp" 13 | "github.com/zatxm/any-proxy/internal/config" 14 | "github.com/zatxm/any-proxy/internal/openai/arkose/har" 15 | "github.com/zatxm/any-proxy/internal/vars" 16 | "github.com/zatxm/any-proxy/pkg/jscrypt" 17 | "github.com/zatxm/fhblade" 18 | tlsClient "github.com/zatxm/tls-client" 19 | "github.com/zatxm/tls-client/profiles" 20 | "go.uber.org/zap" 21 | ) 22 | 23 | var defaultTokenHeader = http.Header{ 24 | "accept": {"*/*"}, 25 | "accept-encoding": {"gzip, deflate, br"}, 26 | "accept-language": {"zh-CN,zh;q=0.9,en;q=0.8"}, 27 | "content-type": {"application/x-www-form-urlencoded; charset=UTF-8"}, 28 | "sec-ch-ua": {`"Microsoft Edge";v="119", "Chromium";v="119", "Not?A_Brand";v="24"`}, 29 | "sec-ch-ua-mobile": {"?0"}, 30 | "sec-ch-ua-platform": {`"Linux"`}, 31 | "sec-fetch-dest": {"empty"}, 32 | "sec-fetch-mode": {"cors"}, 33 | "sec-fetch-site": {"same-origin"}, 34 | } 35 | 36 | type arkoseResJson struct { 37 | Token string `json:"token"` 38 | } 39 | 40 | func DoAkToken() func(*fhblade.Context) error { 41 | return func(c *fhblade.Context) error { 42 | pk := c.Get("pk") 43 | arkoseToken, err := GetTokenByPk(pk, "") 44 | if err != nil { 45 | return c.JSONAndStatus(http.StatusInternalServerError, fhblade.H{"errorMessage": err.Error()}) 46 | } 47 | return c.JSONAndStatus(http.StatusOK, fhblade.H{"token": arkoseToken}) 48 | } 49 | } 50 | 51 | func DoSolveToken() func(*fhblade.Context) error { 52 | return func(c *fhblade.Context) error { 53 | pk := c.Get("pk") 54 | arkoseToken, err := DoToken(pk, "") 55 | if err != nil { 56 | return c.JSONAndStatus(http.StatusInternalServerError, fhblade.H{"errorMessage": err.Error()}) 57 | } 58 | return c.JSONAndStatus(http.StatusOK, fhblade.H{"token": arkoseToken}) 59 | } 60 | } 61 | 62 | func GetTokenByPk(pk, dx string) (string, error) { 63 | arkoseDatas := har.GetArkoseDatas() 64 | if _, ok := arkoseDatas[pk]; !ok { 65 | return "", errors.New("public_key error") 66 | } 67 | mom := arkoseDatas[pk] 68 | datas := mom.Hars 69 | arkoseToken := "" 70 | tCOptions := []tlsClient.HttpClientOption{ 71 | tlsClient.WithTimeoutSeconds(360), 72 | tlsClient.WithClientProfile(profiles.Chrome_117), 73 | tlsClient.WithRandomTLSExtensionOrder(), 74 | tlsClient.WithNotFollowRedirects(), 75 | //tlsClient.WithCookieJar(jar), 76 | } 77 | proxyUrl := config.V().ProxyUrl 78 | if len(datas) > 0 { 79 | for k := range datas { 80 | data := datas[k].Clone() 81 | bt := time.Now().Unix() 82 | bw := jscrypt.GenerateBw(bt) 83 | bv := vars.UserAgent 84 | re := regexp.MustCompile(`{"key"\:"n","value"\:"\S*?"`) 85 | bx := re.ReplaceAllString(data.Bx, `{"key":"n","value":"`+jscrypt.GenerateN(bt)+`"`) 86 | bda, err := jscrypt.Encrypt(bx, bv+bw) 87 | if err != nil { 88 | fhblade.Log.Error("CryptoJsAesEncrypt error", zap.Error(err)) 89 | continue 90 | } 91 | data.Body.Set("bda", bda) 92 | data.Body.Set("rnd", strconv.FormatFloat(rand.Float64(), 'f', -1, 64)) 93 | if dx != "" { 94 | data.Body.Set("data[blob]", dx) 95 | } 96 | 97 | req, _ := http.NewRequest(data.Method, data.Url, strings.NewReader(data.Body.Encode())) 98 | req.Header = data.Headers.Clone() 99 | req.Header.Set("user-agent", bv) 100 | req.Header.Set("x-ark-esync-value", bw) 101 | gClient, _ := tlsClient.NewHttpClient(tlsClient.NewNoopLogger(), tCOptions...) 102 | if proxyUrl != "" { 103 | gClient.SetProxy(proxyUrl) 104 | } 105 | resp, err := gClient.Do(req) 106 | if err != nil { 107 | fhblade.Log.Error("Req arkose token error", zap.Error(err)) 108 | continue 109 | } 110 | defer resp.Body.Close() 111 | if resp.StatusCode != 200 { 112 | fhblade.Log.Debug("Req arkose token status code", zap.String("status", resp.Status)) 113 | continue 114 | } 115 | var arkose arkoseResJson 116 | err = fhblade.Json.NewDecoder(resp.Body).Decode(&arkose) 117 | if err != nil { 118 | fhblade.Log.Error("arkose token json error", zap.Error(err)) 119 | continue 120 | } 121 | arkoseToken = arkose.Token 122 | if strings.Contains(arkose.Token, "sup=1|rid=") { 123 | break 124 | } 125 | } 126 | } 127 | fhblade.Log.Debug("from har get arkose token", zap.String("token", arkoseToken)) 128 | if !strings.Contains(arkoseToken, "sup=1|rid=") { 129 | bt := time.Now().Unix() 130 | bx := har.GenerateBx(pk, bt) 131 | bw := jscrypt.GenerateBw(bt) 132 | bv := vars.UserAgent 133 | bda, err := jscrypt.Encrypt(bx, bv+bw) 134 | if err != nil { 135 | fhblade.Log.Error("Generate CryptoJsAesEncrypt error", zap.Error(err)) 136 | return "", err 137 | } 138 | 139 | bd := make(url.Values) 140 | bd.Set("bda", bda) 141 | bd.Set("public_key", pk) 142 | bd.Set("site", mom.SiteUrl) 143 | bd.Set("userbrowser", bv) 144 | bd.Set("capi_version", "2.3.0") 145 | bd.Set("capi_mode", "lightbox") 146 | bd.Set("style_theme", "default") 147 | bd.Set("rnd", strconv.FormatFloat(rand.Float64(), 'f', -1, 64)) 148 | if dx != "" { 149 | bd.Set("data[blob]", dx) 150 | } 151 | 152 | gUrl := mom.ClientConfigSurl + "/fc/gt2/public_key/" + pk 153 | req, _ := http.NewRequest("POST", gUrl, strings.NewReader(bd.Encode())) 154 | req.Header = defaultTokenHeader 155 | req.Header.Set("origin", mom.ClientConfigSurl) 156 | req.Header.Set("x-ark-esync-value", bw) 157 | req.Header.Set("user-agent", bv) 158 | gClient, _ := tlsClient.NewHttpClient(tlsClient.NewNoopLogger(), tCOptions...) 159 | if proxyUrl != "" { 160 | gClient.SetProxy(proxyUrl) 161 | } 162 | resp, err := gClient.Do(req) 163 | if err != nil { 164 | fhblade.Log.Error("Last req arkose token error", zap.Error(err)) 165 | return "", err 166 | } 167 | defer resp.Body.Close() 168 | if resp.StatusCode != 200 { 169 | fhblade.Log.Debug("Last req arkose token status code", zap.String("status", resp.Status)) 170 | return "", errors.New("req arkose token status code error") 171 | } 172 | var arkose arkoseResJson 173 | err = fhblade.Json.NewDecoder(resp.Body).Decode(&arkose) 174 | if err != nil { 175 | fhblade.Log.Error("Last arkose token json error", zap.Error(err)) 176 | return "", errors.New("req arkose token return error") 177 | } 178 | arkoseToken = arkose.Token 179 | } 180 | if !strings.Contains(arkoseToken, "sup=1|rid=") { 181 | fhblade.Log.Debug("arkose token not sup error", zap.String("token", arkoseToken)) 182 | return arkoseToken, errors.New("arkose error") 183 | } 184 | return arkoseToken, nil 185 | } 186 | -------------------------------------------------------------------------------- /internal/types/claude.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/zatxm/fhblade" 5 | ) 6 | 7 | type ClaudeCompletionRequest struct { 8 | ClaudeCompletionResponse 9 | } 10 | 11 | type ClaudeCompletionResponse struct { 12 | Type string `json:"type,omitempty"` 13 | Index string `json:"index,omitempty"` 14 | Conversation *ClaudeConversation `json:"conversation"` 15 | } 16 | 17 | type ClaudeConversation struct { 18 | Uuid string `json:"uuid"` 19 | Name string `json:"name"` 20 | Summary string `json:"summary"` 21 | Model any `json:"model"` 22 | CreatedAt string `json:"created_at"` 23 | UpdatedAt string `json:"updated_at"` 24 | } 25 | 26 | type ClaudeOrganization struct { 27 | Uuid string `json:"uuid"` 28 | Name string `json:"name"` 29 | Settings map[string]any `json:"settings"` 30 | Capabilities []string `json:"capabilities"` 31 | RateLimitTier string `json:"rate_limit_tier"` 32 | BillingType any `json:"billing_type"` 33 | FreeCreditsStatus any `json:"free_credits_status"` 34 | ApiDisabledReason any `json:"api_disabled_reason"` 35 | ApiDisabledUntil any `json:"api_disabled_until"` 36 | BillableUsagePausedUntil any `json:"billable_usage_paused_until"` 37 | CreatedAt string `json:"created_at"` 38 | UpdatedAt string `json:"updated_at"` 39 | ActiveFlags []any `json:"active_flags"` 40 | } 41 | 42 | type ClaudeWebChatCompletionResponse struct { 43 | Type string `json:"type"` 44 | ID string `json:"id"` 45 | Completion string `json:"completion"` 46 | StopReason any `json:"stop_reason"` 47 | Model string `json:"model"` 48 | Stop any `json:"stop"` 49 | LogId string `json:"log_id"` 50 | MessageLimit map[string]any `json:"messageLimit"` 51 | Error *ClaudeError `json:"error,omitempty"` 52 | } 53 | 54 | type ClaudeError struct { 55 | Type string `json:"type"` 56 | Message string `json:"message"` 57 | } 58 | 59 | type ClaudeApiCompletionRequest struct { 60 | Model string `json:"model"` 61 | Messages []*ClaudeApiMessage `json:"messages"` 62 | MaxTokens int `json:"max_tokens"` 63 | Metadata map[string]string `json:"metadata,omitempty"` 64 | StopSequences []string `json:"stop_sequences,omitempty"` 65 | Stream bool `json:"stream,omitempty"` 66 | System string `json:"system,omitempty"` 67 | Temperature float64 `json:"temperature,omitempty"` 68 | Tools []any `json:"tools,omitempty"` 69 | TopK float64 `json:"top_k,omitempty"` 70 | TopP float64 `json:"top_p,omitempty"` 71 | } 72 | 73 | type ClaudeApiMessage struct { 74 | Role string `json:"role"` 75 | Content string `json:"content"` 76 | MultiContent []*ClaudeApiMessagePart 77 | } 78 | 79 | func (m *ClaudeApiMessage) MarshalJSON() ([]byte, error) { 80 | if m.Content != "" && m.MultiContent != nil { 81 | return nil, ErrContentFieldsMisused 82 | } 83 | if len(m.MultiContent) > 0 { 84 | msg := struct { 85 | Role string `json:"role"` 86 | Content string `json:"-"` 87 | MultiContent []*ClaudeApiMessagePart `json:"content"` 88 | }(*m) 89 | return fhblade.Json.Marshal(msg) 90 | } 91 | msg := struct { 92 | Role string `json:"role"` 93 | Content string `json:"content"` 94 | MultiContent []*ClaudeApiMessagePart `json:"-"` 95 | }(*m) 96 | return fhblade.Json.Marshal(msg) 97 | } 98 | 99 | func (m *ClaudeApiMessage) UnmarshalJSON(bs []byte) error { 100 | msg := struct { 101 | Role string `json:"role"` 102 | Content string `json:"content"` 103 | MultiContent []*ClaudeApiMessagePart 104 | }{} 105 | if err := fhblade.Json.Unmarshal(bs, &msg); err == nil { 106 | *m = ClaudeApiMessage(msg) 107 | return nil 108 | } 109 | multiMsg := struct { 110 | Role string `json:"role"` 111 | Content string `json:"-"` 112 | MultiContent []*ClaudeApiMessagePart `json:"content"` 113 | }{} 114 | if err := fhblade.Json.Unmarshal(bs, &multiMsg); err != nil { 115 | return err 116 | } 117 | *m = ClaudeApiMessage(multiMsg) 118 | return nil 119 | } 120 | 121 | type ClaudeApiMessagePart struct { 122 | Type string `json:"type"` 123 | Source *ClaudeApiSource `json:"source,omitempty"` 124 | Text string `json:"text,omitempty"` 125 | } 126 | 127 | type ClaudeApiSource struct { 128 | Type string `json:"type"` 129 | MediaType string `json:"media_type"` 130 | Data string `json:"data"` 131 | } 132 | 133 | type ClaudeApiCompletionStreamResponse struct { 134 | Type string `json:"type"` 135 | Error *ClaudeError `json:"error,omitempty"` 136 | Message *ClaudeApiCompletionResponse `json:"message,omitempty"` 137 | Index int `json:"index,omitempty"` 138 | Delta *ClaudeApiDelta `json:"delta,omitempty"` 139 | Usage *ClaudeApiUsage `json:"usage,omitempty"` 140 | } 141 | 142 | type ClaudeApiCompletionResponse struct { 143 | ID string `json:"id"` 144 | Type string `json:"type"` 145 | Role string `json:"role"` 146 | Content []*ClaudeApiContent `json:"content"` 147 | Model string `json:"model"` 148 | StopReason NullString `json:"stop_reason"` 149 | StopSequence NullString `json:"stop_sequence"` 150 | Usage *ClaudeApiUsage `json:"usage"` 151 | } 152 | 153 | type ClaudeApiContent struct { 154 | Type string `json:"type,omitempty"` 155 | Text string `json:"text,omitempty"` 156 | Role string `json:"role,omitempty"` 157 | Content string `json:"content,omitempty"` 158 | } 159 | 160 | type ClaudeApiUsage struct { 161 | InputTokens int `json:"input_tokens"` 162 | OutputTokens int `json:"output_tokens"` 163 | } 164 | 165 | type ClaudeApiDelta struct { 166 | Type string `json:"type,omitempty"` 167 | Text string `json:"text,omitempty"` 168 | StopReason NullString `json:"stop_reason,omitempty"` 169 | StopSequence NullString `json:"stop_sequence,omitempty"` 170 | } 171 | 172 | type ClaudeCreateConversationRequest struct { 173 | Uuid string `json:"uuid"` 174 | Name string `json:"name"` 175 | } 176 | 177 | type ClaudeWebChatCompletionRequest struct { 178 | Prompt string `json:"prompt"` 179 | Timezone string `json:"timezone"` 180 | attachments []interface{} `json:"attachments"` 181 | files []interface{} `json:"files"` 182 | } 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AI Chat集成转发、代理 2 | ========== 3 | 4 | golang版ai chat工具集成,目前支持openai、bing、claude、gemini和coze,后续陆续增加 5 | 6 | # 使用 7 | 8 | **1. 源码构建** 9 | 10 | ``` 11 | git clone https://github.com/zatxm/aiproxy 12 | cd aiproxy 13 | go build -ldflags "-s -w" -o aiproxy cmd/main.go 14 | ./aiproxy -c /whereis/c.yaml 15 | ``` 16 | 17 | **2. docker** 18 | 19 | * 本地构建 20 | 21 | ``` 22 | git clone https://github.com/zatxm/aiproxy 23 | cd aiproxy 24 | docker build -t zatxm120/aiproxy . # 镜像名称可以自行定义 25 | ``` 26 | 27 | * docker库 28 | 29 | ``` 30 | docker pull zatxm120/aiproxy 31 | ``` 32 | 33 | * 启用 34 | 35 | ``` 36 | docker run -d --name aiproxy --restart --net=host always zatxm120/aiproxy #自身网络,默认配置端口8999 37 | docker run -d --name aiproxy --restart always -p 8084:8999 zatxm120/aiproxy #映射端口,默认8999,实际还是要看配置文件的端口号 38 | docker run -d --name aiproxy --restart always -p 8084:8999 -v /your-app-data:/anp/data zatxm120/aiproxy #映射文件夹,包含配置文件等 39 | ``` 40 | 41 | ## 配置说明 42 | **1. 创建数据目录** 43 | 44 | 假设放在/opt/aiproxy目录上,目录根据自己需求创建 45 | 46 | ``` 47 | mkdir -p /opt/aiproxy/ssl #放置https证书 48 | mkdir -p /opt/aiproxy/cookies #放置openai chat登录cookie文件 49 | mkdir -p /opt/aiproxy/hars #放置openai登录har 50 | mkdir -p /opt/aiproxy/pics #放置验证码,一般没用到 51 | mkdir -p /opt/aiproxy/etc #配置文件目录,配置文件复制到该目录 52 | ``` 53 | **2. 配置文件c.yaml** 54 | 55 | 源码中的etc/c.yaml为配置实例,复制到/opt/aiproxy/etc目录下修改 56 | 57 | **3. docker映射目录** 58 | 59 | ``` 60 | docker run -d --name aiproxy --restart always -p 8084:8999 -v /opt/aiproxy:/anp/data zatxm120/aiproxy 61 | ``` 62 | 63 | ## 接口应用说明 64 | 65 | **1. 通用接口/c/v1/chat/completions** 66 | 67 | 支持通信头部header加入密钥(不加随机获取配置文件的密钥),bing暂时不需要,一般以下两种: 68 | 69 | * **Authorization**:通用 70 | * **x-auth-id**:对应配置密钥ID,根据此值获取配置文件中设置的密钥 71 | 72 | ``` 73 | curl -X POST http://192.168.0.1:8999/c/v1/chat/completions -d '{ 74 | "messages": [ 75 | { 76 | "role": "user", 77 | "content": "你是谁?" 78 | } 79 | ], 80 | "model": "text-davinci-002-render-sha", 81 | "provider": "openai-chat-web" 82 | }' 83 | ``` 84 | 85 | provider参数说明如下: 86 | 87 | * **openai-chat-web**:openai web chat,支持免登录(有IP要求,一般美国IP就行) 88 | 89 | 额外参数: 90 | 91 | ``` 92 | { 93 | ... 94 | "openai": { 95 | "conversation": { 96 | "conversation_id": "697b28e8-e228-4abb-b356-c8ccdccf82f3", 97 | "index": "1000001", 98 | "parent_message_id": "dd6d9561-bebe-42da-91c6-f8fde6b105d9" 99 | }, 100 | "message_id": "697b28e8-e228-4abb-b356-c8ccdccf82f4", 101 | "arkose_token": "arkose token" 102 | } 103 | } 104 | ``` 105 | * index为密钥ID,头部x-auth-id优先级高于index 106 | * 表示在同一会话基础上进行对话,此值在任一对话通信后会返回 107 | 108 | 额外返回: 109 | 110 | ``` 111 | { 112 | ... 113 | "openai": { 114 | "conversation_id": "697b28e8-e228-4abb-b356-c8ccdccf82f3", 115 | "index": "1000001", 116 | "parent_message_id": "dd6d9561-bebe-42da-91c6-f8fde6b105d9", 117 | "last_message_id": "9017f85d-8cd3-46a8-a88a-2eb7f4c099ea" 118 | } 119 | } 120 | ``` 121 | 122 | * **gemini**:谷歌gemini pro 123 | 124 | 额外参数 125 | 126 | ``` 127 | { 128 | ... 129 | "gemini": { 130 | "type": "api", 131 | "index": "100001" 132 | } 133 | } 134 | ``` 135 | 136 | * index为密钥ID,头部x-auth-id优先级高于index 137 | * 如果传递Authorization鉴权,还可以传递x-version指定api版本,默认v1beta 138 | 139 | 额外返回: 140 | 141 | ``` 142 | { 143 | ... 144 | "gemini": { 145 | "type": "api", 146 | "index": "100001" 147 | } 148 | } 149 | ``` 150 | 151 | * **bing**:微软bing chat,有IP要求,不符合会出验证码 152 | 153 | 额外参数: 154 | 155 | ``` 156 | { 157 | ... 158 | "bing": { 159 | "conversation": { 160 | "conversationId": "xxxx", 161 | "clientId": "xxxx", 162 | "Signature": "xxx", 163 | "TraceId": "xxx" 164 | } 165 | } 166 | } 167 | ``` 168 | 169 | * 表示在同一会话基础上进行对话,此值在任一对话通信后会返回 170 | 171 | 额外返回: 172 | 173 | ``` 174 | { 175 | ... 176 | "bing": { 177 | "conversationId": "xxxx", 178 | "clientId": "xxxx", 179 | "Signature": "xxx", 180 | "TraceId": "xxx" 181 | ... 182 | } 183 | } 184 | ``` 185 | 186 | * **coze**:支持discord和api,走api时model传coze-api 187 | 188 | 额外参数 189 | 190 | ``` 191 | { 192 | ... 193 | "coze": { 194 | "conversation": { 195 | "type": "api", 196 | "bot_id": "xxxx", 197 | "conversation_id": "xxx", 198 | "user": "xxx" 199 | } 200 | } 201 | } 202 | ``` 203 | 204 | * discord只支持一次性对话 205 | * api通信时,user为标识ID,bot_id为coze app ID,头部x-auth-id对应user,x-bot-id对应bot_id,头部优先鉴权 206 | 207 | 额外返回: 208 | 209 | ``` 210 | { 211 | ... 212 | "coze": { 213 | "type": "api", 214 | "bot_id": "xxxx", 215 | "conversation_id": "xxx", 216 | "user": "xxx" 217 | } 218 | } 219 | ``` 220 | 221 | * **claude**:支持web和api 222 | 223 | 额外参数 224 | 225 | ``` 226 | { 227 | ... 228 | "claude": { 229 | "type": "web", 230 | "index": "100001", 231 | "conversation": { 232 | "uuid": "xxxx" 233 | "model" "xxx", 234 | ... 235 | } 236 | } 237 | } 238 | ``` 239 | 240 | * 其中,如果没传claude或者传type为api,走claude api接口,其他情况走web 241 | * index为密钥ID,头部x-auth-id优先级高于index 242 | * conversation,web专用,表示在同一会话基础上进行对话,此值在任一对话通信后会返回 243 | 244 | 额外返回: 245 | 246 | ``` 247 | { 248 | ... 249 | "claude": { 250 | "type": "web", 251 | "index": "100001", 252 | "conversation": { 253 | "uuid": "xxxx" 254 | "model" "xxx", 255 | ... 256 | } 257 | } 258 | } 259 | ``` 260 | 261 | * **不传或不支持**的provider默认走openai的v1/chat/completions接口 262 | 263 | **2. openai相关接口** 264 | 265 | * **转发/public-api/\*path** 266 | * **转发/backend-api/\*path** 267 | * **转发/dashboard/\*path** 268 | * **转发/v1/\*path** 269 | * **post /backend-anon/web2api**,web转api 270 | * **post /backend-api/web2api**,web转api 271 | 272 | ``` 273 | 通信请求数据如下: 274 | { 275 | "action": "next", 276 | "messages": [{ 277 | "id": "aaa2e4da-d561-458e-b731-e3390c08d8f7", 278 | "author": {"role": "user"}, 279 | "content": { 280 | "content_type": "text", 281 | "parts": ["你是谁?"] 282 | }, 283 | "metadata": {} 284 | }], 285 | "parent_message_id": "aaa18093-c3ec-4528-bb92-750c0f85918f", 286 | "model": "text-davinci-002-render-sha", 287 | "timezone_offset_min": -480, 288 | "history_and_training_disabled": false, 289 | "conversation_mode": {"kind": "primary_assistant"}, 290 | "websocket_request_id": "bf740f5f-2335-4903-94df-4003819fdade" 291 | } 292 | ``` 293 | 294 | * **post /auth/token/web**,openai chat web登录获取token 295 | 296 | ``` 297 | 通信请求数据如下: 298 | { 299 | "email": "my@email.com", 300 | "password": "123456", 301 | "arkose_token": "xxxx", 302 | "reset": true 303 | } 304 | ``` 305 | 306 | * arkose_token,不传自动生成,可能会出验证码,自动解析你需要官网登录后下载har文件放到类似/opt/aiproxy/hars目录下 307 | * reset,默认不传为false,会根据上次成功获取token保存cookie,根据cookie刷新token,传true重新获取 308 | 309 | **3. claude相关接口** 310 | 311 | * /claude/web/*path,转发web端,path参数为转发的path,下同 312 | * /claude/api/*path,转发api 313 | * post /claude/api/openai,api转openai api格式,此接口支持头部传递Authorization、x-api-key、x-auth-id鉴权(按此排序依次优先获取),不传随机获取配置密钥 314 | 315 | **4. gemini相关接口** 316 | 317 | * /gemini/*path,转发api,path参数为转发的path 318 | * post /gemini/openai,api转openai api格式,此接口支持头部传递Authorization、x-auth-id鉴权(按此排序依次优先获取),不传随机获取配置密钥 319 | -------------------------------------------------------------------------------- /internal/types/bing.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type BingCompletionRequest struct { 4 | Conversation *BingConversation `json:"conversation,omitempty"` 5 | ImageBase64 string `json:"imageBase64"` 6 | } 7 | 8 | type BingConversation struct { 9 | ConversationId string `json:"conversationId"` 10 | ClientId string `json:"clientId"` 11 | Signature string `json:"signature"` 12 | TraceId string `json:"traceId"` 13 | ImageUrl string `json:"imageUrl,omitempty"` 14 | } 15 | 16 | // 原始发送信息请求结构 17 | type BingSendMessageRequest struct { 18 | Arguments []*BingRequestArgument `json:"arguments"` 19 | InvocationId string `json:"invocationId"` 20 | Target string `json:"target"` 21 | Type int `json:"type"` 22 | } 23 | 24 | type BingRequestArgument struct { 25 | Source string `json:"source"` 26 | OptionsSets []string `json:"optionsSets"` 27 | AllowedMessageTypes []string `json:"allowedMessageTypes"` 28 | SliceIds []string `json:"sliceIds"` 29 | TraceId string `json:"traceId"` 30 | ConversationHistoryOptionsSets []string `json:"conversationHistoryOptionsSets"` 31 | IsStartOfSession bool `json:"isStartOfSession"` 32 | RequestId string `json:"requestId"` 33 | Message *BingMessage `json:"message"` 34 | Scenario string `json:"scenario"` 35 | Tone string `json:"tone"` 36 | SpokenTextMode string `json:"spokenTextMode"` 37 | ConversationId string `json:"conversationId"` 38 | Participant *BingParticipant `json:"participant"` 39 | } 40 | 41 | type BingLocationHint struct { 42 | SourceType int `json:"SourceType"` 43 | RegionType int `json:"RegionType"` 44 | Center *BingCenter `json:"Center"` 45 | CountryName string `json:"CountryName"` 46 | CountryConfidence int `json:"countryConfidence"` 47 | UtcOffset int `json:"utcOffset"` 48 | } 49 | 50 | type BingCenter struct { 51 | Latitude float64 `json:"Latitude"` 52 | Longitude float64 `json:"Longitude"` 53 | Height interface{} `json:"height,omitempty"` 54 | } 55 | 56 | type BingParticipant struct { 57 | Id string `json:"id" binding:"required` 58 | } 59 | 60 | type BingImgBlob struct { 61 | BlobId string `json:"blobId"` 62 | ProcessedBlobId string `json:"processedBlobId"` 63 | } 64 | 65 | type BingConversationDeleteParams struct { 66 | ConversationId string `json:"conversationId" binding:"required"` 67 | ConversationSignature string `json:"conversationSignature" binding:"required"` 68 | Participant *BingParticipant `json:"participant" binding:"required` 69 | Source string `json:"source,omitempty"` 70 | OptionsSets []string `json:"optionsSets,omitempty"` 71 | } 72 | 73 | type BingCompletionResponse struct { 74 | CType int `json:"type"` 75 | Target string `json:"target,omitempty"` 76 | Arguments []*BingCompletionArgument `json:"arguments,omitempty"` 77 | InvocationId string `json:"invocationId,omitempty"` 78 | Item *BingCompletionItem `json:"item,omitempty"` 79 | } 80 | 81 | type BingCompletionArgument struct { 82 | Messages []*BingCompletionMessage `json:"messages"` 83 | Nonce string `json:"nonce"` 84 | RequestId string `json:"requestId"` 85 | } 86 | 87 | type BingCompletionMessage struct { 88 | BingMessage 89 | SuggestedResponses []*BingMessage `json:"completionMessage,omitempty"` 90 | } 91 | 92 | type BingMessage struct { 93 | Text string `json:"text"` 94 | Author string `json:"author"` 95 | From map[string]interface{} `json:"from,omitempty"` 96 | Locale string `json:"locale,omitempty"` 97 | Market string `json:"market,omitempty"` 98 | Region string `json:"region,omitempty"` 99 | Location string `json:"location,omitempty"` 100 | LocationInfo map[string]interface{} `json:"locationInfo,omitempty"` 101 | LocationHints []*BingLocationHint `json:"locationHints,omitempty"` 102 | UserIpAddress string `json:"userIpAddress,omitempty"` 103 | CreatedAt string `json:"createdAt,omitempty"` 104 | Timestamp string `json:"timestamp,omitempty"` 105 | MessageId string `json:"messageId,omitempty"` 106 | RequestId string `json:"requestId,omitempty"` 107 | Offense string `json:"offense,omitempty"` 108 | AdaptiveCards []*AdaptiveCard `json:"adaptiveCards,omitempty"` 109 | SourceAttributions []interface{} `json:"sourceAttributions,omitempty"` 110 | Feedback *BingFeedback `json:"feedback,omitempty"` 111 | ContentOrigin string `json:"contentOrigin,omitempty"` 112 | ContentType string `json:"contentType,omitempty"` 113 | MessageType string `json:"messageType,omitempty"` 114 | Invocation string `json:"invocation,omitempty"` 115 | ImageUrl string `json:"imageUrl,omitempty"` 116 | OriginalImageUrl string `json:"originalImageUrl,omitempty"` 117 | InputMethod string `json:"inputMethod,omitempty"` 118 | } 119 | 120 | type AdaptiveCard struct { 121 | AType string `json:"type"` 122 | Version string `json:"version"` 123 | Body []*AdaptiveCardBody `json:"body"` 124 | } 125 | 126 | type AdaptiveCardBody struct { 127 | BType string `json:"type"` 128 | Text string `json:"text"` 129 | Wrap bool `json:"wrap"` 130 | Inlines []*AdaptiveCardBodyInlines `json:"inlines,omitempty"` 131 | } 132 | 133 | type AdaptiveCardBodyInlines struct { 134 | Text string `json:"text"` 135 | } 136 | 137 | type BingFeedback struct { 138 | Tag interface{} `json:"tag"` 139 | UpdatedOn interface{} `json:"updatedOn"` 140 | Ftype string `json:"type"` 141 | } 142 | 143 | type BingCompletionItem struct { 144 | messages *BingCompletionMessage `json:"messages"` 145 | FirstNewMessageIndex int `json:"firstNewMessageIndex"` 146 | DefaultChatName interface{} `json:"defaultChatName"` 147 | ConversationId string `json:"conversationId"` 148 | RequestId string `json:"requestId"` 149 | ConversationExpiryTime string `json:"conversationExpiryTime"` 150 | Telemetry *BingTelemetry `json:"telemetry"` 151 | Throttling *BingThrottling `json:"throttling"` 152 | Result *BingFinalResult `json:"result"` 153 | } 154 | 155 | type BingTelemetry struct { 156 | StartTime string `json:"startTime"` 157 | } 158 | 159 | type BingThrottling struct { 160 | maxNumUserMessagesInConversation int `json:"maxNumUserMessagesInConversation"` 161 | numUserMessagesInConversation int `json:"numUserMessagesInConversation"` 162 | maxNumLongDocSummaryUserMessagesInConversation int `json:"maxNumLongDocSummaryUserMessagesInConversation"` 163 | numLongDocSummaryUserMessagesInConversation int `json:"numLongDocSummaryUserMessagesInConversation"` 164 | } 165 | 166 | type BingFinalResult struct { 167 | Value string `json:"value"` 168 | Message string `json:"message"` 169 | ServiceVersion string `json:"serviceVersion"` 170 | } 171 | -------------------------------------------------------------------------------- /internal/gemini/gemini.go: -------------------------------------------------------------------------------- 1 | package gemini 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "math/rand" 9 | "strings" 10 | "time" 11 | 12 | http "github.com/bogdanfinn/fhttp" 13 | "github.com/bogdanfinn/fhttp/httputil" 14 | "github.com/google/uuid" 15 | "github.com/zatxm/any-proxy/internal/client" 16 | "github.com/zatxm/any-proxy/internal/config" 17 | "github.com/zatxm/any-proxy/internal/types" 18 | "github.com/zatxm/any-proxy/internal/vars" 19 | "github.com/zatxm/fhblade" 20 | "github.com/zatxm/fhblade/tools" 21 | tlsClient "github.com/zatxm/tls-client" 22 | "go.uber.org/zap" 23 | ) 24 | 25 | const ( 26 | Provider = "gemini" 27 | ApiHost = "generativelanguage.googleapis.com" 28 | ApiUrl = "https://generativelanguage.googleapis.com" 29 | ApiVersion = "v1beta" 30 | DefaultModel = "gemini-pro" 31 | ) 32 | 33 | var ( 34 | startTag = []byte(` "text": "`) 35 | endTag = []byte{34, 10} 36 | ) 37 | 38 | // 转发 39 | func Do() func(*fhblade.Context) error { 40 | return func(c *fhblade.Context) error { 41 | path := "/" + c.Get("path") 42 | // api转openai api 43 | if path == "/openai" && c.Request().Method() == "POST" { 44 | // 参数 45 | var p types.StreamGenerateContent 46 | if err := c.ShouldBindJSON(&p); err != nil { 47 | return c.JSONAndStatus(http.StatusBadRequest, types.ErrorResponse{ 48 | Error: &types.CError{ 49 | Message: "params error", 50 | Type: "invalid_request_error", 51 | Code: "invalid_parameter", 52 | }, 53 | }) 54 | } 55 | return apiToApi(c, p, c.Request().Header("x-auth-id")) 56 | } 57 | query := c.Request().RawQuery() 58 | // 请求头 59 | c.Request().Req().Header = http.Header{ 60 | "content-type": {vars.ContentTypeJSON}, 61 | } 62 | gClient := client.CPool.Get().(tlsClient.HttpClient) 63 | proxyUrl := config.GeminiProxyUrl() 64 | if proxyUrl != "" { 65 | gClient.SetProxy(proxyUrl) 66 | } 67 | defer client.CPool.Put(gClient) 68 | goProxy := httputil.ReverseProxy{ 69 | Director: func(req *http.Request) { 70 | req.Host = ApiHost 71 | req.URL.Host = ApiHost 72 | req.URL.Scheme = "https" 73 | req.URL.Path = path 74 | req.URL.RawQuery = query 75 | }, 76 | Transport: gClient.TClient().Transport, 77 | } 78 | goProxy.ServeHTTP(c.Response().Rw(), c.Request().Req()) 79 | return nil 80 | } 81 | } 82 | 83 | // 通过api请求返回openai格式 84 | func apiToApi(c *fhblade.Context, p types.StreamGenerateContent, idSign string) error { 85 | model := p.Model 86 | if model == "" { 87 | model = config.V().Gemini.Model 88 | if model == "" { 89 | model = DefaultModel 90 | } 91 | } 92 | goUrl, index := parseApiUrl(c, model, idSign) 93 | if goUrl == "" { 94 | return c.JSONAndStatus(http.StatusInternalServerError, types.ErrorResponse{ 95 | Error: &types.CError{ 96 | Message: "key error", 97 | Type: "invalid_request_error", 98 | Code: "request_err", 99 | }, 100 | }) 101 | } 102 | reqJson, _ := fhblade.Json.Marshal(p) 103 | req, err := http.NewRequest(http.MethodPost, goUrl, bytes.NewReader(reqJson)) 104 | if err != nil { 105 | fhblade.Log.Error("gemini v1 send msg new req err", 106 | zap.Error(err), 107 | zap.String("url", goUrl), 108 | zap.ByteString("data", reqJson)) 109 | return c.JSONAndStatus(http.StatusInternalServerError, types.ErrorResponse{ 110 | Error: &types.CError{ 111 | Message: err.Error(), 112 | Type: "invalid_request_error", 113 | Code: "request_err", 114 | }, 115 | }) 116 | } 117 | req.Header = http.Header{ 118 | "content-type": {vars.ContentTypeJSON}, 119 | } 120 | gClient := client.CPool.Get().(tlsClient.HttpClient) 121 | proxyUrl := config.GeminiProxyUrl() 122 | if proxyUrl != "" { 123 | gClient.SetProxy(proxyUrl) 124 | } 125 | resp, err := gClient.Do(req) 126 | client.CPool.Put(gClient) 127 | if err != nil { 128 | fhblade.Log.Error("gemini v1 send msg req err", 129 | zap.Error(err), 130 | zap.String("url", goUrl), 131 | zap.ByteString("data", reqJson)) 132 | return c.JSONAndStatus(http.StatusInternalServerError, types.ErrorResponse{ 133 | Error: &types.CError{ 134 | Message: err.Error(), 135 | Type: "invalid_request_error", 136 | Code: "request_err", 137 | }, 138 | }) 139 | } 140 | defer resp.Body.Close() 141 | rw := c.Response().Rw() 142 | flusher, ok := rw.(http.Flusher) 143 | if !ok { 144 | return c.JSONAndStatus(http.StatusNotImplemented, types.ErrorResponse{ 145 | Error: &types.CError{ 146 | Message: "Flushing not supported", 147 | Type: "invalid_systems_error", 148 | Code: "systems_error", 149 | }, 150 | }) 151 | } 152 | header := rw.Header() 153 | header.Set("Content-Type", vars.ContentTypeStream) 154 | header.Set("Cache-Control", "no-cache") 155 | header.Set("Connection", "keep-alive") 156 | header.Set("Access-Control-Allow-Origin", "*") 157 | rw.WriteHeader(200) 158 | // 读取响应体 159 | reader := bufio.NewReader(resp.Body) 160 | id := uuid.NewString() 161 | now := time.Now().Unix() 162 | for { 163 | line, err := reader.ReadBytes('\n') 164 | if err != nil { 165 | if err != io.EOF { 166 | fhblade.Log.Error("gemini v1 send msg res read err", zap.Error(err)) 167 | } 168 | break 169 | } 170 | if bytes.HasPrefix(line, startTag) { 171 | raw := bytes.TrimPrefix(line, startTag) 172 | raw = bytes.TrimSuffix(raw, endTag) 173 | var choices []*types.ChatCompletionChoice 174 | choices = append(choices, &types.ChatCompletionChoice{ 175 | Index: 0, 176 | Message: &types.ChatCompletionMessage{ 177 | Role: "assistant", 178 | Content: tools.BytesToString(raw), 179 | }, 180 | }) 181 | outRes := &types.ChatCompletionResponse{ 182 | ID: id, 183 | Choices: choices, 184 | Created: now, 185 | Model: model, 186 | Object: "chat.completion.chunk", 187 | Gemini: &types.GeminiCompletionResponse{ 188 | Type: "api", 189 | Index: index, 190 | }, 191 | } 192 | outJson, _ := fhblade.Json.Marshal(outRes) 193 | fmt.Fprintf(rw, "data: %s\n\n", outJson) 194 | flusher.Flush() 195 | } 196 | } 197 | fmt.Fprint(rw, "data: [DONE]\n\n") 198 | flusher.Flush() 199 | return nil 200 | } 201 | 202 | // 目前仅支持文字对话 203 | func DoChatCompletions(c *fhblade.Context, p types.ChatCompletionRequest) error { 204 | var contents []*types.GeminiContent 205 | for k := range p.Messages { 206 | message := p.Messages[k] 207 | if message.MultiContent == nil { 208 | parts := []*types.GeminiPart{&types.GeminiPart{Text: message.Content}} 209 | switch message.Role { 210 | case "assistant": 211 | contents = append(contents, &types.GeminiContent{Parts: parts, Role: "model"}) 212 | case "user": 213 | contents = append(contents, &types.GeminiContent{Parts: parts, Role: "user"}) 214 | } 215 | } 216 | } 217 | if len(contents) == 0 { 218 | return c.JSONAndStatus(http.StatusBadRequest, types.ErrorResponse{ 219 | Error: &types.CError{ 220 | Message: "params error", 221 | Type: "invalid_request_error", 222 | Code: "request_err", 223 | }, 224 | }) 225 | } 226 | goReq := &types.StreamGenerateContent{ 227 | Contents: contents, 228 | GenerationConfig: &types.GenerationConfig{}, 229 | } 230 | if &p.MaxTokens != nil { 231 | goReq.GenerationConfig.MaxOutputTokens = p.MaxTokens 232 | } 233 | goReq.Model = p.Model 234 | reqIndex := c.Request().Header("x-auth-id") 235 | if reqIndex == "" && p.Gemini != nil && p.Gemini.Index != "" { 236 | reqIndex = p.Gemini.Index 237 | } 238 | return apiToApi(c, *goReq, reqIndex) 239 | } 240 | 241 | func parseApiUrl(c *fhblade.Context, model, idSign string) (string, string) { 242 | auth, version, index := parseAuth(c, idSign) 243 | if auth == "" { 244 | return "", "" 245 | } 246 | var apiUrlBuild strings.Builder 247 | apiUrlBuild.WriteString(ApiUrl) 248 | apiUrlBuild.WriteString("/") 249 | apiUrlBuild.WriteString(version) 250 | apiUrlBuild.WriteString("/models/") 251 | apiUrlBuild.WriteString(model) 252 | apiUrlBuild.WriteString(":streamGenerateContent") 253 | apiUrlBuild.WriteString("?key=") 254 | apiUrlBuild.WriteString(auth) 255 | return apiUrlBuild.String(), index 256 | } 257 | 258 | func parseAuth(c *fhblade.Context, index string) (string, string, string) { 259 | auth := c.Request().Header("Authorization") 260 | if auth != "" { 261 | version := c.Request().Header("x-version") 262 | if version == "" { 263 | version = ApiVersion 264 | } 265 | if strings.HasPrefix(auth, "Bearer ") { 266 | return strings.TrimPrefix(auth, "Bearer "), version, "" 267 | } 268 | return auth, version, "" 269 | } 270 | keys := config.V().Gemini.ApiKeys 271 | l := len(keys) 272 | if l == 0 { 273 | return "", "", "" 274 | } 275 | if index != "" { 276 | for k := range keys { 277 | v := keys[k] 278 | if index == v.ID { 279 | return v.Val, v.Version, v.ID 280 | } 281 | } 282 | return "", "", "" 283 | } 284 | 285 | if l == 1 { 286 | return keys[0].Val, keys[0].Version, keys[0].ID 287 | } 288 | 289 | rand.Seed(time.Now().UnixNano()) 290 | i := rand.Intn(l) 291 | v := keys[i] 292 | return v.Val, v.Version, v.ID 293 | } 294 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= 2 | github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 3 | github.com/bogdanfinn/fhttp v0.5.28 h1:G6thT8s8v6z1IuvXMUsX9QKy3ZHseTQTzxuIhSiaaAw= 4 | github.com/bogdanfinn/fhttp v0.5.28/go.mod h1:oJiYPG3jQTKzk/VFmogH8jxjH5yiv2rrOH48Xso2lrE= 5 | github.com/bogdanfinn/utls v1.6.1 h1:dKDYAcXEyFFJ3GaWaN89DEyjyRraD1qb4osdEK89ass= 6 | github.com/bogdanfinn/utls v1.6.1/go.mod h1:VXIbRZaiY/wHZc6Hu+DZ4O2CgTzjhjCg/Ou3V4r/39Y= 7 | github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= 8 | github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= 9 | github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg= 10 | github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 15 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 16 | github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= 17 | github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 18 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 19 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 20 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 21 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 22 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 23 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 24 | github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= 25 | github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 26 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 27 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 28 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 29 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 30 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 31 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= 32 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 33 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 34 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 35 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 36 | github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= 37 | github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 38 | github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= 39 | github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= 40 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 41 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 42 | github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= 43 | github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 44 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 45 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 46 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 47 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 48 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 49 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 50 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 51 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 52 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 53 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 54 | github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= 55 | github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= 56 | github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= 57 | github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= 58 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 59 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 60 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 61 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 62 | github.com/quic-go/quic-go v0.37.4 h1:ke8B73yMCWGq9MfrCCAw0Uzdm7GaViC3i39dsIdDlH4= 63 | github.com/quic-go/quic-go v0.37.4/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU= 64 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 65 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 66 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 67 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 68 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 69 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 70 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 71 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 72 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 73 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 74 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 h1:YqAladjX7xpA6BM04leXMWAEjS0mTZ5kUU9KRBriQJc= 75 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5/go.mod h1:2JjD2zLQYH5HO74y5+aE3remJQvl6q4Sn6aWA2wD1Ng= 76 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 77 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 78 | github.com/zatxm/fhblade v0.0.0-20240108032359-f02aa4f5523f h1:Stmlln0Ay5c1seMcA4yZg5nvqzZ/SOSHiTDsEVDLxA0= 79 | github.com/zatxm/fhblade v0.0.0-20240108032359-f02aa4f5523f/go.mod h1:SFRljspWKn1EAkox9M//RU3n3bSgyLEPK16X030dhdY= 80 | github.com/zatxm/tls-client v0.0.0-20231223102741-4e348055c451 h1:6Mh7UwwbcDNJaxC132lsSmcPtfwfCW93zHudjTynTp4= 81 | github.com/zatxm/tls-client v0.0.0-20231223102741-4e348055c451/go.mod h1:GhmLHvEf7ufGoXchDdlT+0cANaV4EpmSH86TN8/HCG8= 82 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 83 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 84 | go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= 85 | go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 86 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 87 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 88 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 89 | golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= 90 | golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 91 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 92 | golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= 93 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 94 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 95 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= 96 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 97 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 98 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 99 | golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= 100 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 101 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 102 | golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= 103 | golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= 104 | google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= 105 | google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 106 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 107 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 108 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 109 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 110 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 111 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 112 | -------------------------------------------------------------------------------- /internal/openai/arkose/har/har.go: -------------------------------------------------------------------------------- 1 | package har 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/url" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | "time" 11 | 12 | http "github.com/bogdanfinn/fhttp" 13 | "github.com/zatxm/any-proxy/internal/config" 14 | "github.com/zatxm/any-proxy/pkg/jscrypt" 15 | "github.com/zatxm/fhblade" 16 | ) 17 | 18 | var ( 19 | bxTemp = `[{"key":"api_type","value":"js"},{"key":"p","value":1},{"key":"f","value":"5658246a2142c8eb528707b5e9dd130e"},{"key":"n","value":"%s"},{"key":"wh","value":"ebcb832548b5ab8087d8a5a43f8d236c|72627afbfd19a741c7da1732218301ac"},{"key":"enhanced_fp","value":[{"key":"webgl_extensions","value":"ANGLE_instanced_arrays;EXT_blend_minmax;EXT_color_buffer_half_float;EXT_disjoint_timer_query;EXT_float_blend;EXT_frag_depth;EXT_shader_texture_lod;EXT_texture_compression_bptc;EXT_texture_compression_rgtc;EXT_texture_filter_anisotropic;EXT_sRGB;KHR_parallel_shader_compile;OES_element_index_uint;OES_fbo_render_mipmap;OES_standard_derivatives;OES_texture_float;OES_texture_float_linear;OES_texture_half_float;OES_texture_half_float_linear;OES_vertex_array_object;WEBGL_color_buffer_float;WEBGL_compressed_texture_s3tc;WEBGL_compressed_texture_s3tc_srgb;WEBGL_debug_renderer_info;WEBGL_debug_shaders;WEBGL_depth_texture;WEBGL_draw_buffers;WEBGL_lose_context;WEBGL_multi_draw"},{"key":"webgl_extensions_hash","value":"58a5a04a5bef1a78fa88d5c5098bd237"},{"key":"webgl_renderer","value":"WebKit WebGL"},{"key":"webgl_vendor","value":"WebKit"},{"key":"webgl_version","value":"WebGL 1.0 (OpenGL ES 2.0 Chromium)"},{"key":"webgl_shading_language_version","value":"WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)"},{"key":"webgl_aliased_line_width_range","value":"[1, 10]"},{"key":"webgl_aliased_point_size_range","value":"[1, 2047]"},{"key":"webgl_antialiasing","value":"yes"},{"key":"webgl_bits","value":"8,8,24,8,8,0"},{"key":"webgl_max_params","value":"16,64,32768,1024,32768,32,32768,31,16,32,1024"},{"key":"webgl_max_viewport_dims","value":"[32768, 32768]"},{"key":"webgl_unmasked_vendor","value":"Google Inc. (NVIDIA Corporation)"},{"key":"webgl_unmasked_renderer","value":"ANGLE (NVIDIA Corporation, NVIDIA GeForce RTX 3060 Ti/PCIe/SSE2, OpenGL 4.5.0)"},{"key":"webgl_vsf_params","value":"23,127,127,10,15,15,10,15,15"},{"key":"webgl_vsi_params","value":"0,31,30,0,31,30,0,31,30"},{"key":"webgl_fsf_params","value":"23,127,127,10,15,15,10,15,15"},{"key":"webgl_fsi_params","value":"0,31,30,0,31,30,0,31,30"},{"key":"webgl_hash_webgl","value":"7b9cf50d61f3da6ba617d04d2a217c48"},{"key":"user_agent_data_brands","value":"Not.A/Brand,Chromium,Google Chrome"},{"key":"user_agent_data_mobile","value":false},{"key":"navigator_connection_downlink","value":10},{"key":"navigator_connection_downlink_max","value":null},{"key":"network_info_rtt","value":150},{"key":"network_info_save_data","value":false},{"key":"network_info_rtt_type","value":null},{"key":"screen_pixel_depth","value":24},{"key":"navigator_device_memory","value":8},{"key":"navigator_languages","value":"zh-CN,en"},{"key":"window_inner_width","value":0},{"key":"window_inner_height","value":0},{"key":"window_outer_width","value":1920},{"key":"window_outer_height","value":1057},{"key":"browser_detection_firefox","value":false},{"key":"browser_detection_brave","value":false},{"key":"audio_codecs","value":"{\"ogg\":\"probably\",\"mp3\":\"probably\",\"wav\":\"probably\",\"m4a\":\"maybe\",\"aac\":\"probably\"}"},{"key":"video_codecs","value":"{\"ogg\":\"probably\",\"h264\":\"probably\",\"webm\":\"probably\",\"mpeg4v\":\"\",\"mpeg4a\":\"\",\"theora\":\"\"}"},{"key":"media_query_dark_mode","value":true},{"key":"headless_browser_phantom","value":false},{"key":"headless_browser_selenium","value":false},{"key":"headless_browser_nightmare_js","value":false},{"key":"document__referrer","value":""},{"key":"window__ancestor_origins","value":"%s"},{"key":"window__tree_index","value":[2]},{"key":"window__tree_structure","value":"[[],[],[]]"},{"key":"window__location_href","value":"https://tcr9i.chat.openai.com/v2/2.3.0/enforcement.0087e749a89110af598a5fae60fc4762.html#%s"},{"key":"client_config__sitedata_location_href","value":"%s"},{"key":"client_config__surl","value":"%s"},{"key":"mobile_sdk__is_sdk"},{"key":"client_config__language","value":null},{"key":"navigator_battery_charging","value":true},{"key":"audio_fingerprint","value":"124.04347527516074"}]},{"key":"fe","value":"[\"DNT:1\",\"L:zh-CN\",\"D:24\",\"PR:1\",\"S:1920,1080\",\"AS:1920,1080\",\"TO:-480\",\"SS:true\",\"LS:true\",\"IDB:true\",\"B:false\",\"ODB:true\",\"CPUC:unknown\",\"PK:Linux x86_64\",\"CFP:1941002709\",\"FR:false\",\"FOS:false\",\"FB:false\",\"JSF:Arial,Courier,Courier New,Helvetica,Times,Times New Roman\",\"P:Chrome PDF Viewer,Chromium PDF Viewer,Microsoft Edge PDF Viewer,PDF Viewer,WebKit built-in PDF\",\"T:0,false,false\",\"H:16\",\"SWF:false\"]"},{"key":"ife_hash","value":"411c34615014b9640db1d4c3f65dbee5"},{"key":"cs","value":1},{"key":"jsbd","value":"{\"HL\":5,\"NCE\":true,\"DT\":\"\",\"NWD\":\"false\",\"DOTO\":1,\"DMTO\":1}"}]` 20 | arkoseDatas = map[string]*mom{ 21 | // auth 22 | "0A1D34FC-659D-4E23-B17B-694DCFCF6A6C": &mom{ 23 | WindowAncestorOrigins: `["https://auth0.openai.com"]`, 24 | ClientConfigSitedataLocationHref: "https://auth0.openai.com/u/login/password", 25 | ClientConfigSurl: "https://tcr9i.chat.openai.com", 26 | SiteUrl: "https://auth0.openai.com", 27 | }, 28 | // chat3 29 | "3D86FBBA-9D22-402A-B512-3420086BA6CC": &mom{ 30 | WindowAncestorOrigins: `["https://chat.openai.com"]`, 31 | ClientConfigSitedataLocationHref: "https://chat.openai.com/", 32 | ClientConfigSurl: "https://tcr9i.chat.openai.com", 33 | SiteUrl: "https://chat.openai.com", 34 | }, 35 | // chat4 36 | "35536E1E-65B4-4D96-9D97-6ADB7EFF8147": &mom{ 37 | WindowAncestorOrigins: `["https://chat.openai.com"]`, 38 | ClientConfigSitedataLocationHref: "https://chat.openai.com/", 39 | ClientConfigSurl: "https://tcr9i.chat.openai.com", 40 | SiteUrl: "https://chat.openai.com", 41 | }, 42 | // platform 43 | "23AAD243-4799-4A9E-B01D-1166C5DE02DF": &mom{ 44 | WindowAncestorOrigins: `["https://platform.openai.com"]`, 45 | ClientConfigSitedataLocationHref: "https://platform.openai.com/account/api-keys", 46 | ClientConfigSurl: "https://openai-api.arkoselabs.com", 47 | SiteUrl: "https://platform.openai.com", 48 | }, 49 | } 50 | ) 51 | 52 | type mom struct { 53 | WindowAncestorOrigins string 54 | WindowLocationHref string 55 | ClientConfigSitedataLocationHref string 56 | ClientConfigSurl string 57 | SiteUrl string 58 | Hars []*harData 59 | } 60 | 61 | type KvPair struct { 62 | Name string `json:"name"` 63 | Value string `json:"value"` 64 | } 65 | 66 | type harData struct { 67 | Url string 68 | Method string 69 | Bv string 70 | Bx string 71 | Headers http.Header 72 | Body url.Values 73 | } 74 | 75 | func (h *harData) Clone() *harData { 76 | b, _ := fhblade.Json.Marshal(h) 77 | var cloned harData 78 | fhblade.Json.Unmarshal(b, &cloned) 79 | return &cloned 80 | } 81 | 82 | type harFileData struct { 83 | Log harLogData `json:"log"` 84 | } 85 | 86 | type harLogData struct { 87 | Entries []harEntrie `json:"entries"` 88 | } 89 | 90 | type harEntrie struct { 91 | Request harRequest `json:"request"` 92 | StartedDateTime string `json:"startedDateTime"` 93 | } 94 | 95 | type harRequest struct { 96 | Method string `json:"method"` 97 | URL string `json:"url"` 98 | Headers []KvPair `json:"headers,omitempty"` 99 | PostData harRequestBody `json:"postData,omitempty"` 100 | } 101 | 102 | type harRequestBody struct { 103 | Params []KvPair `json:"params"` 104 | } 105 | 106 | func GenerateBx(key string, bt int64) string { 107 | m := arkoseDatas[key] 108 | return fmt.Sprintf(bxTemp, 109 | jscrypt.GenerateN(bt), 110 | m.WindowAncestorOrigins, 111 | m.WindowLocationHref, 112 | m.ClientConfigSitedataLocationHref, 113 | m.ClientConfigSurl) 114 | } 115 | 116 | func GetArkoseDatas() map[string]*mom { 117 | return arkoseDatas 118 | } 119 | 120 | func Parse() error { 121 | var harPath []string 122 | harDirPath := config.V().HarsPath 123 | err := filepath.Walk(harDirPath, func(path string, info os.FileInfo, err error) error { 124 | if err != nil { 125 | return err 126 | } 127 | // 判断是否为普通文件(非文件夹) 128 | if !info.IsDir() { 129 | ext := filepath.Ext(info.Name()) 130 | if ext == ".har" { 131 | harPath = append(harPath, path) 132 | } 133 | } 134 | return nil 135 | }) 136 | if err != nil { 137 | return errors.New("Error: please put HAR files in harPool directory!") 138 | } 139 | if len(harPath) > 0 { 140 | for pk := range harPath { 141 | file, err := os.ReadFile(harPath[pk]) 142 | if err != nil { 143 | fmt.Println(err) 144 | continue 145 | } 146 | var hfData harFileData 147 | err = fhblade.Json.Unmarshal(file, &hfData) 148 | if err != nil { 149 | fmt.Println(err) 150 | continue 151 | } 152 | data := &harData{} 153 | tagKey := "openai.com/fc/gt2/" 154 | arkoseKey := "abc" 155 | for k := range hfData.Log.Entries { 156 | v := hfData.Log.Entries[k] 157 | arkoseKey = "abc" 158 | if !strings.Contains(v.Request.URL, tagKey) || v.StartedDateTime == "" { 159 | continue 160 | } 161 | data.Url = v.Request.URL 162 | data.Method = v.Request.Method 163 | data.Headers = make(http.Header) 164 | for hk := range v.Request.Headers { 165 | h := v.Request.Headers[hk] 166 | if strings.HasPrefix(h.Name, ":") || h.Name == "content-length" || h.Name == "connection" || h.Name == "cookie" { 167 | continue 168 | } 169 | if h.Name == "user-agent" { 170 | data.Bv = h.Value 171 | } else { 172 | data.Headers.Set(h.Name, h.Value) 173 | } 174 | } 175 | if data.Bv == "" { 176 | continue 177 | } 178 | data.Body = make(url.Values) 179 | for pk := range v.Request.PostData.Params { 180 | p := v.Request.PostData.Params[pk] 181 | if p.Name == "bda" { 182 | pcipher, _ := url.QueryUnescape(p.Value) 183 | t, _ := time.Parse(time.RFC3339, v.StartedDateTime) 184 | bt := t.Unix() 185 | bw := jscrypt.GenerateBw(bt) 186 | bx, err := jscrypt.Decrypt(pcipher, data.Bv+bw) 187 | if err != nil { 188 | fmt.Println(err) 189 | } else { 190 | data.Bx = bx 191 | } 192 | } else if p.Name != "rnd" { 193 | q, _ := url.QueryUnescape(p.Value) 194 | data.Body.Set(p.Name, q) 195 | if p.Name == "public_key" { 196 | if _, ok := arkoseDatas[p.Value]; ok { 197 | arkoseKey = p.Value 198 | } 199 | } 200 | } 201 | } 202 | if data.Bx != "" { 203 | break 204 | } 205 | } 206 | if data.Bx != "" && arkoseKey != "abc" { 207 | arkoseDatas[arkoseKey].Hars = append(arkoseDatas[arkoseKey].Hars, data) 208 | } 209 | } 210 | return err 211 | } 212 | return errors.New("Empty HAR files in harPool directory!") 213 | } 214 | -------------------------------------------------------------------------------- /internal/coze/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "math/rand" 10 | "strings" 11 | "time" 12 | 13 | http "github.com/bogdanfinn/fhttp" 14 | "github.com/google/uuid" 15 | "github.com/zatxm/any-proxy/internal/client" 16 | "github.com/zatxm/any-proxy/internal/config" 17 | "github.com/zatxm/any-proxy/internal/coze/discord" 18 | "github.com/zatxm/any-proxy/internal/types" 19 | "github.com/zatxm/any-proxy/internal/vars" 20 | "github.com/zatxm/any-proxy/pkg/support" 21 | "github.com/zatxm/fhblade" 22 | tlsClient "github.com/zatxm/tls-client" 23 | "go.uber.org/zap" 24 | ) 25 | 26 | const ( 27 | Provider = "coze" 28 | ApiChatModel = "coze-api" 29 | ) 30 | 31 | var ( 32 | defaultTimeout int64 = 300 33 | ApiChatUrl = "https://api.coze.com/open_api/v2/chat" 34 | startTag = []byte("data:") 35 | endTag = []byte{10} 36 | ) 37 | 38 | func DoChatCompletions(c *fhblade.Context, p types.ChatCompletionRequest) error { 39 | if p.Model == ApiChatModel { 40 | return doApiChat(c, p) 41 | } 42 | cozeCfg := config.V().Coze.Discord 43 | if !cozeCfg.Enable { 44 | return c.JSONAndStatus(http.StatusInternalServerError, types.ErrorResponse{ 45 | Error: &types.CError{ 46 | Message: "not support coze discord", 47 | Type: "invalid_config_error", 48 | Code: "systems_err", 49 | }, 50 | }) 51 | } 52 | // 判断请求内容 53 | prompt := "" 54 | for k := range p.Messages { 55 | message := p.Messages[k] 56 | if message.Role == "user" { 57 | if message.MultiContent != nil { 58 | var err error 59 | prompt, err = buildGPT4VForImageContent(message.MultiContent) 60 | if err != nil { 61 | return c.JSONAndStatus(http.StatusOK, fhblade.H{ 62 | "success": false, 63 | "message": err.Error()}) 64 | } 65 | } else { 66 | prompt = message.Content 67 | } 68 | } 69 | } 70 | if prompt == "" { 71 | return c.JSONAndStatus(http.StatusBadRequest, types.ErrorResponse{ 72 | Error: &types.CError{ 73 | Message: "params error", 74 | Type: "invalid_request_error", 75 | Code: "discord_request_err", 76 | }, 77 | }) 78 | } 79 | sentMsg, err := discord.SendMessage(prompt, "") 80 | if err != nil { 81 | return c.JSONAndStatus(http.StatusBadRequest, types.ErrorResponse{ 82 | Error: &types.CError{ 83 | Message: err.Error(), 84 | Type: "invalid_request_error", 85 | Code: "discord_request_err", 86 | }, 87 | }) 88 | } 89 | 90 | replyChan := make(chan types.ChatCompletionResponse) 91 | discord.RepliesOpenAIChans[sentMsg.ID] = replyChan 92 | defer delete(discord.RepliesOpenAIChans, sentMsg.ID) 93 | 94 | stopChan := make(chan discord.ChannelStopChan) 95 | discord.ReplyStopChans[sentMsg.ID] = stopChan 96 | defer delete(discord.ReplyStopChans, sentMsg.ID) 97 | 98 | duration := cozeCfg.RequestStreamOutTime 99 | if duration == 0 { 100 | duration = defaultTimeout 101 | } 102 | durationTime := time.Duration(duration) * time.Second 103 | timer := time.NewTimer(durationTime) 104 | rw := c.Response().Rw() 105 | flusher, ok := rw.(http.Flusher) 106 | if !ok { 107 | return c.JSONAndStatus(http.StatusNotImplemented, types.ErrorResponse{ 108 | Error: &types.CError{ 109 | Message: "Flushing not supported", 110 | Type: "invalid_systems_error", 111 | Code: "systems_error", 112 | }, 113 | }) 114 | } 115 | header := rw.Header() 116 | header.Set("Content-Type", vars.ContentTypeStream) 117 | header.Set("Cache-Control", "no-cache") 118 | header.Set("Connection", "keep-alive") 119 | header.Set("Access-Control-Allow-Origin", "*") 120 | rw.WriteHeader(200) 121 | clientGone := rw.(http.CloseNotifier).CloseNotify() 122 | lastMsg := "" 123 | for { 124 | select { 125 | case <-clientGone: 126 | return nil 127 | default: 128 | select { 129 | case reply := <-replyChan: 130 | timer.Reset(durationTime) 131 | tMsg := strings.TrimPrefix(reply.Choices[0].Message.Content, lastMsg) 132 | lastMsg = reply.Choices[0].Message.Content 133 | if tMsg != "" { 134 | reply.Choices[0].Message.Content = tMsg 135 | reply.Object = "chat.completion.chunk" 136 | bs, _ := fhblade.Json.MarshalToString(reply) 137 | fmt.Fprintf(rw, "data: %s\n\n", bs) 138 | flusher.Flush() 139 | } 140 | case <-timer.C: 141 | fmt.Fprint(rw, "data: [DONE]\n\n") 142 | flusher.Flush() 143 | return nil 144 | case <-stopChan: 145 | fmt.Fprint(rw, "data: [DONE]\n\n") 146 | flusher.Flush() 147 | return nil 148 | } 149 | } 150 | } 151 | } 152 | 153 | func buildGPT4VForImageContent(objs []*types.ChatMessagePart) (string, error) { 154 | var contentBuilder strings.Builder 155 | for k := range objs { 156 | obj := objs[k] 157 | if k == 0 && obj.Type == "text" { 158 | contentBuilder.WriteString(obj.Text) 159 | continue 160 | } else if k == 1 && obj.Type == "image_url" { 161 | if support.EqURL(obj.ImageURL.URL) { 162 | contentBuilder.WriteString("\n") 163 | contentBuilder.WriteString(obj.ImageURL.URL) 164 | } else if support.EqImageBase64(obj.ImageURL.URL) { 165 | url, err := discord.UploadToDiscordURL(obj.ImageURL.URL) 166 | if err != nil { 167 | return "", err 168 | } 169 | contentBuilder.WriteString("\n") 170 | contentBuilder.WriteString(url) 171 | } else { 172 | return "", errors.New("Img error") 173 | } 174 | } else { 175 | return "", errors.New("Request messages error") 176 | } 177 | } 178 | content := contentBuilder.String() 179 | if len([]rune(content)) > 2000 { 180 | return "", errors.New("Prompt max token 2000") 181 | } 182 | return contentBuilder.String(), nil 183 | } 184 | 185 | func doApiChat(c *fhblade.Context, p types.ChatCompletionRequest) error { 186 | prompt := p.ParsePromptText() 187 | if prompt == "" { 188 | return c.JSONAndStatus(http.StatusBadRequest, types.ErrorResponse{ 189 | Error: &types.CError{ 190 | Message: "params error", 191 | Type: "invalid_request_error", 192 | Code: "request_err", 193 | }, 194 | }) 195 | } 196 | botId, user, token := parseAuth(c, p) 197 | if botId == "" || user == "" || token == "" { 198 | return c.JSONAndStatus(http.StatusInternalServerError, types.ErrorResponse{ 199 | Error: &types.CError{ 200 | Message: "config error", 201 | Type: "invalid_config_error", 202 | Code: "systems_err", 203 | }, 204 | }) 205 | } 206 | if !strings.HasPrefix(token, "Bearer ") { 207 | token = "Bearer " + token 208 | } 209 | r := &types.CozeApiChatRequest{ 210 | BotId: botId, 211 | User: user, 212 | Query: prompt, 213 | Stream: true, 214 | } 215 | if p.Coze != nil && p.Coze.Conversation != nil && p.Coze.Conversation.ConversationId != "" { 216 | r.ConversationId = p.Coze.Conversation.ConversationId 217 | } else { 218 | r.ConversationId = uuid.NewString() 219 | } 220 | reqJson, _ := fhblade.Json.MarshalToString(r) 221 | req, err := http.NewRequest(http.MethodPost, ApiChatUrl, strings.NewReader(reqJson)) 222 | if err != nil { 223 | fhblade.Log.Error("coze chat api v1 send msg new req err", 224 | zap.Error(err), 225 | zap.String("data", reqJson)) 226 | return c.JSONAndStatus(http.StatusBadRequest, types.ErrorResponse{ 227 | Error: &types.CError{ 228 | Message: err.Error(), 229 | Type: "invalid_request_error", 230 | Code: "systems_err", 231 | }, 232 | }) 233 | } 234 | req.Header = http.Header{ 235 | "accept": {vars.AcceptAll}, 236 | "authorization": {token}, 237 | "connection": {"Keep-alive"}, 238 | "content-type": {vars.ContentTypeJSON}, 239 | } 240 | gClient := client.CPool.Get().(tlsClient.HttpClient) 241 | proxyUrl := config.CozeProxyUrl() 242 | if proxyUrl != "" { 243 | gClient.SetProxy(proxyUrl) 244 | } 245 | resp, err := gClient.Do(req) 246 | client.CPool.Put(gClient) 247 | if err != nil { 248 | fhblade.Log.Error("coze chat api v1 send msg req err", 249 | zap.Error(err), 250 | zap.String("data", reqJson)) 251 | return c.JSONAndStatus(http.StatusBadRequest, types.ErrorResponse{ 252 | Error: &types.CError{ 253 | Message: err.Error(), 254 | Type: "invalid_request_error", 255 | Code: "systems_err", 256 | }, 257 | }) 258 | } 259 | defer resp.Body.Close() 260 | rw := c.Response().Rw() 261 | flusher, ok := rw.(http.Flusher) 262 | if !ok { 263 | return c.JSONAndStatus(http.StatusInternalServerError, types.ErrorResponse{ 264 | Error: &types.CError{ 265 | Message: "Flushing not supported", 266 | Type: "invalid_request_error", 267 | Code: "systems_err", 268 | }, 269 | }) 270 | } 271 | header := rw.Header() 272 | header.Set("Content-Type", vars.ContentTypeStream) 273 | header.Set("Cache-Control", "no-cache") 274 | header.Set("Connection", "keep-alive") 275 | header.Set("Access-Control-Allow-Origin", "*") 276 | rw.WriteHeader(200) 277 | // 读取响应体 278 | reader := bufio.NewReader(resp.Body) 279 | now := time.Now().Unix() 280 | for { 281 | line, err := reader.ReadBytes('\n') 282 | if err != nil { 283 | if err != io.EOF { 284 | fhblade.Log.Error("coze chat api v1 send msg res read err", zap.Error(err)) 285 | } 286 | break 287 | } 288 | if bytes.HasPrefix(line, startTag) { 289 | raw := bytes.TrimPrefix(line, startTag) 290 | raw = bytes.TrimSuffix(raw, endTag) 291 | chatRes := &types.CozeApiChatResponse{} 292 | err := fhblade.Json.Unmarshal(raw, &chatRes) 293 | if err != nil { 294 | fhblade.Log.Error("coze chat api v1 wc deal data err", 295 | zap.Error(err), 296 | zap.ByteString("data", line)) 297 | continue 298 | } 299 | if chatRes.Event == "done" { 300 | break 301 | } 302 | if chatRes.Event == "error" { 303 | fmt.Fprintf(rw, "data: %s\n\n", raw) 304 | flusher.Flush() 305 | break 306 | } 307 | if chatRes.Message.Type == "answer" && chatRes.Message.Content != "" { 308 | var choices []*types.ChatCompletionChoice 309 | choices = append(choices, &types.ChatCompletionChoice{ 310 | Index: chatRes.Index, 311 | Message: &types.ChatCompletionMessage{ 312 | Role: "assistant", 313 | Content: chatRes.Message.Content, 314 | }, 315 | }) 316 | outRes := &types.ChatCompletionResponse{ 317 | ID: chatRes.ConversationId, 318 | Choices: choices, 319 | Created: now, 320 | Model: ApiChatModel, 321 | Object: "chat.completion.chunk", 322 | Coze: &types.CozeConversation{ 323 | Type: "api", 324 | BotId: botId, 325 | ConversationId: chatRes.ConversationId, 326 | User: user, 327 | }, 328 | } 329 | outJson, _ := fhblade.Json.Marshal(outRes) 330 | fmt.Fprintf(rw, "data: %s\n\n", outJson) 331 | flusher.Flush() 332 | } 333 | } 334 | } 335 | fmt.Fprint(rw, "data: [DONE]\n\n") 336 | flusher.Flush() 337 | 338 | return nil 339 | } 340 | 341 | // 随机获取设置的coze bot id 342 | func parseAuth(c *fhblade.Context, p types.ChatCompletionRequest) (string, string, string) { 343 | // 优先取header再取body传值 344 | token := c.Request().Header("Authorization") 345 | user := c.Request().Header("x-auth-id") 346 | botId := c.Request().Header("x-bot-id") 347 | // 头部全部有值就返回了 348 | if token != "" && user != "" && botId != "" { 349 | if strings.HasPrefix(token, "Bearer ") { 350 | token = strings.TrimPrefix(token, "Bearer ") 351 | } 352 | return botId, user, token 353 | } 354 | if p.Coze != nil && p.Coze.Conversation != nil && p.Coze.Conversation.BotId != "" && p.Coze.Conversation.User != "" { 355 | botId = p.Coze.Conversation.BotId 356 | user = p.Coze.Conversation.User 357 | } 358 | 359 | // 根据user和botId查找配置 360 | if user != "" && botId != "" { 361 | if token != "" { 362 | if strings.HasPrefix(token, "Bearer ") { 363 | token = strings.TrimPrefix(token, "Bearer ") 364 | } 365 | return botId, user, token 366 | } 367 | cozeApiChatCfg := config.V().Coze.ApiChat 368 | botCfgs := cozeApiChatCfg.Bots 369 | exist := false 370 | for k := range botCfgs { 371 | botCfg := botCfgs[k] 372 | if botId == botCfg.BotId && user == botCfg.User { 373 | token = botCfg.AccessToken 374 | exist = true 375 | break 376 | } 377 | } 378 | if !exist { 379 | return "", "", "" //不匹配 380 | } 381 | if token == "" { 382 | token = cozeApiChatCfg.AccessToken 383 | } 384 | return botId, user, token 385 | } 386 | 387 | // 随机获取 388 | cozeApiChatCfg := config.V().Coze.ApiChat 389 | botCfgs := cozeApiChatCfg.Bots 390 | l := len(botCfgs) 391 | if l == 0 { 392 | return "", "", "" 393 | } 394 | if l == 1 { 395 | token = botCfgs[0].AccessToken 396 | if token == "" { 397 | token = cozeApiChatCfg.AccessToken 398 | } 399 | return botCfgs[0].BotId, botCfgs[0].User, token 400 | } 401 | 402 | rand.Seed(time.Now().UnixNano()) 403 | index := rand.Intn(l) 404 | botCfg := botCfgs[index] 405 | token = botCfg.AccessToken 406 | if token == "" { 407 | token = cozeApiChatCfg.AccessToken 408 | } 409 | 410 | return botCfg.BotId, botCfg.User, token 411 | } 412 | -------------------------------------------------------------------------------- /internal/coze/discord/discord.go: -------------------------------------------------------------------------------- 1 | package discord 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/base64" 7 | "errors" 8 | "math/rand" 9 | "net/http" 10 | "net/url" 11 | "regexp" 12 | "strings" 13 | "time" 14 | 15 | "github.com/bwmarrin/discordgo" 16 | "github.com/h2non/filetype" 17 | "github.com/zatxm/any-proxy/internal/config" 18 | "github.com/zatxm/any-proxy/internal/types" 19 | "github.com/zatxm/any-proxy/pkg/support" 20 | "github.com/zatxm/fhblade" 21 | "go.uber.org/zap" 22 | ) 23 | 24 | var ( 25 | Session *discordgo.Session 26 | ReplyStopChans = make(map[string]chan ChannelStopChan) 27 | RepliesChans = make(map[string]chan ReplyResp) 28 | RepliesOpenAIChans = make(map[string]chan types.ChatCompletionResponse) 29 | RepliesOpenAIImageChans = make(map[string]chan types.ImagesGenerationResponse) 30 | 31 | CozeDailyLimitError = "You have exceeded the daily limit for sending messages to the bot. Please try again later." 32 | ) 33 | 34 | func Parse(ctx context.Context) { 35 | cozeCfg := config.V().Coze.Discord 36 | token := cozeCfg.ChatBotToken 37 | var err error 38 | Session, err = discordgo.New("Bot " + token) 39 | if err != nil { 40 | fhblade.Log.Error("error creating Discord session", zap.Error(err)) 41 | return 42 | } 43 | 44 | // 处理代理 45 | proxyUrl := config.CozeProxyUrl() 46 | if proxyUrl != "" { 47 | proxyUrlParse, err := url.Parse(proxyUrl) 48 | if err != nil { 49 | fhblade.Log.Error("coze discord proxy url err", zap.Error(err)) 50 | return 51 | } 52 | Session.Client = &http.Client{ 53 | Transport: &http.Transport{ 54 | Proxy: http.ProxyURL(proxyUrlParse), 55 | }, 56 | } 57 | Session.Dialer.Proxy = http.ProxyURL(proxyUrlParse) 58 | } 59 | // 注册消息处理函数 60 | Session.AddHandler(messageCreate) 61 | Session.AddHandler(messageUpdate) 62 | 63 | // 打开websocket连接并开始监听 64 | err = Session.Open() 65 | if err != nil { 66 | fhblade.Log.Error("coze discord opening connection err", zap.Error(err)) 67 | return 68 | } 69 | 70 | fhblade.Log.Debug("Discord bot is now running") 71 | 72 | // 活跃机器人 73 | NewLiveDiscordBot() 74 | 75 | go func() { 76 | <-ctx.Done() 77 | if err := Session.Close(); err != nil { 78 | fhblade.Log.Error("Discord close session err", zap.Error(err)) 79 | } 80 | }() 81 | } 82 | 83 | func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { 84 | if m.ReferencedMessage == nil { 85 | return 86 | } 87 | stopChan, ok := ReplyStopChans[m.ReferencedMessage.ID] 88 | if !ok { 89 | return 90 | } 91 | 92 | // 如果作者为nil或消息来自bot本身,则发送停止信号 93 | if m.Author == nil || m.Author.ID == s.State.User.ID { 94 | stopChan <- ChannelStopChan{ 95 | Id: m.ChannelID, 96 | } 97 | return 98 | } 99 | 100 | replyChan, ok := RepliesChans[m.ReferencedMessage.ID] 101 | if ok { 102 | reply := dealMessageCreate(m) 103 | replyChan <- reply 104 | } else { 105 | replyOpenAIChan, ok := RepliesOpenAIChans[m.ReferencedMessage.ID] 106 | if ok { 107 | reply := dealOpenAIMessageCreate(m) 108 | replyOpenAIChan <- reply 109 | } else { 110 | replyOpenAIImageChan, ok := RepliesOpenAIImageChans[m.ReferencedMessage.ID] 111 | if ok { 112 | reply := dealOpenAIImageMessageCreate(m) 113 | replyOpenAIImageChan <- reply 114 | } else { 115 | return 116 | } 117 | } 118 | } 119 | 120 | // 如果消息包含组件或嵌入,则发送停止信号 121 | if len(m.Message.Components) > 0 { 122 | replyOpenAIChan, ok := RepliesOpenAIChans[m.ReferencedMessage.ID] 123 | if ok { 124 | reply := dealOpenAIMessageCreate(m) 125 | reply.Choices[0].FinishReason = "stop" 126 | replyOpenAIChan <- reply 127 | } 128 | 129 | stopChan <- ChannelStopChan{ 130 | Id: m.ChannelID, 131 | } 132 | } 133 | 134 | return 135 | } 136 | 137 | func messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { 138 | if m.ReferencedMessage == nil { 139 | return 140 | } 141 | stopChan, ok := ReplyStopChans[m.ReferencedMessage.ID] 142 | if !ok { 143 | return 144 | } 145 | 146 | // 如果作者为nil或消息来自bot本身,则发送停止信号 147 | if m.Author == nil || m.Author.ID == s.State.User.ID { 148 | stopChan <- ChannelStopChan{ 149 | Id: m.ChannelID, 150 | } 151 | return 152 | } 153 | 154 | replyChan, ok := RepliesChans[m.ReferencedMessage.ID] 155 | if ok { 156 | reply := dealMessageUpdate(m) 157 | replyChan <- reply 158 | } else { 159 | replyOpenAIChan, ok := RepliesOpenAIChans[m.ReferencedMessage.ID] 160 | if ok { 161 | reply := dealOpenAIMessageUpdate(m) 162 | replyOpenAIChan <- reply 163 | } else { 164 | replyOpenAIImageChan, ok := RepliesOpenAIImageChans[m.ReferencedMessage.ID] 165 | if ok { 166 | reply := dealOpenAIImageMessageUpdate(m) 167 | replyOpenAIImageChan <- reply 168 | } else { 169 | return 170 | } 171 | } 172 | } 173 | 174 | // 如果消息包含组件或嵌入,则发送停止信号 175 | if len(m.Message.Components) > 0 { 176 | replyOpenAIChan, ok := RepliesOpenAIChans[m.ReferencedMessage.ID] 177 | if ok { 178 | reply := dealOpenAIMessageUpdate(m) 179 | reply.Choices[0].FinishReason = "stop" 180 | replyOpenAIChan <- reply 181 | } 182 | 183 | stopChan <- ChannelStopChan{ 184 | Id: m.ChannelID, 185 | } 186 | } 187 | 188 | return 189 | } 190 | 191 | func dealMessageCreate(m *discordgo.MessageCreate) ReplyResp { 192 | var embedUrls []string 193 | for k := range m.Embeds { 194 | embed := m.Embeds[k] 195 | if embed.Image != nil { 196 | embedUrls = append(embedUrls, embed.Image.URL) 197 | } 198 | } 199 | return ReplyResp{ 200 | Content: m.Content, 201 | EmbedUrls: embedUrls, 202 | } 203 | } 204 | 205 | func dealOpenAIMessageCreate(m *discordgo.MessageCreate) types.ChatCompletionResponse { 206 | if len(m.Embeds) > 0 { 207 | for k := range m.Embeds { 208 | embed := m.Embeds[k] 209 | if embed.Image != nil && !strings.Contains(m.Content, embed.Image.URL) { 210 | var mc strings.Builder 211 | mc.WriteString(m.Content) 212 | if m.Content != "" { 213 | mc.WriteString("\n") 214 | } 215 | mc.WriteString(embed.Image.URL) 216 | mc.WriteString("\n![Image](") 217 | mc.WriteString(embed.Image.URL) 218 | mc.WriteString(")") 219 | m.Content = mc.String() 220 | } 221 | } 222 | } 223 | 224 | var choices []*types.ChatCompletionChoice 225 | choices = append(choices, &types.ChatCompletionChoice{ 226 | Index: 0, 227 | Message: &types.ChatCompletionMessage{ 228 | Role: "assistant", 229 | Content: m.Content, 230 | }, 231 | }) 232 | return types.ChatCompletionResponse{ 233 | ID: m.ID, 234 | Choices: choices, 235 | Created: time.Now().Unix(), 236 | Model: "coze-discord", 237 | Object: "chat.completion.chunk", 238 | } 239 | } 240 | 241 | func dealOpenAIImageMessageCreate(m *discordgo.MessageCreate) types.ImagesGenerationResponse { 242 | var response types.ImagesGenerationResponse 243 | 244 | if CozeDailyLimitError == m.Content { 245 | return types.ImagesGenerationResponse{ 246 | Created: time.Now().Unix(), 247 | Data: response.Data, 248 | DailyLimit: true, 249 | } 250 | } 251 | 252 | re := regexp.MustCompile(`]\((https?://\S+)\)`) 253 | submatches := re.FindAllStringSubmatch(m.Content, -1) 254 | for k := range submatches { 255 | match := submatches[k] 256 | response.Data = append(response.Data, &types.ChatMessageImageURL{URL: match[1]}) 257 | } 258 | if len(m.Embeds) > 0 { 259 | for k := range m.Embeds { 260 | embed := m.Embeds[k] 261 | if embed.Image != nil && !strings.Contains(m.Content, embed.Image.URL) { 262 | if m.Content != "" { 263 | m.Content += "\n" 264 | } 265 | response.Data = append(response.Data, &types.ChatMessageImageURL{URL: embed.Image.URL}) 266 | } 267 | } 268 | } 269 | return types.ImagesGenerationResponse{ 270 | Created: time.Now().Unix(), 271 | Data: response.Data, 272 | } 273 | } 274 | 275 | func dealMessageUpdate(m *discordgo.MessageUpdate) ReplyResp { 276 | var embedUrls []string 277 | for k := range m.Embeds { 278 | embed := m.Embeds[k] 279 | if embed.Image != nil { 280 | embedUrls = append(embedUrls, embed.Image.URL) 281 | } 282 | } 283 | return ReplyResp{ 284 | Content: m.Content, 285 | EmbedUrls: embedUrls, 286 | } 287 | } 288 | 289 | func dealOpenAIMessageUpdate(m *discordgo.MessageUpdate) types.ChatCompletionResponse { 290 | if len(m.Embeds) > 0 { 291 | for k := range m.Embeds { 292 | embed := m.Embeds[k] 293 | if embed.Image != nil && !strings.Contains(m.Content, embed.Image.URL) { 294 | var mc strings.Builder 295 | mc.WriteString(m.Content) 296 | if m.Content != "" { 297 | mc.WriteString("\n") 298 | } 299 | mc.WriteString(embed.Image.URL) 300 | mc.WriteString("\n![Image](") 301 | mc.WriteString(embed.Image.URL) 302 | mc.WriteString(")") 303 | m.Content = mc.String() 304 | } 305 | } 306 | } 307 | 308 | var choices []*types.ChatCompletionChoice 309 | choices = append(choices, &types.ChatCompletionChoice{ 310 | Index: 0, 311 | Message: &types.ChatCompletionMessage{ 312 | Role: "assistant", 313 | Content: m.Content, 314 | }, 315 | }) 316 | return types.ChatCompletionResponse{ 317 | ID: m.ID, 318 | Choices: choices, 319 | Created: time.Now().Unix(), 320 | Model: "coze-discord", 321 | Object: "chat.completion.chunk", 322 | } 323 | } 324 | 325 | func dealOpenAIImageMessageUpdate(m *discordgo.MessageUpdate) types.ImagesGenerationResponse { 326 | var response types.ImagesGenerationResponse 327 | 328 | if CozeDailyLimitError == m.Content { 329 | return types.ImagesGenerationResponse{ 330 | Created: time.Now().Unix(), 331 | Data: response.Data, 332 | DailyLimit: true, 333 | } 334 | } 335 | 336 | re := regexp.MustCompile(`]\((https?://\S+)\)`) 337 | submatches := re.FindAllStringSubmatch(m.Content, -1) 338 | for k := range submatches { 339 | match := submatches[k] 340 | response.Data = append(response.Data, &types.ChatMessageImageURL{URL: match[1]}) 341 | } 342 | if len(m.Embeds) > 0 { 343 | for k := range m.Embeds { 344 | embed := m.Embeds[k] 345 | if embed.Image != nil && !strings.Contains(m.Content, embed.Image.URL) { 346 | if m.Content != "" { 347 | m.Content += "\n" 348 | } 349 | response.Data = append(response.Data, &types.ChatMessageImageURL{URL: embed.Image.URL}) 350 | } 351 | } 352 | } 353 | return types.ImagesGenerationResponse{ 354 | Created: time.Now().Unix(), 355 | Data: response.Data, 356 | } 357 | } 358 | 359 | func SendMessage(message, cozeBotId string) (*discordgo.Message, error) { 360 | if Session == nil { 361 | fhblade.Log.Error("Discord session is not parse") 362 | return nil, errors.New("Discord session is not parsed") 363 | } 364 | if cozeBotId == "" { 365 | cozeBotId = getRandomCozeBotId() 366 | if cozeBotId == "" { 367 | return nil, errors.New("coze bot error or not config") 368 | } 369 | } 370 | 371 | var contentBuilder strings.Builder 372 | contentBuilder.WriteString(message) 373 | contentBuilder.WriteString(" \n <@") 374 | contentBuilder.WriteString(cozeBotId) 375 | contentBuilder.WriteString(">") 376 | content := contentBuilder.String() 377 | content = strings.Replace(content, `\u0026`, "&", -1) 378 | content = strings.Replace(content, `\u003c`, "<", -1) 379 | content = strings.Replace(content, `\u003e`, ">", -1) 380 | 381 | if len([]rune(content)) > 1888 { 382 | fhblade.Log.Error("Discord prompt over max 1888", zap.String("Data", content)) 383 | return nil, errors.New("Discord prompt over max 1888") 384 | } 385 | 386 | content = strings.ReplaceAll(content, "\\n", "\n") 387 | sentMsgId, err := SendMsg(content) 388 | if err != nil { 389 | fhblade.Log.Error("Discord sending message error", zap.Error(err)) 390 | return nil, errors.New("Discord sending message error") 391 | } 392 | 393 | return &discordgo.Message{ID: sentMsgId}, nil 394 | } 395 | 396 | func UploadToDiscordURL(base64Data string) (string, error) { 397 | dataParts := strings.Split(base64Data, ";base64,") 398 | if len(dataParts) != 2 { 399 | return "", errors.New("Img base64 data error") 400 | } 401 | base64Data = dataParts[1] 402 | data, err := base64.StdEncoding.DecodeString(base64Data) 403 | if err != nil { 404 | return "", err 405 | } 406 | file := bytes.NewReader(data) 407 | kind, err := filetype.Match(data) 408 | if err != nil { 409 | return "", errors.New("Img type error") 410 | } 411 | 412 | // 上传图片、发送信息 413 | var imgNameBuilder strings.Builder 414 | imgNameBuilder.WriteString("image-") 415 | imgNameBuilder.WriteString(support.TimeString()) 416 | imgNameBuilder.WriteString(".") 417 | imgNameBuilder.WriteString(kind.Extension) 418 | imgName := imgNameBuilder.String() 419 | req := &discordgo.MessageSend{ 420 | Files: []*discordgo.File{ 421 | { 422 | Name: imgName, 423 | Reader: file, 424 | }, 425 | }, 426 | } 427 | message, err := Session.ChannelMessageSendComplex(config.V().Coze.Discord.ChannelId, req) 428 | if err != nil { 429 | return "", err 430 | } 431 | if len(message.Attachments) > 0 { 432 | return message.Attachments[0].URL, nil 433 | } 434 | return "", errors.New("Attachment not found") 435 | } 436 | 437 | // 随机获取设置的coze bot id 438 | func getRandomCozeBotId() string { 439 | cozeBots := config.V().Coze.Discord.CozeBot 440 | l := len(cozeBots) 441 | if l == 0 { 442 | return "" 443 | } 444 | if l == 1 { 445 | return cozeBots[0] 446 | } 447 | 448 | rand.Seed(time.Now().UnixNano()) 449 | index := rand.Intn(l) 450 | return cozeBots[index] 451 | } 452 | -------------------------------------------------------------------------------- /internal/openai/arkose/funcaptcha/funcaptcha.go: -------------------------------------------------------------------------------- 1 | package funcaptcha 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math/rand" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | http "github.com/bogdanfinn/fhttp" 12 | "github.com/zatxm/any-proxy/internal/client" 13 | "github.com/zatxm/any-proxy/internal/config" 14 | "github.com/zatxm/any-proxy/pkg/jscrypt" 15 | "github.com/zatxm/any-proxy/pkg/support" 16 | "github.com/zatxm/fhblade" 17 | tlsClient "github.com/zatxm/tls-client" 18 | "go.uber.org/zap" 19 | ) 20 | 21 | var ( 22 | Bv = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" 23 | ArkosePicPath = "/home/zatxm/web/go/work/src/gpt-proxy/pics/" 24 | ArkoseHeaders = http.Header{ 25 | "Accept": []string{"*/*"}, 26 | "Accept-Encoding": []string{"gzip, deflate, br"}, 27 | "Accept-Language": []string{"en-US,en;q=0.5"}, 28 | "Cache-Control": []string{"no-cache"}, 29 | "Connection": []string{"keep-alive"}, 30 | "Content-Type": []string{"application/x-www-form-urlencoded; charset=UTF-8"}, 31 | "Host": []string{"client-api.arkoselabs.com"}, 32 | "Origin": []string{"https://client-api.arkoselabs.com"}, 33 | "User-Agent": []string{Bv}, 34 | "X-Requested-With": []string{"XMLHttpRequest"}, 35 | } 36 | Yz = map[int]struct { 37 | Value map[string]ValueFunc 38 | Key map[string]KeyFunc 39 | }{ 40 | 4: { 41 | Value: map[string]ValueFunc{ 42 | "alpha": func(c AnswerInput) AnswerInput { 43 | yValueStr := strconv.Itoa(c.Index) // 转换为字符串 44 | combinedStr := yValueStr + strconv.Itoa(1) // 加1 45 | combinedInt, _ := strconv.Atoi(combinedStr) // 将合并后的字符串转回为整数 46 | return AnswerInput{Index: combinedInt - 2} 47 | }, 48 | "beta": func(c AnswerInput) AnswerInput { return AnswerInput{Index: -c.Index} }, 49 | "gamma": func(c AnswerInput) AnswerInput { return AnswerInput{Index: 3 * (3 - c.Index)} }, 50 | "delta": func(c AnswerInput) AnswerInput { return AnswerInput{Index: 7 * c.Index} }, 51 | "epsilon": func(c AnswerInput) AnswerInput { return AnswerInput{Index: 2 * c.Index} }, 52 | "zeta": func(c AnswerInput) AnswerInput { 53 | if c.Index != 0 { 54 | return AnswerInput{Index: 100 / c.Index} 55 | } 56 | return AnswerInput{Index: c.Index} 57 | }, 58 | }, 59 | Key: map[string]KeyFunc{ 60 | "alpha": func(c AnswerInput) interface{} { 61 | return []int{rand.Intn(100), c.Index, rand.Intn(100)} 62 | }, 63 | "beta": func(c AnswerInput) interface{} { 64 | return map[string]int{ 65 | "size": 50 - c.Index, 66 | "id": c.Index, 67 | "limit": 10 * c.Index, 68 | "req_timestamp": int(time.Now().UnixNano() / int64(time.Millisecond)), 69 | } 70 | }, 71 | "gamma": func(c AnswerInput) interface{} { 72 | return c.Index 73 | }, 74 | "delta": func(c AnswerInput) interface{} { 75 | return map[string]int{"index": c.Index} 76 | }, 77 | "epsilon": func(c AnswerInput) interface{} { 78 | arr := make([]int, rand.Intn(5)+1) 79 | randIndex := rand.Intn(len(arr)) 80 | for i := range arr { 81 | if i == randIndex { 82 | arr[i] = c.Index 83 | } else { 84 | arr[i] = rand.Intn(10) 85 | } 86 | } 87 | return append(arr, randIndex) 88 | }, 89 | "zeta": func(c AnswerInput) interface{} { 90 | return append(make([]int, rand.Intn(5)+1), c.Index) 91 | }, 92 | }, 93 | }, 94 | } 95 | ) 96 | 97 | type AnswerInput struct { 98 | Index int 99 | } 100 | type ValueFunc func(AnswerInput) AnswerInput 101 | type KeyFunc func(AnswerInput) interface{} 102 | 103 | type ArkoseSession struct { 104 | Sid string `json:"sid"` 105 | SessionToken string `json:"session_token"` 106 | Hex string `json:"hex"` 107 | ChallengeLogger ArkoseChallengeLogger `json:"challenge_logger"` 108 | Challenge ArkoseChallenge `json:"challenge"` 109 | ConciseChallenge ArkoseConciseChallenge `json:"concise_challenge"` 110 | Headers http.Header `json:"headers"` 111 | } 112 | 113 | type ArkoseChallengeLogger struct { 114 | Sid string `json:"sid"` 115 | SessionToken string `json:"session_token"` 116 | AnalyticsTier int `json:"analytics_tier"` 117 | RenderType string `json:"render_type"` 118 | Category string `json:"category"` 119 | Action string `json:"action"` 120 | GameToken string `json:"game_token,omitempty"` 121 | GameType string `json:"game_type,omitempty"` 122 | } 123 | 124 | type ArkoseChallenge struct { 125 | SessionToken string `json:"session_token"` 126 | ChallengeID string `json:"challengeID"` 127 | ChallengeURL string `json:"challengeURL"` 128 | AudioChallengeURLs []string `json:"audio_challenge_urls"` 129 | AudioGameRateLimited interface{} `json:"audio_game_rate_limited"` 130 | Sec int `json:"sec"` 131 | EndURL interface{} `json:"end_url"` 132 | GameData struct { 133 | GameType int `json:"gameType"` 134 | GameVariant string `json:"game_variant"` 135 | InstructionString string `json:"instruction_string"` 136 | CustomGUI struct { 137 | ChallengeIMGs []string `json:"_challenge_imgs"` 138 | ApiBreaker *ApiBreaker `json:"api_breaker"` 139 | ApiBreakerV2Enabled int `json:"api_breaker_v2_enabled"` 140 | } `json:"customGUI"` 141 | } `json:"game_data"` 142 | GameSID string `json:"game_sid"` 143 | SID string `json:"sid"` 144 | Lang string `json:"lang"` 145 | StringTablePrefixes []interface{} `json:"string_table_prefixes"` 146 | StringTable map[string]string `json:"string_table"` 147 | EarlyVictoryMessage interface{} `json:"earlyVictoryMessage"` 148 | FontSizeAdjustments interface{} `json:"font_size_adjustments"` 149 | StyleTheme string `json:"style_theme"` 150 | } 151 | 152 | type ArkoseConciseChallenge struct { 153 | GameType string `json:"game_type"` 154 | URLs []string `json:"urls"` 155 | Instructions string `json:"instructions"` 156 | } 157 | 158 | type ApiBreaker struct { 159 | Key string `json:"key"` 160 | Value []string `json:"value"` 161 | } 162 | 163 | type ArkoseRequestChallenge struct { 164 | Sid string `json:"sid"` 165 | Token string `json:"token"` 166 | AnalyticsTier int `json:"analytics_tier"` 167 | RenderType string `json:"render_type"` 168 | Lang string `json:"lang"` 169 | IsAudioGame bool `json:"isAudioGame"` 170 | APIBreakerVersion string `json:"apiBreakerVersion"` 171 | } 172 | 173 | type SubmitArkoseChallenge struct { 174 | SessionToken string `json:"session_token"` 175 | Sid string `json:"sid"` 176 | GameToken string `json:"game_token"` 177 | Guess string `json:"guess"` 178 | RenderType string `json:"render_type"` 179 | AnalyticsTier int `json:"analytics_tier"` 180 | Bio string `json:"bio"` 181 | } 182 | 183 | func (a *ArkoseSession) GoArkoseChallenge(isAudioGame bool) (*ApiBreaker, error) { 184 | arkoseRequestChallenge := ArkoseRequestChallenge{ 185 | Sid: a.Sid, 186 | Token: a.SessionToken, 187 | AnalyticsTier: 40, 188 | RenderType: "canvas", 189 | Lang: "en-us", 190 | IsAudioGame: isAudioGame, 191 | APIBreakerVersion: "green", 192 | } 193 | 194 | payload := support.StructToFormByJson(arkoseRequestChallenge) 195 | req, _ := http.NewRequest(http.MethodPost, "https://client-api.arkoselabs.com/fc/gfct/", strings.NewReader(payload)) 196 | req.Header = a.Headers 197 | req.Header.Set("X-NewRelic-Timestamp", support.TimeStamp()) 198 | gClient := client.CPool.Get().(tlsClient.HttpClient) 199 | resp, err := gClient.Do(req) 200 | if err != nil { 201 | client.CPool.Put(gClient) 202 | fhblade.Log.Error("go arkose challenge req error", zap.Error(err)) 203 | return nil, err 204 | } 205 | defer resp.Body.Close() 206 | client.CPool.Put(gClient) 207 | if resp.StatusCode != 200 { 208 | return nil, fmt.Errorf("fc/gfct Status code %d", resp.StatusCode) 209 | } 210 | var challenge ArkoseChallenge 211 | err = fhblade.Json.NewDecoder(resp.Body).Decode(&challenge) 212 | if err != nil { 213 | fhblade.Log.Error("go arkose challenge res error", zap.Error(err)) 214 | return nil, err 215 | } 216 | a.Challenge = challenge 217 | a.Loged(challenge.ChallengeID, challenge.GameData.GameType, "loaded", "game loaded") 218 | 219 | // Build concise challenge 220 | var challengeType string 221 | var challengeUrls []string 222 | var key string 223 | var apiBreaker *ApiBreaker 224 | switch challenge.GameData.GameType { 225 | case 4: 226 | challengeType = "image" 227 | challengeUrls = challenge.GameData.CustomGUI.ChallengeIMGs 228 | instructionStr := challenge.GameData.InstructionString 229 | key = "4.instructions-" + instructionStr 230 | if challenge.GameData.CustomGUI.ApiBreakerV2Enabled == 1 { 231 | apiBreaker = challenge.GameData.CustomGUI.ApiBreaker 232 | } 233 | case 101: 234 | challengeType = "audio" 235 | challengeUrls = challenge.AudioChallengeURLs 236 | instructionStr := challenge.GameData.GameVariant 237 | key = "audio_game.instructions-" + instructionStr 238 | 239 | default: 240 | challengeType = "unknown" 241 | challengeUrls = []string{} 242 | } 243 | 244 | a.ConciseChallenge = ArkoseConciseChallenge{ 245 | GameType: challengeType, 246 | URLs: challengeUrls, 247 | Instructions: strings.ReplaceAll(strings.ReplaceAll(challenge.StringTable[key], "", ""), "", ""), 248 | } 249 | return apiBreaker, err 250 | } 251 | 252 | func (a *ArkoseSession) SubmitAnswer(indices []int, isAudio bool, apiBreaker *ApiBreaker) error { 253 | submission := SubmitArkoseChallenge{ 254 | SessionToken: a.SessionToken, 255 | Sid: a.Sid, 256 | GameToken: a.Challenge.ChallengeID, 257 | RenderType: "canvas", 258 | AnalyticsTier: 40, 259 | Bio: "eyJtYmlvIjoiMTUwLDAsMTE3LDIzOTszMDAsMCwxMjEsMjIxOzMxNywwLDEyNCwyMTY7NTUwLDAsMTI5LDIxMDs1NjcsMCwxMzQsMjA3OzYxNywwLDE0NCwyMDU7NjUwLDAsMTU1LDIwNTs2NjcsMCwxNjUsMjA1OzY4NCwwLDE3MywyMDc7NzAwLDAsMTc4LDIxMjs4MzQsMCwyMjEsMjI4OzI2MDY3LDAsMTkzLDM1MTsyNjEwMSwwLDE4NSwzNTM7MjYxMDEsMCwxODAsMzU3OzI2MTM0LDAsMTcyLDM2MTsyNjE4NCwwLDE2NywzNjM7MjYyMTcsMCwxNjEsMzY1OzI2MzM0LDAsMTU2LDM2NDsyNjM1MSwwLDE1MiwzNTQ7MjYzNjcsMCwxNTIsMzQzOzI2Mzg0LDAsMTUyLDMzMTsyNjQ2NywwLDE1MSwzMjU7MjY0NjcsMCwxNTEsMzE3OzI2NTAxLDAsMTQ5LDMxMTsyNjY4NCwxLDE0NywzMDc7MjY3NTEsMiwxNDcsMzA3OzMwNDUxLDAsMzcsNDM3OzMwNDY4LDAsNTcsNDI0OzMwNDg0LDAsNjYsNDE0OzMwNTAxLDAsODgsMzkwOzMwNTAxLDAsMTA0LDM2OTszMDUxOCwwLDEyMSwzNDk7MzA1MzQsMCwxNDEsMzI0OzMwNTUxLDAsMTQ5LDMxNDszMDU4NCwwLDE1MywzMDQ7MzA2MTgsMCwxNTUsMjk2OzMwNzUxLDAsMTU5LDI4OTszMDc2OCwwLDE2NywyODA7MzA3ODQsMCwxNzcsMjc0OzMwODE4LDAsMTgzLDI3MDszMDg1MSwwLDE5MSwyNzA7MzA4ODQsMCwyMDEsMjY4OzMwOTE4LDAsMjA4LDI2ODszMTIzNCwwLDIwNCwyNjM7MzEyNTEsMCwyMDAsMjU3OzMxMzg0LDAsMTk1LDI1MTszMTQxOCwwLDE4OSwyNDk7MzE1NTEsMSwxODksMjQ5OzMxNjM0LDIsMTg5LDI0OTszMTcxOCwxLDE4OSwyNDk7MzE3ODQsMiwxODksMjQ5OzMxODg0LDEsMTg5LDI0OTszMTk2OCwyLDE4OSwyNDk7MzIyODQsMCwyMDIsMjQ5OzMyMzE4LDAsMjE2LDI0NzszMjMxOCwwLDIzNCwyNDU7MzIzMzQsMCwyNjksMjQ1OzMyMzUxLDAsMzAwLDI0NTszMjM2OCwwLDMzOSwyNDE7MzIzODQsMCwzODgsMjM5OzMyNjE4LDAsMzkwLDI0NzszMjYzNCwwLDM3NCwyNTM7MzI2NTEsMCwzNjUsMjU1OzMyNjY4LDAsMzUzLDI1NzszMjk1MSwxLDM0OCwyNTc7MzMwMDEsMiwzNDgsMjU3OzMzNTY4LDAsMzI4LDI3MjszMzU4NCwwLDMxOSwyNzg7MzM2MDEsMCwzMDcsMjg2OzMzNjUxLDAsMjk1LDI5NjszMzY1MSwwLDI5MSwzMDA7MzM2ODQsMCwyODEsMzA5OzMzNjg0LDAsMjcyLDMxNTszMzcxOCwwLDI2NiwzMTc7MzM3MzQsMCwyNTgsMzIzOzMzNzUxLDAsMjUyLDMyNzszMzc1MSwwLDI0NiwzMzM7MzM3NjgsMCwyNDAsMzM3OzMzNzg0LDAsMjM2LDM0MTszMzgxOCwwLDIyNywzNDc7MzM4MzQsMCwyMjEsMzUzOzM0MDUxLDAsMjE2LDM1NDszNDA2OCwwLDIxMCwzNDg7MzQwODQsMCwyMDQsMzQ0OzM0MTAxLDAsMTk4LDM0MDszNDEzNCwwLDE5NCwzMzY7MzQ1ODQsMSwxOTIsMzM0OzM0NjUxLDIsMTkyLDMzNDsiLCJ0YmlvIjoiIiwia2JpbyI6IiJ9", 260 | } 261 | var answerIndex []string 262 | if isAudio { 263 | for k := range indices { 264 | answerIndex = append(answerIndex, strconv.Itoa(indices[k])) 265 | } 266 | } else { 267 | for k := range indices { 268 | input := AnswerInput{Index: indices[k]} 269 | encoder := yb(4, apiBreaker) 270 | result := encoder(input) 271 | marshal, _ := fhblade.Json.MarshalToString(result) 272 | answerIndex = append(answerIndex, marshal) 273 | } 274 | } 275 | answer := "[" + strings.Join(answerIndex, ",") + "]" 276 | submission.Guess, _ = jscrypt.Encrypt(answer, a.SessionToken) 277 | payload := support.StructToFormByJson(submission) 278 | req, _ := http.NewRequest(http.MethodPost, "https://client-api.arkoselabs.com/fc/ca/", strings.NewReader(payload)) 279 | req.Header = a.Headers 280 | req.Header.Set("X-Requested-ID", generateAnswerRequestId(a.SessionToken)) 281 | req.Header.Set("X-NewRelic-Timestamp", support.TimeStamp()) 282 | gClient := client.CPool.Get().(tlsClient.HttpClient) 283 | resp, err := gClient.Do(req) 284 | if err != nil { 285 | client.CPool.Put(gClient) 286 | fhblade.Log.Error("submit answer req error", zap.Error(err)) 287 | return err 288 | } 289 | defer resp.Body.Close() 290 | client.CPool.Put(gClient) 291 | var aRes struct { 292 | Error string `json:"error,omitempty"` 293 | Response string `json:"response"` 294 | Solved bool `json:"solved"` 295 | IncorrectGuess string `json:"incorrect_guess"` 296 | Score bool `json:"score"` 297 | } 298 | err = fhblade.Json.NewDecoder(resp.Body).Decode(&aRes) 299 | if err != nil { 300 | fhblade.Log.Error("submit answer res error", zap.Error(err)) 301 | return err 302 | } 303 | if aRes.Error != "" { 304 | return errors.New(aRes.Error) 305 | } 306 | if !aRes.Solved { 307 | return fmt.Errorf("incorrect guess: %s", aRes.IncorrectGuess) 308 | } 309 | 310 | return nil 311 | } 312 | 313 | func (a *ArkoseSession) Loged(gameToken string, gameType int, category, action string) error { 314 | cl := a.ChallengeLogger 315 | cl.GameToken = gameToken 316 | if gameType != 0 { 317 | cl.GameType = fmt.Sprintf("%d", gameType) 318 | } 319 | cl.Category = category 320 | cl.Action = action 321 | 322 | req, _ := http.NewRequest(http.MethodPost, "https://client-api.arkoselabs.com/fc/a/", strings.NewReader(support.StructToFormByJson(cl))) 323 | req.Header = ArkoseHeaders 324 | gClient := client.CPool.Get().(tlsClient.HttpClient) 325 | resp, err := gClient.Do(req) 326 | if err != nil { 327 | client.CPool.Put(gClient) 328 | fhblade.Log.Error("go arkose challenge loged req error", zap.Error(err)) 329 | return err 330 | } 331 | defer resp.Body.Close() 332 | client.CPool.Put(gClient) 333 | if resp.StatusCode != 200 { 334 | return fmt.Errorf("status code %d", resp.StatusCode) 335 | } 336 | return nil 337 | } 338 | 339 | func NewArkoseSession(sid, sessionToken, hSession string) *ArkoseSession { 340 | a := &ArkoseSession{ 341 | Sid: sid, 342 | SessionToken: sessionToken, 343 | Headers: ArkoseHeaders} 344 | a.Headers.Set("Referer", "https://client-api.arkoselabs.com/fc/assets/ec-game-core/game-core/"+config.V().Arkose.GameCoreVersion+"/standard/index.html?session="+hSession) 345 | a.ChallengeLogger = ArkoseChallengeLogger{ 346 | Sid: sid, 347 | SessionToken: sessionToken, 348 | AnalyticsTier: 40, 349 | RenderType: "canvas", 350 | } 351 | return a 352 | } 353 | 354 | func yb(gameType int, apiBreaker *ApiBreaker) func(AnswerInput) interface{} { 355 | return func(input AnswerInput) interface{} { 356 | for _, valueFuncName := range apiBreaker.Value { 357 | input = Yz[gameType].Value[valueFuncName](input) 358 | } 359 | return Yz[gameType].Key[apiBreaker.Key](input) 360 | } 361 | } 362 | 363 | func generateAnswerRequestId(sessionId string) string { 364 | pwd := fmt.Sprintf("REQUESTED%sID", sessionId) 365 | r, _ := jscrypt.Encrypt(`{"sc":[147,307]}`, pwd) 366 | return r 367 | } 368 | -------------------------------------------------------------------------------- /internal/types/openai.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/zatxm/fhblade" 7 | "github.com/zatxm/fhblade/tools" 8 | ) 9 | 10 | var ( 11 | ErrContentFieldsMisused = errors.New("can't use both Content and MultiContent properties simultaneously") 12 | ) 13 | 14 | type ChatCompletionRequest struct { 15 | Messages []*ChatCompletionMessage `json:"messages" binding:"required"` 16 | Model string `json:"model" binding:"required"` 17 | FrequencyPenalty float64 `json:"frequency_penalty,omitempty"` 18 | LogitBias map[string]int `json:"logit_bias,omitempty"` 19 | Logprobs bool `json:"logprobs,omitempty"` 20 | TopLogprobs int `json:"top_logprobs,omitempty"` 21 | MaxTokens int `json:"max_tokens,omitempty"` 22 | N int `json:"n,omitempty"` 23 | PresencePenalty float64 `json:"presence_penalty,omitempty"` 24 | ResponseFormat *ChatCompletionResponseFormat `json:"response_format,omitempty"` 25 | Seed int `json:"seed,omitempty"` 26 | Stop []string `json:"stop,omitempty"` 27 | Stream bool `json:"stream,omitempty"` 28 | Temperature float64 `json:"temperature,omitempty"` 29 | TopP float64 `json:"top_p,omitempty"` 30 | Tools []Tool `json:"tools,omitempty"` 31 | ToolChoice any `json:"tool_choice,omitempty"` 32 | User string `json:"user,omitempty"` 33 | StreamOptions *StreamOptions `json:"stream_options,omitempty"` 34 | Provider string `json:"provider,omitempty"` 35 | Gemini *GeminiCompletionRequest `json:"gemini,omitempty"` 36 | OpenAi *OpenAiCompletionRequest `json:"openai,omitempty"` 37 | Bing *BingCompletionRequest `json:"bing,omitempty"` 38 | Coze *CozeCompletionRequest `json:"coze,omitempty"` 39 | Claude *ClaudeCompletionRequest `json:"claude,omitempty"` 40 | } 41 | 42 | type ChatCompletionMessage struct { 43 | Role string `json:"role"` 44 | Content string `json:"content"` 45 | MultiContent []*ChatMessagePart 46 | Name string `json:"name,omitempty"` 47 | FunctionCall *FunctionCall `json:"function_call,omitempty"` 48 | ToolCalls []*ToolCall `json:"tool_calls,omitempty"` 49 | ToolCallID string `json:"tool_call_id,omitempty"` 50 | } 51 | 52 | func (m *ChatCompletionMessage) MarshalJSON() ([]byte, error) { 53 | if m.Content != "" && m.MultiContent != nil { 54 | return nil, ErrContentFieldsMisused 55 | } 56 | if len(m.MultiContent) > 0 { 57 | msg := struct { 58 | Role string `json:"role"` 59 | Content string `json:"-"` 60 | MultiContent []*ChatMessagePart `json:"content,omitempty"` 61 | Name string `json:"name,omitempty"` 62 | FunctionCall *FunctionCall `json:"function_call,omitempty"` 63 | ToolCalls []*ToolCall `json:"tool_calls,omitempty"` 64 | ToolCallID string `json:"tool_call_id,omitempty"` 65 | }(*m) 66 | return fhblade.Json.Marshal(msg) 67 | } 68 | msg := struct { 69 | Role string `json:"role"` 70 | Content string `json:"content"` 71 | MultiContent []*ChatMessagePart `json:"-"` 72 | Name string `json:"name,omitempty"` 73 | FunctionCall *FunctionCall `json:"function_call,omitempty"` 74 | ToolCalls []*ToolCall `json:"tool_calls,omitempty"` 75 | ToolCallID string `json:"tool_call_id,omitempty"` 76 | }(*m) 77 | return fhblade.Json.Marshal(msg) 78 | } 79 | 80 | func (m *ChatCompletionMessage) UnmarshalJSON(bs []byte) error { 81 | msg := struct { 82 | Role string `json:"role"` 83 | Content string `json:"content"` 84 | MultiContent []*ChatMessagePart 85 | Name string `json:"name,omitempty"` 86 | FunctionCall *FunctionCall `json:"function_call,omitempty"` 87 | ToolCalls []*ToolCall `json:"tool_calls,omitempty"` 88 | ToolCallID string `json:"tool_call_id,omitempty"` 89 | }{} 90 | if err := fhblade.Json.Unmarshal(bs, &msg); err == nil { 91 | *m = ChatCompletionMessage(msg) 92 | return nil 93 | } 94 | multiMsg := struct { 95 | Role string `json:"role"` 96 | Content string 97 | MultiContent []*ChatMessagePart `json:"content"` 98 | Name string `json:"name,omitempty"` 99 | FunctionCall *FunctionCall `json:"function_call,omitempty"` 100 | ToolCalls []*ToolCall `json:"tool_calls,omitempty"` 101 | ToolCallID string `json:"tool_call_id,omitempty"` 102 | }{} 103 | if err := fhblade.Json.Unmarshal(bs, &multiMsg); err != nil { 104 | return err 105 | } 106 | *m = ChatCompletionMessage(multiMsg) 107 | return nil 108 | } 109 | 110 | type ToolCall struct { 111 | Index *int `json:"index,omitempty"` 112 | ID string `json:"id"` 113 | Type string `json:"type"` 114 | Function FunctionCall `json:"function"` 115 | } 116 | 117 | type FunctionCall struct { 118 | Name string `json:"name,omitempty"` 119 | Arguments string `json:"arguments,omitempty"` 120 | } 121 | 122 | type ChatMessagePart struct { 123 | Type string `json:"type,omitempty"` 124 | Text string `json:"text,omitempty"` 125 | ImageURL *ChatMessageImageURL `json:"image_url,omitempty"` 126 | } 127 | 128 | type ChatMessageImageURL struct { 129 | URL string `json:"url,omitempty"` 130 | Detail string `json:"detail,omitempty"` 131 | } 132 | 133 | type ChatCompletionResponseFormat struct { 134 | Type string `json:"type"` 135 | } 136 | 137 | type Tool struct { 138 | Type string `json:"type"` 139 | Function *FunctionDefinition `json:"function,omitempty"` 140 | } 141 | 142 | type FunctionDefinition struct { 143 | Name string `json:"name"` 144 | Description string `json:"description,omitempty"` 145 | Parameters any `json:"parameters"` 146 | } 147 | 148 | type StreamOptions struct { 149 | IncludeUsage bool `json:"include_usage,omitempty"` 150 | } 151 | 152 | type ErrorResponse struct { 153 | Error *CError `json:"error"` 154 | } 155 | 156 | type CError struct { 157 | Message string `json:"message"` 158 | Type string `json:"type"` 159 | Param string `json:"param"` 160 | Code string `json:"code"` 161 | } 162 | 163 | type ChatCompletionResponse struct { 164 | ID string `json:"id"` 165 | Choices []*ChatCompletionChoice `json:"choices"` 166 | Created int64 `json:"created"` 167 | Model string `json:"model"` 168 | SystemFingerprint string `json:"system_fingerprint"` 169 | Object string `json:"object"` 170 | Usage *Usage `json:"usage,omitempty"` 171 | Gemini *GeminiCompletionResponse `json:"gemini,omitempty"` 172 | OpenAi *OpenAiConversation `json:"openai,omitempty"` 173 | Bing *BingConversation `json:"bing,omitempty"` 174 | Coze *CozeConversation `json:"coze,omitempty"` 175 | Claude *ClaudeCompletionResponse `json:"claude,omitempty"` 176 | } 177 | 178 | type ChatCompletionChoice struct { 179 | Index int `json:"index"` 180 | Message *ChatCompletionMessage `json:"message,omitempty"` 181 | LogProbs *LogProbs `json:"logprobs"` 182 | FinishReason string `json:"finish_reason"` 183 | Delta *ChatCompletionMessage `json:"delta,omitempty"` 184 | } 185 | 186 | type LogProbs struct { 187 | // Content is a list of message content tokens with log probability information. 188 | Content []LogProb `json:"content"` 189 | } 190 | 191 | type LogProb struct { 192 | Token string `json:"token"` 193 | LogProb float64 `json:"logprob"` 194 | Bytes []byte `json:"bytes,omitempty"` // Omitting the field if it is null 195 | TopLogProbs []TopLogProbs `json:"top_logprobs"` 196 | } 197 | 198 | type TopLogProbs struct { 199 | Token string `json:"token"` 200 | LogProb float64 `json:"logprob"` 201 | Bytes []byte `json:"bytes,omitempty"` 202 | } 203 | 204 | type Usage struct { 205 | PromptTokens int `json:"prompt_tokens"` 206 | CompletionTokens int `json:"completion_tokens"` 207 | TotalTokens int `json:"total_tokens"` 208 | } 209 | 210 | type ImagesGenerationRequest struct { 211 | Model string `json:"model"` 212 | Prompt string `json:"prompt"` 213 | } 214 | 215 | type ImagesGenerationResponse struct { 216 | Created int64 `json:"created"` 217 | Data []*ChatMessageImageURL `json:"data"` 218 | DailyLimit bool `json:"dailyLimit,omitempty"` 219 | } 220 | 221 | type OpenAiCompletionRequest struct { 222 | Conversation *OpenAiConversation `json:"conversation,omitempty"` 223 | MessageId string `json:"message_id,omitempty"` 224 | ArkoseToken string `json:"arkose_token,omitempty"` 225 | } 226 | 227 | type OpenAiConversation struct { 228 | ID string `json:"conversation_id"` 229 | Index string `json:"index,omitempty"` 230 | ParentMessageId string `json:"parent_message_id"` 231 | LastMessageId string `json:"last_message_id"` 232 | } 233 | 234 | // open web聊天请求数据 235 | type OpenAiCompletionChatRequest struct { 236 | Action string `json:"action,omitempty"` 237 | ConversationId string `json:"conversation_id,omitempty"` 238 | Messages []*OpenAiMessage `json:"messages" binding:"required"` 239 | ParentMessageId string `json:"parent_message_id" binding:"required"` 240 | Model string `json:"model" binding:"required"` 241 | TimezoneOffsetMin float64 `json:"timezone_offset_min,omitempty"` 242 | Suggestions []string `json:"suggestions"` 243 | HistoryAndTrainingDisabled bool `json:"history_and_training_disabled"` 244 | ConversationMode map[string]string `json:"conversation_mode"` 245 | ForceParagen bool `json:"force_paragen"` 246 | ForceParagenModelSlug string `json:"force_paragen_model_slug"` 247 | ForceNulligen bool `json:"force_nulligen"` 248 | ForceRateLimit bool `json:"force_rate_limit"` 249 | WebsocketRequestId string `json:"websocket_request_id"` 250 | ArkoseToken string `json:"arkose_token,omitempty"` 251 | } 252 | 253 | type OpenAiMessage struct { 254 | ID string `json:"id" binding:"required"` 255 | Author *OpenAiAuthor `json:"author" binding:"required"` 256 | Content *OpenAiContent `json:"content" binding:"required"` 257 | } 258 | 259 | type OpenAiAuthor struct { 260 | Role string `json:"role"` 261 | Name any `json:"name,omitempty"` 262 | Metadata *OpenAiMetadata `json:"metadata,omitempty"` 263 | } 264 | 265 | type OpenAiContent struct { 266 | ContentType string `json:"content_type" binding:"required"` 267 | Parts []string `json:"parts" binding:"required"` 268 | } 269 | 270 | type OpenAiMetadata struct { 271 | RequestId string `json:"request_id,omitempty"` 272 | Timestamp string `json:"timestamp_,omitempty"` 273 | FinishDetails *FinishDetails `json:"finish_details,omitempty"` 274 | Citations []*Citation `json:"citations,omitempty"` 275 | GizmoId any `json:"gizmo_id,omitempty"` 276 | IsComplete bool `json:"is_complete,omitempty"` 277 | AggregateResult *OpenAiAggregateResult `json:"aggregate_result,omitempty"` 278 | MessageType string `json:"message_type,omitempty"` 279 | ModelSlug string `json:"model_slug,omitempty"` 280 | DefaultModelSlug string `json:"default_model_slug,omitempty"` 281 | Pad string `json:"pad,omitempty"` 282 | ParentId string `json:"parent_id,omitempty"` 283 | ModelSwitcherDeny []any `json:"model_switcher_deny,omitempty"` 284 | } 285 | 286 | type OpenAiAggregateResult struct { 287 | Status string `json:"status"` 288 | RunId string `json:"run_id"` 289 | StartTime float64 `json:"start_time"` 290 | UpdateTime float64 `json:"update_time"` 291 | code string `json:"code"` 292 | EndTime any `json:"end_time"` 293 | FinalExpressionOutput any `json:"final_expression_output"` 294 | InKernelException any `json:"in_kernel_exception"` 295 | SystemException any `json:"system_exception"` 296 | Messages []*OpenAiAggregateResultMessage `json:"messages"` 297 | JupyterMessages []*OpenAiAggregateResultJupyterMessage `json:"jupyter_messages"` 298 | TimeoutTriggered any `json:"timeout_triggered"` 299 | } 300 | 301 | type OpenAiAggregateResultMessage struct { 302 | MessageType string `json:"message_type"` 303 | Time float64 `json:"time"` 304 | Sender string `json:"sender"` 305 | ImagePayload any `json:"image_payload"` 306 | ImageUrl string `json:"image_url"` 307 | Width int `json:"width"` 308 | Height int `json:"height"` 309 | } 310 | 311 | type OpenAiAggregateResultJupyterMessage struct { 312 | MsgType string `json:"msg_type"` 313 | ParentHeader *JupyterMessageParentHeader `json:"parent_header"` 314 | } 315 | 316 | type JupyterMessageParentHeader struct { 317 | MsgId string `json:"msg_id"` 318 | Version string `json:"version"` 319 | Content *JupyterMessageContent `json:"content,omitempty"` 320 | } 321 | 322 | type JupyterMessageContent struct { 323 | ExecutionState string `json:"execution_state,omitempty"` 324 | Data map[string]any `json:"JupyterMessageContentData,omitempty"` 325 | } 326 | 327 | type FinishDetails struct { 328 | MType string `json:"type"` 329 | StopTokens []int `json:"stop_tokens"` 330 | } 331 | 332 | type Citation struct { 333 | Metadata CitaMeta `json:"metadata"` 334 | StartIx int `json:"start_ix"` 335 | EndIx int `json:"end_ix"` 336 | } 337 | type CitaMeta struct { 338 | URL string `json:"url"` 339 | Title string `json:"title"` 340 | } 341 | 342 | type OpenAiCompletionChatResponse struct { 343 | Message *OpenAiMessageResponse `json:"message"` 344 | ConversationID string `json:"conversation_id"` 345 | Error any `json:"error"` 346 | } 347 | 348 | type OpenAiMessageResponse struct { 349 | ID string `json:"id"` 350 | Author *OpenAiAuthor `json:"author"` 351 | CreateTime float64 `json:"create_time"` 352 | UpdateTime any `json:"update_time"` 353 | Content *OpenAiContent `json:"content"` 354 | Status string `json:"status"` 355 | EndTurn any `json:"end_turn"` 356 | Weight float64 `json:"weight"` 357 | Metadata *OpenAiMetadata `json:"metadata"` 358 | Recipient string `json:"recipient"` 359 | } 360 | 361 | type RequirementsTokenResponse struct { 362 | Persona string `json:"persona"` 363 | Arkose *RequirementArkose `json:"arkose"` 364 | Turnstile *RequirementTurnstile `json:"turnstile"` 365 | Proofofwork *RequirementProofOfWork `json:"proofofwork"` 366 | Token string `json:"token"` 367 | } 368 | 369 | type RequirementArkose struct { 370 | Required bool `json:"required"` 371 | DX string `json:"dx,omitempty"` 372 | } 373 | 374 | type RequirementTurnstile struct { 375 | Required bool `json:"required"` 376 | } 377 | 378 | type RequirementProofOfWork struct { 379 | Required bool `json:"required"` 380 | Difficulty string `json:"difficulty,omitempty"` 381 | Seed string `json:"seed,omitempty"` 382 | } 383 | 384 | func (c *ChatCompletionRequest) ParsePromptText() string { 385 | prompt := "" 386 | for k := range c.Messages { 387 | message := c.Messages[k] 388 | if message.Role == "user" { 389 | if message.MultiContent == nil { 390 | prompt = message.Content 391 | } 392 | } 393 | } 394 | return prompt 395 | } 396 | 397 | type NullString string 398 | 399 | func (n NullString) MarshalJSON() ([]byte, error) { 400 | if n == "null" || n == "" { 401 | return tools.StringToBytes("null"), nil 402 | } 403 | return tools.StringToBytes(`"` + string(n) + `"`), nil 404 | } 405 | -------------------------------------------------------------------------------- /internal/openai/auth/web.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "encoding/base64" 5 | "errors" 6 | "net/url" 7 | "os" 8 | "regexp" 9 | "strings" 10 | "time" 11 | 12 | http "github.com/bogdanfinn/fhttp" 13 | "github.com/zatxm/any-proxy/internal/client" 14 | "github.com/zatxm/any-proxy/internal/config" 15 | "github.com/zatxm/any-proxy/internal/openai/arkose/solve" 16 | "github.com/zatxm/any-proxy/internal/openai/cst" 17 | "github.com/zatxm/any-proxy/internal/types" 18 | "github.com/zatxm/any-proxy/internal/vars" 19 | "github.com/zatxm/any-proxy/pkg/support" 20 | "github.com/zatxm/fhblade" 21 | "github.com/zatxm/fhblade/tools" 22 | tlsClient "github.com/zatxm/tls-client" 23 | "go.uber.org/zap" 24 | ) 25 | 26 | var ( 27 | chatgptDomain, _ = url.Parse(cst.ChatOriginUrl) 28 | cookieFileDealErr = errors.New("Deal Fail, you can pass the parameter reset to 1 to start again") 29 | ) 30 | 31 | type openaiAuth struct { 32 | Email string 33 | Password string 34 | ArkoseToken string 35 | Reset bool 36 | CookieFileName string 37 | LastCookies []*http.Cookie 38 | client tlsClient.HttpClient 39 | } 40 | 41 | func (oa *openaiAuth) closeClient() { 42 | client.CcPool.Put(oa.client) 43 | } 44 | 45 | // 通过保存的cookie获取token 46 | func (oa *openaiAuth) renew() (*types.OpenAiWebAuthTokenResponse, int, error) { 47 | // 保存的文件名 48 | path := config.V().Openai.CookiePath 49 | path = strings.TrimSuffix(path, "/") 50 | oa.CookieFileName = path + "/" + base64.StdEncoding.EncodeToString(tools.StringToBytes(oa.Email)) 51 | if support.FileExists(oa.CookieFileName) && !oa.Reset { 52 | file, err := os.Open(oa.CookieFileName) 53 | if err != nil { 54 | fhblade.Log.Error("openai auth open cookie file err", zap.Error(err)) 55 | return nil, http.StatusInternalServerError, cookieFileDealErr 56 | } 57 | defer file.Close() 58 | savedCookies := []*http.Cookie{} 59 | decoder := fhblade.Json.NewDecoder(file) 60 | err = decoder.Decode(&savedCookies) 61 | if err != nil { 62 | fhblade.Log.Error("openai auth open file decode json err", zap.Error(err)) 63 | return nil, http.StatusInternalServerError, cookieFileDealErr 64 | } 65 | if len(savedCookies) == 0 { 66 | fhblade.Log.Debug("openai auth open file decode json no cookies") 67 | return nil, http.StatusInternalServerError, cookieFileDealErr 68 | } 69 | oa.LastCookies = savedCookies 70 | savedCookies = append(savedCookies, &http.Cookie{ 71 | Name: "oai-dm-tgt-c-240329", 72 | Value: "2024-04-02", 73 | }) 74 | oa.client.GetCookieJar().SetCookies(chatgptDomain, savedCookies) 75 | return oa.authSession() 76 | } 77 | return nil, 0, nil 78 | } 79 | 80 | // 获取token 81 | func (oa *openaiAuth) authSession() (*types.OpenAiWebAuthTokenResponse, int, error) { 82 | req, _ := http.NewRequest(http.MethodGet, cst.ChatAuthSessionUrl, nil) 83 | req.Header.Set("user-agent", vars.UserAgent) 84 | resp, err := oa.client.Do(req) 85 | if err != nil { 86 | oa.closeClient() 87 | fhblade.Log.Error("web access token req err", zap.Error(err)) 88 | return nil, http.StatusInternalServerError, err 89 | } 90 | if resp.StatusCode != http.StatusOK { 91 | oa.closeClient() 92 | b, _ := tools.ReadAll(resp.Body) 93 | resp.Body.Close() 94 | fhblade.Log.Error("web chat openai auth res status err", 95 | zap.Error(err), 96 | zap.ByteString("data", b), 97 | zap.Int("code", resp.StatusCode)) 98 | if resp.StatusCode == http.StatusTooManyRequests { 99 | errRes := map[string]string{} 100 | fhblade.Json.NewDecoder(resp.Body).Decode(&errRes) 101 | return nil, resp.StatusCode, errors.New(errRes["detail"]) 102 | } 103 | return nil, resp.StatusCode, errors.New("Http to get access token failed") 104 | } 105 | var auth types.OpenAiWebAuthTokenResponse 106 | b, _ := tools.ReadAll(resp.Body) 107 | err = fhblade.Json.Unmarshal(b, &auth) 108 | resp.Body.Close() 109 | if err != nil { 110 | oa.closeClient() 111 | fhblade.Log.Error("web chat openai auth res decode json err", 112 | zap.ByteString("data", b), 113 | zap.Error(err)) 114 | return nil, http.StatusNotFound, errors.New("Access token return error") 115 | } 116 | if auth.AccessToken == "" { 117 | oa.closeClient() 118 | fhblade.Log.Debug("web auth return", zap.Any("data", auth)) 119 | return nil, http.StatusNotFound, errors.New("Missing access token") 120 | } 121 | 122 | // 处理最后的cookie 123 | aCookies := oa.client.GetCookieJar().Cookies(chatgptDomain) 124 | oa.closeClient() 125 | if len(aCookies) > 0 { 126 | if len(oa.LastCookies) == 0 { 127 | oa.LastCookies = aCookies 128 | } else { 129 | oCookies := oa.LastCookies 130 | for i := range aCookies { 131 | aCookie := aCookies[i] 132 | exists := false 133 | InnerLoop: 134 | for j := range oCookies { 135 | oCookie := oCookies[j] 136 | if oCookie.Name == aCookie.Name { 137 | oa.LastCookies[j] = aCookie 138 | exists = true 139 | break InnerLoop 140 | } 141 | } 142 | if !exists { 143 | oa.LastCookies = append(oa.LastCookies, aCookie) 144 | } 145 | } 146 | } 147 | } 148 | go oa.saveCookie() 149 | 150 | return &auth, http.StatusOK, nil 151 | } 152 | 153 | func (oa *openaiAuth) saveCookie() { 154 | fileDatas := []*http.Cookie{} 155 | expireTime := time.Now().AddDate(0, 0, 7) 156 | for k := range oa.LastCookies { 157 | cookie := oa.LastCookies[k] 158 | if cookie.Expires.After(expireTime) { 159 | fileDatas = append(fileDatas, cookie) 160 | } 161 | } 162 | // 写入文件 163 | file, err := os.OpenFile(oa.CookieFileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) 164 | if err != nil { 165 | fhblade.Log.Error("web access token write file err", zap.Error(err)) 166 | return 167 | } 168 | defer file.Close() 169 | encoder := fhblade.Json.NewEncoder(file) 170 | err = encoder.Encode(fileDatas) 171 | if err != nil { 172 | fhblade.Log.Error("web access token write file json err", zap.Error(err)) 173 | return 174 | } 175 | } 176 | 177 | // 新的cookie开始 178 | func (oa *openaiAuth) initCookie() { 179 | oaiCookies := tlsClient.NewCookieJar() 180 | oaiCookies.SetCookies(chatgptDomain, []*http.Cookie{{ 181 | Name: "oai-dm-tgt-c-240329", 182 | Value: "2024-04-02", 183 | }}) 184 | oa.client.SetCookieJar(oaiCookies) 185 | } 186 | 187 | func (oa *openaiAuth) prepare() (int, error) { 188 | // csrfToken 189 | req, _ := http.NewRequest(http.MethodGet, cst.ChatCsrfUrl, nil) 190 | req.Header = http.Header{ 191 | "accept": {vars.AcceptAll}, 192 | "content-type": {vars.ContentTypeJSON}, 193 | "referer": {cst.ChatRefererUrl}, 194 | "user-agent": {vars.UserAgent}, 195 | } 196 | resp, err := oa.client.Do(req) 197 | if err != nil { 198 | oa.closeClient() 199 | fhblade.Log.Error("csrfToken req err", zap.Error(err)) 200 | return http.StatusInternalServerError, err 201 | } 202 | var csrf types.OpenAiCsrfTokenResponse 203 | err = fhblade.Json.NewDecoder(resp.Body).Decode(&csrf) 204 | resp.Body.Close() 205 | if err != nil { 206 | oa.closeClient() 207 | fhblade.Log.Error("csrfToken res err", zap.Error(err)) 208 | return http.StatusInternalServerError, errors.New("get csrftoken error") 209 | } 210 | csrfToken := csrf.Token 211 | 212 | // 获取authorize_url 213 | authorizeBody := url.Values{ 214 | "callbackUrl": {"/"}, 215 | "csrfToken": {csrfToken}, 216 | "json": {"true"}, 217 | } 218 | req, _ = http.NewRequest(http.MethodPost, cst.ChatPromptLoginUrl, strings.NewReader(authorizeBody.Encode())) 219 | req.Header.Set("content-type", vars.ContentType) 220 | req.Header.Set("origin", cst.ChatOriginUrl) 221 | req.Header.Set("referer", cst.ChatRefererUrl) 222 | req.Header.Set("user-agent", vars.UserAgent) 223 | resp, err = oa.client.Do(req) 224 | if err != nil { 225 | oa.closeClient() 226 | fhblade.Log.Error("web authorize url req err", zap.Error(err)) 227 | return http.StatusInternalServerError, err 228 | } 229 | if resp.StatusCode != http.StatusOK { 230 | oa.closeClient() 231 | resp.Body.Close() 232 | fhblade.Log.Error("web authorize url req status err", zap.Int("code", resp.StatusCode)) 233 | return http.StatusInternalServerError, errors.New("Failed to get authorized url.") 234 | } 235 | authorize := map[string]string{} 236 | err = fhblade.Json.NewDecoder(resp.Body).Decode(&authorize) 237 | resp.Body.Close() 238 | if err != nil { 239 | oa.closeClient() 240 | fhblade.Log.Error("web authorize url res err", zap.Error(err)) 241 | return http.StatusInternalServerError, errors.New("get authorize url error") 242 | } 243 | authorizedUrl := authorize["url"] 244 | 245 | // 获取state,是个重定向 246 | req, err = http.NewRequest(http.MethodGet, authorizedUrl, nil) 247 | req.Header.Set("user-agent", vars.UserAgent) 248 | resp, err = oa.client.Do(req) 249 | if err != nil { 250 | oa.closeClient() 251 | fhblade.Log.Error("web state req err", 252 | zap.String("url", authorizedUrl), 253 | zap.Error(err)) 254 | return http.StatusInternalServerError, err 255 | } 256 | if resp.StatusCode != http.StatusOK { 257 | oa.closeClient() 258 | b, _ := tools.ReadAll(resp.Body) 259 | resp.Body.Close() 260 | fhblade.Log.Error("web state res status err", 261 | zap.ByteString("data", b), 262 | zap.String("url", authorizedUrl), 263 | zap.Int("code", resp.StatusCode)) 264 | return resp.StatusCode, errors.New("request login url state error") 265 | } 266 | resp.Body.Close() 267 | 268 | // 验证登录用户 269 | au, _ := url.Parse(authorizedUrl) 270 | query := au.Query() 271 | query.Del("prompt") 272 | query.Set("redirect_uri", cst.ChatAuthRedirectUri) 273 | query.Set("max_age", "0") 274 | query.Set("login_hint", oa.Email) 275 | aUrl := cst.Auth0OriginUrl + "/authorize?" + query.Encode() 276 | req, err = http.NewRequest(http.MethodGet, aUrl, nil) 277 | if err != nil { 278 | oa.closeClient() 279 | fhblade.Log.Error("web check email req err", 280 | zap.Error(err), 281 | zap.String("url", aUrl)) 282 | return http.StatusInternalServerError, err 283 | } 284 | req.Header.Set("referer", cst.AuthRefererUrl) 285 | req.Header.Set("user-agent", vars.UserAgent) 286 | oa.client.SetFollowRedirect(false) 287 | resp, err = oa.client.Do(req) 288 | if err != nil { 289 | oa.closeClient() 290 | fhblade.Log.Error("web check email req do err", 291 | zap.Error(err), 292 | zap.String("url", aUrl)) 293 | return http.StatusInternalServerError, err 294 | } 295 | if resp.StatusCode != http.StatusFound { 296 | oa.closeClient() 297 | b, _ := tools.ReadAll(resp.Body) 298 | resp.Body.Close() 299 | fhblade.Log.Error("web check email res status err", 300 | zap.Error(err), 301 | zap.Int("code", resp.StatusCode), 302 | zap.String("url", aUrl), 303 | zap.ByteString("data", b)) 304 | return http.StatusInternalServerError, errors.New("Email is not valid") 305 | } 306 | resp.Body.Close() 307 | redir := resp.Header.Get("Location") 308 | rUrl := cst.Auth0OriginUrl + redir 309 | req, _ = http.NewRequest(http.MethodGet, rUrl, nil) 310 | req.Header.Set("referer", cst.AuthRefererUrl) 311 | req.Header.Set("user-agent", vars.UserAgent) 312 | resp, err = oa.client.Do(req) 313 | if err != nil { 314 | oa.closeClient() 315 | fhblade.Log.Error("web check email res next err", 316 | zap.Error(err), 317 | zap.String("url", rUrl)) 318 | return http.StatusInternalServerError, errors.New("Check email error") 319 | } 320 | body, err := tools.ReadAll(resp.Body) 321 | resp.Body.Close() 322 | if err != nil { 323 | oa.closeClient() 324 | fhblade.Log.Error("web check email res next body err", 325 | zap.Error(err), 326 | zap.String("url", rUrl)) 327 | return http.StatusInternalServerError, errors.New("Check email return error") 328 | } 329 | var dx string 330 | re := regexp.MustCompile(`blob: "([^"]+?)"`) 331 | matches := re.FindStringSubmatch(tools.BytesToString(body)) 332 | if len(matches) > 1 { 333 | dx = matches[1] 334 | } 335 | aru, _ := url.Parse(redir) 336 | state := aru.Query().Get("state") 337 | 338 | // arkose token 339 | arkoseToken := oa.ArkoseToken 340 | if arkoseToken == "" { 341 | var err error 342 | arkoseToken, err = solve.DoToken("0A1D34FC-659D-4E23-B17B-694DCFCF6A6C", dx) 343 | if err != nil { 344 | oa.closeClient() 345 | return http.StatusInternalServerError, errors.New("Arkose token error") 346 | } 347 | } 348 | u, _ := url.Parse("https://openai.com") 349 | cookies := []*http.Cookie{} 350 | oa.client.GetCookieJar().SetCookies(u, append(cookies, &http.Cookie{Name: "arkoseToken", Value: arkoseToken})) 351 | 352 | // 验证登录密码 353 | passwdCheckBody := url.Values{ 354 | "state": {state}, 355 | "username": {oa.Email}, 356 | "password": {oa.Password}, 357 | } 358 | pwdUrl := cst.LoginPasswordUrl + state 359 | req, err = http.NewRequest(http.MethodPost, cst.LoginPasswordUrl+state, strings.NewReader(passwdCheckBody.Encode())) 360 | req.Header.Set("content-type", vars.ContentType) 361 | req.Header.Set("origin", cst.Auth0OriginUrl) 362 | req.Header.Set("referer", pwdUrl) 363 | req.Header.Set("user-agent", vars.UserAgent) 364 | oa.client.SetFollowRedirect(false) 365 | resp, err = oa.client.Do(req) 366 | if err != nil { 367 | oa.closeClient() 368 | fhblade.Log.Error("web check email password err", zap.Error(err)) 369 | return http.StatusInternalServerError, err 370 | } 371 | resp.Body.Close() 372 | if resp.StatusCode != http.StatusFound { 373 | oa.closeClient() 374 | fhblade.Log.Error("web check email password status err", zap.Int("code", resp.StatusCode)) 375 | return resp.StatusCode, errors.New("login error") 376 | } 377 | 378 | // 登录返回验证 379 | checkBackUrl := cst.Auth0OriginUrl + resp.Header.Get("Location") 380 | req, _ = http.NewRequest(http.MethodGet, checkBackUrl, nil) 381 | req.Header.Set("referer", pwdUrl) 382 | req.Header.Set("user-agent", vars.UserAgent) 383 | oa.client.SetFollowRedirect(false) 384 | resp, err = oa.client.Do(req) 385 | if err != nil { 386 | oa.closeClient() 387 | fhblade.Log.Error("web login back req err", 388 | zap.Error(err), 389 | zap.String("url", checkBackUrl)) 390 | return http.StatusInternalServerError, err 391 | } 392 | resp.Body.Close() 393 | if resp.StatusCode != http.StatusFound { 394 | oa.closeClient() 395 | fhblade.Log.Error("web login back req status err", 396 | zap.Int("code", resp.StatusCode), 397 | zap.String("url", checkBackUrl)) 398 | return http.StatusInternalServerError, errors.New("Email or password check not correct") 399 | } 400 | 401 | // 获取chat_openai_url 402 | location := resp.Header.Get("Location") 403 | if strings.HasPrefix(location, "/u/mfa-otp-challenge") { 404 | oa.closeClient() 405 | return http.StatusBadRequest, errors.New("Login with two-factor authentication enabled is not supported currently.") 406 | } 407 | req, _ = http.NewRequest(http.MethodGet, location, nil) 408 | req.Header.Set("user-agent", vars.UserAgent) 409 | resp, err = oa.client.Do(req) 410 | if err != nil { 411 | oa.closeClient() 412 | fhblade.Log.Error("web chat openai url req err", 413 | zap.Error(err), 414 | zap.String("url", location)) 415 | return http.StatusInternalServerError, err 416 | } 417 | if resp.StatusCode != http.StatusFound { 418 | oa.closeClient() 419 | b, _ := tools.ReadAll(resp.Body) 420 | resp.Body.Close() 421 | fhblade.Log.Error("web chat openai url res status err", 422 | zap.Error(err), 423 | zap.String("url", location), 424 | zap.ByteString("data", b), 425 | zap.Int("code", resp.StatusCode)) 426 | if resp.StatusCode == http.StatusTemporaryRedirect { 427 | errorDescription := req.URL.Query().Get("error_description") 428 | if errorDescription != "" { 429 | return resp.StatusCode, errors.New(errorDescription) 430 | } 431 | } 432 | return resp.StatusCode, errors.New("openai url error") 433 | } 434 | resp.Body.Close() 435 | return 0, nil 436 | } 437 | 438 | func DoWeb() func(*fhblade.Context) error { 439 | return func(c *fhblade.Context) error { 440 | var p types.OpenAiWebAuthTokenRequest 441 | if err := c.ShouldBindJSON(&p); err != nil { 442 | return c.JSONAndStatus(http.StatusBadRequest, fhblade.H{"errorMessage": "params error"}) 443 | } 444 | 445 | oa := &openaiAuth{ 446 | Email: p.Email, 447 | Password: p.Password, 448 | ArkoseToken: p.ArkoseToken, 449 | Reset: p.Reset, 450 | } 451 | gClient := client.CcPool.Get().(tlsClient.HttpClient) 452 | proxyUrl := config.OpenaiAuthProxyUrl() 453 | if proxyUrl != "" { 454 | gClient.SetProxy(proxyUrl) 455 | } 456 | oa.client = gClient 457 | 458 | auth, code, err := oa.renew() 459 | if err != nil { 460 | return c.JSONAndStatus(code, fhblade.H{"errorMessage": err.Error()}) 461 | } 462 | if auth != nil { 463 | return c.JSONAndStatus(http.StatusOK, auth) 464 | } 465 | 466 | oa.initCookie() 467 | 468 | if code, err := oa.prepare(); err != nil { 469 | return c.JSONAndStatus(code, fhblade.H{"errorMessage": err.Error()}) 470 | } 471 | 472 | auth, code, err = oa.authSession() 473 | if err != nil { 474 | return c.JSONAndStatus(code, fhblade.H{"errorMessage": err.Error()}) 475 | } 476 | return c.JSONAndStatus(http.StatusOK, auth) 477 | } 478 | } 479 | -------------------------------------------------------------------------------- /internal/bing/bing.go: -------------------------------------------------------------------------------- 1 | package bing 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "mime/multipart" 8 | ohttp "net/http" 9 | "net/url" 10 | "strings" 11 | "time" 12 | 13 | http "github.com/bogdanfinn/fhttp" 14 | "github.com/google/uuid" 15 | "github.com/gorilla/websocket" 16 | "github.com/zatxm/any-proxy/internal/client" 17 | "github.com/zatxm/any-proxy/internal/config" 18 | "github.com/zatxm/any-proxy/internal/types" 19 | "github.com/zatxm/any-proxy/internal/vars" 20 | "github.com/zatxm/any-proxy/pkg/support" 21 | "github.com/zatxm/fhblade" 22 | "github.com/zatxm/fhblade/tools" 23 | tlsClient "github.com/zatxm/tls-client" 24 | "go.uber.org/zap" 25 | ) 26 | 27 | const ( 28 | Provider = "bing" 29 | ThisModel = "gpt-4-bing" 30 | ) 31 | 32 | var ( 33 | DefaultCookies = []string{ 34 | "SRCHD=AF=NOFORM", 35 | "PPLState=1", 36 | "KievRPSSecAuth=", 37 | "SUID=", 38 | "SRCHUSR=", 39 | "BCP=AD=1&AL=1&SM=1", 40 | } 41 | 42 | DefaultHeaders = http.Header{ 43 | "accept": {vars.ContentTypeJSON}, 44 | "accept-encoding": {vars.AcceptEncoding}, 45 | "accept-language": {"en-US,en;q=0.9"}, 46 | "referer": {"https://www.bing.com/chat?q=Bing+AI&FORM=hpcodx"}, 47 | "sec-ch-ua": {`"Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"`}, 48 | "sec-ch-ua-arch": {`"x86"`}, 49 | "sec-ch-ua-bitness": {`"64"`}, 50 | "sec-ch-ua-full-version": {`"123.0.2420.65"`}, 51 | "sec-ch-ua-full-version-list": {`"Microsoft Edge";v="123.0.2420.65", "Not:A-Brand";v="8.0.0.0", "Chromium";v="123.0.6312.87"`}, 52 | "sec-ch-ua-mobile": {"?0"}, 53 | "sec-ch-ua-model": {`""`}, 54 | "sec-ch-ua-platform": {`"Linux"`}, 55 | "sec-ch-ua-platform-version": {`"6.7.11"`}, 56 | "sec-fetch-dest": {"empty"}, 57 | "sec-fetch-mode": {"cors"}, 58 | "sec-fetch-site": {"same-origin"}, 59 | "user-agent": {vars.UserAgent}, 60 | "x-ms-useragent": {"azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.15.1 OS/Linux"}, 61 | } 62 | OptionDefaultSets = []string{"nlu_direct_response_filter", "deepleo", 63 | "disable_emoji_spoken_text", "responsible_ai_policy_235", "enablemm", 64 | "dv3sugg", "iyxapbing", "iycapbing", "h3imaginative", "uquopt", 65 | "techinstgnd", "rctechalwlst", "eredirecturl", "bcechat", 66 | "clgalileo", "gencontentv3"} 67 | AllowedMessageTypes = []string{"ActionRequest", "Chat", "ConfirmationCard", 68 | "Context", "InternalSearchQuery", "InternalSearchResult", "Disengaged", 69 | "InternalLoaderMessage", "Progress", "RenderCardRequest", 70 | "RenderContentRequest", "AdsQuery", "SemanticSerp", 71 | "GenerateContentQuery", "SearchQuery", "GeneratedCode", "InternalTasksMessage"} 72 | SliceIds = []string{"stcheckcf", "invldrqcf", "v6voice", "rdlidn", "autotts", 73 | "dlid", "rdlid", "sydoroff", "voicemap", "sappbcbt", "revfschelpm", 74 | "cmcpupsalltf", "sydtransctrl", "thdnsrchcf", "0301techgnd", 75 | "220dcl1bt15", "0215wcrwip", "0130gpt4ts0", "bingfccf", 76 | "fpsticycf", "222gentech", "0225unsticky1", "gptbuildernew", 77 | "gcownprock", "gptcreator", "defquerycf", "enrrmeta", 78 | "create500cf", "3022tpv"} 79 | knowledgeRequestJsonStr = `{"imageInfo":{},"knowledgeRequest":{"invokedSkills":["ImageById"],"subscriptionId":"Bing.Chat.Multimodal","invokedSkillsRequestData":{"enableFaceBlur":true},"convoData":{"convoid":"","convotone":"Creative"}}}` 80 | 81 | WsDelimiterByte byte = 30 82 | OriginUrl = "https://www.bing.com" 83 | ListConversationApiUrl = "https://www.bing.com/turing/conversation/chats" 84 | CreateConversationApiUrl = "https://www.bing.com/turing/conversation/create?bundleVersion=1.1694.0" 85 | DeleteConversationApiUrl = "https://sydney.bing.com/sydney/DeleteSingleConversation" 86 | ImageUploadRefererUrl = "https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx" 87 | ImageUrl = "https://www.bing.com/images/blob?bcid=" 88 | ImageUploadApiUrl = "https://www.bing.com/images/kblob" 89 | WssScheme = "wss" 90 | WssHost = "sydney.bing.com" 91 | WssPath = "/sydney/ChatHub" 92 | ) 93 | 94 | func DoDeleteConversation() func(*fhblade.Context) error { 95 | return func(c *fhblade.Context) error { 96 | var p types.BingConversationDeleteParams 97 | if err := c.ShouldBindJSON(&p); err != nil { 98 | return c.JSONAndStatus(http.StatusBadRequest, fhblade.H{"errorMessage": "params error"}) 99 | } 100 | p.Source = "cib" 101 | p.OptionsSets = []string{"autosave"} 102 | cookiesStr := parseCookies() 103 | payload, _ := fhblade.Json.MarshalToString(&p) 104 | req, _ := http.NewRequest(http.MethodGet, DeleteConversationApiUrl, strings.NewReader(payload)) 105 | req.Header = DefaultHeaders 106 | req.Header.Set("x-ms-client-request-id", uuid.NewString()) 107 | req.Header.Set("Cookie", cookiesStr) 108 | gClient := client.CPool.Get().(tlsClient.HttpClient) 109 | proxyUrl := config.BingProxyUrl() 110 | if proxyUrl != "" { 111 | gClient.SetProxy(proxyUrl) 112 | } 113 | resp, err := gClient.Do(req) 114 | client.CPool.Put(gClient) 115 | if err != nil { 116 | fhblade.Log.Error("bing DoDeleteConversation() req err", zap.Error(err)) 117 | return c.JSONAndStatus(http.StatusBadRequest, fhblade.H{"errorMessage": err.Error()}) 118 | } 119 | defer resp.Body.Close() 120 | return c.Reader(resp.Body) 121 | } 122 | } 123 | 124 | func DoListConversation() func(*fhblade.Context) error { 125 | return func(c *fhblade.Context) error { 126 | cookiesStr := parseCookies() 127 | req, _ := http.NewRequest(http.MethodGet, ListConversationApiUrl, nil) 128 | req.Header = DefaultHeaders 129 | req.Header.Set("x-ms-client-request-id", uuid.NewString()) 130 | req.Header.Set("Cookie", cookiesStr) 131 | gClient := client.CPool.Get().(tlsClient.HttpClient) 132 | proxyUrl := config.BingProxyUrl() 133 | if proxyUrl != "" { 134 | gClient.SetProxy(proxyUrl) 135 | } 136 | resp, err := gClient.Do(req) 137 | client.CPool.Put(gClient) 138 | if err != nil { 139 | fhblade.Log.Error("bing DoListConversation() req err", zap.Error(err)) 140 | return c.JSONAndStatus(http.StatusBadRequest, fhblade.H{"errorMessage": err.Error()}) 141 | } 142 | defer resp.Body.Close() 143 | return c.Reader(resp.Body) 144 | } 145 | } 146 | 147 | func DoCreateConversation() func(*fhblade.Context) error { 148 | return func(c *fhblade.Context) error { 149 | conversation, err := createConversation() 150 | if err != nil { 151 | return c.JSONAndStatus(http.StatusBadRequest, fhblade.H{"errorMessage": err.Error()}) 152 | } 153 | return c.JSONAndStatus(http.StatusOK, conversation) 154 | } 155 | } 156 | 157 | func DoSendMessage() func(*fhblade.Context) error { 158 | return func(c *fhblade.Context) error { 159 | var p types.ChatCompletionRequest 160 | if err := c.ShouldBindJSON(&p); err != nil { 161 | return c.JSONAndStatus(http.StatusBadRequest, fhblade.H{"errorMessage": "params error"}) 162 | } 163 | return DoChatCompletions(c, p) 164 | } 165 | } 166 | 167 | func DoChatCompletions(c *fhblade.Context, p types.ChatCompletionRequest) error { 168 | prompt := p.ParsePromptText() 169 | if prompt == "" { 170 | return c.JSONAndStatus(http.StatusBadRequest, types.ErrorResponse{ 171 | Error: &types.CError{ 172 | Message: "params error", 173 | Type: "invalid_request_error", 174 | Code: "request_err", 175 | }, 176 | }) 177 | } 178 | if p.Bing == nil { 179 | p.Bing = &types.BingCompletionRequest{} 180 | } 181 | jpgBase64 := "" 182 | if p.Bing.ImageBase64 != "" { 183 | var err error 184 | jpgBase64, err = processImageBase64(p.Bing.ImageBase64) 185 | if err != nil { 186 | return c.JSONAndStatus(http.StatusBadRequest, types.ErrorResponse{ 187 | Error: &types.CError{ 188 | Message: err.Error(), 189 | Type: "invalid_request_error", 190 | Code: "request_err", 191 | }, 192 | }) 193 | } 194 | } 195 | // 没有的话先创建会话 196 | if p.Bing.Conversation == nil { 197 | conversation, err := createConversation() 198 | if err != nil { 199 | return c.JSONAndStatus(http.StatusBadRequest, types.ErrorResponse{ 200 | Error: &types.CError{ 201 | Message: err.Error(), 202 | Type: "invalid_request_error", 203 | Code: "request_err", 204 | }, 205 | }) 206 | } 207 | p.Bing.Conversation = conversation 208 | } 209 | isStartOfSession := false 210 | if p.Bing.Conversation.TraceId == "" { 211 | p.Bing.Conversation.TraceId = support.RandHex(16) 212 | isStartOfSession = true 213 | } 214 | // 处理图片 215 | if p.Bing.ImageBase64 != "" { 216 | cookiesStr := parseCookies() 217 | dheaders := DefaultHeaders 218 | dheaders.Set("x-ms-client-request-id", uuid.NewString()) 219 | dheaders.Set("Cookie", cookiesStr) 220 | dheaders.Set("referer", ImageUploadRefererUrl) 221 | dheaders.Set("origin", OriginUrl) 222 | var requestBody bytes.Buffer 223 | writer := multipart.NewWriter(&requestBody) 224 | boundary := generateBoundary() 225 | writer.SetBoundary(boundary) 226 | textPart, _ := writer.CreateFormField("knowledgeRequest") 227 | textPart.Write(tools.StringToBytes(knowledgeRequestJsonStr)) 228 | textPart, _ = writer.CreateFormField("imageBase64") 229 | textPart.Write(tools.StringToBytes(jpgBase64)) 230 | writer.Close() 231 | dheaders.Set("content-type", writer.FormDataContentType()) 232 | req, err := http.NewRequest(http.MethodPost, ImageUploadApiUrl, &requestBody) 233 | if err != nil { 234 | fhblade.Log.Error("bing DoSendMessage() img upload http.NewRequest err", 235 | zap.Error(err), 236 | zap.String("data", requestBody.String())) 237 | return c.JSONAndStatus(http.StatusInternalServerError, types.ErrorResponse{ 238 | Error: &types.CError{ 239 | Message: err.Error(), 240 | Type: "invalid_request_error", 241 | Code: "request_err", 242 | }, 243 | }) 244 | } 245 | req.Header = dheaders 246 | gClient := client.CPool.Get().(tlsClient.HttpClient) 247 | proxyUrl := config.BingProxyUrl() 248 | if proxyUrl != "" { 249 | gClient.SetProxy(proxyUrl) 250 | } 251 | resp, err := gClient.Do(req) 252 | client.CPool.Put(gClient) 253 | if err != nil { 254 | fhblade.Log.Error("bing DoSendMessage() img upload gClient.Do err", 255 | zap.Error(err), 256 | zap.String("data", requestBody.String())) 257 | return c.JSONAndStatus(http.StatusInternalServerError, types.ErrorResponse{ 258 | Error: &types.CError{ 259 | Message: err.Error(), 260 | Type: "invalid_request_error", 261 | Code: "request_err", 262 | }, 263 | }) 264 | } 265 | defer resp.Body.Close() 266 | imgRes := &types.BingImgBlob{} 267 | if err := fhblade.Json.NewDecoder(resp.Body).Decode(imgRes); err != nil { 268 | fhblade.Log.Error("bing DoSendMessage() img upload res json err", 269 | zap.Error(err), 270 | zap.String("data", requestBody.String())) 271 | return c.JSONAndStatus(http.StatusInternalServerError, types.ErrorResponse{ 272 | Error: &types.CError{ 273 | Message: err.Error(), 274 | Type: "invalid_request_error", 275 | Code: "request_err", 276 | }, 277 | }) 278 | } 279 | imgUrlId := imgRes.BlobId 280 | if imgRes.ProcessedBlobId != "" { 281 | imgUrlId = imgRes.ProcessedBlobId 282 | } 283 | p.Bing.Conversation.ImageUrl = ImageUrl + imgUrlId 284 | } 285 | 286 | msgByte := generateMessage(p.Bing.Conversation, prompt, isStartOfSession) 287 | 288 | urlParams := url.Values{"sec_access_token": {p.Bing.Conversation.Signature}} 289 | u := url.URL{ 290 | Scheme: WssScheme, 291 | Host: WssHost, 292 | Path: WssPath, 293 | RawQuery: urlParams.Encode(), 294 | } 295 | dialer := websocket.DefaultDialer 296 | proxyCfgUrl := config.V().Bing.ProxyUrl 297 | if proxyCfgUrl != "" { 298 | proxyURL, err := url.Parse(proxyCfgUrl) 299 | if err != nil { 300 | fhblade.Log.Error("bing DoSendMessage() set proxy err", 301 | zap.Error(err), 302 | zap.String("url", proxyCfgUrl)) 303 | return c.JSONAndStatus(http.StatusInternalServerError, types.ErrorResponse{ 304 | Error: &types.CError{ 305 | Message: err.Error(), 306 | Type: "invalid_request_error", 307 | Code: "request_err", 308 | }, 309 | }) 310 | } 311 | dialer.Proxy = ohttp.ProxyURL(proxyURL) 312 | } 313 | headers := make(ohttp.Header) 314 | headers.Set("Origin", OriginUrl) 315 | headers.Set("User-Agent", vars.UserAgent) 316 | cookiesStr := parseCookies() 317 | headers.Set("Cookie", cookiesStr) 318 | wssUrl := u.String() 319 | wc, _, err := dialer.Dial(wssUrl, headers) 320 | if err != nil { 321 | fhblade.Log.Error("bing DoSendMessage() wc req err", 322 | zap.String("url", wssUrl), 323 | zap.Error(err)) 324 | return c.JSONAndStatus(http.StatusInternalServerError, types.ErrorResponse{ 325 | Error: &types.CError{ 326 | Message: err.Error(), 327 | Type: "invalid_request_error", 328 | Code: "request_err", 329 | }, 330 | }) 331 | } 332 | defer wc.Close() 333 | 334 | rw := c.Response().Rw() 335 | flusher, ok := rw.(http.Flusher) 336 | if !ok { 337 | return c.JSONAndStatus(http.StatusNotImplemented, types.ErrorResponse{ 338 | Error: &types.CError{ 339 | Message: "Flushing not supported", 340 | Type: "invalid_systems_error", 341 | Code: "systems_error", 342 | }, 343 | }) 344 | } 345 | header := rw.Header() 346 | header.Set("Content-Type", vars.ContentTypeStream) 347 | header.Set("Cache-Control", "no-cache") 348 | header.Set("Connection", "keep-alive") 349 | header.Set("Access-Control-Allow-Origin", "*") 350 | rw.WriteHeader(200) 351 | 352 | splitByte := []byte{WsDelimiterByte} 353 | endByteTag := []byte(`{"type":3`) 354 | cancle := make(chan struct{}) 355 | // 处理返回数据 356 | go func() { 357 | lastMsg := "" 358 | for { 359 | _, msg, err := wc.ReadMessage() 360 | if err != nil { 361 | fhblade.Log.Error("bing DoSendMessage() wc read err", zap.Error(err)) 362 | close(cancle) 363 | return 364 | } 365 | msgArr := bytes.Split(msg, splitByte) 366 | for k := range msgArr { 367 | if len(msgArr[k]) > 0 { 368 | if bytes.HasPrefix(msgArr[k], endByteTag) { 369 | fmt.Fprint(rw, "data: [DONE]\n\n") 370 | flusher.Flush() 371 | close(cancle) 372 | return 373 | } 374 | resArr := &types.BingCompletionResponse{} 375 | err := fhblade.Json.Unmarshal(msgArr[k], &resArr) 376 | if err != nil { 377 | fhblade.Log.Error("bing DoSendMessage() wc deal data err", 378 | zap.Error(err), 379 | zap.ByteString("data", msgArr[k])) 380 | } 381 | resMsg := "" 382 | now := time.Now().Unix() 383 | switch resArr.CType { 384 | case 1: 385 | if resArr.Arguments != nil && len(resArr.Arguments) > 0 { 386 | argument := resArr.Arguments[0] 387 | if len(argument.Messages) > 0 { 388 | msgArr := argument.Messages[0] 389 | if msgArr.CreatedAt != "" { 390 | parsedTime, err := time.Parse(time.RFC3339, msgArr.CreatedAt) 391 | if err == nil { 392 | now = parsedTime.Unix() 393 | } 394 | } 395 | if msgArr.AdaptiveCards != nil && len(msgArr.AdaptiveCards) > 0 { 396 | card := msgArr.AdaptiveCards[0].Body[0] 397 | if card.Text != "" { 398 | resMsg += card.Text 399 | } 400 | if card.Inlines != nil && len(card.Inlines) > 0 { 401 | cardLnline := card.Inlines[0] 402 | if cardLnline.Text != "" { 403 | resMsg += cardLnline.Text + "\n" 404 | } 405 | } 406 | } else if msgArr.ContentType == "IMAGE" { 407 | if msgArr.Text != "" { 408 | resMsg += "\nhttps://www.bing.com/images/create?q=" + url.QueryEscape(msgArr.Text) 409 | } 410 | } 411 | } 412 | } 413 | case 2: 414 | fmt.Fprint(rw, "data: [DONE]\n\n") 415 | flusher.Flush() 416 | close(cancle) 417 | return 418 | } 419 | if resMsg != "" { 420 | tMsg := strings.TrimPrefix(resMsg, lastMsg) 421 | lastMsg = resMsg 422 | if tMsg != "" { 423 | var choices []*types.ChatCompletionChoice 424 | choices = append(choices, &types.ChatCompletionChoice{ 425 | Index: 0, 426 | Message: &types.ChatCompletionMessage{ 427 | Role: "assistant", 428 | Content: tMsg, 429 | }, 430 | }) 431 | outRes := types.ChatCompletionResponse{ 432 | ID: p.Bing.Conversation.ConversationId, 433 | Choices: choices, 434 | Created: now, 435 | Model: ThisModel, 436 | Object: "chat.completion.chunk", 437 | Bing: p.Bing.Conversation} 438 | resJson, _ := fhblade.Json.Marshal(outRes) 439 | fmt.Fprintf(rw, "data: %s\n\n", resJson) 440 | flusher.Flush() 441 | } 442 | } 443 | } 444 | } 445 | } 446 | }() 447 | 448 | // 发送数据 449 | msgStart := append([]byte(`{"protocol":"json","version":1}`), WsDelimiterByte) 450 | err = wc.WriteMessage(websocket.TextMessage, msgStart) 451 | if err != nil { 452 | fhblade.Log.Error("bing DoSendMessage() wc write err", zap.Error(err)) 453 | return c.JSONAndStatus(http.StatusInternalServerError, types.ErrorResponse{ 454 | Error: &types.CError{ 455 | Message: err.Error(), 456 | Type: "invalid_request_error", 457 | Code: "request_err", 458 | }, 459 | }) 460 | } 461 | msgInput := append([]byte(`{"type":6}`), WsDelimiterByte) 462 | err = wc.WriteMessage(websocket.TextMessage, msgInput) 463 | if err != nil { 464 | fhblade.Log.Error("bing DoSendMessage() wc write input err", zap.Error(err)) 465 | return c.JSONAndStatus(http.StatusInternalServerError, types.ErrorResponse{ 466 | Error: &types.CError{ 467 | Message: err.Error(), 468 | Type: "invalid_request_error", 469 | Code: "request_err", 470 | }, 471 | }) 472 | } 473 | err = wc.WriteMessage(websocket.TextMessage, msgByte) 474 | if err != nil { 475 | fhblade.Log.Error("bing DoSendMessage() wc write msg err", zap.Error(err)) 476 | return c.JSONAndStatus(http.StatusInternalServerError, types.ErrorResponse{ 477 | Error: &types.CError{ 478 | Message: err.Error(), 479 | Type: "invalid_request_error", 480 | Code: "request_err", 481 | }, 482 | }) 483 | } 484 | 485 | timer := time.NewTimer(900 * time.Second) 486 | defer timer.Stop() 487 | for { 488 | select { 489 | case <-cancle: 490 | wc.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) 491 | return nil 492 | case <-timer.C: 493 | close(cancle) 494 | wc.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) 495 | return nil 496 | } 497 | } 498 | } 499 | 500 | func parseCookies() string { 501 | cookies := DefaultCookies 502 | currentTime := time.Now() 503 | cookies = append(cookies, fmt.Sprintf("SRCHHPGUSR=HV=%d", currentTime.Unix())) 504 | todayFormat := currentTime.Format("2006-01-02") 505 | cookies = append(cookies, fmt.Sprintf("_Rwho=u=d&ts=%s", todayFormat)) 506 | cookiesStr := strings.Join(cookies, "; ") 507 | return cookiesStr 508 | } 509 | 510 | // 处理图片转成base64 511 | // bing要求图片格式是jpg 512 | // Todo 转换图片格式为jpg 513 | func processImageBase64(originBase64 string) (string, error) { 514 | if originBase64 == "" { 515 | return "", errors.New("image empty") 516 | } 517 | if strings.HasPrefix(originBase64, "/9j/") { 518 | return originBase64, nil 519 | } 520 | return "", errors.New("Only support jpg") 521 | } 522 | 523 | func createConversation() (*types.BingConversation, error) { 524 | cookiesStr := parseCookies() 525 | req, _ := http.NewRequest(http.MethodGet, CreateConversationApiUrl, nil) 526 | req.Header = DefaultHeaders 527 | req.Header.Set("x-ms-client-request-id", uuid.NewString()) 528 | req.Header.Set("Cookie", cookiesStr) 529 | gClient := client.CPool.Get().(tlsClient.HttpClient) 530 | proxyUrl := config.BingProxyUrl() 531 | if proxyUrl != "" { 532 | gClient.SetProxy(proxyUrl) 533 | } 534 | resp, err := gClient.Do(req) 535 | client.CPool.Put(gClient) 536 | if err != nil { 537 | fhblade.Log.Error("bing CreateConversation() req err", zap.Error(err)) 538 | return nil, err 539 | } 540 | defer resp.Body.Close() 541 | conversation := &types.BingConversation{} 542 | if err := fhblade.Json.NewDecoder(resp.Body).Decode(conversation); err != nil { 543 | fhblade.Log.Error("bing CreateConversation() res err", zap.Error(err)) 544 | return nil, errors.New("Create conversation return error") 545 | } 546 | conversation.Signature = resp.Header.Get("X-Sydney-Encryptedconversationsignature") 547 | return conversation, nil 548 | } 549 | 550 | func generateBoundary() string { 551 | return "----WebKitFormBoundary" + support.GenerateRandomString(16) 552 | } 553 | 554 | func generateMessage(c *types.BingConversation, prompt string, isStartOfSession bool) []byte { 555 | id := uuid.NewString() 556 | ct := &types.BingCenter{ 557 | Latitude: 34.0536909, 558 | Longitude: -118.242766} 559 | lth := &types.BingLocationHint{ 560 | SourceType: 1, 561 | RegionType: 2, 562 | Center: ct, 563 | CountryName: "United States", 564 | CountryConfidence: 8, 565 | UtcOffset: 8} 566 | msg := &types.BingMessage{ 567 | Locale: "en-US", 568 | Market: "en-US", 569 | Region: "US", 570 | LocationHints: []*types.BingLocationHint{lth}, 571 | Author: "user", 572 | InputMethod: "Keyboard", 573 | Text: prompt, 574 | MessageType: "Chat", 575 | RequestId: id, 576 | MessageId: id} 577 | if c.ImageUrl != "" { 578 | msg.ImageUrl = c.ImageUrl 579 | msg.OriginalImageUrl = c.ImageUrl 580 | } 581 | pc := &types.BingParticipant{Id: c.ClientId} 582 | arg := &types.BingRequestArgument{ 583 | Source: "cib", 584 | OptionsSets: OptionDefaultSets, 585 | AllowedMessageTypes: AllowedMessageTypes, 586 | SliceIds: SliceIds, 587 | TraceId: c.TraceId, 588 | ConversationHistoryOptionsSets: []string{"threads_bce", "savemem", "uprofupd", "uprofgen"}, 589 | IsStartOfSession: isStartOfSession, 590 | RequestId: id, 591 | Message: msg, 592 | Scenario: "SERP", 593 | Tone: "Creative", 594 | SpokenTextMode: "None", 595 | ConversationId: c.ConversationId, 596 | Participant: pc} 597 | smr := &types.BingSendMessageRequest{ 598 | Arguments: []*types.BingRequestArgument{arg}, 599 | InvocationId: uuid.NewString(), 600 | Target: "chat", 601 | Type: 4} 602 | 603 | opMsg, _ := fhblade.Json.Marshal(smr) 604 | opMsg = append(opMsg, WsDelimiterByte) 605 | return opMsg 606 | } 607 | --------------------------------------------------------------------------------