Compare commits

..

No commits in common. "8cb096239b3af7b441d77e018376816c9855fae3" and "2188e6a776729d1c78318d473d39014070108777" have entirely different histories.

5 changed files with 139 additions and 293 deletions

View File

@ -1,15 +1,12 @@
# share-copilot
- 作为代理服务器中转copilot插件的API的请求
- 支持vscode插件和jetbrains插件
- 支持多用户共享一个token
- 优化请求逻辑降低token被ban概率别万人骑基本不可能封
![软件系统网络架构.png](https://img1.imgtp.com/2023/09/10/qTL8A2u9.png)
# 一、自行编译:
```
测试环境:Linux4.18.0-305.3.1.el8.x86_64 GNU/Linux
需要 go环境 出问题自行chatgpt
```
# 一、自编译:
需要 go环境 出问题自行chatgpt
```sh
wget https://dl.google.com/go/go1.21.1.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.21.1.linux-amd64.tar.gz
@ -48,7 +45,7 @@ bash install.sh
##### 2.1 config.json文件说明
```js
domain//监听域名或ip 可用nginx反代
domain//绑定域名
host //ip
@ -93,15 +90,15 @@ verification//自定义验证
### 3.启动
```
scop r --运行 [Esc退出]
scop r --运行
scop rb --后台运行
scop st --停止
scop v --查看状态
```
### 4.完整例子
### 4.完整示例一
##### 4.1 服务端配置修改config.json
config.json
```json
{
@ -123,51 +120,25 @@ scop v --查看状态
}
```
##### 4.2 运行成功截图
运行截图
![微信截图_20230910202900.png](https://img1.imgtp.com/2023/09/10/TbXIaziq.png)
![demo.png](https://img1.imgtp.com/2023/09/09/jNTqxhR8.png)
[Jetbrains]:用于Jetbrains插件
[Vscode]: 用于Vscode插件
[User Request]: 插件请求代理服务器成功次数--统计次数在00:00:00归零
[GithubApi request]: 代理服务器请求githubApi次数
`正常情况下GithubApi request小于User Request因为请求的Token 几十分钟才过期`
时间-合计请求次数: 0 | 成功次数 0 | 失败次数 0 统计次数在00:00:00归零
------
##### 4.3本地配置修改:
**以windows为例**
- **jetbrains插件修改%userprofile%\AppData\Local\github-copilot\host.json**
修改%userprofile%\AppData\Local\github-copilot\host.json
```json
{
"github.com":{
"user":"suibian",//随意填写
"oauth_token":"i_am_free",//与上面verification对应
"user":"suibian",
"oauth_token":"i_am_free",
"dev_override":{
"copilot_token_url":"https://api.example.com/copilot_internal/v2/token"
//你的地址
"copilot_token_url":"https://api.example.com/copilot_internal/v2/token"
}
}
}
```
- **vscode插件修改%userprofile%\.vscode\extensions\github.copilot-xxxx\dist\extension.js**
```js
//添加下列代码注意vscode插件更新需要重新添加jetbrains则不用
process.env.GITHUB_TOKEN="i_am_free"; //与上面verification对应
process.env.GITHUB_API_URL="https://api.example.com"; //你的地址
process.env.CODESPACES="true";
process.env.GITHUB_SERVER_URL="https://github.com";
```
------
##### **4.4测试成功:**
测试:
![demo2.png](https://img1.imgtp.com/2023/09/09/FHDNLixL.png)
# 三、求投喂!谢谢您!
![微信截图_20230910204613.png](https://img1.imgtp.com/2023/09/10/G7qMbyxx.png)

Binary file not shown.

View File

@ -1,6 +1,7 @@
package main
import (
"github.com/nsf/termbox-go"
"sync"
)
@ -19,11 +20,17 @@ type Config struct {
Verification string `json:"verification"`
}
// InfoItem 表示要显示的内容项
type InfoItem struct {
Title string
Value string
TitleColor termbox.Attribute // 标题颜色
ValueColor termbox.Attribute // 内容颜色
}
var (
//初始化需要返回给客户端的响应体
tokenMap = make(map[string]map[string]interface{})
//有效的token列表
validTokenList = make(map[string]bool)
responseData map[string]interface{}
requestCountMutex sync.Mutex
githubApiCount = 0
requestCount = 0

View File

@ -4,9 +4,9 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"github.com/fatih/color"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2"
"github.com/nsf/termbox-go"
"io"
"log"
"math/rand"
@ -15,7 +15,6 @@ import (
"path/filepath"
"strconv"
"strings"
"sync"
"time"
)
@ -31,18 +30,13 @@ func main() {
if err != nil {
log.Fatal(err)
}
// 初始化有效的token列表
initValidTokenList()
// 定义路由
domainDefault := engine.Group("/", DomainMiddleware(configFile.Server.Domain))
domainDefault.GET("/copilot_internal/v2/token", getGithubToken())
// 初始化服务器
domainDefault.GET("/copilot_internal/v2/token", getToken())
initServer(engine)
// 显示信息
showMsg()
}
// 初始化服务器
func initServer(engine *gin.Engine) {
// 配置支持的应用程序协议
server := &http.Server{
@ -68,28 +62,74 @@ func initServer(engine *gin.Engine) {
}
}()
}
// 初始化有效的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)
func showMsg() {
exitChan := make(chan bool)
dataGetter := func() []InfoItem {
var url = ""
if configFile.Server.Port == 80 {
url = "http://" + configFile.Server.Domain
} else if configFile.Server.Port == 443 {
url = "https://" + configFile.Server.Domain
} else {
url = "http://" + configFile.Server.Domain + ":" + strconv.Itoa(configFile.Server.Port)
}
requestCountMutex.Lock()
sCount := successCount
gCount := githubApiCount
requestCountMutex.Unlock()
currentTime := time.Now().Format("2006-01-02 15:04:05")
if "00:00:00" == currentTime {
resetRequestCount()
}
return []InfoItem{
{
Title: "[Jetbrains]",
Value: url + "/copilot_internal/v2/token",
TitleColor: termbox.ColorGreen,
ValueColor: termbox.ColorYellow,
},
{
Title: "[Vscode]",
Value: url,
TitleColor: termbox.ColorGreen,
ValueColor: termbox.ColorYellow,
},
{
Title: "[User Request]:",
Value: strconv.Itoa(sCount),
TitleColor: termbox.ColorGreen,
ValueColor: termbox.ColorYellow,
},
{
Title: "[GithubApi request]:",
Value: strconv.Itoa(gCount),
TitleColor: termbox.ColorGreen,
ValueColor: termbox.ColorYellow,
},
{
Title: "Time:",
Value: currentTime,
TitleColor: termbox.ColorGreen,
ValueColor: termbox.ColorYellow,
},
}
}
exitChan = make(chan bool)
// 调用显示函数,传递数据获取器函数
go DisplayInfo(dataGetter, exitChan)
for {
ev := termbox.PollEvent()
if ev.Type == termbox.EventKey && ev.Key == termbox.KeyEsc {
exitChan <- true // 发送退出信号
break
}
}
wg.Wait()
}
// 获取Github的token
func getGithubToken() gin.HandlerFunc {
}
func getToken() gin.HandlerFunc {
return func(c *gin.Context) {
// 请求计数
requestCount++
incrementRequestCount()
// 如果配置了verification则需要获取请求头中的Authorization令牌
if configFile.Verification != "" {
token := c.GetHeader("Authorization")
@ -97,91 +137,61 @@ func getGithubToken() gin.HandlerFunc {
configCert := strings.ReplaceAll(configFile.Verification, " ", "")
if tokenStr != "token"+configCert {
// 拒绝不符合Verification的请求
badRequest(c)
c.JSON(http.StatusBadRequest, gin.H{
"message": "Bad credentials",
"documentation_url": "https://docs.github.com/rest"})
return
}
}
//从有效的token列表中随机获取一个token
token := getRandomToken(validTokenList)
//判断tokenMap 里的token是否存在
if _, exists := tokenMap[token]; exists {
respDataMap := tokenMap[token]
//判断时间戳key是否存在
if _, exists := respDataMap["expires_at"]; exists {
// 获取当前时间的Unix时间戳
currentTime := time.Now().Unix()
if expiresAt, ok := respDataMap["expires_at"].(float64); ok {
// 判断expires_at是否已经过期
expiresAtInt64 := int64(expiresAt)
//提前一分钟请求
if expiresAtInt64 > currentTime+60 {
//fmt.Println("\n未过期无需请求")
proxyResp(c, tokenMap[token])
} else {
//fmt.Println("\n已过期重新请求")
if getGithubApi(token) {
proxyResp(c, tokenMap[token])
return
} else {
badRequest(c)
}
}
//判断时间戳key是否存在
if _, exists := responseData["expires_at"]; exists {
// 获取当前时间的Unix时间戳
currentTime := time.Now().Unix()
if expiresAt, ok := responseData["expires_at"].(float64); ok {
// 判断expires_at是否已经过期
expiresAtInt64 := int64(expiresAt)
//提前一分钟请求
if expiresAtInt64 > currentTime+60 {
//fmt.Println("\n未过期无需请求")
respProxy(c)
} else {
badRequest(c)
//fmt.Println("\n已过期重新请求")
getGithubApi(c)
respProxy(c)
}
} else {
//tokenMap里的token对应的返回体不存在expires_at
if getGithubApi(token) {
proxyResp(c, tokenMap[token])
return
} else {
badRequest(c)
}
fmt.Println("Age is not an int")
}
} else {
//不存在则githubApi发送请求并存到tokenMap
if getGithubApi(token) {
proxyResp(c, tokenMap[token])
return
} else {
badRequest(c)
}
//向githubApi发送请求
//fmt.Println("\n第一次请求")
getGithubApi(c)
respProxy(c)
}
}
}
// 请求错误
func badRequest(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{
"message": "Bad credentials",
"documentation_url": "https://docs.github.com/rest"})
}
// 本服务器响应
func proxyResp(c *gin.Context, respDataMap map[string]interface{}) {
func respProxy(c *gin.Context) {
// 将map转换为JSON字符串
responseJSON, err := json.Marshal(respDataMap)
responseJSON, err := json.Marshal(responseData)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "JSON marshaling error"})
}
// 请求成功统计
successCount++
incrementSuccessCount()
// 将JSON字符串作为响应体返回
c.Header("Content-Type", "application/json")
c.String(http.StatusOK, string(responseJSON))
}
// 请求githubApi
func getGithubApi(token string) bool {
func getGithubApi(c *gin.Context) {
githubApiCount++
// 设置请求头
headers := map[string]string{
"Authorization": "token " + token,
/*"editor-version": c.GetHeader("editor-version"),
"Authorization": "token " + getRandomToken(configFile.CopilotConfig.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"),*/
"accept-encoding": c.GetHeader("accept-encoding"),
}
// 发起GET请求
response, err := resty.New().R().
@ -189,28 +199,17 @@ func getGithubApi(token string) bool {
Get(configFile.CopilotConfig.GithubApiUrl)
if err != nil {
// 处理请求错误
return false
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// 判断响应状态码
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
err = json.Unmarshal(response.Body(), &responseData)
if err != nil {
// 处理JSON解析错误
c.JSON(http.StatusInternalServerError, gin.H{"error": "JSON parsing error"})
return
}
}
// 初始化配置文件
func initConfig() Config {
// 读取配置文件
exePath, err := os.Executable()
@ -237,30 +236,26 @@ func initConfig() Config {
}
return config
}
// 重置请求计数
func incrementRequestCount() {
requestCount++
}
func incrementSuccessCount() {
successCount++
}
func resetRequestCount() {
requestCountMutex.Lock()
defer requestCountMutex.Unlock()
requestCount = 0
successCount = 0
}
// 从map中随机获取一个key
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 {
func getRandomToken(tokens []string) string {
if len(tokens) == 0 {
return "" // 返回空字符串或处理其他错误情况
}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
randomIndex := r.Intn(len(keys))
return keys[randomIndex]
randomIndex := r.Intn(len(tokens))
return tokens[randomIndex]
}
// DomainMiddleware 域名中间件
func DomainMiddleware(domain string) gin.HandlerFunc {
return func(c *gin.Context) {
// 检查域名是否匹配
@ -273,44 +268,3 @@ func DomainMiddleware(domain string) gin.HandlerFunc {
}
}
}
// 显示信息
func showMsg() {
var url = ""
if configFile.Server.Port == 80 {
url = "http://" + configFile.Server.Domain
} else if configFile.Server.Port == 443 {
url = "https://" + configFile.Server.Domain
} 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("-----------------------------------------------------------------------"))
for {
requestCountMutex.Lock()
sCount := successCount
tCount := requestCount
gCount := githubApiCount
requestCountMutex.Unlock()
currentTime := time.Now().Format("2006-01-02 15:04:05")
if "00:00:00" == currentTime {
resetRequestCount()
}
var s2 = color.WhiteString("[Succeed]")
var s3 = color.WhiteString("[Failed]")
var s4 = color.WhiteString("[GithubApi]")
// 打印文本
fmt.Printf("\033[G%s - %s: %s %s: %s %s: %s ",
color.HiYellowString(currentTime),
s2, color.GreenString(strconv.Itoa(sCount)),
s3, color.RedString(strconv.Itoa(tCount-sCount)),
s4, color.CyanString(strconv.Itoa(gCount)))
time.Sleep(1 * time.Second) //
}
}

View File

@ -1,86 +0,0 @@
package main
import (
"encoding/json"
"github.com/gin-gonic/gin"
"net/http"
)
func getGithubTest(c *gin.Context, token string) {
githubApiCount++
data1 := `
{
"chat_enabled": false,
"code_quote_enabled": false,
"code_quote_v2_enabled": false,
"copilotignore_enabled": false,
"expires_at": 3194360727,
"prompt_8k": true,
"public_suggestions": "disabled",
"refresh_in": 1500,
"sku": "free_educational",
"telemetry": "disabled",
"token": "tid=;exp=1694360727;sku=free_educational;st=dotcom;8kp=1:",
"tracking_id": ""
}
`
data2 := `
{
"chat_enabled": false,
"code_quote_enabled": false,
"code_quote_v2_enabled": false,
"copilotignore_enabled": false,
"expires_at": 2294360727,
"prompt_8k": true,
"public_suggestions": "disabled",
"refresh_in": 1500,
"sku": "free_educational",
"telemetry": "disabled",
"token": "tid=;exp=1694360727;sku=free_educational;st=dotcom;8kp=1:",
"tracking_id": ""
}
`
data3 := `
{
"chat_enabled": false,
"code_quote_enabled": false,
"code_quote_v2_enabled": false,
"copilotignore_enabled": false,
"expires_at": 3394360727,
"prompt_8k": true,
"public_suggestions": "disabled",
"refresh_in": 1500,
"sku": "free_educational",
"telemetry": "disabled",
"token": "tid=;exp=1694360727;sku=free_educational;st=dotcom;8kp=1:",
"tracking_id": ""
}
`
//响应体map
var respDataMap = make(map[string]interface{})
if token == "1" {
err := json.Unmarshal([]byte(data1), &respDataMap)
if err != nil {
// 处理JSON解析错误
c.JSON(http.StatusInternalServerError, gin.H{"error": "JSON parsing error"})
return
}
} else if token == "2" {
err := json.Unmarshal([]byte(data2), &respDataMap)
if err != nil {
// 处理JSON解析错误
c.JSON(http.StatusInternalServerError, gin.H{"error": "JSON parsing error"})
return
}
} else {
err := json.Unmarshal([]byte(data3), &respDataMap)
if err != nil {
// 处理JSON解析错误
c.JSON(http.StatusInternalServerError, gin.H{"error": "JSON parsing error"})
return
}
}
//token map
tokenMap[token] = respDataMap
}