mirror of
https://github.com/nitezs/sub2clash.git
synced 2024-12-24 11:54:41 -05:00
feat: 增加国家策略组排序策略
feat: 支持指定本地模板 mod: Rule-Provider 可以自定义名称
This commit is contained in:
parent
fd1ff69711
commit
72635ce0fc
22
README.md
22
README.md
@ -41,16 +41,17 @@
|
|||||||
|
|
||||||
获取 Clash/Clash.Meta 配置链接
|
获取 Clash/Clash.Meta 配置链接
|
||||||
|
|
||||||
| Query 参数 | 类型 | 是否必须 | 说明 |
|
| Query 参数 | 类型 | 是否必须 | 默认值 | 说明 |
|
||||||
|--------------|--------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|------------------|--------|-------------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| sub | string | sub/proxy 至少有一项存在 | 订阅链接(可以输入多个,用 `,` 分隔) |
|
| sub | string | sub/proxy 至少有一项存在 | - | 订阅链接(可以输入多个,用 `,` 分隔) |
|
||||||
| proxy | string | sub/proxy 至少有一项存在 | 节点分享链接(可以输入多个,用 `,` 分隔) |
|
| proxy | string | sub/proxy 至少有一项存在 | - | 节点分享链接(可以输入多个,用 `,` 分隔) |
|
||||||
| refresh | bool | 否(默认 `false`) | 强制刷新配置(默认缓存 5 分钟) |
|
| refresh | bool | 否 | `false` | 强制刷新配置(默认缓存 5 分钟) |
|
||||||
| template | string | 否 | 外部模板 |
|
| template | string | 否 | - | 外部模板链接或内部模板名称 |
|
||||||
| ruleProvider | string | 否 | 格式 `[Behavior,Url,Group,Prepend],[Behavior,Url,Group,Prepend],...`,其中 `Group` 是该规则集所走的策略组名,`Prepend` 为 bool 类型,如果为 `true` 规则将被添加到规则列表顶部,否则添加到规则列表底部(会调整到MATCH规则之前) |
|
| ruleProvider | string | 否 | - | 格式 `[Behavior,Url,Group,Prepend,Name],[Behavior,Url,Group,Prepend,Name],...`,其中 `Group` 是该规则集所走的策略组名,`Prepend` 为 bool 类型,如果为 `true` 规则将被添加到规则列表顶部,否则添加到规则列表底部(会调整到MATCH规则之前) |
|
||||||
| rule | string | 否 | 格式 `[Rule,Prepend],[Rule,Prepend]...`,其中 `Prepend` 为 bool 类型,如果为 `true` 规则将被添加到规则列表顶部,否则添加到规则列表底部(会调整到MATCH规则之前) |
|
| rule | string | 否 | - | 格式 `[Rule,Prepend],[Rule,Prepend]...`,其中 `Prepend` 为 bool 类型,如果为 `true` 规则将被添加到规则列表顶部,否则添加到规则列表底部(会调整到MATCH规则之前) |
|
||||||
| autoTest | bool | 否(默认 `false`) | 指定国家策略组是否自动测速 |
|
| autoTest | bool | 否 | `false` | 指定国家策略组是否自动测速 |
|
||||||
| lazy | bool | 否(默认 `false`) | 自动测速是否启用 lazy |
|
| lazy | bool | 否 | `false` | 自动测速是否启用 lazy |
|
||||||
|
| countryGroupSort | string | 否 | `nameasc` | 国家策略组排序策略,可选值 `nameasc`、`namedesc`、`sizeasc`、`sizedesc` |
|
||||||
|
|
||||||
## 默认模板
|
## 默认模板
|
||||||
|
|
||||||
@ -63,3 +64,4 @@
|
|||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
|
- [ ] 可视化面板
|
@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sub2clash/model"
|
"sub2clash/model"
|
||||||
@ -17,13 +18,13 @@ func BuildSub(query validator.SubQuery, template string) (
|
|||||||
*model.Subscription, error,
|
*model.Subscription, error,
|
||||||
) {
|
) {
|
||||||
// 定义变量
|
// 定义变量
|
||||||
var externalTemplate = query.Template != ""
|
|
||||||
var temp *model.Subscription
|
var temp *model.Subscription
|
||||||
var sub *model.Subscription
|
var sub *model.Subscription
|
||||||
var err error
|
var err error
|
||||||
var templateBytes []byte
|
var templateBytes []byte
|
||||||
// 加载模板
|
// 加载模板
|
||||||
if !externalTemplate {
|
_, err = url.ParseRequestURI(template)
|
||||||
|
if err != nil {
|
||||||
templateBytes, err = utils.LoadTemplate(template)
|
templateBytes, err = utils.LoadTemplate(template)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("加载模板失败: " + err.Error())
|
return nil, errors.New("加载模板失败: " + err.Error())
|
||||||
@ -63,10 +64,14 @@ func BuildSub(query validator.SubQuery, template string) (
|
|||||||
} else {
|
} else {
|
||||||
proxyList = sub.Proxies
|
proxyList = sub.Proxies
|
||||||
}
|
}
|
||||||
utils.AddProxy(temp, query.AutoTest, query.Lazy, proxyList...)
|
utils.AddProxy(sub, query.AutoTest, query.Lazy, query.Sort, proxyList...)
|
||||||
}
|
}
|
||||||
// 处理自定义代理
|
// 处理自定义代理
|
||||||
utils.AddProxy(temp, query.AutoTest, query.Lazy, utils.ParseProxy(query.Proxies...)...)
|
utils.AddProxy(
|
||||||
|
sub, query.AutoTest, query.Lazy, query.Sort,
|
||||||
|
utils.ParseProxy(query.Proxies...)...,
|
||||||
|
)
|
||||||
|
MergeSubAndTemplate(temp, sub)
|
||||||
// 处理自定义规则
|
// 处理自定义规则
|
||||||
for _, v := range query.Rules {
|
for _, v := range query.Rules {
|
||||||
if v.Prepend {
|
if v.Prepend {
|
||||||
@ -88,13 +93,34 @@ func BuildSub(query validator.SubQuery, template string) (
|
|||||||
}
|
}
|
||||||
if v.Prepend {
|
if v.Prepend {
|
||||||
utils.PrependRuleProvider(
|
utils.PrependRuleProvider(
|
||||||
temp, name, v.Group, provider,
|
temp, v.Name, v.Group, provider,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
utils.AppenddRuleProvider(
|
utils.AppenddRuleProvider(
|
||||||
temp, name, v.Group, provider,
|
temp, v.Name, v.Group, provider,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return temp, nil
|
return temp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MergeSubAndTemplate(temp *model.Subscription, sub *model.Subscription) {
|
||||||
|
// 只合并节点、策略组
|
||||||
|
// 统计所有国家策略组名称
|
||||||
|
var newCountryGroupNames []string
|
||||||
|
for _, proxyGroup := range sub.ProxyGroups {
|
||||||
|
if proxyGroup.IsCountryGrop {
|
||||||
|
newCountryGroupNames = append(
|
||||||
|
newCountryGroupNames, proxyGroup.Name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 将订阅中的节点添加到模板中
|
||||||
|
temp.Proxies = append(temp.Proxies, sub.Proxies...)
|
||||||
|
// 将订阅中的策略组添加到模板中
|
||||||
|
for i := range temp.ProxyGroups {
|
||||||
|
temp.ProxyGroups[i].Proxies = append(temp.ProxyGroups[i].Proxies, newCountryGroupNames...)
|
||||||
|
}
|
||||||
|
temp.ProxyGroups = append(temp.ProxyGroups, sub.ProxyGroups...)
|
||||||
|
temp.Rules = append(temp.Rules, sub.Rules...)
|
||||||
|
}
|
||||||
|
58
model/proxy_group.go
Normal file
58
model/proxy_group.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/text/collate"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProxyGroup struct {
|
||||||
|
Name string `yaml:"name,omitempty"`
|
||||||
|
Type string `yaml:"type,omitempty"`
|
||||||
|
Proxies []string `yaml:"proxies,omitempty"`
|
||||||
|
IsCountryGrop bool `yaml:"-"`
|
||||||
|
Url string `yaml:"url,omitempty"`
|
||||||
|
Interval int `yaml:"interval,omitempty"`
|
||||||
|
Tolerance int `yaml:"tolerance,omitempty"`
|
||||||
|
Lazy bool `yaml:"lazy"`
|
||||||
|
Size int `yaml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyGroupsSortByName []ProxyGroup
|
||||||
|
type ProxyGroupsSortBySize []ProxyGroup
|
||||||
|
|
||||||
|
func (p ProxyGroupsSortByName) Len() int {
|
||||||
|
return len(p)
|
||||||
|
}
|
||||||
|
func (p ProxyGroupsSortBySize) Len() int {
|
||||||
|
return len(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProxyGroupsSortByName) Less(i, j int) bool {
|
||||||
|
// 定义一组备选语言:首选英语,其次中文
|
||||||
|
tags := []language.Tag{
|
||||||
|
language.English,
|
||||||
|
language.Chinese,
|
||||||
|
}
|
||||||
|
matcher := language.NewMatcher(tags)
|
||||||
|
|
||||||
|
// 假设我们的请求语言是 "zh"(中文),则使用匹配器找到最佳匹配的语言
|
||||||
|
bestMatch, _, _ := matcher.Match(language.Make("zh"))
|
||||||
|
// 使用最佳匹配的语言进行排序
|
||||||
|
c := collate.New(bestMatch)
|
||||||
|
|
||||||
|
return c.CompareString(p[i].Name, p[j].Name) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProxyGroupsSortBySize) Less(i, j int) bool {
|
||||||
|
if p[i].Size == p[j].Size {
|
||||||
|
return p[i].Name < p[j].Name
|
||||||
|
}
|
||||||
|
return p[i].Size < p[j].Size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProxyGroupsSortByName) Swap(i, j int) {
|
||||||
|
p[i], p[j] = p[j], p[i]
|
||||||
|
}
|
||||||
|
func (p ProxyGroupsSortBySize) Swap(i, j int) {
|
||||||
|
p[i], p[j] = p[j], p[i]
|
||||||
|
}
|
11
model/sub.go
11
model/sub.go
@ -13,17 +13,6 @@ type Subscription struct {
|
|||||||
RuleProviders map[string]RuleProvider `yaml:"rule-providers,omitempty,omitempty"`
|
RuleProviders map[string]RuleProvider `yaml:"rule-providers,omitempty,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyGroup struct {
|
|
||||||
Name string `yaml:"name,omitempty"`
|
|
||||||
Type string `yaml:"type,omitempty"`
|
|
||||||
Proxies []string `yaml:"proxies,omitempty"`
|
|
||||||
IsCountryGrop bool `yaml:"-"`
|
|
||||||
Url string `yaml:"url,omitempty"`
|
|
||||||
Interval int `yaml:"interval,omitempty"`
|
|
||||||
Tolerance int `yaml:"tolerance,omitempty"`
|
|
||||||
Lazy bool `yaml:"lazy"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RuleProvider struct {
|
type RuleProvider struct {
|
||||||
Type string `yaml:"type,omitempty"`
|
Type string `yaml:"type,omitempty"`
|
||||||
Behavior string `yaml:"behavior,omitempty"`
|
Behavior string `yaml:"behavior,omitempty"`
|
||||||
|
@ -57,6 +57,5 @@ rules:
|
|||||||
- GEOIP,CN,全球直连
|
- GEOIP,CN,全球直连
|
||||||
- GEOSITE,category-games,游戏平台
|
- GEOSITE,category-games,游戏平台
|
||||||
- GEOSITE,geolocation-!cn,节点选择
|
- GEOSITE,geolocation-!cn,节点选择
|
||||||
- GEOIP,ad,广告拦截
|
|
||||||
- GEOSITE,category-ads-all,广告拦截
|
- GEOSITE,category-ads-all,广告拦截
|
||||||
- MATCH,漏网之鱼
|
- MATCH,漏网之鱼
|
@ -1,6 +1,7 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sub2clash/model"
|
"sub2clash/model"
|
||||||
"sub2clash/parser"
|
"sub2clash/parser"
|
||||||
@ -27,35 +28,30 @@ func GetContryName(proxy model.Proxy) string {
|
|||||||
return "其他地区"
|
return "其他地区"
|
||||||
}
|
}
|
||||||
|
|
||||||
var skipGroups = map[string]bool{
|
func AddProxy(
|
||||||
"手动切换": true,
|
sub *model.Subscription, autotest bool, lazy bool, sortStrategy string,
|
||||||
"全球直连": true,
|
proxies ...model.Proxy,
|
||||||
"广告拦截": true,
|
) {
|
||||||
"应用净化": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddProxy(sub *model.Subscription, autotest bool, lazy bool, proxies ...model.Proxy) {
|
|
||||||
newCountryGroupNames := make([]string, 0)
|
newCountryGroupNames := make([]string, 0)
|
||||||
|
// 添加节点
|
||||||
for _, proxy := range proxies {
|
for _, proxy := range proxies {
|
||||||
sub.Proxies = append(sub.Proxies, proxy)
|
sub.Proxies = append(sub.Proxies, proxy)
|
||||||
|
|
||||||
haveProxyGroup := false
|
haveProxyGroup := false
|
||||||
countryName := GetContryName(proxy)
|
countryName := GetContryName(proxy)
|
||||||
|
|
||||||
for i := range sub.ProxyGroups {
|
for i := range sub.ProxyGroups {
|
||||||
group := &sub.ProxyGroups[i]
|
group := &sub.ProxyGroups[i]
|
||||||
|
|
||||||
if group.Name == countryName {
|
if group.Name == countryName {
|
||||||
group.Proxies = append(group.Proxies, proxy.Name)
|
group.Proxies = append(group.Proxies, proxy.Name)
|
||||||
|
group.Size++
|
||||||
haveProxyGroup = true
|
haveProxyGroup = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.Name == "手动切换" {
|
if group.Name == "手动切换" {
|
||||||
group.Proxies = append(group.Proxies, proxy.Name)
|
group.Proxies = append(group.Proxies, proxy.Name)
|
||||||
|
group.Size++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !haveProxyGroup {
|
if !haveProxyGroup {
|
||||||
var newGroup model.ProxyGroup
|
var newGroup model.ProxyGroup
|
||||||
if !autotest {
|
if !autotest {
|
||||||
@ -64,6 +60,7 @@ func AddProxy(sub *model.Subscription, autotest bool, lazy bool, proxies ...mode
|
|||||||
Type: "select",
|
Type: "select",
|
||||||
Proxies: []string{proxy.Name},
|
Proxies: []string{proxy.Name},
|
||||||
IsCountryGrop: true,
|
IsCountryGrop: true,
|
||||||
|
Size: 1,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newGroup = model.ProxyGroup{
|
newGroup = model.ProxyGroup{
|
||||||
@ -75,23 +72,32 @@ func AddProxy(sub *model.Subscription, autotest bool, lazy bool, proxies ...mode
|
|||||||
Interval: 300,
|
Interval: 300,
|
||||||
Tolerance: 50,
|
Tolerance: 50,
|
||||||
Lazy: lazy,
|
Lazy: lazy,
|
||||||
|
Size: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sub.ProxyGroups = append(sub.ProxyGroups, newGroup)
|
sub.ProxyGroups = append(sub.ProxyGroups, newGroup)
|
||||||
newCountryGroupNames = append(newCountryGroupNames, countryName)
|
newCountryGroupNames = append(newCountryGroupNames, countryName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 统计国家策略组数量
|
||||||
|
countryGroupCount := 0
|
||||||
for i := range sub.ProxyGroups {
|
for i := range sub.ProxyGroups {
|
||||||
if sub.ProxyGroups[i].IsCountryGrop {
|
if sub.ProxyGroups[i].IsCountryGrop {
|
||||||
continue
|
countryGroupCount++
|
||||||
}
|
}
|
||||||
if !skipGroups[sub.ProxyGroups[i].Name] {
|
|
||||||
combined := make([]string, len(newCountryGroupNames)+len(sub.ProxyGroups[i].Proxies))
|
|
||||||
copy(combined, newCountryGroupNames)
|
|
||||||
copy(combined[len(newCountryGroupNames):], sub.ProxyGroups[i].Proxies)
|
|
||||||
sub.ProxyGroups[i].Proxies = combined
|
|
||||||
}
|
}
|
||||||
|
// 对国家策略组进行排序
|
||||||
|
switch sortStrategy {
|
||||||
|
case "sizeasc":
|
||||||
|
sort.Sort(model.ProxyGroupsSortBySize(sub.ProxyGroups[:countryGroupCount]))
|
||||||
|
case "sizedesc":
|
||||||
|
sort.Sort(sort.Reverse(model.ProxyGroupsSortBySize(sub.ProxyGroups[:countryGroupCount])))
|
||||||
|
case "nameasc":
|
||||||
|
sort.Sort(model.ProxyGroupsSortByName(sub.ProxyGroups[:countryGroupCount]))
|
||||||
|
case "namedesc":
|
||||||
|
sort.Sort(sort.Reverse(model.ProxyGroupsSortByName(sub.ProxyGroups[:countryGroupCount])))
|
||||||
|
default:
|
||||||
|
sort.Sort(model.ProxyGroupsSortByName(sub.ProxyGroups[:countryGroupCount]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package validator
|
package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -21,6 +23,7 @@ type SubQuery struct {
|
|||||||
Rules []RuleStruct `form:"-" binding:""`
|
Rules []RuleStruct `form:"-" binding:""`
|
||||||
AutoTest bool `form:"autoTest,default=false" binding:""`
|
AutoTest bool `form:"autoTest,default=false" binding:""`
|
||||||
Lazy bool `form:"lazy,default=false" binding:""`
|
Lazy bool `form:"lazy,default=false" binding:""`
|
||||||
|
Sort string `form:"sort" binding:""`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleProviderStruct struct {
|
type RuleProviderStruct struct {
|
||||||
@ -28,6 +31,7 @@ type RuleProviderStruct struct {
|
|||||||
Url string
|
Url string
|
||||||
Group string
|
Group string
|
||||||
Prepend bool
|
Prepend bool
|
||||||
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleStruct struct {
|
type RuleStruct struct {
|
||||||
@ -87,7 +91,7 @@ func ParseQuery(c *gin.Context) (SubQuery, error) {
|
|||||||
for i := range ruleProviders {
|
for i := range ruleProviders {
|
||||||
length := len(ruleProviders)
|
length := len(ruleProviders)
|
||||||
parts := strings.Split(ruleProviders[length-i-1][1], ",")
|
parts := strings.Split(ruleProviders[length-i-1][1], ",")
|
||||||
if len(parts) != 4 {
|
if len(parts) < 4 {
|
||||||
return SubQuery{}, errors.New("参数错误: ruleProvider 格式错误")
|
return SubQuery{}, errors.New("参数错误: ruleProvider 格式错误")
|
||||||
}
|
}
|
||||||
u := parts[1]
|
u := parts[1]
|
||||||
@ -100,12 +104,17 @@ func ParseQuery(c *gin.Context) (SubQuery, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return SubQuery{}, errors.New("参数错误: " + err.Error())
|
return SubQuery{}, errors.New("参数错误: " + err.Error())
|
||||||
}
|
}
|
||||||
|
if len(parts) == 4 {
|
||||||
|
hash := md5.Sum([]byte(u))
|
||||||
|
parts = append(parts, hex.EncodeToString(hash[:]))
|
||||||
|
}
|
||||||
query.RuleProviders = append(
|
query.RuleProviders = append(
|
||||||
query.RuleProviders, RuleProviderStruct{
|
query.RuleProviders, RuleProviderStruct{
|
||||||
Behavior: parts[0],
|
Behavior: parts[0],
|
||||||
Url: u,
|
Url: u,
|
||||||
Group: parts[2],
|
Group: parts[2],
|
||||||
Prepend: parts[3] == "true",
|
Prepend: parts[3] == "true",
|
||||||
|
Name: parts[4],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user