2024-03-20 08:54:23 -04:00
|
|
|
package common
|
2024-03-11 11:39:58 -04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2024-03-11 13:34:08 -04:00
|
|
|
"fmt"
|
2024-03-11 11:39:58 -04:00
|
|
|
"os"
|
2024-03-18 10:32:33 -04:00
|
|
|
"path/filepath"
|
2024-03-11 11:39:58 -04:00
|
|
|
"regexp"
|
2024-03-19 09:02:53 -04:00
|
|
|
"sort"
|
2024-03-11 11:39:58 -04:00
|
|
|
"strings"
|
2024-09-19 06:12:24 -04:00
|
|
|
|
2024-09-30 13:27:22 -04:00
|
|
|
"github.com/nitezs/sub2sing-box/constant"
|
2024-09-19 06:12:24 -04:00
|
|
|
C "github.com/nitezs/sub2sing-box/constant"
|
|
|
|
"github.com/nitezs/sub2sing-box/model"
|
|
|
|
"github.com/nitezs/sub2sing-box/parser"
|
|
|
|
"github.com/nitezs/sub2sing-box/util"
|
2024-10-02 13:24:54 -04:00
|
|
|
"github.com/sagernet/sing-box/option"
|
2024-03-11 11:39:58 -04:00
|
|
|
)
|
|
|
|
|
2024-03-19 09:02:53 -04:00
|
|
|
func Convert(
|
|
|
|
subscriptions []string,
|
|
|
|
proxies []string,
|
2024-09-30 13:27:22 -04:00
|
|
|
templatePath string,
|
2024-03-19 09:02:53 -04:00
|
|
|
delete string,
|
|
|
|
rename map[string]string,
|
2024-10-19 12:46:01 -04:00
|
|
|
enableGroup bool,
|
2024-03-19 09:02:53 -04:00
|
|
|
groupType string,
|
|
|
|
sortKey string,
|
|
|
|
sortType string,
|
|
|
|
) (string, error) {
|
2024-03-11 13:34:08 -04:00
|
|
|
result := ""
|
|
|
|
var err error
|
|
|
|
|
2024-03-20 12:02:38 -04:00
|
|
|
outbounds, err := ConvertSubscriptionsToSProxy(subscriptions)
|
2024-03-11 13:34:08 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
for _, proxy := range proxies {
|
|
|
|
p, err := ConvertCProxyToSProxy(proxy)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2024-03-20 12:02:38 -04:00
|
|
|
outbounds = append(outbounds, p)
|
2024-03-11 13:34:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if delete != "" {
|
2024-03-20 12:02:38 -04:00
|
|
|
outbounds, err = DeleteProxy(outbounds, delete)
|
2024-03-11 13:34:08 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range rename {
|
2024-03-20 12:02:38 -04:00
|
|
|
outbounds, err = RenameProxy(outbounds, k, v)
|
2024-03-11 13:34:08 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
keep := make(map[int]bool)
|
|
|
|
set := make(map[string]struct {
|
2024-03-20 12:02:38 -04:00
|
|
|
Proxy model.Outbound
|
2024-03-11 13:34:08 -04:00
|
|
|
Count int
|
|
|
|
})
|
2024-03-20 12:02:38 -04:00
|
|
|
for i, p := range outbounds {
|
2024-03-11 13:34:08 -04:00
|
|
|
if _, exists := set[p.Tag]; !exists {
|
|
|
|
keep[i] = true
|
|
|
|
set[p.Tag] = struct {
|
2024-03-20 12:02:38 -04:00
|
|
|
Proxy model.Outbound
|
2024-03-11 13:34:08 -04:00
|
|
|
Count int
|
|
|
|
}{p, 0}
|
|
|
|
} else {
|
|
|
|
p1, _ := json.Marshal(p)
|
|
|
|
p2, _ := json.Marshal(set[p.Tag])
|
|
|
|
if string(p1) != string(p2) {
|
|
|
|
set[p.Tag] = struct {
|
2024-03-20 12:02:38 -04:00
|
|
|
Proxy model.Outbound
|
2024-03-11 13:34:08 -04:00
|
|
|
Count int
|
|
|
|
}{p, set[p.Tag].Count + 1}
|
|
|
|
keep[i] = true
|
2024-03-20 12:02:38 -04:00
|
|
|
outbounds[i].Tag = fmt.Sprintf("%s %d", p.Tag, set[p.Tag].Count)
|
2024-03-11 13:34:08 -04:00
|
|
|
} else {
|
|
|
|
keep[i] = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-10-19 12:46:01 -04:00
|
|
|
if enableGroup {
|
|
|
|
outbounds = AddCountryGroup(outbounds, groupType, sortKey, sortType)
|
|
|
|
}
|
2024-09-30 13:27:22 -04:00
|
|
|
if templatePath != "" {
|
|
|
|
templateDate, err := ReadTemplate(templatePath)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
reg := regexp.MustCompile("\"<[A-Za-z]{2}>\"")
|
2024-10-01 00:45:55 -04:00
|
|
|
group := false
|
|
|
|
for _, v := range model.CountryEnglishName {
|
|
|
|
if strings.Contains(templateDate, v) {
|
|
|
|
group = true
|
|
|
|
}
|
|
|
|
}
|
2024-10-19 12:46:01 -04:00
|
|
|
if !enableGroup && (reg.MatchString(templateDate) || strings.Contains(templateDate, constant.AllCountryTags) || group) {
|
2024-09-30 13:27:22 -04:00
|
|
|
outbounds = AddCountryGroup(outbounds, groupType, sortKey, sortType)
|
|
|
|
}
|
2024-10-02 13:24:54 -04:00
|
|
|
var template model.Options
|
2024-09-30 13:27:22 -04:00
|
|
|
if err = json.Unmarshal([]byte(templateDate), &template); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
result, err = MergeTemplate(outbounds, &template)
|
2024-03-11 13:34:08 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
} else {
|
2024-03-19 09:02:53 -04:00
|
|
|
r, err := json.Marshal(outbounds)
|
2024-03-11 13:34:08 -04:00
|
|
|
result = string(r)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(result), nil
|
|
|
|
}
|
|
|
|
|
2024-03-20 12:02:38 -04:00
|
|
|
func AddCountryGroup(proxies []model.Outbound, groupType string, sortKey string, sortType string) []model.Outbound {
|
|
|
|
newGroup := make(map[string]model.Outbound)
|
2024-03-19 09:02:53 -04:00
|
|
|
for _, p := range proxies {
|
2024-03-20 12:02:38 -04:00
|
|
|
if p.Type != C.TypeSelector && p.Type != C.TypeURLTest {
|
|
|
|
country := model.GetContryName(p.Tag)
|
2024-03-19 09:02:53 -04:00
|
|
|
if group, ok := newGroup[country]; ok {
|
2024-03-20 12:02:38 -04:00
|
|
|
group.SetOutbounds(append(group.GetOutbounds(), p.Tag))
|
2024-03-19 09:02:53 -04:00
|
|
|
newGroup[country] = group
|
|
|
|
} else {
|
2024-09-30 13:27:22 -04:00
|
|
|
if groupType == C.TypeSelector || groupType == "" {
|
2024-03-20 12:02:38 -04:00
|
|
|
newGroup[country] = model.Outbound{
|
2024-10-02 13:24:54 -04:00
|
|
|
Outbound: option.Outbound{
|
|
|
|
Tag: country,
|
|
|
|
Type: groupType,
|
|
|
|
SelectorOptions: option.SelectorOutboundOptions{
|
|
|
|
Outbounds: []string{p.Tag},
|
|
|
|
InterruptExistConnections: true,
|
|
|
|
},
|
2024-03-20 12:02:38 -04:00
|
|
|
},
|
|
|
|
}
|
|
|
|
} else if groupType == C.TypeURLTest {
|
|
|
|
newGroup[country] = model.Outbound{
|
2024-10-02 13:24:54 -04:00
|
|
|
Outbound: option.Outbound{
|
|
|
|
Tag: country,
|
|
|
|
Type: groupType,
|
|
|
|
URLTestOptions: option.URLTestOutboundOptions{
|
|
|
|
Outbounds: []string{p.Tag},
|
|
|
|
InterruptExistConnections: true,
|
|
|
|
},
|
2024-03-20 12:02:38 -04:00
|
|
|
},
|
|
|
|
}
|
2024-03-19 09:02:53 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-03-20 12:02:38 -04:00
|
|
|
var groups []model.Outbound
|
2024-03-19 09:02:53 -04:00
|
|
|
for _, p := range newGroup {
|
|
|
|
groups = append(groups, p)
|
|
|
|
}
|
|
|
|
if sortType == "asc" {
|
|
|
|
switch sortKey {
|
|
|
|
case "tag":
|
2024-03-20 12:02:38 -04:00
|
|
|
sort.Sort(model.SortByTag(groups))
|
2024-03-19 09:02:53 -04:00
|
|
|
case "num":
|
2024-03-20 12:02:38 -04:00
|
|
|
sort.Sort(model.SortByNumber(groups))
|
2024-03-19 09:02:53 -04:00
|
|
|
default:
|
2024-03-20 12:02:38 -04:00
|
|
|
sort.Sort(model.SortByTag(groups))
|
2024-03-19 09:02:53 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch sortKey {
|
|
|
|
case "tag":
|
2024-03-20 12:02:38 -04:00
|
|
|
sort.Sort(sort.Reverse(model.SortByTag(groups)))
|
2024-03-19 09:02:53 -04:00
|
|
|
case "num":
|
2024-03-20 12:02:38 -04:00
|
|
|
sort.Sort(sort.Reverse(model.SortByNumber(groups)))
|
2024-03-19 09:02:53 -04:00
|
|
|
default:
|
2024-03-20 12:02:38 -04:00
|
|
|
sort.Sort(sort.Reverse(model.SortByTag(groups)))
|
2024-03-19 09:02:53 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return append(proxies, groups...)
|
|
|
|
}
|
|
|
|
|
2024-09-30 13:27:22 -04:00
|
|
|
func ReadTemplate(template string) (string, error) {
|
|
|
|
var data string
|
2024-03-19 05:01:53 -04:00
|
|
|
var err error
|
2024-09-30 13:27:22 -04:00
|
|
|
isNetworkFile, _ := regexp.MatchString(`^https?://`, template)
|
2024-05-09 02:12:55 -04:00
|
|
|
if isNetworkFile {
|
2024-09-30 13:27:22 -04:00
|
|
|
data, err = util.Fetch(template, 3)
|
2024-03-19 05:01:53 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2024-09-30 13:27:22 -04:00
|
|
|
return data, nil
|
2024-03-19 05:01:53 -04:00
|
|
|
} else {
|
|
|
|
if !strings.Contains(template, string(filepath.Separator)) {
|
2024-05-23 04:57:19 -04:00
|
|
|
path := filepath.Join("templates", template)
|
|
|
|
if _, err := os.Stat(path); err == nil {
|
|
|
|
template = path
|
2024-03-19 05:01:53 -04:00
|
|
|
}
|
2024-03-18 10:32:33 -04:00
|
|
|
}
|
2024-09-30 13:27:22 -04:00
|
|
|
dataBytes, err := os.ReadFile(template)
|
2024-03-20 12:02:38 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2024-09-30 13:27:22 -04:00
|
|
|
return string(dataBytes), nil
|
2024-03-18 10:32:33 -04:00
|
|
|
}
|
2024-09-30 13:27:22 -04:00
|
|
|
}
|
|
|
|
|
2024-10-02 13:24:54 -04:00
|
|
|
func MergeTemplate(outbounds []model.Outbound, template *model.Options) (string, error) {
|
2024-09-30 13:27:22 -04:00
|
|
|
var err error
|
2024-03-11 11:39:58 -04:00
|
|
|
proxyTags := make([]string, 0)
|
2024-03-19 09:02:53 -04:00
|
|
|
groupTags := make([]string, 0)
|
2024-03-20 12:02:38 -04:00
|
|
|
groups := make(map[string]model.Outbound)
|
2024-03-19 09:02:53 -04:00
|
|
|
for _, p := range outbounds {
|
2024-03-20 12:02:38 -04:00
|
|
|
if model.IsCountryGroup(p.Tag) {
|
2024-03-19 09:02:53 -04:00
|
|
|
groupTags = append(groupTags, p.Tag)
|
|
|
|
reg := regexp.MustCompile("[A-Za-z]{2}")
|
|
|
|
country := reg.FindString(p.Tag)
|
|
|
|
groups[country] = p
|
|
|
|
} else {
|
|
|
|
proxyTags = append(proxyTags, p.Tag)
|
|
|
|
}
|
2024-03-11 11:39:58 -04:00
|
|
|
}
|
2024-03-19 09:02:53 -04:00
|
|
|
reg := regexp.MustCompile("<[A-Za-z]{2}>")
|
2024-09-30 13:27:22 -04:00
|
|
|
for i, outbound := range template.Outbounds {
|
2024-03-20 12:02:38 -04:00
|
|
|
if outbound.Type == C.TypeSelector || outbound.Type == C.TypeURLTest {
|
|
|
|
var parsedOutbound []string = make([]string, 0)
|
|
|
|
for _, o := range outbound.GetOutbounds() {
|
2024-09-30 13:27:22 -04:00
|
|
|
if o == constant.AllProxyTags {
|
2024-03-20 12:02:38 -04:00
|
|
|
parsedOutbound = append(parsedOutbound, proxyTags...)
|
2024-09-30 13:27:22 -04:00
|
|
|
} else if o == constant.AllCountryTags {
|
2024-03-20 12:02:38 -04:00
|
|
|
parsedOutbound = append(parsedOutbound, groupTags...)
|
|
|
|
} else if reg.MatchString(o) {
|
|
|
|
country := strings.ToUpper(strings.Trim(reg.FindString(o), "<>"))
|
|
|
|
if group, ok := groups[country]; ok {
|
|
|
|
parsedOutbound = append(parsedOutbound, group.GetOutbounds()...)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
parsedOutbound = append(parsedOutbound, o)
|
2024-03-19 09:02:53 -04:00
|
|
|
}
|
2024-03-11 11:39:58 -04:00
|
|
|
}
|
2024-09-30 13:27:22 -04:00
|
|
|
template.Outbounds[i].SetOutbounds(parsedOutbound)
|
2024-03-11 11:39:58 -04:00
|
|
|
}
|
|
|
|
}
|
2024-09-30 13:27:22 -04:00
|
|
|
template.Outbounds = append(template.Outbounds, outbounds...)
|
|
|
|
data, err := json.Marshal(template)
|
2024-03-11 11:39:58 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return string(data), nil
|
|
|
|
}
|
|
|
|
|
2024-03-20 12:02:38 -04:00
|
|
|
func ConvertCProxyToSProxy(proxy string) (model.Outbound, error) {
|
2024-03-11 11:39:58 -04:00
|
|
|
for prefix, parseFunc := range parser.ParserMap {
|
|
|
|
if strings.HasPrefix(proxy, prefix) {
|
|
|
|
proxy, err := parseFunc(proxy)
|
|
|
|
if err != nil {
|
2024-03-20 12:02:38 -04:00
|
|
|
return model.Outbound{}, err
|
2024-03-11 11:39:58 -04:00
|
|
|
}
|
|
|
|
return proxy, nil
|
|
|
|
}
|
|
|
|
}
|
2024-03-22 04:10:15 -04:00
|
|
|
return model.Outbound{}, errors.New("unknown proxy format")
|
2024-03-11 11:39:58 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func ConvertCProxyToJson(proxy string) (string, error) {
|
|
|
|
sProxy, err := ConvertCProxyToSProxy(proxy)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
data, err := json.Marshal(&sProxy)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return string(data), nil
|
|
|
|
}
|
|
|
|
|
2024-03-20 12:02:38 -04:00
|
|
|
func ConvertSubscriptionsToSProxy(urls []string) ([]model.Outbound, error) {
|
|
|
|
proxyList := make([]model.Outbound, 0)
|
2024-03-11 11:39:58 -04:00
|
|
|
for _, url := range urls {
|
2024-03-19 05:01:53 -04:00
|
|
|
data, err := util.Fetch(url, 3)
|
2024-03-11 11:39:58 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-08-04 05:46:11 -04:00
|
|
|
proxy := data
|
|
|
|
if !strings.Contains(data, "://") {
|
|
|
|
proxy, err = util.DecodeBase64(data)
|
|
|
|
}
|
2024-03-11 11:39:58 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
proxies := strings.Split(proxy, "\n")
|
|
|
|
for _, p := range proxies {
|
|
|
|
for prefix, parseFunc := range parser.ParserMap {
|
|
|
|
if strings.HasPrefix(p, prefix) {
|
|
|
|
proxy, err := parseFunc(p)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
proxyList = append(proxyList, proxy)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return proxyList, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ConvertSubscriptionsToJson(urls []string) (string, error) {
|
|
|
|
proxyList, err := ConvertSubscriptionsToSProxy(urls)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
result, err := json.Marshal(proxyList)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return string(result), nil
|
|
|
|
}
|
|
|
|
|
2024-03-20 12:02:38 -04:00
|
|
|
func DeleteProxy(proxies []model.Outbound, regex string) ([]model.Outbound, error) {
|
2024-03-11 11:39:58 -04:00
|
|
|
reg, err := regexp.Compile(regex)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-03-20 12:02:38 -04:00
|
|
|
var newProxies []model.Outbound
|
2024-03-11 11:39:58 -04:00
|
|
|
for _, p := range proxies {
|
|
|
|
if !reg.MatchString(p.Tag) {
|
|
|
|
newProxies = append(newProxies, p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return newProxies, nil
|
|
|
|
}
|
|
|
|
|
2024-03-20 12:02:38 -04:00
|
|
|
func RenameProxy(proxies []model.Outbound, regex string, replaceText string) ([]model.Outbound, error) {
|
2024-03-11 11:39:58 -04:00
|
|
|
reg, err := regexp.Compile(regex)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for i, p := range proxies {
|
|
|
|
if reg.MatchString(p.Tag) {
|
|
|
|
proxies[i].Tag = reg.ReplaceAllString(p.Tag, replaceText)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return proxies, nil
|
|
|
|
}
|