diff --git a/API_README.md b/API_README.md new file mode 100644 index 0000000..17e3599 --- /dev/null +++ b/API_README.md @@ -0,0 +1,36 @@ +# `/clash`, `/meta` + +获取 Clash/Clash.Meta 配置链接 + +| Query 参数 | 类型 | 是否必须 | 默认值 | 说明 | +|--------------|--------|-------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| sub | string | sub/proxy 至少有一项存在 | - | 订阅链接(可以输入多个,用 `,` 分隔) | +| proxy | string | sub/proxy 至少有一项存在 | - | 节点分享链接(可以输入多个,用 `,` 分隔) | +| refresh | bool | 否 | `false` | 强制刷新配置(默认缓存 5 分钟) | +| template | string | 否 | - | 外部模板链接或内部模板名称 | +| 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规则之前) | +| autoTest | bool | 否 | `false` | 国家策略组是否自动测速 | +| lazy | bool | 否 | `false` | 自动测速是否启用 lazy | +| sort | string | 否 | `nameasc` | 国家策略组排序策略,可选值 `nameasc`、`namedesc`、`sizeasc`、`sizedesc` | +| replace | string | 否 | - | 通过正则表达式重命名节点,格式 `[ReplaceKey,ReplaceTo]` | +| remove | string | 否 | - | 通过正则表达式删除节点 | + +# `/short` + +获取短链,Content-Type 为 `application/json` +具体参考使用可以参考 [api\templates\index.html](./api/templates/index.html) + +| Body 参数 | 类型 | 是否必须 | 默认值 | 说明 | +|----------|--------|------|-----|------------------| +| url | string | 是 | - | 需要转换的 Query 参数部分 | +| password | string | 否 | - | 短链密码 | + +# `/s/:hash` + +短链跳转 +`hash` 为动态路由参数,可以通过 `/short` 接口获取 + +| Query 参数 | 类型 | 是否必须 | 默认值 | 说明 | +|----------|--------|------|-----|------| +| password | string | 否 | - | 短链密码 | diff --git a/README.md b/README.md index ba8b5d5..0550fed 100644 --- a/README.md +++ b/README.md @@ -39,23 +39,17 @@ ### API -#### `/clash`, `/meta` +[API文档](./API_README.md) -获取 Clash/Clash.Meta 配置链接 +### 模板 -| Query 参数 | 类型 | 是否必须 | 默认值 | 说明 | -|--------------|--------|-------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| sub | string | sub/proxy 至少有一项存在 | - | 订阅链接(可以输入多个,用 `,` 分隔) | -| proxy | string | sub/proxy 至少有一项存在 | - | 节点分享链接(可以输入多个,用 `,` 分隔) | -| refresh | bool | 否 | `false` | 强制刷新配置(默认缓存 5 分钟) | -| template | string | 否 | - | 外部模板链接或内部模板名称 | -| 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规则之前) | -| autoTest | bool | 否 | `false` | 国家策略组是否自动测速 | -| lazy | bool | 否 | `false` | 自动测速是否启用 lazy | -| sort | string | 否 | `nameasc` | 国家策略组排序策略,可选值 `nameasc`、`namedesc`、`sizeasc`、`sizedesc` | +可以通过变脸自定义模板中的策略组代理节点 +解释的不太清楚,可以参考下方默认模板 -## 默认模板 +- `` 为添加所有节点 +- `` 为添加所有国家策略组 + +#### 默认模板 - [Clash](./templates/template_clash.yaml) - [Clash.Meta](./templates/template_meta.yaml) @@ -63,5 +57,3 @@ ## 已知问题 [代理链接解析](./parser)还没有经过严格测试,可能会出现解析错误的情况,如果出现问题请提交 issue - -## TODO diff --git a/api/controller/default.go b/api/controller/default.go index 2c561aa..ae4b885 100644 --- a/api/controller/default.go +++ b/api/controller/default.go @@ -9,6 +9,7 @@ import ( "net/url" "regexp" "sort" + "strconv" "strings" "sub2clash/logger" "sub2clash/model" @@ -88,6 +89,46 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template proxyList = append(proxyList, sub.Proxies...) } } + // 去重 + proxies := make(map[string]*model.Proxy) + for i := range proxyList { + key := proxyList[i].Server + ":" + strconv.Itoa(proxyList[i].Port) + ":" + proxyList[i].Type + if _, exist := proxies[key]; exist { + proxyList = append(proxyList[:i], proxyList[i+1:]...) + } + } + // 重名检测 + names := make(map[string]bool) + for i := range proxyList { + if _, exist := names[proxyList[i].Name]; exist { + proxyList[i].Name = proxyList[i].Name + "@" + proxyList[i].Server + ":" + strconv.Itoa(proxyList[i].Port) + } + names[proxyList[i].Name] = true + } + // 删除节点、改名 + if strings.TrimSpace(query.Remove) != "" { + removeReg, err := regexp.Compile(query.Remove) + if err != nil { + logger.Logger.Debug("remove regexp compile failed", zap.Error(err)) + return nil, errors.New("remove 参数非法: " + err.Error()) + } + replaceReg, err := regexp.Compile(query.ReplaceKey) + if err != nil { + logger.Logger.Debug("replace regexp compile failed", zap.Error(err)) + return nil, errors.New("replaceName 参数非法: " + err.Error()) + } + newProxyList := make([]model.Proxy, 0, len(proxyList)) + for i := range proxyList { + if removeReg.MatchString(proxyList[i].Name) { + continue // 如果匹配到要删除的元素,跳过该元素,不添加到新切片中 + } + if replaceReg.MatchString(proxyList[i].Name) { + proxyList[i].Name = replaceReg.ReplaceAllString(proxyList[i].Name, query.ReplaceTo) + } + newProxyList = append(newProxyList, proxyList[i]) // 将要保留的元素添加到新切片中 + } + proxyList = newProxyList + } // 将新增节点都添加到临时变量 t 中,防止策略组排序错乱 var t = &model.Subscription{} utils.AddProxy(t, query.AutoTest, query.Lazy, clashType, proxyList...) @@ -154,28 +195,28 @@ func MergeSubAndTemplate(temp *model.Subscription, sub *model.Subscription) { ) } } + var proxyNames []string + for _, proxy := range sub.Proxies { + proxyNames = append(proxyNames, proxy.Name) + } // 将订阅中的节点添加到模板中 temp.Proxies = append(temp.Proxies, sub.Proxies...) // 将订阅中的策略组添加到模板中 - skipGroups := []string{"全球直连", "广告拦截", "手动切换"} for i := range temp.ProxyGroups { - skip := false - for _, v := range skipGroups { - if strings.Contains(temp.ProxyGroups[i].Name, v) { - if v == "手动切换" { - proxies := make([]string, 0, len(sub.Proxies)) - for _, p := range sub.Proxies { - proxies = append(proxies, p.Name) - } - temp.ProxyGroups[i].Proxies = proxies - } - skip = true - continue + if temp.ProxyGroups[i].IsCountryGrop { + continue + } + newProxies := make([]string, 0, len(temp.ProxyGroups[i].Proxies)) + for j := range temp.ProxyGroups[i].Proxies { + if temp.ProxyGroups[i].Proxies[j] == "" { + newProxies = append(newProxies, proxyNames...) + } else if temp.ProxyGroups[i].Proxies[j] == "" { + newProxies = append(newProxies, countryGroupNames...) + } else { + newProxies = append(newProxies, temp.ProxyGroups[i].Proxies[j]) } } - if !skip { - temp.ProxyGroups[i].Proxies = append(temp.ProxyGroups[i].Proxies, countryGroupNames...) - } + temp.ProxyGroups[i].Proxies = newProxies } temp.ProxyGroups = append(temp.ProxyGroups, sub.ProxyGroups...) } diff --git a/api/controller/short_link.go b/api/controller/short_link.go index 1eea508..de5fad7 100644 --- a/api/controller/short_link.go +++ b/api/controller/short_link.go @@ -50,15 +50,20 @@ func ShortLinkGenHandler(c *gin.Context) { Hash: hash, Url: params.Url, LastRequestTime: -1, + Password: params.Password, }, ) // 返回短链接 + if params.Password != "" { + hash += "/?password=" + params.Password + } c.String(200, hash) } func ShortLinkGetHandler(c *gin.Context) { // 获取动态路由 hash := c.Param("hash") + password := c.Query("password") if strings.TrimSpace(hash) == "" { c.String(400, "参数错误") return @@ -71,6 +76,10 @@ func ShortLinkGetHandler(c *gin.Context) { c.String(404, "未找到短链接") return } + if shortLink.Password != "" && shortLink.Password != password { + c.String(403, "密码错误") + return + } // 更新最后访问时间 shortLink.LastRequestTime = time.Now().Unix() database.SaveShortLink(&shortLink) diff --git a/api/templates/index.html b/api/templates/index.html index 4b3bf5e..d3354d3 100644 --- a/api/templates/index.html +++ b/api/templates/index.html @@ -168,6 +168,39 @@ + + +
+ + +
+ + +
+ +
+ + +
+
@@ -188,6 +221,12 @@
+