mirror of
https://github.com/nitezs/sub2sing-box.git
synced 2024-12-23 14:24:42 -05:00
add: 国家策略组功能
This commit is contained in:
parent
3c180ae61e
commit
891db1975f
66
README.md
66
README.md
@ -10,41 +10,53 @@
|
||||
|
||||
## api
|
||||
|
||||
##### GET /convert
|
||||
### GET /convert?data=xxx
|
||||
|
||||
- `data`: Base64 编码(url safe)的 JSON 字符串,包含以下字段:
|
||||
- `subscription`: []string
|
||||
- `proxy`: []string
|
||||
- `delete`: string 可选
|
||||
- `rename`: string 可选
|
||||
- `template`: map[string]string 可选
|
||||
|
||||
示例
|
||||
data 为 base64 URL 编码的请求体,示例
|
||||
|
||||
```
|
||||
{
|
||||
"subscription": ["url1", "url2"],
|
||||
"proxy": ["p1", "p2"],
|
||||
"delete": "reg",
|
||||
"template": "t",
|
||||
"rename": {
|
||||
"text": "replaceTo"
|
||||
}
|
||||
"subscriptions": ["订阅地址1", "订阅地址2"],
|
||||
"proxies": ["代理1", "代理2"],
|
||||
"template": "模板路径",
|
||||
"delete": "",
|
||||
"rename": {"原文本": "新文本"},
|
||||
"group": false,
|
||||
"group-type": "selector",
|
||||
"sort": "name",
|
||||
"sort-type": "asc"
|
||||
}
|
||||
```
|
||||
|
||||
## Template
|
||||
## Template 占位符
|
||||
|
||||
Template 中使用 `<all-proxy-tags>` 指明节点插入位置,例如
|
||||
|
||||
```
|
||||
{
|
||||
"type": "selector",
|
||||
"tag": "节点选择",
|
||||
"outbounds": ["<all-proxy-tags>", "direct"],
|
||||
"interrupt_exist_connections": true
|
||||
},
|
||||
```
|
||||
- `<all-proxy-tags>`: 插入所有节点标签
|
||||
```
|
||||
{
|
||||
"type": "selector",
|
||||
"tag": "节点选择",
|
||||
"outbounds": ["<all-proxy-tags>", "direct"],
|
||||
"interrupt_exist_connections": true
|
||||
}
|
||||
```
|
||||
- `<all-country-tags>`: 插入所有国家标签
|
||||
```
|
||||
{
|
||||
"type": "selector",
|
||||
"tag": "节点选择",
|
||||
"outbounds": ["<all-country-tags>", "direct"],
|
||||
"interrupt_exist_connections": true
|
||||
}
|
||||
```
|
||||
- `<国家(地区)二字码>`: 插入国家(地区)所有节点标签,例如 `<tw>`
|
||||
```
|
||||
{
|
||||
"type": "selector",
|
||||
"tag": "巴哈姆特",
|
||||
"outbounds": ["<tw>", "direct"],
|
||||
"interrupt_exist_connections": true
|
||||
}
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
|
@ -38,7 +38,17 @@ func Convert(c *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
result, err := putil.Convert(data.Subscriptions, data.Proxies, data.Template, data.Delete, data.Rename)
|
||||
result, err := putil.Convert(
|
||||
data.Subscriptions,
|
||||
data.Proxies,
|
||||
data.Template,
|
||||
data.Delete,
|
||||
data.Rename,
|
||||
data.Group,
|
||||
data.GroupType,
|
||||
data.SortKey,
|
||||
data.SortType,
|
||||
)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
"error": err.Error(),
|
||||
|
@ -6,4 +6,8 @@ type ConvertRequest struct {
|
||||
Template string `form:"template" json:"template"`
|
||||
Delete string `form:"delete" json:"delete"`
|
||||
Rename map[string]string `form:"rename" json:"rename"`
|
||||
Group bool `form:"group" json:"group"`
|
||||
GroupType string `form:"group-type" json:"group-type"`
|
||||
SortKey string `form:"sort" json:"sort"`
|
||||
SortType string `form:"sort-type" json:"sort-type"`
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<html lang="zh-CN" data-bs-theme="light">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
@ -17,73 +17,144 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<h2>sub2sing-box</h2>
|
||||
<div id="form">
|
||||
<!-- Subscription -->
|
||||
<div class="form-group">
|
||||
<label for="subscription">Subscription:</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="subscription"
|
||||
name="subscription"
|
||||
placeholder="一行一个"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Proxy -->
|
||||
<div class="form-group">
|
||||
<label for="proxy">Proxy:</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="proxy"
|
||||
name="proxy"
|
||||
placeholder="一行一个"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Delete -->
|
||||
<div class="form-group">
|
||||
<label for="delete">Delete:</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="delete"
|
||||
name="delete"
|
||||
placeholder="支持正则表达式"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Template -->
|
||||
<div class="form-group">
|
||||
<label for="template">Template:</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="template"
|
||||
name="template"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Rename -->
|
||||
<label for="renameContainer">Rename:</label>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary mb-2"
|
||||
onclick="addRenameField()"
|
||||
<div class="container my-5">
|
||||
<h2>
|
||||
<a
|
||||
href="https://github.com/nitezs/sub2sing-box"
|
||||
target="_blank"
|
||||
class="text-decoration-none"
|
||||
>sub2sing-box</a
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<div id="renameContainer"></div>
|
||||
</div>
|
||||
</h2>
|
||||
<div id="form">
|
||||
<div class="card my-4">
|
||||
<div class="card-header">节点</div>
|
||||
<div class="card-body">
|
||||
<!-- Subscription -->
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text">订阅链接</span>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="subscription"
|
||||
name="subscription"
|
||||
placeholder="一行一个"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Output -->
|
||||
<div class="form-group">
|
||||
<label for="output">Link:</label>
|
||||
<textarea class="form-control" id="output" name="output"></textarea>
|
||||
<!-- Proxy -->
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text">节点分享链接</span>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="proxy"
|
||||
name="proxy"
|
||||
placeholder="一行一个"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Delete -->
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text">删除节点:</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="delete"
|
||||
name="delete"
|
||||
placeholder="支持正则表达式"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Rename -->
|
||||
<div class="input-group mb-2">
|
||||
<span class="input-group-text">重命名节点</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
onclick="addRenameField()"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="renameContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card my-4">
|
||||
<div class="card-header">模板</div>
|
||||
<div class="card-body">
|
||||
<!-- Template -->
|
||||
<div class="form-group">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="template"
|
||||
name="template"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card my-4">
|
||||
<div class="card-header">国家策略组</div>
|
||||
<div class="card-body">
|
||||
<!-- Group -->
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="group"
|
||||
name="group"
|
||||
/>
|
||||
<label for="group">启用</label>
|
||||
</div>
|
||||
|
||||
<!-- GroupType -->
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text">类型</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="group-type"
|
||||
name="group-type"
|
||||
value="selector"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Sort -->
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text">排序依据</span>
|
||||
<select class="form-select" name="sort" id="sort">
|
||||
<option value="tag" selected>节点名</option>
|
||||
<option value="num">节点数量</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- SortType -->
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">排序方式</span>
|
||||
<select class="form-select" name="sort-type" id="sort-type">
|
||||
<option value="asc" selected>升序</option>
|
||||
<option value="desc">降序</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header">生成链接</div>
|
||||
<div class="card-body">
|
||||
<!-- Output -->
|
||||
<div class="form-group">
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="output"
|
||||
name="output"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
@ -130,6 +201,10 @@
|
||||
for (let input of inputs) {
|
||||
input.addEventListener("input", generateLink);
|
||||
}
|
||||
const selects = document.querySelectorAll("#form select");
|
||||
for (let select of selects) {
|
||||
select.addEventListener("change", generateLink);
|
||||
}
|
||||
}
|
||||
|
||||
function cleanLisnter() {
|
||||
@ -137,14 +212,18 @@
|
||||
for (let input of inputs) {
|
||||
input.removeEventListener("input", generateLink);
|
||||
}
|
||||
const selects = document.querySelectorAll("#form select");
|
||||
for (let select of selects) {
|
||||
select.removeEventListener("change", generateLink);
|
||||
}
|
||||
}
|
||||
|
||||
function addRenameField() {
|
||||
cleanLisnter();
|
||||
const container = document.getElementById("renameContainer");
|
||||
const fieldHTML = `<div class="rename-group d-flex align-items-center">
|
||||
<input type="text" class="form-control mr-2" name="rename_from[]" placeholder="Old Name">
|
||||
<input type="text" class="form-control mr-2" name="rename_to[]" placeholder="New Name">
|
||||
const fieldHTML = `<div class="rename-group input-group">
|
||||
<input type="text" class="form-control" name="rename_from[]" placeholder="原字符(支持正则表达式)">
|
||||
<input type="text" class="form-control" name="rename_to[]" placeholder="替换字符">
|
||||
<button type="button" class="btn btn-danger" onclick="removeThisField(this)">-</button>
|
||||
</div>`;
|
||||
container.insertAdjacentHTML("beforeend", fieldHTML);
|
||||
@ -176,6 +255,10 @@
|
||||
document.getElementsByName("rename_to[]")
|
||||
).map((input) => input.value);
|
||||
const output = document.getElementById("output");
|
||||
const group = document.getElementById("group").checked;
|
||||
const groupType = document.getElementById("group-type").value;
|
||||
const sort = document.getElementById("sort").value;
|
||||
const sortType = document.getElementById("sort-type").value;
|
||||
|
||||
let rename = {};
|
||||
for (let i = 0; i < renameFrom.length; i++) {
|
||||
@ -189,6 +272,10 @@
|
||||
delete: deleteRule,
|
||||
template,
|
||||
rename,
|
||||
group,
|
||||
"group-type": groupType,
|
||||
sort,
|
||||
"sort-type": sortType,
|
||||
};
|
||||
|
||||
output.value = `${window.location.origin}/convert?data=${encodeBase64(
|
||||
|
@ -14,6 +14,10 @@ var template string
|
||||
var output string
|
||||
var delete string
|
||||
var rename map[string]string
|
||||
var group bool
|
||||
var groupType string
|
||||
var sortKey string
|
||||
var sortType string
|
||||
|
||||
func init() {
|
||||
convertCmd.Flags().StringSliceVarP(&subscriptions, "subscription", "s", []string{}, "subscription urls")
|
||||
@ -22,6 +26,10 @@ func init() {
|
||||
convertCmd.Flags().StringVarP(&output, "output", "o", "", "output file path")
|
||||
convertCmd.Flags().StringVarP(&delete, "delete", "d", "", "delete proxy with regex")
|
||||
convertCmd.Flags().StringToStringVarP(&rename, "rename", "r", map[string]string{}, "rename proxy with regex")
|
||||
convertCmd.Flags().BoolVarP(&group, "group", "g", false, "group proxies by country")
|
||||
convertCmd.Flags().StringVarP(&groupType, "group-type", "G", "selector", "group type, selector or urltest")
|
||||
convertCmd.Flags().StringVarP(&sortKey, "sort", "S", "tag", "sort key, tag or num")
|
||||
convertCmd.Flags().StringVarP(&sortType, "sort-type", "T", "asc", "sort type, asc or desc")
|
||||
RootCmd.AddCommand(convertCmd)
|
||||
}
|
||||
|
||||
@ -32,7 +40,17 @@ var convertCmd = &cobra.Command{
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
result := ""
|
||||
var err error
|
||||
result, err = Convert(subscriptions, proxies, template, delete, rename)
|
||||
result, err = Convert(
|
||||
subscriptions,
|
||||
proxies,
|
||||
template,
|
||||
delete,
|
||||
rename,
|
||||
group,
|
||||
groupType,
|
||||
sortKey,
|
||||
sortType,
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
|
@ -1,5 +1,11 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// https://zh.wikipedia.org/wiki/%E5%8C%BA%E5%9F%9F%E6%8C%87%E7%A4%BA%E7%AC%A6
|
||||
// https://zh.wikipedia.org/zh-sg/ISO_3166-1%E4%BA%8C%E4%BD%8D%E5%AD%97%E6%AF%8D%E4%BB%A3%E7%A0%81
|
||||
|
||||
@ -1042,3 +1048,41 @@ var CountryISO = map[string]string{
|
||||
"ZM": "赞比亚(ZM)",
|
||||
"ZW": "津巴布韦(ZW)",
|
||||
}
|
||||
|
||||
func GetContryName(tag string) string {
|
||||
reg := regexp.MustCompile(`(\s?[A-Za-z]{2}[\s-_/]|[\(\[][A-Za-z]{2}[\)\]])`)
|
||||
tagSlice := reg.FindStringSubmatch(tag)
|
||||
for i := range tagSlice {
|
||||
tagSlice[i] = strings.ToLower(strings.Trim(tagSlice[i], "()[] -_/"))
|
||||
}
|
||||
countryMaps := []map[string]string{
|
||||
CountryFlag,
|
||||
CountryChineseName,
|
||||
CountryISO,
|
||||
CountryEnglishName,
|
||||
}
|
||||
for _, countryMap := range countryMaps {
|
||||
for k, v := range countryMap {
|
||||
if slices.Contains(tagSlice, strings.ToLower(k)) {
|
||||
return v
|
||||
}
|
||||
if strings.Contains(tag, strings.ToLower(k)) {
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
return "其他地区"
|
||||
}
|
||||
|
||||
var values []string
|
||||
|
||||
func IsCountryGroup(tag string) bool {
|
||||
return slices.Contains(values, tag)
|
||||
}
|
||||
|
||||
func init() {
|
||||
values = make([]string, 0, len(CountryISO))
|
||||
for _, v := range CountryISO {
|
||||
values = append(values, v)
|
||||
}
|
||||
}
|
||||
|
27
internal/model/sort.go
Normal file
27
internal/model/sort.go
Normal file
@ -0,0 +1,27 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"golang.org/x/text/collate"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type SortByNumber []Outbound
|
||||
|
||||
func (a SortByNumber) Len() int { return len(a) }
|
||||
func (a SortByNumber) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a SortByNumber) Less(i, j int) bool { return len(a[i].Outbounds) < len(a[j].Outbounds) }
|
||||
|
||||
type SortByTag []Outbound
|
||||
|
||||
func (a SortByTag) Len() int { return len(a) }
|
||||
func (a SortByTag) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a SortByTag) Less(i, j int) bool {
|
||||
tags := []language.Tag{
|
||||
language.English,
|
||||
language.Chinese,
|
||||
}
|
||||
matcher := language.NewMatcher(tags)
|
||||
bestMatch, _, _ := matcher.Match(language.Make("zh"))
|
||||
c := collate.New(bestMatch)
|
||||
return c.CompareString(a[i].Tag, a[j].Tag) < 0
|
||||
}
|
@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"sub2sing-box/internal/model"
|
||||
@ -14,7 +15,17 @@ import (
|
||||
"sub2sing-box/pkg/parser"
|
||||
)
|
||||
|
||||
func Convert(subscriptions []string, proxies []string, template string, delete string, rename map[string]string) (string, error) {
|
||||
func Convert(
|
||||
subscriptions []string,
|
||||
proxies []string,
|
||||
template string,
|
||||
delete string,
|
||||
rename map[string]string,
|
||||
group bool,
|
||||
groupType string,
|
||||
sortKey string,
|
||||
sortType string,
|
||||
) (string, error) {
|
||||
result := ""
|
||||
var err error
|
||||
|
||||
@ -78,14 +89,25 @@ func Convert(subscriptions []string, proxies []string, template string, delete s
|
||||
}
|
||||
}
|
||||
proxyList = newProxyList
|
||||
|
||||
var outbounds []model.Outbound
|
||||
ps, err := json.Marshal(&proxyList)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = json.Unmarshal(ps, &outbounds)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if group {
|
||||
outbounds = AddCountryGroup(outbounds, groupType, sortKey, sortType)
|
||||
}
|
||||
if template != "" {
|
||||
result, err = MergeTemplate(proxyList, template)
|
||||
result, err = MergeTemplate(outbounds, template)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
r, err := json.Marshal(proxyList)
|
||||
r, err := json.Marshal(outbounds)
|
||||
result = string(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -95,7 +117,51 @@ func Convert(subscriptions []string, proxies []string, template string, delete s
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
func MergeTemplate(proxies []model.Proxy, template string) (string, error) {
|
||||
func AddCountryGroup(proxies []model.Outbound, groupType string, sortKey string, sortType string) []model.Outbound {
|
||||
newGroup := make(map[string]model.Outbound)
|
||||
for _, p := range proxies {
|
||||
if p.Type != "selector" && p.Type != "urltest" {
|
||||
country := model.GetContryName(p.Tag)
|
||||
if group, ok := newGroup[country]; ok {
|
||||
group.Outbounds = append(group.Outbounds, p.Tag)
|
||||
newGroup[country] = group
|
||||
} else {
|
||||
newGroup[country] = model.Outbound{
|
||||
Tag: country,
|
||||
Type: groupType,
|
||||
Outbounds: []string{p.Tag},
|
||||
InterruptExistConnections: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var groups []model.Outbound
|
||||
for _, p := range newGroup {
|
||||
groups = append(groups, p)
|
||||
}
|
||||
if sortType == "asc" {
|
||||
switch sortKey {
|
||||
case "tag":
|
||||
sort.Sort(model.SortByTag(groups))
|
||||
case "num":
|
||||
sort.Sort(model.SortByNumber(groups))
|
||||
default:
|
||||
sort.Sort(model.SortByTag(groups))
|
||||
}
|
||||
} else {
|
||||
switch sortKey {
|
||||
case "tag":
|
||||
sort.Sort(sort.Reverse(model.SortByTag(groups)))
|
||||
case "num":
|
||||
sort.Sort(sort.Reverse(model.SortByNumber(groups)))
|
||||
default:
|
||||
sort.Sort(sort.Reverse(model.SortByTag(groups)))
|
||||
}
|
||||
}
|
||||
return append(proxies, groups...)
|
||||
}
|
||||
|
||||
func MergeTemplate(outbounds []model.Outbound, template string) (string, error) {
|
||||
var config model.Config
|
||||
var err error
|
||||
if strings.HasPrefix(template, "http") {
|
||||
@ -116,34 +182,41 @@ func MergeTemplate(proxies []model.Proxy, template string) (string, error) {
|
||||
config, err = ReadTemplate(template)
|
||||
}
|
||||
proxyTags := make([]string, 0)
|
||||
groupTags := make([]string, 0)
|
||||
groups := make(map[string]model.Outbound)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, p := range proxies {
|
||||
proxyTags = append(proxyTags, p.Tag)
|
||||
}
|
||||
ps, err := json.Marshal(&proxies)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var newOutbounds []model.Outbound
|
||||
err = json.Unmarshal(ps, &newOutbounds)
|
||||
if err != nil {
|
||||
return "", err
|
||||
for _, p := range outbounds {
|
||||
if model.IsCountryGroup(p.Tag) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
reg := regexp.MustCompile("<[A-Za-z]{2}>")
|
||||
for i, outbound := range config.Outbounds {
|
||||
var parsedOutbound []string = make([]string, 0)
|
||||
for _, o := range outbound.Outbounds {
|
||||
if o == "<all-proxy-tags>" {
|
||||
parsedOutbound = append(parsedOutbound, proxyTags...)
|
||||
} else if o == "<all-country-tags>" {
|
||||
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.Outbounds...)
|
||||
}
|
||||
} else {
|
||||
parsedOutbound = append(parsedOutbound, o)
|
||||
}
|
||||
}
|
||||
config.Outbounds[i].Outbounds = parsedOutbound
|
||||
}
|
||||
config.Outbounds = append(config.Outbounds, newOutbounds...)
|
||||
//TODO: 国家策略组
|
||||
config.Outbounds = append(config.Outbounds, outbounds...)
|
||||
data, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -254,20 +327,3 @@ func RenameProxy(proxies []model.Proxy, regex string, replaceText string) ([]mod
|
||||
}
|
||||
return proxies, nil
|
||||
}
|
||||
|
||||
func GetContryName(proxyName string) string {
|
||||
countryMaps := []map[string]string{
|
||||
model.CountryFlag,
|
||||
model.CountryChineseName,
|
||||
model.CountryISO,
|
||||
model.CountryEnglishName,
|
||||
}
|
||||
for _, countryMap := range countryMaps {
|
||||
for k, v := range countryMap {
|
||||
if strings.Contains(proxyName, k) {
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
return "其他地区"
|
||||
}
|
||||
|
266
templates/tun-fakeip-without-dns-leaks-country-group.json
Normal file
266
templates/tun-fakeip-without-dns-leaks-country-group.json
Normal file
@ -0,0 +1,266 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "info",
|
||||
"timestamp": true
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "google",
|
||||
"address": "tls://8.8.8.8"
|
||||
},
|
||||
{
|
||||
"tag": "local",
|
||||
"address": "https://223.5.5.5/dns-query",
|
||||
"detour": "direct"
|
||||
},
|
||||
{
|
||||
"tag": "remote",
|
||||
"address": "fakeip"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"outbound": "any",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"query_type": ["A", "AAAA"],
|
||||
"server": "remote"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Direct",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Global",
|
||||
"server": "google"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-geolocation-cn",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{
|
||||
"rule_set": "geosite-geolocation-!cn"
|
||||
},
|
||||
{
|
||||
"rule_set": "geoip-cn"
|
||||
}
|
||||
],
|
||||
"server": "google",
|
||||
"client_subnet": "114.114.114.114"
|
||||
}
|
||||
],
|
||||
"fakeip": {
|
||||
"enabled": true,
|
||||
"inet4_range": "198.18.0.0/15",
|
||||
"inet6_range": "fc00::/18"
|
||||
},
|
||||
"independent_cache": true
|
||||
},
|
||||
"route": {
|
||||
"rule_set": [
|
||||
{
|
||||
"tag": "geosite-geolocation-cn",
|
||||
"type": "remote",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs",
|
||||
"download_detour": "节点选择"
|
||||
},
|
||||
{
|
||||
"tag": "geosite-geolocation-!cn",
|
||||
"type": "remote",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-!cn.srs",
|
||||
"download_detour": "节点选择"
|
||||
},
|
||||
{
|
||||
"tag": "geoip-cn",
|
||||
"type": "remote",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs",
|
||||
"download_detour": "节点选择"
|
||||
},
|
||||
{
|
||||
"tag": "geosite-category-ads-all",
|
||||
"type": "remote",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-category-ads-all.srs",
|
||||
"download_detour": "节点选择"
|
||||
},
|
||||
{
|
||||
"tag": "geosite-microsoft",
|
||||
"type": "remote",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-microsoft.srs",
|
||||
"download_detour": "节点选择"
|
||||
},
|
||||
{
|
||||
"tag": "geosite-bilibili",
|
||||
"type": "remote",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-bilibili.srs",
|
||||
"download_detour": "节点选择"
|
||||
},
|
||||
{
|
||||
"tag": "geosite-bahamut",
|
||||
"type": "remote",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-bahamut.srs",
|
||||
"download_detour": "节点选择"
|
||||
},
|
||||
{
|
||||
"tag": "geosite-category-games@cn",
|
||||
"type": "remote",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-category-games@cn.srs",
|
||||
"download_detour": "节点选择"
|
||||
},
|
||||
{
|
||||
"tag": "geosite-category-games",
|
||||
"type": "remote",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-category-games.srs",
|
||||
"download_detour": "节点选择"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns"
|
||||
},
|
||||
{
|
||||
"port": 53
|
||||
}
|
||||
],
|
||||
"outbound": "dns-out"
|
||||
},
|
||||
{
|
||||
"ip_is_private": true,
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"rule_set": ["geoip-cn", "geosite-geolocation-cn"],
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-category-ads-all",
|
||||
"outbound": "Ads"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-microsoft",
|
||||
"outbound": "Microsoft"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-bilibili",
|
||||
"outbound": "Bilibili"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-category-games@cn",
|
||||
"outbound": "Games(中国)"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-category-games",
|
||||
"outbound": "Games(全球)"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-bahamut",
|
||||
"outbound": "Bahamut"
|
||||
}
|
||||
],
|
||||
"final": "节点选择",
|
||||
"auto_detect_interface": true
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||
"auto_route": true,
|
||||
"strict_route": true,
|
||||
"sniff": true,
|
||||
"sniff_override_destination": false
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "selector",
|
||||
"tag": "节点选择",
|
||||
"outbounds": ["<all-country-tags>", "direct"],
|
||||
"interrupt_exist_connections": true
|
||||
},
|
||||
{
|
||||
"type": "selector",
|
||||
"tag": "Ads",
|
||||
"outbounds": ["direct", "block"],
|
||||
"default": "block",
|
||||
"interrupt_exist_connections": true
|
||||
},
|
||||
{
|
||||
"type": "selector",
|
||||
"tag": "Microsoft",
|
||||
"outbounds": ["节点选择", "<all-country-tags>", "direct"],
|
||||
"default": "节点选择",
|
||||
"interrupt_exist_connections": true
|
||||
},
|
||||
{
|
||||
"type": "selector",
|
||||
"tag": "Bilibili",
|
||||
"outbounds": ["节点选择", "<all-country-tags>", "direct"],
|
||||
"default": "direct",
|
||||
"interrupt_exist_connections": true
|
||||
},
|
||||
{
|
||||
"type": "selector",
|
||||
"tag": "Games(全球)",
|
||||
"outbounds": ["节点选择", "<all-country-tags>", "direct"],
|
||||
"default": "节点选择",
|
||||
"interrupt_exist_connections": true
|
||||
},
|
||||
{
|
||||
"type": "selector",
|
||||
"tag": "Games(中国)",
|
||||
"outbounds": ["节点选择", "<all-country-tags>", "direct"],
|
||||
"default": "direct",
|
||||
"interrupt_exist_connections": true
|
||||
},
|
||||
{
|
||||
"type": "selector",
|
||||
"tag": "Bahamut",
|
||||
"outbounds": ["节点选择", "<all-country-tags>", "direct"],
|
||||
"default": "节点选择",
|
||||
"interrupt_exist_connections": true
|
||||
},
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
}
|
||||
],
|
||||
"experimental": {
|
||||
"cache_file": {
|
||||
"enabled": true,
|
||||
"store_rdrc": true
|
||||
},
|
||||
"clash_api": {
|
||||
"default_mode": "Enhanced",
|
||||
"external_controller": "127.0.0.1:9090",
|
||||
"external_ui": "./ui",
|
||||
"external_ui_download_detour": "节点选择"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user