Compare commits

..

6 Commits

Author SHA1 Message Date
ben Gutier
8cb096239b 修复多个token请求混乱问题 2023-09-11 01:31:07 +08:00
ben Gutier
6ac0e083e6 修复无法后台运行 2023-09-10 23:10:55 +08:00
ben Gutier
e331cb9885 修复无法后台运行 2023-09-10 23:04:45 +08:00
ben Gutier
d274cf819a 修复无法后台运行 2023-09-10 23:04:14 +08:00
ben Gutier
088c15e54b 1.0.0 2023-09-10 20:57:46 +08:00
ben Gutier
23b7c3e825 修改 2023-09-10 20:48:39 +08:00
5 changed files with 293 additions and 139 deletions

View File

@ -1,12 +1,15 @@
# 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
```
```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
@ -45,7 +48,7 @@ bash install.sh
##### 2.1 config.json文件说明
```js
domain//绑定域名
domain//监听域名或ip 可用nginx反代
host //ip
@ -90,15 +93,15 @@ verification//自定义验证
### 3.启动
```
scop r --运行
scop r --运行 [Esc退出]
scop rb --后台运行
scop st --停止
scop v --查看状态
```
### 4.完整示例一
### 4.完整例子
config.json
##### 4.1 服务端配置修改config.json
```json
{
@ -120,25 +123,51 @@ config.json
}
```
运行截图
##### 4.2 运行成功截图
![demo.png](https://img1.imgtp.com/2023/09/09/jNTqxhR8.png)
![微信截图_20230910202900.png](https://img1.imgtp.com/2023/09/10/TbXIaziq.png)
时间-合计请求次数: 0 | 成功次数 0 | 失败次数 0 统计次数在00:00:00归零
[Jetbrains]:用于Jetbrains插件
[Vscode]: 用于Vscode插件
[User Request]: 插件请求代理服务器成功次数--统计次数在00:00:00归零
[GithubApi request]: 代理服务器请求githubApi次数
`正常情况下GithubApi request小于User Request因为请求的Token 几十分钟才过期`
修改%userprofile%\AppData\Local\github-copilot\host.json
------
##### 4.3本地配置修改:
**以windows为例**
- **jetbrains插件修改%userprofile%\AppData\Local\github-copilot\host.json**
```json
{
"github.com":{
"user":"suibian",
"oauth_token":"i_am_free",
"user":"suibian",//随意填写
"oauth_token":"i_am_free",//与上面verification对应
"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,7 +1,6 @@
package main
import (
"github.com/nsf/termbox-go"
"sync"
)
@ -20,17 +19,11 @@ type Config struct {
Verification string `json:"verification"`
}
// InfoItem 表示要显示的内容项
type InfoItem struct {
Title string
Value string
TitleColor termbox.Attribute // 标题颜色
ValueColor termbox.Attribute // 内容颜色
}
var (
//初始化需要返回给客户端的响应体
responseData map[string]interface{}
tokenMap = make(map[string]map[string]interface{})
//有效的token列表
validTokenList = make(map[string]bool)
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,6 +15,7 @@ import (
"path/filepath"
"strconv"
"strings"
"sync"
"time"
)
@ -30,13 +31,18 @@ func main() {
if err != nil {
log.Fatal(err)
}
// 初始化有效的token列表
initValidTokenList()
// 定义路由
domainDefault := engine.Group("/", DomainMiddleware(configFile.Server.Domain))
domainDefault.GET("/copilot_internal/v2/token", getToken())
domainDefault.GET("/copilot_internal/v2/token", getGithubToken())
// 初始化服务器
initServer(engine)
// 显示信息
showMsg()
}
// 初始化服务器
func initServer(engine *gin.Engine) {
// 配置支持的应用程序协议
server := &http.Server{
@ -62,74 +68,28 @@ func initServer(engine *gin.Engine) {
}
}()
}
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
}
}
// 初始化有效的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()
}
func getToken() gin.HandlerFunc {
// 获取Github的token
func getGithubToken() gin.HandlerFunc {
return func(c *gin.Context) {
// 请求计数
incrementRequestCount()
requestCount++
// 如果配置了verification则需要获取请求头中的Authorization令牌
if configFile.Verification != "" {
token := c.GetHeader("Authorization")
@ -137,61 +97,91 @@ func getToken() gin.HandlerFunc {
configCert := strings.ReplaceAll(configFile.Verification, " ", "")
if tokenStr != "token"+configCert {
// 拒绝不符合Verification的请求
c.JSON(http.StatusBadRequest, gin.H{
"message": "Bad credentials",
"documentation_url": "https://docs.github.com/rest"})
badRequest(c)
return
}
}
//判断时间戳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)
//从有效的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)
}
}
} else {
//fmt.Println("\n已过期重新请求")
getGithubApi(c)
respProxy(c)
badRequest(c)
}
} else {
fmt.Println("Age is not an int")
//tokenMap里的token对应的返回体不存在expires_at
if getGithubApi(token) {
proxyResp(c, tokenMap[token])
return
} else {
badRequest(c)
}
}
} else {
//向githubApi发送请求
//fmt.Println("\n第一次请求")
getGithubApi(c)
respProxy(c)
//不存在则githubApi发送请求并存到tokenMap
if getGithubApi(token) {
proxyResp(c, tokenMap[token])
return
} else {
badRequest(c)
}
}
}
}
func respProxy(c *gin.Context) {
// 请求错误
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{}) {
// 将map转换为JSON字符串
responseJSON, err := json.Marshal(responseData)
responseJSON, err := json.Marshal(respDataMap)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "JSON marshaling error"})
}
// 请求成功统计
incrementSuccessCount()
successCount++
// 将JSON字符串作为响应体返回
c.Header("Content-Type", "application/json")
c.String(http.StatusOK, string(responseJSON))
}
func getGithubApi(c *gin.Context) {
// 请求githubApi
func getGithubApi(token string) bool {
githubApiCount++
// 设置请求头
headers := map[string]string{
"Authorization": "token " + getRandomToken(configFile.CopilotConfig.Token),
"editor-version": c.GetHeader("editor-version"),
"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"),
"accept-encoding": c.GetHeader("accept-encoding"),*/
}
// 发起GET请求
response, err := resty.New().R().
@ -199,17 +189,28 @@ func getGithubApi(c *gin.Context) {
Get(configFile.CopilotConfig.GithubApiUrl)
if err != nil {
// 处理请求错误
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
return false
}
err = json.Unmarshal(response.Body(), &responseData)
if err != nil {
// 处理JSON解析错误
c.JSON(http.StatusInternalServerError, gin.H{"error": "JSON parsing 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
}
}
// 初始化配置文件
func initConfig() Config {
// 读取配置文件
exePath, err := os.Executable()
@ -236,26 +237,30 @@ func initConfig() Config {
}
return config
}
func incrementRequestCount() {
requestCount++
}
func incrementSuccessCount() {
successCount++
}
// 重置请求计数
func resetRequestCount() {
requestCountMutex.Lock()
defer requestCountMutex.Unlock()
requestCount = 0
successCount = 0
}
func getRandomToken(tokens []string) string {
if len(tokens) == 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 {
return "" // 返回空字符串或处理其他错误情况
}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
randomIndex := r.Intn(len(tokens))
return tokens[randomIndex]
randomIndex := r.Intn(len(keys))
return keys[randomIndex]
}
// DomainMiddleware 域名中间件
func DomainMiddleware(domain string) gin.HandlerFunc {
return func(c *gin.Context) {
// 检查域名是否匹配
@ -268,3 +273,44 @@ 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) //
}
}

86
source/test.go Normal file
View File

@ -0,0 +1,86 @@
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
}