From 465426e4e118678440a90fc1716b66fd06ad5543 Mon Sep 17 00:00:00 2001 From: luyoyu <8829470+chuangxxt@user.noreply.gitee.com> Date: Wed, 20 Sep 2023 23:51:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BB=A3=E7=A0=81=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E4=BF=AE=E6=94=B9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 33 ++++++++++++++++++-------- src/global.go | 8 +++++++ src/routers.go | 9 ++++++++ src/server.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index fb8b517..306a3ef 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ - [中文](README.md) | [English](README-EN.md) -# share-copilot +# share-copilot -- 作为代理服务器,中转copilot插件的API的请求 +- 作为代理服务器,中转copilot插件的API的请求(也可以代理无良商贩的,自行研究) - 支持vscode插件和jetbrains插件 - 支持多用户共享一个token - 优化请求逻辑,降低suspended概率(别万人骑基本不会) @@ -13,30 +12,44 @@ 简单说一下 本地插件的请求逻辑: -1.本地插件携ghu_token请求github的API +1.本地插件携ghu_token 请求github的API -2.返回带有时间戳的copilot的token(下面简称co_token), +2.返回带有时间戳的copilot的token(简称co_token), 代码提示都是用的co_token 3.co_token到期->ghu_token重新请求->获得新的co_token ------------------------------------------------------------- - 代理服务器的逻辑: 代理的是"ghu_token请求"这一环节 -1.携ghu_token请求github的API,暂存co_token +1.使用ghu_token请求github的API,暂存co_token -2.多用户请求时,判断co_token有没有过期,没过期直接返回, - 减少ghu_token的请求次数(大约10-20分钟过期) +2.多用户请求时,判断co_token有没有过期,没过期直接返回co_token, + 减少对ghu_token的请求次数(大约10-20分钟过期) 3.至于本地co_token,也就是代码提示没有走代理(可以代理,但是要修改插件,jetbrains的插件还要重新build) 猜测风控目前也只是停留在请求ghu_token这一层 + "cocopilot也是代理了ghu_token请求,并没有破解插件" ``` -![软件系统网络架构.png](https://img1.imgtp.com/2023/09/10/qTL8A2u9.png) +------ + +```xml +| 文件名 | 说明 +| -------------- | ------------------- +| config.json | 配置文件 +| copilot_api | guh_token和co_token相关逻辑 +| global | 全局变量、常量或配置 +| main | 主要入口文件 +| middleware | 中间件,验证域名,验证自定义请求头 +| routers | 路由信息,定义不同请求路径的处理逻辑。 +| server | 服务端的代码或相关配置,用于启动和运行服务器。 +| show_msg | 用于显示消息或信息的函数,展示内容。 + +``` *** # 一、自行编译: diff --git a/src/global.go b/src/global.go index 7c3e31f..fb859cf 100644 --- a/src/global.go +++ b/src/global.go @@ -39,3 +39,11 @@ var ( //请求成功计数 successCount = 0 ) + +// 测试的玩意儿 +var ( + diyMsg = ";//授权已经过期,重新获取-> https://www.sharecopilot.top" + completionUrl = "https://copilot-proxy.githubusercontent.com/v1/engines/copilot-codex/completions" + telemetryUrl = "https://copilot-telemetry.githubusercontent.com/telemetry" + isModMsg = false +) diff --git a/src/routers.go b/src/routers.go index 9bca1ba..72b016d 100644 --- a/src/routers.go +++ b/src/routers.go @@ -2,6 +2,15 @@ package main // Routes 自定义代理服务器路由 附加中间件(域名验证和请求验证) func Routes() { + // 定义根路由 copilotApi := copilotGinEngine.Group("/copilot_internal", DomainMiddleware(configFile.Server.Domain), VerifyRequestMiddleware()) + + // 定义代理github token的路由 copilotApi.GET("/v2/token", getCopilotToken()) + + // 定义 completions路由的反向代理 + registerProxyRoute(copilotGinEngine, "/v1/engines/copilot-codex/completions", completionUrl) + + // 定义 telemetry路由的反向代理 , + registerProxyRoute(copilotGinEngine, "/telemetry", telemetryUrl) } diff --git a/src/server.go b/src/server.go index fb2434b..f9ba540 100644 --- a/src/server.go +++ b/src/server.go @@ -1,14 +1,19 @@ package main import ( + "bytes" "crypto/tls" "encoding/json" + "fmt" "github.com/gin-gonic/gin" "io" "log" "net/http" + "net/http/httputil" + "net/url" "os" "path/filepath" + "regexp" "strconv" ) @@ -76,3 +81,61 @@ func diyBadRequest(c *gin.Context, code int, errorMessage string) { "documentation_url": "https://docs.github.com/rest", }) } + +// registerProxyRoute 反向代理路由 +func registerProxyRoute(r *gin.Engine, routePath, targetURL string) { + target, err := url.Parse(targetURL) + if err != nil { + panic(err) + } + proxy := &httputil.ReverseProxy{ + Director: func(req *http.Request) { + req.URL.Host = target.Host + req.URL.Scheme = "https" + req.Host = target.Host + }, + ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { + }, + } + r.Any(routePath, func(c *gin.Context) { + proxy.Director = func(req *http.Request) { + req.URL.Host = target.Host + req.URL.Scheme = "https" + req.Host = target.Host + } + if isModMsg { + modProxyResp(c, proxy) + } + proxy.ServeHTTP(c.Writer, c.Request) + }) +} + +// 修改代码提示内容 +func modProxyResp(c *gin.Context, proxy *httputil.ReverseProxy) { + proxy.ModifyResponse = func(response *http.Response) error { + responseBuffer := new(bytes.Buffer) + _, readErr := responseBuffer.ReadFrom(response.Body) + if readErr != nil { + return readErr + } + responseData := responseBuffer.Bytes() + pattern := `(.*?)data:` + regex := regexp.MustCompile(pattern) + match := regex.FindStringSubmatch(string(responseData)) + var replacedData = "" + if len(match) >= 2 { + replacedData = match[1] + } else { + fmt.Println("No match found.") + } + runes := []rune(diyMsg) + newStr := "" + for _, r := range runes { + newStr += fmt.Sprintf("data: {\"id\":\"cmpl-7xy1GLgssjHEubVrPyt534VRYVF0t\",\"model\":\"cushman-ml\",\"created\":1694526422,\"choices\":[{\"text\":\"%c\",\"index\":0,\"finish_reason\":null,\"logprobs\":null}]}\n", r) + } + newStr = replacedData + newStr + "data: [DONE]\n" + fmt.Println(newStr) + c.Data(response.StatusCode, response.Header.Get("Content-Type"), []byte(newStr)) + return nil + } +}