mirror of
				https://gitee.com/chuangxxt/share-copilot
				synced 2025-10-30 02:29:49 +00:00 
			
		
		
		
	Compare commits
	
		
			5 Commits
		
	
	
		
			934820d691
			...
			fd1dea3ab3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | fd1dea3ab3 | ||
|  | 465426e4e1 | ||
|  | f166316c6f | ||
|  | e897820767 | ||
|  | 78206866d4 | 
							
								
								
									
										41
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								README.md
									
									
									
									
									
								
							| @ -1,9 +1,9 @@ | ||||
| [中文](README.md) |[English](README-EN.md)  | ||||
| [中文](README.md) | [English](README-EN.md)  | ||||
| 
 | ||||
| *** | ||||
| # share-copilot    | ||||
| 
 | ||||
| - 作为代理服务器,中转copilot插件的API的请求 | ||||
| # share-copilot | ||||
| 
 | ||||
| - 作为代理服务器,中转copilot插件的API的请求(也可以代理无良商贩的,自行研究) | ||||
| - 支持vscode插件和jetbrains插件 | ||||
| - 支持多用户共享一个token | ||||
| - 优化请求逻辑,降低suspended概率(别万人骑基本不会) | ||||
| @ -12,31 +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这一层 | ||||
|    | ||||
|  猜测风控目前也只是停留在请求ghu_token这一层 | ||||
|   | ||||
| "cocopilot也是代理了ghu_token请求,并没有破解插件" | ||||
| 
 | ||||
| ``` | ||||
| 
 | ||||
|  | ||||
| ------ | ||||
| 
 | ||||
| ```xml | ||||
| | 文件名          | 说明                 | ||||
| | -------------- | ------------------- | ||||
| | config.json    | 配置文件              | ||||
| | copilot_api    | guh_token和co_token相关逻辑 | ||||
| | global         | 全局变量、常量或配置 | ||||
| | main           | 主要入口文件    | ||||
| | middleware     | 中间件,验证域名,验证自定义请求头    | ||||
| | routers        | 路由信息,定义不同请求路径的处理逻辑。     | ||||
| | server         | 服务端的代码或相关配置,用于启动和运行服务器。  | ||||
| | show_msg       | 用于显示消息或信息的函数,展示内容。      | ||||
| 
 | ||||
| ``` | ||||
| 
 | ||||
| *** | ||||
| # 一、自行编译: | ||||
| @ -179,7 +192,7 @@ GithubApi:      --服务器请求gh_api次数 | ||||
| ``` | ||||
| ------ | ||||
| 
 | ||||
| ##### 4.3本地配置修改: | ||||
| ##### 4.3本地配置修改:可以使用zhile大佬的[cocopilot脚本](https://gitee.com/chuangxxt/share-copilot/releases/download/v1.0.3/cocopilot-master.zip) 改成自己的api一键配置 | ||||
| 
 | ||||
| **以windows为例** | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										112
									
								
								src/copilot_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/copilot_api.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,112 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/go-resty/resty/v2" | ||||
| 	"math/rand" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // 初始化有效的github token列表
 | ||||
| func initValidGhuTokenMap() { | ||||
| 	for _, token := range configFile.CopilotConfig.Token { | ||||
| 		if getGithubApi(token) { | ||||
| 			// 有效的token
 | ||||
| 			validGhuTokenMap[token] = true | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // 代理服务器返回copilot token
 | ||||
| func proxyResp(c *gin.Context, respDataMap map[string]interface{}) { | ||||
| 	// 将map转换为JSON字符串
 | ||||
| 	responseJSON, _ := json.Marshal(respDataMap) | ||||
| 	// 请求成功统计
 | ||||
| 	successCount++ | ||||
| 	// 将响应体作为JSON字符串返回
 | ||||
| 	c.Header("Content-Type", "application/json") | ||||
| 	c.String(http.StatusOK, string(responseJSON)) | ||||
| } | ||||
| 
 | ||||
| // 获取有效的 copilot token
 | ||||
| func getCopilotToken() gin.HandlerFunc { | ||||
| 	return func(c *gin.Context) { | ||||
| 		//用户请求代理服务器的计数
 | ||||
| 		requestCount++ | ||||
| 		//随机从有效的github token列表中获取一个token
 | ||||
| 		token := getRandomToken(validGhuTokenMap) | ||||
| 		//通过token取对应的copilot token的map数据
 | ||||
| 		if respDataMap, exists := getTokenData(token); exists { | ||||
| 			//没过期直接返回
 | ||||
| 			if !isTokenExpired(respDataMap) { | ||||
| 				proxyResp(c, respDataMap) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		//过期了或者没取到,重新获取
 | ||||
| 		if getGithubApi(token) { | ||||
| 			proxyResp(c, copilotTokenMap[token]) | ||||
| 		} else { | ||||
| 			// 重新获取失败,返回自定义消息 400
 | ||||
| 			diyBadRequest(c, 400, "can't get copilot token") | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // 请求github api
 | ||||
| func getGithubApi(token string) bool { | ||||
| 	// githubApi请求计数
 | ||||
| 	githubApiCount++ | ||||
| 	// 设置请求头
 | ||||
| 	headers := map[string]string{ | ||||
| 		"Authorization": "token " + token, | ||||
| 	} | ||||
| 	// 发起GET请求
 | ||||
| 	response, _ := resty.New().R(). | ||||
| 		SetHeaders(headers). | ||||
| 		Get(configFile.CopilotConfig.GithubApiUrl) | ||||
| 	// 判断响应状态码
 | ||||
| 	if response.StatusCode() == http.StatusOK { | ||||
| 		// 响应状态码为200 OK
 | ||||
| 		respDataMap := map[string]interface{}{} | ||||
| 		_ = json.Unmarshal(response.Body(), &respDataMap) | ||||
| 		copilotTokenMap[token] = respDataMap | ||||
| 		return true | ||||
| 	} else { | ||||
| 		// 响应状态码不为200  map删除无效token
 | ||||
| 		delete(validGhuTokenMap, token) | ||||
| 		return false | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // 从map中获取github token对应的copilot token
 | ||||
| func getTokenData(token string) (map[string]interface{}, bool) { | ||||
| 	respDataMap, exists := copilotTokenMap[token] | ||||
| 	return respDataMap, exists | ||||
| } | ||||
| 
 | ||||
| // 检测copilot token是否过期
 | ||||
| func isTokenExpired(respDataMap map[string]interface{}) bool { | ||||
| 	if expiresAt, ok := respDataMap["expires_at"].(float64); ok { | ||||
| 		currentTime := time.Now().Unix() | ||||
| 		expiresAtInt64 := int64(expiresAt) | ||||
| 		return expiresAtInt64 <= currentTime+60 | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // 从map中随机获取一个github token
 | ||||
| func getRandomToken(m map[string]bool) string { | ||||
| 	ghuTokenArray := make([]string, 0, len(m)) | ||||
| 	for k := range m { | ||||
| 		ghuTokenArray = append(ghuTokenArray, k) | ||||
| 	} | ||||
| 	if len(ghuTokenArray) == 0 { | ||||
| 		return "" // 没有有效的token,返回空字符串
 | ||||
| 	} | ||||
| 	r := rand.New(rand.NewSource(time.Now().UnixNano())) | ||||
| 	randomIndex := r.Intn(len(ghuTokenArray)) | ||||
| 	return ghuTokenArray[randomIndex] | ||||
| } | ||||
| @ -1,32 +0,0 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"sync" | ||||
| ) | ||||
| 
 | ||||
| type Config struct { | ||||
| 	Server struct { | ||||
| 		Domain   string `json:"domain"` | ||||
| 		Host     string `json:"host"` | ||||
| 		Port     int    `json:"port"` | ||||
| 		CertPath string `json:"certPath"` | ||||
| 		KeyPath  string `json:"keyPath"` | ||||
| 	} `json:"server"` | ||||
| 	CopilotConfig struct { | ||||
| 		GithubApiUrl string   `json:"github_api_url"` | ||||
| 		Token        []string `json:"token"` | ||||
| 	} `json:"copilot_config"` | ||||
| 	Verification string `json:"verification"` | ||||
| } | ||||
| 
 | ||||
| var ( | ||||
| 	//初始化需要返回给客户端的响应体
 | ||||
| 	tokenMap = make(map[string]map[string]interface{}) | ||||
| 	//有效的token列表
 | ||||
| 	validTokenList    = make(map[string]bool) | ||||
| 	requestCountMutex sync.Mutex | ||||
| 	githubApiCount    = 0 | ||||
| 	requestCount      = 0 | ||||
| 	successCount      = 0 | ||||
| 	configFile        Config | ||||
| ) | ||||
| @ -1,142 +0,0 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/go-resty/resty/v2" | ||||
| 	"math/rand" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // 初始化有效的github token列表
 | ||||
| func initValidTokenList() { | ||||
| 	//为了安全起见,应该等待请求完成并处理其响应。
 | ||||
| 	var wg sync.WaitGroup | ||||
| 	for _, token := range configFile.CopilotConfig.Token { | ||||
| 		wg.Add(1) | ||||
| 		go func(token string) { | ||||
| 			defer wg.Done() | ||||
| 			if getGithubApi(token) { | ||||
| 				validTokenList[token] = true | ||||
| 			} | ||||
| 		}(token) | ||||
| 	} | ||||
| 	wg.Wait() | ||||
| } | ||||
| 
 | ||||
| // 请求github api
 | ||||
| func getGithubApi(token string) bool { | ||||
| 	githubApiCount++ | ||||
| 	// 设置请求头
 | ||||
| 	headers := map[string]string{ | ||||
| 		"Authorization": "token " + token, | ||||
| 		/*"editor-version":        c.GetHeader("editor-version"), | ||||
| 		"editor-plugin-version": c.GetHeader("editor-plugin-version"), | ||||
| 		"user-agent":            c.GetHeader("user-agent"), | ||||
| 		"accept":                c.GetHeader("accept"), | ||||
| 		"accept-encoding":       c.GetHeader("accept-encoding"),*/ | ||||
| 	} | ||||
| 	// 发起GET请求
 | ||||
| 	response, err := resty.New().R(). | ||||
| 		SetHeaders(headers). | ||||
| 		Get(configFile.CopilotConfig.GithubApiUrl) | ||||
| 	if err != nil { | ||||
| 		// 处理请求错误
 | ||||
| 		return false | ||||
| 	} | ||||
| 	// 判断响应状态码
 | ||||
| 	if response.StatusCode() == http.StatusOK { | ||||
| 		// 响应状态码为200 OK
 | ||||
| 		respDataMap := map[string]interface{}{} | ||||
| 		err = json.Unmarshal(response.Body(), &respDataMap) | ||||
| 		if err != nil { | ||||
| 			// 处理JSON解析错误
 | ||||
| 			return false | ||||
| 		} | ||||
| 		//token map
 | ||||
| 		tokenMap[token] = respDataMap | ||||
| 		return true | ||||
| 	} else { | ||||
| 		// 处理其他状态码
 | ||||
| 		delete(validTokenList, token) | ||||
| 		return false | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // 获取copilot token
 | ||||
| func getGithubToken() gin.HandlerFunc { | ||||
| 	return func(c *gin.Context) { | ||||
| 		requestCount++ | ||||
| 		if err := verifyRequest(c); err != nil { | ||||
| 			badRequest(c) | ||||
| 			return | ||||
| 		} | ||||
| 		token := getRandomToken(validTokenList) | ||||
| 		if respDataMap, exists := getTokenData(token); exists { | ||||
| 			if !isTokenExpired(respDataMap) { | ||||
| 				proxyResp(c, respDataMap) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		if getGithubApi(token) { | ||||
| 			proxyResp(c, tokenMap[token]) | ||||
| 		} else { | ||||
| 			badRequest(c) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // 验证请求代理请求token
 | ||||
| func verifyRequest(c *gin.Context) error { | ||||
| 	if configFile.Verification != "" { | ||||
| 		token := c.GetHeader("Authorization") | ||||
| 		tokenStr := strings.ReplaceAll(token, " ", "") | ||||
| 		configCert := strings.ReplaceAll(configFile.Verification, " ", "") | ||||
| 		if tokenStr != "token"+configCert { | ||||
| 			return errors.New("verification failed") | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // 从map中获取github token对应的copilot token
 | ||||
| func getTokenData(token string) (map[string]interface{}, bool) { | ||||
| 	respDataMap, exists := tokenMap[token] | ||||
| 	return respDataMap, exists | ||||
| } | ||||
| 
 | ||||
| // 检测copilot token是否过期
 | ||||
| func isTokenExpired(respDataMap map[string]interface{}) bool { | ||||
| 	if expiresAt, ok := respDataMap["expires_at"].(float64); ok { | ||||
| 		currentTime := time.Now().Unix() | ||||
| 		expiresAtInt64 := int64(expiresAt) | ||||
| 		return expiresAtInt64 <= currentTime+60 | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // 重置请求计数
 | ||||
| func resetRequestCount() { | ||||
| 	requestCountMutex.Lock() | ||||
| 	defer requestCountMutex.Unlock() | ||||
| 	requestCount = 0 | ||||
| 	successCount = 0 | ||||
| } | ||||
| 
 | ||||
| // 从map中随机获取一个github token
 | ||||
| func getRandomToken(m map[string]bool) string { | ||||
| 	keys := make([]string, 0, len(m)) | ||||
| 	for k := range m { | ||||
| 		keys = append(keys, k) | ||||
| 	} | ||||
| 	if len(keys) == 0 { | ||||
| 		return "" // 返回空字符串或处理其他错误情况
 | ||||
| 	} | ||||
| 	r := rand.New(rand.NewSource(time.Now().UnixNano())) | ||||
| 	randomIndex := r.Intn(len(keys)) | ||||
| 	return keys[randomIndex] | ||||
| } | ||||
							
								
								
									
										49
									
								
								src/global.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/global.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"sync" | ||||
| ) | ||||
| 
 | ||||
| // Config 配置文件结构体
 | ||||
| type Config struct { | ||||
| 	Server struct { | ||||
| 		Domain   string `json:"domain"` | ||||
| 		Host     string `json:"host"` | ||||
| 		Port     int    `json:"port"` | ||||
| 		CertPath string `json:"certPath"` | ||||
| 		KeyPath  string `json:"keyPath"` | ||||
| 	} `json:"server"` | ||||
| 	CopilotConfig struct { | ||||
| 		GithubApiUrl string   `json:"github_api_url"` | ||||
| 		Token        []string `json:"token"` | ||||
| 	} `json:"copilot_config"` | ||||
| 	Verification string `json:"verification"` | ||||
| } | ||||
| 
 | ||||
| // 全局变量
 | ||||
| var ( | ||||
| 	copilotGinEngine *gin.Engine | ||||
| 	//有效ghu_token的map
 | ||||
| 	validGhuTokenMap = make(map[string]bool) | ||||
| 	//与有效ghu_token对于的co_token的map
 | ||||
| 	copilotTokenMap = make(map[string]map[string]interface{}) | ||||
| 	//服务器配置文件
 | ||||
| 	configFile Config | ||||
| 	//请求计数锁
 | ||||
| 	requestCountMutex sync.Mutex | ||||
| 	//githubApi请求计数
 | ||||
| 	githubApiCount = 0 | ||||
| 	//总请求计数
 | ||||
| 	requestCount = 0 | ||||
| 	//请求成功计数
 | ||||
| 	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      = true | ||||
| ) | ||||
							
								
								
									
										28
									
								
								src/main.go
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								src/main.go
									
									
									
									
									
								
							| @ -1,21 +1,13 @@ | ||||
| package main | ||||
| 
 | ||||
| func main() { | ||||
| 	// 初始化配置文件
 | ||||
| 	configFile = initConfig() | ||||
| 
 | ||||
| 	// 创建Gin引擎
 | ||||
| 	engine := setupGinEngine() | ||||
| 
 | ||||
| 	// 初始化有效的token列表
 | ||||
| 	initValidTokenList() | ||||
| 
 | ||||
| 	// 定义路由
 | ||||
| 	setupRoutes(engine) | ||||
| 
 | ||||
| 	// 初始化并启动服务器
 | ||||
| 	initAndStartServer(engine) | ||||
| 
 | ||||
| 	// 显示信息
 | ||||
| 	showMsg() | ||||
| func init() { | ||||
| 	loadConfig()           // 1.加载服务器配置文件
 | ||||
| 	initGinEngine()        // 2.初始化Gin引擎
 | ||||
| 	initValidGhuTokenMap() // 3.初始化有效Ghu_token
 | ||||
| } | ||||
| 
 | ||||
| func main() { | ||||
| 	Routes()      // 1.url路由
 | ||||
| 	StartServer() // 2.启动服务器
 | ||||
| 	showMsg()     // 3.控制台信息显示
 | ||||
| } | ||||
|  | ||||
							
								
								
									
										37
									
								
								src/middleware.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/middleware.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| //1.DomainMiddleware 域名中间件
 | ||||
| //2.VerifyRequestMiddleware 代理服务器自定义的token验证中间件
 | ||||
| 
 | ||||
| // DomainMiddleware 域名中间件
 | ||||
| func DomainMiddleware(domain string) gin.HandlerFunc { | ||||
| 	return func(c *gin.Context) { | ||||
| 		// 获取请求的域名(不包括端口)
 | ||||
| 		requestDomain := strings.Split(c.Request.Host, ":")[0] | ||||
| 		// 使用子域名的情况下,检查域名是否匹配或为本地地址
 | ||||
| 		if requestDomain == domain || requestDomain == "127.0.0.1" || requestDomain == "localhost" { | ||||
| 			c.Next() | ||||
| 			return | ||||
| 		} | ||||
| 		c.AbortWithStatus(403) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // VerifyRequestMiddleware 代理服务器自定义的token验证中间件
 | ||||
| func VerifyRequestMiddleware() gin.HandlerFunc { | ||||
| 	return func(c *gin.Context) { | ||||
| 		if configFile.Verification != "" { | ||||
| 			authHeader := c.GetHeader("Authorization") | ||||
| 			if authHeader != "token "+configFile.Verification { | ||||
| 				c.JSON(401, gin.H{"error": "Unauthorized"}) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		c.Next() | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										16
									
								
								src/routers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/routers.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| 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) | ||||
| } | ||||
							
								
								
									
										173
									
								
								src/server.go
									
									
									
									
									
								
							
							
						
						
									
										173
									
								
								src/server.go
									
									
									
									
									
								
							| @ -1,85 +1,71 @@ | ||||
| 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" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| // 初始化配置文件
 | ||||
| func initConfig() Config { | ||||
| 	// 读取配置文件
 | ||||
| 	exePath, err := os.Executable() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	// 获取执行文件所在目录
 | ||||
| // 加载服务器配置文件
 | ||||
| func loadConfig() { | ||||
| 	// 获取可执行文件所在目录  ./config.json
 | ||||
| 	exePath, _ := os.Executable() | ||||
| 	exeDir := filepath.Dir(exePath) | ||||
| 	configFile, err := os.Open(exeDir + "/config.json") | ||||
| 	configFileTemp, err := os.Open(exeDir + "/config.json") | ||||
| 	if err != nil { | ||||
| 		panic("file \"./config.json\" not found") | ||||
| 	} | ||||
| 	//函数退出时关闭文件流
 | ||||
| 	defer func(configFile *os.File) { | ||||
| 		err := configFile.Close() | ||||
| 		if err != nil { | ||||
| 			panic("close file \"./config.json\" err") | ||||
| 		} | ||||
| 	}(configFile) | ||||
| 	decoder := json.NewDecoder(configFile) | ||||
| 	config := Config{} | ||||
| 	err = decoder.Decode(&config) | ||||
| 		_ = configFile.Close() | ||||
| 	}(configFileTemp) | ||||
| 	//解析json
 | ||||
| 	decoder := json.NewDecoder(configFileTemp) | ||||
| 	err = decoder.Decode(&configFile) | ||||
| 	if err != nil { | ||||
| 		panic("config format err") | ||||
| 	} | ||||
| 	return config | ||||
| } | ||||
| 
 | ||||
| // 创建和配置Gin引擎
 | ||||
| func setupGinEngine() *gin.Engine { | ||||
| // 初始化Gin引擎
 | ||||
| func initGinEngine() { | ||||
| 	// 设置gin模式为发布模式
 | ||||
| 	gin.SetMode(gin.ReleaseMode) | ||||
| 	//关闭gin日志输出  自认为小项目没什么用
 | ||||
| 	gin.DefaultWriter = io.Discard | ||||
| 	engine := gin.New() | ||||
| 	// 设置信任的代理
 | ||||
| 	if err := engine.SetTrustedProxies([]string{"127.0.0.1"}); err != nil { | ||||
| 	// 创建Gin引擎
 | ||||
| 	copilotGinEngine = gin.New() | ||||
| 	// 设置信任的前置代理  用nginx反代需要  不写这个编译会有个警告看着难受
 | ||||
| 	if err := copilotGinEngine.SetTrustedProxies([]string{"127.0.0.1"}); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	return engine | ||||
| } | ||||
| 
 | ||||
| // 定义路由和中间件
 | ||||
| func setupRoutes(engine *gin.Engine) { | ||||
| 	domainDefault := engine.Group("/", DomainMiddleware(configFile.Server.Domain)) | ||||
| 	domainDefault.GET("/copilot_internal/v2/token", getGithubToken()) | ||||
| } | ||||
| 
 | ||||
| // DomainMiddleware 域名中间件
 | ||||
| func DomainMiddleware(domain string) gin.HandlerFunc { | ||||
| 	return func(c *gin.Context) { | ||||
| 		// 检查域名是否匹配
 | ||||
| 		requestDomain := strings.Split(c.Request.Host, ":")[0] | ||||
| 		if requestDomain == domain || requestDomain == "127.0.0.1" { | ||||
| 			c.Next() | ||||
| 		} else { | ||||
| 			c.String(403, "Forbidden") | ||||
| 			c.Abort() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // 初始化和启动服务器
 | ||||
| func initAndStartServer(engine *gin.Engine) { | ||||
| // StartServer 启动服务器并监听ip和端口
 | ||||
| func StartServer() { | ||||
| 	//监听地址是host+port
 | ||||
| 	listenAddress := configFile.Server.Host + ":" + strconv.Itoa(configFile.Server.Port) | ||||
| 	server := createTLSServer(engine, listenAddress) | ||||
| 	server := &http.Server{ | ||||
| 		Addr: listenAddress, | ||||
| 		TLSConfig: &tls.Config{ | ||||
| 			NextProtos: []string{"http/1.1", "http/1.2", "http/2"}, | ||||
| 		}, | ||||
| 		Handler: copilotGinEngine, | ||||
| 	} | ||||
| 	go func() { | ||||
| 		if configFile.Server.Port != 443 { | ||||
| 			err := engine.Run(listenAddress) | ||||
| 			err := copilotGinEngine.Run(listenAddress) | ||||
| 			log.Fatal(err) | ||||
| 		} else { | ||||
| 			err := server.ListenAndServeTLS(configFile.Server.CertPath, configFile.Server.KeyPath) | ||||
| @ -88,34 +74,75 @@ func initAndStartServer(engine *gin.Engine) { | ||||
| 	}() | ||||
| } | ||||
| 
 | ||||
| // 创建TLS服务器配置
 | ||||
| func createTLSServer(engine *gin.Engine, address string) *http.Server { | ||||
| 	return &http.Server{ | ||||
| 		Addr: address, | ||||
| 		TLSConfig: &tls.Config{ | ||||
| 			NextProtos: []string{"http/1.1", "http/1.2", "http/2"}, | ||||
| 		}, | ||||
| 		Handler: engine, | ||||
| 	} | ||||
| // 自定义请求失败返回的状态码和错误信息
 | ||||
| func diyBadRequest(c *gin.Context, code int, errorMessage string) { | ||||
| 	c.JSON(code, gin.H{ | ||||
| 		"message":           errorMessage, | ||||
| 		"documentation_url": "https://docs.github.com/rest", | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // 本服务器响应
 | ||||
| func proxyResp(c *gin.Context, respDataMap map[string]interface{}) { | ||||
| 	// 将map转换为JSON字符串
 | ||||
| 	responseJSON, err := json.Marshal(respDataMap) | ||||
| // registerProxyRoute 反向代理路由
 | ||||
| func registerProxyRoute(r *gin.Engine, routePath, targetURL string) { | ||||
| 	target, err := url.Parse(targetURL) | ||||
| 	if err != nil { | ||||
| 		c.JSON(http.StatusInternalServerError, gin.H{"error": "JSON marshaling error"}) | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	// 请求成功统计
 | ||||
| 	successCount++ | ||||
| 	// 将JSON字符串作为响应体返回
 | ||||
| 	c.Header("Content-Type", "application/json") | ||||
| 	c.String(http.StatusOK, string(responseJSON)) | ||||
| 	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 badRequest(c *gin.Context) { | ||||
| 	c.JSON(http.StatusBadRequest, gin.H{ | ||||
| 		"message":           "Bad credentials", | ||||
| 		"documentation_url": "https://docs.github.com/rest"}) | ||||
| // 修改代码提示内容
 | ||||
| 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.") | ||||
| 		} | ||||
| 		// 将字符串转换为[]rune类型的字符数组
 | ||||
| 		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 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -7,7 +7,15 @@ import ( | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // 控制台显示信息
 | ||||
| // 重置计数
 | ||||
| func resetRequestCount() { | ||||
| 	requestCountMutex.Lock() | ||||
| 	defer requestCountMutex.Unlock() | ||||
| 	requestCount = 0 | ||||
| 	successCount = 0 | ||||
| } | ||||
| 
 | ||||
| // 控制台显示信息  无关紧要的内容
 | ||||
| func showMsg() { | ||||
| 	fmt.Println(color.WhiteString("-----------------------------------------------------------------------")) | ||||
| 	fmt.Println(color.HiBlueString("     _                                          _ _       _   \n ___| |__   __ _ _ __ ___        ___ ___  _ __ (_) | ___ | |_ \n/ __| '_ \\ / _` | '__/ _ \\_____ / __/ _ \\| '_ \\| | |/ _ \\| __|\n\\__ \\ | | | (_| | | |  __/_____| (_| (_) | |_) | | | (_) | |_ \n|___/_| |_|\\__,_|_|  \\___|      \\___\\___/| .__/|_|_|\\___/ \\__|\n                                         |_|                  \n")) | ||||
| @ -21,13 +29,10 @@ func showMsg() { | ||||
| 	} else { | ||||
| 		url = "http://" + configFile.Server.Domain + ":" + strconv.Itoa(configFile.Server.Port) | ||||
| 	} | ||||
| 	var jetStr = color.WhiteString("[Jetbrains]") | ||||
| 	var vsStr = color.WhiteString("[Vscode]") | ||||
| 	var valid = color.WhiteString("[Valid tokens]") | ||||
| 	fmt.Println(jetStr + ": " + color.HiBlueString(url+"/copilot_internal/v2/token")) | ||||
| 	fmt.Println(vsStr + ": " + color.HiBlueString(url)) | ||||
| 	fmt.Println(valid + ": " + color.HiBlueString(strconv.Itoa(len(validTokenList)))) | ||||
| 	fmt.Println(color.WhiteString("-----------------------------------------------------------------------")) | ||||
| 	jetStr, vsStr, valid := color.WhiteString("[Jetbrains]"), color.WhiteString("[Vscode]"), color.WhiteString("[Valid tokens]") | ||||
| 	fmt.Printf("%s: %s/copilot_internal/v2/token\n%s: %s\n%s: %d\n", | ||||
| 		jetStr, color.HiBlueString(url), vsStr, color.HiBlueString(url), valid, len(validGhuTokenMap)) | ||||
| 	fmt.Println("-----------------------------------------------------------------------") | ||||
| 	for { | ||||
| 		requestCountMutex.Lock() | ||||
| 		sCount := successCount | ||||
| @ -38,9 +43,7 @@ func showMsg() { | ||||
| 		if "00:00:00" == currentTime { | ||||
| 			resetRequestCount() | ||||
| 		} | ||||
| 		var s2 = color.WhiteString("[Succeed]") | ||||
| 		var s3 = color.WhiteString("[Failed]") | ||||
| 		var s4 = color.WhiteString("[GithubApi]") | ||||
| 		s2, s3, s4 := color.WhiteString("[Succeed]"), color.WhiteString("[Failed]"), color.WhiteString("[GithubApi]") | ||||
| 		// 打印文本
 | ||||
| 		fmt.Printf("\033[G%s  -  %s: %s    %s: %s    %s: %s  ", | ||||
| 			color.HiYellowString(currentTime), | ||||
| @ -82,5 +82,5 @@ func getGithubTest(c *gin.Context, token string) { | ||||
| 	} | ||||
| 
 | ||||
| 	//token map
 | ||||
| 	tokenMap[token] = respDataMap | ||||
| 	copilotTokenMap[token] = respDataMap | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user