2024-01-15 17:17:05 +03:30
|
|
|
package config
|
2023-08-19 14:08:39 +03:30
|
|
|
|
|
|
|
|
import (
|
2024-01-19 22:19:53 +03:30
|
|
|
"bytes"
|
2024-02-15 14:47:27 +01:00
|
|
|
"context"
|
2023-08-20 23:50:13 +03:30
|
|
|
_ "embed"
|
2024-01-17 15:31:23 +03:30
|
|
|
"encoding/json"
|
2023-08-19 14:08:39 +03:30
|
|
|
"fmt"
|
2024-02-10 11:03:30 +01:00
|
|
|
"path/filepath"
|
2024-01-25 23:09:24 +01:00
|
|
|
|
2023-08-19 14:08:39 +03:30
|
|
|
"os"
|
|
|
|
|
|
2023-09-09 20:05:05 +00:00
|
|
|
"github.com/hiddify/ray2sing/ray2sing"
|
2023-08-19 14:08:39 +03:30
|
|
|
"github.com/sagernet/sing-box/experimental/libbox"
|
2024-01-30 19:13:14 +01:00
|
|
|
"github.com/sagernet/sing-box/option"
|
2024-02-15 14:47:27 +01:00
|
|
|
"github.com/sagernet/sing/common/batch"
|
2024-01-21 10:40:21 +00:00
|
|
|
SJ "github.com/sagernet/sing/common/json"
|
2023-08-19 14:08:39 +03:30
|
|
|
"github.com/xmdhs/clash2singbox/convert"
|
|
|
|
|
"github.com/xmdhs/clash2singbox/model/clash"
|
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
|
)
|
|
|
|
|
|
2023-08-20 23:50:13 +03:30
|
|
|
//go:embed config.json.template
|
|
|
|
|
var configByte []byte
|
|
|
|
|
|
2024-01-15 17:59:45 +03:30
|
|
|
func ParseConfig(path string, debug bool) ([]byte, error) {
|
|
|
|
|
content, err := os.ReadFile(path)
|
2024-02-10 10:08:46 +01:00
|
|
|
os.Chdir(filepath.Dir(path))
|
2023-08-19 14:08:39 +03:30
|
|
|
if err != nil {
|
2024-01-15 17:59:45 +03:30
|
|
|
return nil, err
|
2023-08-19 14:08:39 +03:30
|
|
|
}
|
2024-02-10 16:32:28 +01:00
|
|
|
var jsonObj map[string]interface{} = make(map[string]interface{})
|
2024-01-25 23:09:24 +01:00
|
|
|
|
|
|
|
|
fmt.Printf("Convert using json\n")
|
2024-02-10 11:03:30 +01:00
|
|
|
var tmpJsonResult any
|
2024-01-19 22:19:53 +03:30
|
|
|
jsonDecoder := json.NewDecoder(SJ.NewCommentFilter(bytes.NewReader(content)))
|
2024-02-10 11:03:30 +01:00
|
|
|
if err := jsonDecoder.Decode(&tmpJsonResult); err == nil {
|
|
|
|
|
if tmpJsonObj, ok := tmpJsonResult.(map[string]interface{}); ok {
|
|
|
|
|
if tmpJsonObj["outbounds"] == nil {
|
2024-02-10 16:32:28 +01:00
|
|
|
jsonObj["outbounds"] = []interface{}{jsonObj}
|
2024-02-10 11:03:30 +01:00
|
|
|
} else {
|
|
|
|
|
jsonObj["outbounds"] = tmpJsonObj["outbounds"]
|
2024-02-10 10:08:46 +01:00
|
|
|
}
|
2024-02-10 11:03:30 +01:00
|
|
|
} else if jsonArray, ok := tmpJsonResult.([]map[string]interface{}); ok {
|
2024-02-10 16:32:28 +01:00
|
|
|
jsonObj["outbounds"] = jsonArray
|
2024-02-10 11:03:30 +01:00
|
|
|
} else {
|
|
|
|
|
return nil, fmt.Errorf("[SingboxParser] Incorrect Json Format")
|
2023-10-12 00:16:55 +03:30
|
|
|
}
|
2024-01-27 02:44:08 +01:00
|
|
|
|
2024-01-25 23:09:24 +01:00
|
|
|
newContent, _ := json.MarshalIndent(jsonObj, "", " ")
|
2024-02-10 16:32:28 +01:00
|
|
|
return patchConfig(newContent, "SingboxParser")
|
2023-08-19 14:08:39 +03:30
|
|
|
}
|
2024-02-10 16:32:28 +01:00
|
|
|
|
2024-01-17 15:31:23 +03:30
|
|
|
v2rayStr, err := ray2sing.Ray2Singbox(string(content))
|
|
|
|
|
if err == nil {
|
2024-01-30 19:13:14 +01:00
|
|
|
return patchConfig([]byte(v2rayStr), "V2rayParser")
|
2023-09-09 14:06:54 +00:00
|
|
|
}
|
2024-01-25 23:09:24 +01:00
|
|
|
fmt.Printf("Convert using clash\n")
|
2024-01-17 15:31:23 +03:30
|
|
|
clashObj := clash.Clash{}
|
|
|
|
|
if err := yaml.Unmarshal(content, &clashObj); err == nil && clashObj.Proxies != nil {
|
|
|
|
|
if len(clashObj.Proxies) == 0 {
|
|
|
|
|
return nil, fmt.Errorf("[ClashParser] no outbounds found")
|
|
|
|
|
}
|
|
|
|
|
converted, err := convert.Clash2sing(clashObj)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("[ClashParser] converting clash to sing-box error: %w", err)
|
|
|
|
|
}
|
|
|
|
|
output := configByte
|
|
|
|
|
output, err = convert.Patch(output, converted, "", "", nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("[ClashParser] patching clash config error: %w", err)
|
|
|
|
|
}
|
2024-01-30 19:13:14 +01:00
|
|
|
return patchConfig(output, "ClashParser")
|
2023-08-19 14:08:39 +03:30
|
|
|
}
|
|
|
|
|
|
2024-01-17 15:31:23 +03:30
|
|
|
return nil, fmt.Errorf("unable to determine config format")
|
2023-08-19 14:08:39 +03:30
|
|
|
}
|
2023-10-12 00:16:55 +03:30
|
|
|
|
2024-01-30 19:13:14 +01:00
|
|
|
func patchConfig(content []byte, name string) ([]byte, error) {
|
|
|
|
|
options := option.Options{}
|
|
|
|
|
err := json.Unmarshal(content, &options)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("[SingboxParser] unmarshal error: %w", err)
|
|
|
|
|
}
|
2024-02-20 09:18:53 +01:00
|
|
|
b, _ := batch.New(context.Background(), batch.WithConcurrencyNum[*option.Outbound](2))
|
2024-02-15 14:47:27 +01:00
|
|
|
for _, base := range options.Outbounds {
|
|
|
|
|
out := base
|
|
|
|
|
b.Go(base.Tag, func() (*option.Outbound, error) {
|
|
|
|
|
err := patchWarp(&out)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("[Warp] patch warp error: %w", err)
|
|
|
|
|
}
|
|
|
|
|
// options.Outbounds[i] = base
|
|
|
|
|
return &out, nil
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
if res, err := b.WaitAndGetResult(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
} else {
|
2024-02-15 18:14:48 +01:00
|
|
|
for i, base := range options.Outbounds {
|
|
|
|
|
options.Outbounds[i] = *res[base.Tag].Value
|
2024-01-30 19:13:14 +01:00
|
|
|
}
|
2024-02-15 18:14:48 +01:00
|
|
|
|
2024-01-30 19:13:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
content, _ = json.MarshalIndent(options, "", " ")
|
|
|
|
|
fmt.Printf("%s\n", content)
|
|
|
|
|
return validateResult(content, name)
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-17 15:31:23 +03:30
|
|
|
func validateResult(content []byte, name string) ([]byte, error) {
|
2024-01-30 19:13:14 +01:00
|
|
|
|
2024-01-17 15:31:23 +03:30
|
|
|
err := libbox.CheckConfig(string(content))
|
2024-01-16 23:57:58 +00:00
|
|
|
if err != nil {
|
2024-01-17 15:31:23 +03:30
|
|
|
return nil, fmt.Errorf("[%s] invalid sing-box config: %w", name, err)
|
2023-10-12 00:16:55 +03:30
|
|
|
}
|
|
|
|
|
return content, nil
|
|
|
|
|
}
|