This commit is contained in:
cdle
2023-07-05 17:19:56 +08:00
parent 873bccce55
commit 643ecd0f9c
21 changed files with 573 additions and 81 deletions
+23
View File
@@ -0,0 +1,23 @@
.PHONY: build
BINARY_NAME=sillyGirl
build:
GOARCH=amd64 GOOS=darwin go build -o ${BINARY_NAME}-darwin -ldflags "-s -w" main.go
GOARCH=amd64 GOOS=linux go build -o ${BINARY_NAME}-linux -ldflags "-s -w" main.go
release:
GOARCH=amd64 GOOS=darwin garble build -o ${BINARY_NAME}-darwin -a -ldflags "-s -w" -trimpath -buildmode=pie main.go
GOARCH=amd64 GOOS=linux garble build -o ${BINARY_NAME}-linux -a -ldflags "-s -w" -trimpath -buildmode=pie main.go
pre-release:
go install mvdan.cc/garble@latest
run:
go build -o ${BINARY_NAME} main.go
./${BINARY_NAME}
clean:
go clean
rm ${BINARY_NAME}-darwin
rm ${BINARY_NAME}-linux
+96
View File
@@ -0,0 +1,96 @@
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
func getJS(user, pass, title string) string {
baseURL := "http://aut.zhelee.cn/plugin/download"
queryParams := url.Values{}
queryParams.Set("title", title)
queryParams.Set("username", user)
queryParams.Set("password", pass)
queryParams.Set("version", "1")
u, err := url.Parse(baseURL)
if err != nil {
panic(err)
}
u.RawQuery = queryParams.Encode()
urlStr := u.String()
method := "POST"
client := &http.Client{}
req, err := http.NewRequest(method, urlStr, nil)
if err != nil {
fmt.Println(err)
return ""
}
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return ""
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
return ""
}
// fmt.Println(string(body))
return string(body)
}
func main() {
user := "test1234"
pass := "test1234"
title := "hook"
plaintext := getJS(user, pass, title)
key := getKey("test1234")
panic(key)
decrypted, err := decrypt(string(plaintext), []byte(key))
// 打印解密后的数据
fmt.Println(err)
fmt.Printf("解密后的数据:%s\n", decrypted)
}
func getKey(user string) string {
t := user + "killsillygirltodeath109times"
if len(t) > 32 {
return t[:32]
}
// if len(t) > 24 {
// return t[:24]
// }
// return t[:16]
return t[:24]
}
// 使用AES-CBC模式进行解密
func decrypt(encrypted string, key []byte) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
ciphertext, err := base64.StdEncoding.DecodeString(encrypted)
if err != nil {
return "", err
}
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]
decrypter := cipher.NewCBCDecrypter(block, iv)
decrypted := make([]byte, len(ciphertext))
decrypter.CryptBlocks(decrypted, ciphertext)
return string(decrypted), nil
}
+4
View File
@@ -613,6 +613,10 @@ func (sender *CustomSender) Reply(msgs ...interface{}) (string, error) {
push = true
case string:
args = append(args, item)
default:
if item != nil {
args = append(args, fmt.Sprint(item))
}
}
}
if len(args) == 0 {
+1 -1
View File
@@ -8,6 +8,6 @@
</head>
<body>
<div id="root"></div>
<script src="/admin/umi.29bb0b44.js"></script>
<script src="/admin/umi.7bcd86f8.js"></script>
</body></html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+36
View File
@@ -319,6 +319,7 @@ type CarryGroup struct {
// CARRY API
func init() {
GinApi(GET, "/api/carry/groups", RequireAuth, func(ctx *gin.Context) {
current := utils.Int(ctx.Query("current"))
pageSize := utils.Int(ctx.Query("pageSize"))
@@ -364,6 +365,41 @@ func init() {
"data": names,
})
})
GinApi(GET, "/api/proxy/scripts", RequireAuth, func(ctx *gin.Context) {
var scripts = map[string]string{}
functions := Functions
for _, function := range functions {
if function.UUID != "" {
scripts[function.UUID] = function.Title + ".js"
}
}
ctx.JSON(200, map[string]interface{}{
"success": true,
"data": scripts,
})
})
var isNumeric = func(keyword string) bool {
for _, c := range keyword {
if c != '.' && (c < '0' || c > '9') {
return false
}
}
return true
}
GinApi(GET, "/api/proxy/rules", RequireAuth, func(ctx *gin.Context) {
keyword := ctx.Query("keyword")
var scripts = map[string]string{}
scripts[keyword] = keyword
if strings.HasSuffix(keyword, ".") && !isNumeric(keyword) {
for _, suffix := range []string{"com", "cn"} {
scripts[keyword+suffix] = keyword + suffix
}
}
ctx.JSON(200, map[string]interface{}{
"success": true,
"data": scripts,
})
})
GinApi(GET, "/api/carry/group_selects", RequireAuth, func(ctx *gin.Context) {
chat_id := ctx.Query("chat_id")
platform := ctx.Query("platform")
+3 -3
View File
@@ -2,9 +2,9 @@ package core
import cron "github.com/robfig/cron/v3"
var C *cron.Cron
var CRON *cron.Cron
func init() {
C = cron.New(cron.WithSeconds())
C.Start()
CRON = cron.New(cron.WithSeconds())
CRON.Start()
}
+1 -1
View File
@@ -383,7 +383,7 @@ func AddCommand(cmds []*common.Function) {
if len(regexp.MustCompile(`\S+`).FindAllString(cron, -1)) == 5 {
Cron = "0 " + Cron
}
cronId, err := C.AddFunc(Cron, func() {
cronId, err := CRON.AddFunc(Cron, func() {
cmds[j].Handle(&Faker{
Admin: true,
Type: plt,
+38 -2
View File
@@ -3,6 +3,8 @@ package core
import (
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"os"
"regexp"
@@ -33,14 +35,43 @@ func Init() {
sillyGirl.Set("started_at", time.Now().Format("2006-01-02 15:04:05"))
storage.Watch(sillyGirl, "compiled_at", func(old, new, key string) *storage.Final {
if old != new {
var transport *http.Transport
instance, err := GetProxyTransport("https://raw.githubusercontent.com", "", nil)
if err != nil {
console.Error("升级代理错误:%s", err)
return &storage.Final{
Error: fmt.Errorf("升级代理错误::%s", err),
}
}
if instance != nil {
defer instance.Close()
}
if instance != nil {
transport = &http.Transport{
Dial: func(string, string) (net.Conn, error) {
return instance, nil
},
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}
var client = &http.Client{}
if transport != nil {
client.Transport = transport
}
console.Debug("正在从 cdle/binary 获取版本号...")
data, err := httplib.Get("https://raw.githubusercontent.com/cdle/binary/main/compile_time.go").Bytes()
req, _ := http.NewRequest("GET", "https://raw.githubusercontent.com/cdle/binary/main/compile_time.go", strings.NewReader(""))
resp, err := client.Do(req)
if err != nil {
console.Error("获取版本号错误:%s", err)
return &storage.Final{
Error: fmt.Errorf("貌似网络不太行啊:%s", err),
}
}
defer resp.Body.Close()
var data, _ = ioutil.ReadAll(resp.Body)
latest_version := regexp.MustCompile(`\d{13}`).FindString(string(data))
if latest_version <= compiled_at {
console.Debug("当前版本 %s 已是最新,无需升级", compiled_at)
@@ -48,12 +79,17 @@ func Init() {
Message: fmt.Sprintf("当前版本 %s 已是最新,无需升级", compiled_at),
}
}
client = &http.Client{}
if transport != nil {
client.Transport = transport
}
console.Debug("正在从 cdle/binary 获取最新版本 %s 编译文件...", latest_version)
qurl := "https://raw.githubusercontent.com/cdle/binary/master/sillyGirl_" + runtime.GOOS + "_" + runtime.GOARCH + "_" + latest_version
if runtime.GOOS == "windows" {
qurl += ".exe"
}
resp, err := http.Get(qurl)
req, _ = http.NewRequest("GET", qurl, strings.NewReader(""))
resp, err = client.Do(req)
if err != nil {
console.Error("获取最新编译文件错误:%s", err)
return &storage.Final{
+8 -5
View File
@@ -18,9 +18,12 @@ func MakeBucketObject(vm *goja.Runtime, uuid string, on_start bool, bucket stora
})
return rt
})
obj.Set("set", func(key, value interface{}) error {
_, err := SetBucketKeyValue(bucket, key, value)
return err
obj.Set("set", func(key, value interface{}) interface{} {
msg, err := SetBucketKeyValue(bucket, key, value)
if err != nil {
panic(Error(vm, err))
}
return msg
})
obj.Set("delete", func(key interface{}) error {
_, err := bucket.Set(key, "")
@@ -84,10 +87,10 @@ func JsBucket(vm *goja.Runtime, name string, uuid string, on_start bool) goja.Pr
return vm.ToValue(result)
},
Set: func(target *goja.Object, property string, value, receiver goja.Value) (success bool) {
result := target.Get("set").Export().(func(interface{}, interface{}) error)(
target.Get("set").Export().(func(interface{}, interface{}) interface{})(
property, value.Export(),
)
return result == nil
return true
},
})
}
+55 -20
View File
@@ -3,18 +3,20 @@ package core
import (
"bytes"
"fmt"
"net"
"net/http"
u "net/url"
"strconv"
"strings"
"time"
C "github.com/Dreamacro/clash/constant"
"github.com/cdle/sillyplus/utils"
"github.com/dop251/goja"
"github.com/goccy/go-json"
)
func fetch(vm *goja.Runtime, wts ...interface{}) (interface{}, error) {
func fetch(vm *goja.Runtime, uuid string, wts ...interface{}) (interface{}, error) {
var method = "get"
var url = ""
var req *http.Request
@@ -31,6 +33,7 @@ func fetch(vm *goja.Runtime, wts ...interface{}) (interface{}, error) {
// var useproxy bool
var timeout time.Duration = 0
var login = false
var instance C.Conn
for _, wt := range wts {
switch wt := wt.(type) {
case string:
@@ -79,30 +82,63 @@ func fetch(vm *goja.Runtime, wts ...interface{}) (interface{}, error) {
case "form":
formData = props[i].(map[string]interface{})
case "proxy":
var url, user, password string
for k, v := range props[i].(map[string]interface{}) {
if k == "url" {
url = fmt.Sprint(v)
}
if k == "user" {
user = fmt.Sprint(v)
}
if k == "password" {
password = fmt.Sprint(v)
}
var err error
var params = props[i].(map[string]interface{})
if _, ok := params["name"]; !ok {
params["name"] = "临时"
}
if url != "" {
var err error
transport, err = GetTransport(url, user, password)
if err != nil {
return nil, err
}
instance, err = GetProxyTransport(url, uuid, params)
if err != nil {
panic(Error(vm, err))
}
if instance != nil {
defer instance.Close()
}
// var url, user, password string
// for k, v := range props[i].(map[string]interface{}) {
// if k == "url" {
// url = fmt.Sprint(v)
// }
// if k == "user" {
// user = fmt.Sprint(v)
// }
// if k == "password" {
// password = fmt.Sprint(v)
// }
// }
// if url != "" {
// var err error
// transport, err = GetTransport(url, user, password)
// if err != nil {
// return nil, err
// }
// }
}
}
}
method = strings.ToUpper(method)
var err error
if instance == nil {
instance, err = GetProxyTransport(url, uuid, nil)
if err != nil {
panic(Error(vm, err))
}
if instance != nil {
defer instance.Close()
}
}
if instance != nil {
transport = &http.Transport{
Dial: func(string, string) (net.Conn, error) {
return instance, nil
},
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}
method = strings.ToUpper(method)
if len(formData) > 0 {
data := u.Values{}
for key, value := range formData {
@@ -118,7 +154,6 @@ func fetch(vm *goja.Runtime, wts ...interface{}) (interface{}, error) {
data.Set(key, fmt.Sprintf("%v", v))
}
}
req, err = http.NewRequest(method, url, strings.NewReader(data.Encode()))
if err != nil {
return nil, err
+1 -1
View File
@@ -214,7 +214,7 @@ func initPlugins() {
Functions[i].Running = false
if len(Functions[i].CronIds) != 0 {
for _, id := range Functions[i].CronIds {
C.Remove(cron.EntryID(id))
CRON.Remove(cron.EntryID(id))
}
}
Functions = append(Functions[:i], Functions[i+1:]...)
+5 -5
View File
@@ -105,7 +105,7 @@ func CancelPluginCrons(uuid string) {
v, ok := crons.Load(uuid)
if ok {
for _, id := range *v.(*[]cron.EntryID) {
C.Remove(id)
CRON.Remove(id)
}
}
}
@@ -207,7 +207,7 @@ func SetPluginMethod(vm *goja.Runtime, uuid string, on_start bool, running func(
if len(regexp.MustCompile(`\S+`).FindAllString(cron, -1)) == 5 {
cron = "0 " + cron
}
id, err := C.AddFunc(cron, func() {
id, err := CRON.AddFunc(cron, func() {
// mutex := GetMutex(uuid)
// mutex.Lock()
// defer mutex.Unlock()
@@ -228,7 +228,7 @@ func SetPluginMethod(vm *goja.Runtime, uuid string, on_start bool, running func(
}
})
o.Set("remove", func(id cron.EntryID) {
C.Remove(id)
CRON.Remove(id)
})
return o
})
@@ -351,7 +351,7 @@ func SetPluginMethod(vm *goja.Runtime, uuid string, on_start bool, running func(
vm.Set("fetch", func(wts ...interface{}) interface{} {
promise, resolve, reject := vm.NewPromise()
go func() {
obj, err := fetch(vm, wts...)
obj, err := fetch(vm, uuid, wts...)
if err != nil {
reject(err)
} else {
@@ -361,7 +361,7 @@ func SetPluginMethod(vm *goja.Runtime, uuid string, on_start bool, running func(
return promise
})
vm.Set("request", func(wts ...interface{}) interface{} {
obj, err := fetch(vm, wts...)
obj, err := fetch(vm, uuid, wts...)
if err != nil {
panic(Error(vm, err))
}
+253
View File
@@ -0,0 +1,253 @@
package core
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"reflect"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/adapter"
C "github.com/Dreamacro/clash/constant"
"github.com/cdle/sillyplus/core/storage"
"github.com/cdle/sillyplus/utils"
"github.com/google/uuid"
)
// type ProxyKey struct {
// }
type ProxyConfig struct {
Type string `json:"type"`
Server string `json:"server"`
Port int `json:"port"`
Name string `json:"name"`
// ProxyKey
// RuleMatcher *RuleMatcher `json:"-"`
Conn C.Proxy `json:"-"`
Rules []string `json:"rules"`
Plugins []string `json:"plugins"`
Cipher string `json:"cipher,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
SkipCertVerify bool `json:"skip-cert-verify,omitempty"`
TLS bool `json:"tls,omitempty"`
CreatedAt int `json:"created_at,omitempty"`
Remark string `json:"remark,omitempty"`
Enable bool `json:"enable,omitempty"`
// UDP bool `json:"udp,omitempty"`
// Plugin string `json:"plugin,omitempty"`
// PluginOpts map[string]string `json:"plugin-opts,omitempty"`
// Obfs string `json:"obfs,omitempty"`
// ObfsOpts map[string]string `json:"obfs-opts,omitempty"`
// Enable bool `json:"enable,omitempty"`
// ExcludeIPs []string `json:"exclude-ip,omitempty"`
// ProxyGroups []string `json:"proxy-groups,omitempty"`
}
var Proxies sync.Map
var proxies = MakeBucket("proxies")
func GetProxyTransport(rawURL string, uuid string, params map[string]interface{}) (C.Conn, error) {
u, err := url.Parse(rawURL)
if err != nil {
return nil, err
}
port := u.Port()
if port == "" {
switch u.Scheme {
case "https":
port = "443"
case "http":
port = "80"
default:
err = fmt.Errorf("%s scheme not Support", rawURL)
return nil, err
}
}
addr := C.Metadata{
Host: u.Hostname(),
DstIP: nil,
DstPort: port,
}
var p *ProxyConfig
if params != nil {
params["port"] = utils.Int(params["port"])
conn, err := adapter.ParseProxy(params)
if err != nil {
err = fmt.Errorf("代理配置错误:%v", err)
return nil, err
}
i, err := conn.DialContext(context.Background(), &addr)
if err != nil {
err = fmt.Errorf("代理连接错误:%v", err)
}
return i, err
}
var plugins = []*ProxyConfig{}
Proxies.Range(func(key, value any) bool {
cfg := value.(*ProxyConfig)
if Contains(cfg.Rules, addr.Host) {
p = cfg
return false
}
if Contains(cfg.Plugins, uuid) {
plugins = append(plugins, cfg)
}
return true
})
if p != nil {
i, err := p.Conn.DialContext(context.Background(), &addr)
if err != nil {
err = fmt.Errorf("%s(%s)代理错误:%v", p.Type, p.Name, err)
}
return i, err
}
if len(plugins) != 0 {
i, err := plugins[0].Conn.DialContext(context.Background(), &addr)
if err != nil {
err = fmt.Errorf("%s(%s)代理错误:%v", p.Type, p.Name, err)
}
return i, err
}
return nil, nil
}
func init() {
proxies.Foreach(func(b1, b2 []byte) error {
new := string(b2)
var ncfg = ProxyConfig{}
if strings.HasPrefix(new, "o:") {
err := json.Unmarshal([]byte(strings.Replace(new, "o:", "", 1)), &ncfg)
if err != nil {
console.Log("无法解析的代理数据:", err)
return nil
}
// ncfg.RuleMatcher, err = preprocessRules(ncfg.Rules)
if err != nil {
console.Log("无法处理的代理匹配规则:", err)
return nil
}
p, err := adapter.ParseProxy(structToMap(ncfg))
if err != nil {
console.Log("无法解析的代理:", err)
return nil
}
ncfg.Conn = p
// fmt.Println("===", string(utils.JsonMarshal(ncfg)))
// fmt.Println(ncfg.RuleMatcher.Match("106.52.87.206"))
// fmt.Println(ncfg.RuleMatcher.Match("api.telegram.org"))
Proxies.Store(string(b1), &ncfg)
}
return nil
})
storage.Watch(proxies, "*", func(old, new, key string) *storage.Final {
_, err := uuid.Parse(key)
if err != nil {
return &storage.Final{
Error: errors.New("非法的UUID"),
}
}
// var ocfg = ProxyConfig{}
// if strings.HasPrefix(new, "o:") {
// json.Unmarshal([]byte(strings.Replace(old, "o:", "", 1)), &ocfg)
// }
// var okey = ProxyKey{
// Type: ocfg.Type,
// Server: ocfg.Server,
// Port: ocfg.Port,
// }
// var nkey = ProxyKey{}
var ncfg = ProxyConfig{}
if new == "" { //删除逻辑
Proxies.Delete(key)
return nil
}
if strings.HasPrefix(new, "o:") {
err := json.Unmarshal([]byte(strings.Replace(new, "o:", "", 1)), &ncfg)
if err != nil {
return &storage.Final{
Error: err,
}
}
// nkey = ProxyKey{
// Type: ncfg.Type,
// Server: ncfg.Server,
// Port: ncfg.Port,
// }
}
// ov, ok := Proxies.Load(nkey)
// if ok && (!IsDifferent(ocfg, ncfg, []string{"Name", "UUID", "Rules", "Plugins"}) || checkProxy(ov.(C.Proxy))) { //代理依旧有效
// return nil
// }
// ncfg.RuleMatcher, err = preprocessRules(ncfg.Rules)
if err != nil {
return &storage.Final{
Error: errors.New("不支持的代理匹配规则:" + err.Error()),
}
}
p, err := adapter.ParseProxy(structToMap(ncfg))
if err != nil {
return &storage.Final{
Error: err,
}
}
ncfg.Conn = p
ncfg.CreatedAt = int(time.Now().Unix())
Proxies.Store(key, &ncfg)
return nil
})
}
func structToMap(s interface{}) map[string]interface{} {
result := make(map[string]interface{})
d, _ := json.Marshal(s)
json.Unmarshal(d, &result)
return result
}
func IsDifferent(a, b interface{}, ignoreFields []string) bool {
aVal := reflect.ValueOf(a)
bVal := reflect.ValueOf(b)
if aVal.Kind() != reflect.Struct || bVal.Kind() != reflect.Struct {
// 如果不是 struct 类型,则返回 true(不同)
return true
}
// 获取 struct 的字段数
numFields := aVal.NumField()
// 遍历 struct 的所有字段,检查是否有不同
for i := 0; i < numFields; i++ {
aField := aVal.Field(i)
bField := bVal.Field(i)
// 获取字段名
fieldName := aVal.Type().Field(i).Name
// 如果字段名在 ignoreFields 中,则跳过比较
if Contains(ignoreFields, fieldName) {
continue
}
// 如果字段类型不同,则返回 true(不同)
if aField.Type() != bField.Type() {
return true
}
// 如果字段值不同,则返回 true(不同)
if !reflect.DeepEqual(aField.Interface(), bField.Interface()) {
return true
}
}
// 如果所有字段都相同,则返回 false(相同)
return false
}
+13 -8
View File
@@ -312,11 +312,16 @@ func initWeb() {
// handleHTTP(c.Writer, c.Request)
})
port := sillyGirl.GetString("port", "8080")
port := sillyGirl.GetInt("port")
if port == 0 {
sillyGirl.Set("port", 8080)
port = 8080
}
srvs := []*http.Server{{
Addr: ":" + port,
Addr: ":" + fmt.Sprint(port),
Handler: Server,
}}
storage.Watch(sillyGirl, "port", func(old, new, key string) *storage.Final {
if new == "" {
new = "8080"
@@ -334,9 +339,9 @@ func initWeb() {
srvs = append(srvs, srv)
go func() {
logs.Info("Http服务(%s)重新运行", port)
logs.Info("Http服务(%d)重新运行", port)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logs.Error("Http服务(%s)运行失败:%s", port, err.Error())
logs.Error("Http服务(%d)运行失败:%s", port, err.Error())
ch <- err
}
}()
@@ -350,7 +355,7 @@ func initWeb() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srvs[0].Shutdown(ctx); err == nil {
logs.Info("Http服务(%s)关闭", old)
logs.Info("Http服务(%d)关闭", old)
}
srvs = srvs[1:]
}
@@ -362,12 +367,12 @@ func initWeb() {
// logs.Info("Http服务(%s)开始运行", port)
logs.Info("管理员面板:")
logs.Info(" > 本机: http://localhost:%s/admin", port)
logs.Info(" > 本机: http://localhost:%d/admin", port)
local_ip := getLocalIP()
logs.Info(" > 局域网: http://%s:%s/admin", local_ip, port)
logs.Info(" > 局域网: http://%s:%d/admin", local_ip, port)
ip := sillyGirl.GetString("ip")
if ip != "" {
logs.Info(" > 广域网: http://%s:%s/admin", ip, port)
logs.Info(" > 广域网: http://%s:%d/admin", ip, port)
}
sillyGirl.Set("local_ip", local_ip)
go func() {