mirror of
https://github.com/bestnite/sub2clash.git
synced 2025-07-04 20:02:34 +08:00
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
f166c6a54a | |||
c0e6b62625 | |||
354379b12a | |||
4f4a633035 | |||
be9bdd269e | |||
6b08b2cb86 | |||
38dbea4a2a | |||
1788541e04 | |||
d38d5bcb70 | |||
918521682c | |||
3318f5f2db |
@ -5,3 +5,4 @@ REQUEST_RETRY_TIMES=3
|
|||||||
REQUEST_MAX_FILE_SIZE=1048576
|
REQUEST_MAX_FILE_SIZE=1048576
|
||||||
CACHE_EXPIRE=300
|
CACHE_EXPIRE=300
|
||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
|
BASE_PATH=/
|
4
.github/workflows/go.yml
vendored
4
.github/workflows/go.yml
vendored
@ -117,7 +117,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.up
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: ./output/sub2clash-windows-386.exe
|
asset_path: ./output/sub2clash-windows-386.exe
|
||||||
asset_name: sub2clash-windows-386.exe
|
asset_name: sub2clash-windows-386.exe
|
||||||
asset_content_type: application/octet-stream
|
asset_content_type: application/octet-stream
|
||||||
@ -147,7 +147,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.up
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: ./output/sub2clash-windows-arm64.exe
|
asset_path: ./output/sub2clash-windows-arm64.exe
|
||||||
asset_name: sub2clash-windows-arm64.exe
|
asset_name: sub2clash-windows-arm64.exe
|
||||||
asset_content_type: application/octet-stream
|
asset_content_type: application/octet-stream
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,4 +3,4 @@ dist
|
|||||||
subs
|
subs
|
||||||
test
|
test
|
||||||
logs
|
logs
|
||||||
dist/
|
sub2clash.db
|
@ -1,6 +1,6 @@
|
|||||||
before:
|
#before:
|
||||||
hooks:
|
# hooks:
|
||||||
- go mod tidy
|
# - go mod tidy
|
||||||
builds:
|
builds:
|
||||||
- env:
|
- env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
|
@ -26,7 +26,8 @@
|
|||||||
可以通过编辑 .env 文件来修改默认配置,docker 直接添加环境变量
|
可以通过编辑 .env 文件来修改默认配置,docker 直接添加环境变量
|
||||||
|
|
||||||
| 变量名 | 说明 | 默认值 |
|
| 变量名 | 说明 | 默认值 |
|
||||||
|-----------------------|----------------------------------------|-----------------------|
|
|-----------------------|-----------------------------------------------------------|-----------------------|
|
||||||
|
| BASE_PATH | 程序运行子路径,例如将服务反代在 `https://example.com/sub` 则此变量值应为 `/sub` | `/` |
|
||||||
| PORT | 端口 | `8011` |
|
| PORT | 端口 | `8011` |
|
||||||
| META_TEMPLATE | meta 模板文件名 | `template_meta.yaml` |
|
| META_TEMPLATE | meta 模板文件名 | `template_meta.yaml` |
|
||||||
| CLASH_TEMPLATE | clash 模板文件名 | `template_clash.yaml` |
|
| CLASH_TEMPLATE | clash 模板文件名 | `template_clash.yaml` |
|
||||||
@ -49,7 +50,7 @@
|
|||||||
| template | string | 否 | - | 外部模板链接或内部模板名称 |
|
| template | string | 否 | - | 外部模板链接或内部模板名称 |
|
||||||
| ruleProvider | string | 否 | - | 格式 `[Behavior,Url,Group,Prepend,Name],[Behavior,Url,Group,Prepend,Name]...`,其中 `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 |
|
||||||
| sort | string | 否 | `nameasc` | 国家策略组排序策略,可选值 `nameasc`、`namedesc`、`sizeasc`、`sizedesc` |
|
| sort | string | 否 | `nameasc` | 国家策略组排序策略,可选值 `nameasc`、`namedesc`、`sizeasc`、`sizedesc` |
|
||||||
|
|
||||||
@ -63,5 +64,3 @@
|
|||||||
[代理链接解析](./parser)还没有经过严格测试,可能会出现解析错误的情况,如果出现问题请提交 issue
|
[代理链接解析](./parser)还没有经过严格测试,可能会出现解析错误的情况,如果出现问题请提交 issue
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- [ ] 可视化面板
|
|
@ -5,6 +5,7 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sub2clash/config"
|
"sub2clash/config"
|
||||||
|
"sub2clash/model"
|
||||||
"sub2clash/validator"
|
"sub2clash/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ func SubmodHandler(c *gin.Context) {
|
|||||||
c.String(http.StatusBadRequest, err.Error())
|
c.String(http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sub, err := BuildSub(query, config.Default.ClashTemplate)
|
sub, err := BuildSub(model.Clash, query, config.Default.ClashTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.String(http.StatusInternalServerError, err.Error())
|
c.String(http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
@ -14,12 +14,12 @@ import (
|
|||||||
"sub2clash/validator"
|
"sub2clash/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
func BuildSub(query validator.SubQuery, template string) (
|
func BuildSub(clashType model.ClashType, query validator.SubValidator, template string) (
|
||||||
*model.Subscription, error,
|
*model.Subscription, error,
|
||||||
) {
|
) {
|
||||||
// 定义变量
|
// 定义变量
|
||||||
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
|
||||||
// 加载模板
|
// 加载模板
|
||||||
@ -67,11 +67,11 @@ func BuildSub(query validator.SubQuery, template string) (
|
|||||||
} else {
|
} else {
|
||||||
proxyList = sub.Proxies
|
proxyList = sub.Proxies
|
||||||
}
|
}
|
||||||
utils.AddProxy(sub, query.AutoTest, query.Lazy, query.Sort, proxyList...)
|
utils.AddProxy(sub, query.AutoTest, query.Lazy, query.Sort, clashType, proxyList...)
|
||||||
}
|
}
|
||||||
// 处理自定义代理
|
// 处理自定义代理
|
||||||
utils.AddProxy(
|
utils.AddProxy(
|
||||||
sub, query.AutoTest, query.Lazy, query.Sort,
|
sub, query.AutoTest, query.Lazy, query.Sort, clashType,
|
||||||
utils.ParseProxy(query.Proxies...)...,
|
utils.ParseProxy(query.Proxies...)...,
|
||||||
)
|
)
|
||||||
MergeSubAndTemplate(temp, sub)
|
MergeSubAndTemplate(temp, sub)
|
||||||
@ -85,7 +85,7 @@ func BuildSub(query validator.SubQuery, template string) (
|
|||||||
}
|
}
|
||||||
// 处理自定义 ruleProvider
|
// 处理自定义 ruleProvider
|
||||||
for _, v := range query.RuleProviders {
|
for _, v := range query.RuleProviders {
|
||||||
hash := md5.Sum([]byte(v.Url))
|
hash := sha256.Sum224([]byte(v.Url))
|
||||||
name := hex.EncodeToString(hash[:])
|
name := hex.EncodeToString(hash[:])
|
||||||
provider := model.RuleProvider{
|
provider := model.RuleProvider{
|
||||||
Type: "http",
|
Type: "http",
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sub2clash/config"
|
"sub2clash/config"
|
||||||
|
"sub2clash/model"
|
||||||
"sub2clash/validator"
|
"sub2clash/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,7 +17,7 @@ func SubHandler(c *gin.Context) {
|
|||||||
c.String(http.StatusBadRequest, err.Error())
|
c.String(http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sub, err := BuildSub(query, config.Default.MetaTemplate)
|
sub, err := BuildSub(model.ClashMeta, query, config.Default.MetaTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.String(http.StatusInternalServerError, err.Error())
|
c.String(http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
|
53
api/controller/short_link.go
Normal file
53
api/controller/short_link.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
"sub2clash/config"
|
||||||
|
"sub2clash/model"
|
||||||
|
"sub2clash/utils/database"
|
||||||
|
"sub2clash/validator"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ShortLinkGenHandler(c *gin.Context) {
|
||||||
|
// 从请求中获取参数
|
||||||
|
var params validator.ShortLinkGenValidator
|
||||||
|
if err := c.ShouldBind(¶ms); err != nil {
|
||||||
|
c.String(400, "参数错误: "+err.Error())
|
||||||
|
}
|
||||||
|
// 生成短链接
|
||||||
|
//hash := utils.RandomString(6)
|
||||||
|
shortLink := sha256.Sum224([]byte(params.Url))
|
||||||
|
hash := hex.EncodeToString(shortLink[:])
|
||||||
|
// 存入数据库
|
||||||
|
database.DB.FirstOrCreate(
|
||||||
|
&model.ShortLink{
|
||||||
|
Hash: hash,
|
||||||
|
Url: params.Url,
|
||||||
|
LastRequestTime: -1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// 返回短链接
|
||||||
|
c.String(200, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShortLinkGetHandler(c *gin.Context) {
|
||||||
|
// 获取动态路由
|
||||||
|
hash := c.Param("hash")
|
||||||
|
// 查询数据库
|
||||||
|
var shortLink model.ShortLink
|
||||||
|
result := database.DB.Where("hash = ?", hash).First(&shortLink)
|
||||||
|
// 更新最后访问时间
|
||||||
|
shortLink.LastRequestTime = time.Now().Unix()
|
||||||
|
database.DB.Save(&shortLink)
|
||||||
|
// 重定向
|
||||||
|
if result.Error != nil {
|
||||||
|
c.String(404, "未找到短链接")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uri := config.Default.BasePath + shortLink.Url
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, uri)
|
||||||
|
}
|
22
api/route.go
22
api/route.go
@ -1,13 +1,25 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"embed"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"html/template"
|
||||||
"sub2clash/api/controller"
|
"sub2clash/api/controller"
|
||||||
"sub2clash/middleware"
|
"sub2clash/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed templates/*
|
||||||
|
var templates embed.FS
|
||||||
|
|
||||||
func SetRoute(r *gin.Engine) {
|
func SetRoute(r *gin.Engine) {
|
||||||
r.Use(middleware.ZapLogger())
|
r.Use(middleware.ZapLogger())
|
||||||
|
// 使用内嵌的模板文件
|
||||||
|
r.SetHTMLTemplate(template.Must(template.New("").ParseFS(templates, "templates/*")))
|
||||||
|
r.GET(
|
||||||
|
"/", func(c *gin.Context) {
|
||||||
|
c.HTML(200, "index.html", nil)
|
||||||
|
},
|
||||||
|
)
|
||||||
r.GET(
|
r.GET(
|
||||||
"/clash", func(c *gin.Context) {
|
"/clash", func(c *gin.Context) {
|
||||||
controller.SubmodHandler(c)
|
controller.SubmodHandler(c)
|
||||||
@ -18,4 +30,14 @@ func SetRoute(r *gin.Engine) {
|
|||||||
controller.SubHandler(c)
|
controller.SubHandler(c)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
r.POST(
|
||||||
|
"/short", func(c *gin.Context) {
|
||||||
|
controller.ShortLinkGenHandler(c)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
r.GET(
|
||||||
|
"/s/:hash", func(c *gin.Context) {
|
||||||
|
controller.ShortLinkGetHandler(c)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
317
api/templates/index.html
Normal file
317
api/templates/index.html
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>sub2clash</title>
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
|
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<!-- Bootstrap JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
<!-- Axios -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-xs {
|
||||||
|
padding: 2px 2px; /* 调整内边距以减小按钮大小 */
|
||||||
|
font-size: 10px; /* 设置字体大小 */
|
||||||
|
line-height: 1.2; /* 调整行高 */
|
||||||
|
border-radius: 3px; /* 可选的边框半径调整 */
|
||||||
|
height: 25px;
|
||||||
|
width: 25px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-light">
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="mb-4">
|
||||||
|
<h2>sub2clash</h2>
|
||||||
|
<span class="text-muted fst-italic">通用订阅链接转 Clash(Meta) 配置工具 <a
|
||||||
|
href="https://github.com/nitezs/sub2clash#clash-meta" target="_blank">使用文档</a></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="apiForm">
|
||||||
|
|
||||||
|
<!-- API Endpoint -->
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label for="endpoint">客户端类型:</label>
|
||||||
|
<select class="form-control" id="endpoint" name="endpoint">
|
||||||
|
<option value="clash">Clash</option>
|
||||||
|
<option value="meta">Clash.Meta</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Subscription Link -->
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label for="sub">订阅链接:</label>
|
||||||
|
<textarea class="form-control" id="sub" name="sub" rows="5" placeholder="每行输入一个订阅链接"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Proxy Link -->
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label for="proxy">节点分享链接:</label>
|
||||||
|
<textarea class="form-control" id="proxy" name="proxy" rows="5"
|
||||||
|
placeholder="每行输入一个节点分享链接"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Refresh -->
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input type="checkbox" class="form-check-input" id="refresh" name="refresh">
|
||||||
|
<label class="form-check-label" for="refresh">强制刷新配置</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Template -->
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label for="template">模板链接或名称(可选):</label>
|
||||||
|
<input type="text" class="form-control" id="template" name="template"
|
||||||
|
placeholder="输入外部模板链接或内部模板名称">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rule Provider -->
|
||||||
|
<div class="form-group mb-3" id="ruleProviderGroup">
|
||||||
|
<label>Rule Provider:</label>
|
||||||
|
<button type="button" class="btn btn-primary mb-1 btn-xs" onclick="addRuleProvider()">+</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rule -->
|
||||||
|
<div class="form-group mb-3" id="ruleGroup">
|
||||||
|
<label>规则:</label>
|
||||||
|
<button type="button" class="btn btn-primary mb-1 btn-xs" onclick="addRule()">+</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Auto Test -->
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input type="checkbox" class="form-check-input" id="autoTest" name="autoTest">
|
||||||
|
<label class="form-check-label" for="autoTest">指定国家策略组是否自动测速</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lazy -->
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input type="checkbox" class="form-check-input" id="lazy" name="lazy">
|
||||||
|
<label class="form-check-label" for="lazy">自动测速是否启用 lazy</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sort -->
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label for="sort">国家策略组排序策略:</label>
|
||||||
|
<select class="form-control" id="sort" name="sort">
|
||||||
|
<option value="nameasc">名称(升序)</option>
|
||||||
|
<option value="namedesc">名称(降序)</option>
|
||||||
|
<option value="sizeasc">节点数量(升序)</option>
|
||||||
|
<option value="sizedesc">节点数量(降序)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Display the API Link -->
|
||||||
|
<div class="form-group mb-5">
|
||||||
|
<label for="apiLink">配置链接:</label>
|
||||||
|
<div class="input-group mb-2">
|
||||||
|
<input type="text" class="form-control" id="apiLink" readonly>
|
||||||
|
<button class="btn btn-primary" type="button" onclick="generateURL()">生成链接</button>
|
||||||
|
<button class="btn btn-primary" type="button" onclick="copyToClipboard('apiLink',this)">复制链接</button>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" id="apiShortLink" readonly>
|
||||||
|
<button class="btn btn-primary" type="button" onclick="generateShortLink()">生成短链</button>
|
||||||
|
<button class="btn btn-primary" type="button" onclick="copyToClipboard('apiShortLink',this)">复制短链
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- footer-->
|
||||||
|
<footer>
|
||||||
|
<p class="text-center">Powered by <a class="link-primary"
|
||||||
|
href="https://github.com/nitezs/sub2clash">sub2clash</a></p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function copyToClipboard(elem, e) {
|
||||||
|
const apiLinkInput = document.querySelector(`#${elem}`).value;
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(apiLinkInput);
|
||||||
|
let text = e.textContent;
|
||||||
|
e.addEventListener("mouseout", function () {
|
||||||
|
e.textContent = text;
|
||||||
|
});
|
||||||
|
e.textContent = "复制成功";
|
||||||
|
} catch (err) {
|
||||||
|
console.error('复制到剪贴板失败:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRuleProvider() {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.classList.add('input-group', 'mb-2');
|
||||||
|
div.innerHTML = `
|
||||||
|
<input type="text" class="form-control" name="ruleProvider" placeholder="Behavior">
|
||||||
|
<input type="text" class="form-control" name="ruleProvider" placeholder="Url">
|
||||||
|
<input type="text" class="form-control" name="ruleProvider" placeholder="Group">
|
||||||
|
<input type="text" class="form-control" name="ruleProvider" placeholder="Prepend">
|
||||||
|
<input type="text" class="form-control" name="ruleProvider" placeholder="Name">
|
||||||
|
<button type="button" class="btn btn-danger" onclick="removeElement(this)">删除</button>
|
||||||
|
`;
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRule() {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.classList.add('input-group', 'mb-2');
|
||||||
|
div.innerHTML = `
|
||||||
|
<input type="text" class="form-control" name="rule" placeholder="Rule">
|
||||||
|
<input type="text" class="form-control" name="rule" placeholder="Prepend">
|
||||||
|
<input type="text" class="form-control" name="rule" placeholder="Group">
|
||||||
|
<button type="button" class="btn btn-danger" onclick="removeElement(this)">删除</button>
|
||||||
|
`;
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addRuleProvider() {
|
||||||
|
const div = createRuleProvider();
|
||||||
|
document.getElementById('ruleProviderGroup').appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addRule() {
|
||||||
|
const div = createRule();
|
||||||
|
document.getElementById('ruleGroup').appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeElement(button) {
|
||||||
|
button.parentElement.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateURI() {
|
||||||
|
const queryParams = [];
|
||||||
|
|
||||||
|
// 获取 API Endpoint
|
||||||
|
const endpoint = document.getElementById("endpoint").value;
|
||||||
|
|
||||||
|
// 获取并组合订阅链接
|
||||||
|
let subLines = document.getElementById("sub").value.split('\n').filter(line => line.trim() !== "");
|
||||||
|
let noSub = false
|
||||||
|
// 去除 subLines 中空元素
|
||||||
|
subLines = subLines.map((item) => {
|
||||||
|
if (item !== "") {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (subLines.length > 0) {
|
||||||
|
queryParams.push(`sub=${encodeURIComponent(subLines.join(','))}`);
|
||||||
|
} else {
|
||||||
|
noSub = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取并组合节点分享链接
|
||||||
|
let proxyLines = document.getElementById("proxy").value.split('\n').filter(line => line.trim() !== "");
|
||||||
|
let noProxy = false
|
||||||
|
// 去除 proxyLines 中空元素
|
||||||
|
proxyLines = proxyLines.map((item) => {
|
||||||
|
if (item !== "") {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (proxyLines.length > 0) {
|
||||||
|
queryParams.push(`proxy=${encodeURIComponent(proxyLines.join(','))}`);
|
||||||
|
} else {
|
||||||
|
noProxy = true
|
||||||
|
}
|
||||||
|
if (noSub && noProxy) {
|
||||||
|
alert("订阅链接和节点分享链接不能同时为空!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 获取复选框的值
|
||||||
|
const refresh = document.getElementById("refresh").checked;
|
||||||
|
queryParams.push(`refresh=${refresh ? 'true' : 'false'}`);
|
||||||
|
const autoTest = document.getElementById("autoTest").checked;
|
||||||
|
queryParams.push(`autoTest=${autoTest ? 'true' : 'false'}`);
|
||||||
|
const lazy = document.getElementById("lazy").checked;
|
||||||
|
queryParams.push(`lazy=${lazy ? 'true' : 'false'}`);
|
||||||
|
|
||||||
|
// 获取模板链接或名称(如果存在)
|
||||||
|
const template = document.getElementById("template").value;
|
||||||
|
if (template.trim() !== "") {
|
||||||
|
queryParams.push(`template=${encodeURIComponent(template)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取Rule Provider和规则
|
||||||
|
const ruleProviders = document.getElementsByName("ruleProvider");
|
||||||
|
const rules = document.getElementsByName("rule");
|
||||||
|
let providers = [];
|
||||||
|
for (let i = 0; i < ruleProviders.length / 5; i++) {
|
||||||
|
let baseIndex = i * 5;
|
||||||
|
let behavior = ruleProviders[baseIndex].value;
|
||||||
|
let url = ruleProviders[baseIndex + 1].value;
|
||||||
|
let group = ruleProviders[baseIndex + 2].value;
|
||||||
|
let prepend = ruleProviders[baseIndex + 3].value;
|
||||||
|
let name = ruleProviders[baseIndex + 4].value;
|
||||||
|
// 是否存在空值
|
||||||
|
if (behavior.trim() === "" || url.trim() === "" || group.trim() === "" || prepend.trim() === "" || name.trim() === "") {
|
||||||
|
alert("Rule Provider 中存在空值,请检查后重试!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
providers.push(`[${behavior},${url},${group},${prepend},${name}]`);
|
||||||
|
}
|
||||||
|
queryParams.push(`ruleProvider=${encodeURIComponent(providers.join(','))}`);
|
||||||
|
|
||||||
|
let ruleList = [];
|
||||||
|
for (let i = 0; i < rules.length / 3; i++) {
|
||||||
|
if (rules[i * 3].value.trim() !== "") {
|
||||||
|
let rule = rules[i * 3].value;
|
||||||
|
let prepend = rules[i * 3 + 1].value;
|
||||||
|
let group = rules[i * 3 + 2].value;
|
||||||
|
// 是否存在空值
|
||||||
|
if (rule.trim() === "" || prepend.trim() === "" || group.trim() === "") {
|
||||||
|
alert("Rule 中存在空值,请检查后重试!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ruleList.push(`[${rule},${prepend},${group}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queryParams.push(`rule=${encodeURIComponent(ruleList.join(','))}`);
|
||||||
|
|
||||||
|
// 获取排序策略
|
||||||
|
const sort = document.getElementById("sort").value;
|
||||||
|
queryParams.push(`sort=${sort}`);
|
||||||
|
return `${endpoint}?${queryParams.join('&')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateURL() {
|
||||||
|
const apiLink = document.getElementById("apiLink");
|
||||||
|
apiLink.value = `${window.location.origin}${window.location.pathname}${generateURI()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateShortLink() {
|
||||||
|
const apiShortLink = document.getElementById("apiShortLink");
|
||||||
|
|
||||||
|
axios.post("./short", {
|
||||||
|
"url": generateURI()
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
}).then((response) => {
|
||||||
|
apiShortLink.value = `${window.location.origin}${window.location.pathname}s/${response.data}`;
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
alert("生成短链失败,请重试!");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -14,6 +14,7 @@ type Config struct {
|
|||||||
RequestMaxFileSize int64
|
RequestMaxFileSize int64
|
||||||
CacheExpire int64
|
CacheExpire int64
|
||||||
LogLevel string
|
LogLevel string
|
||||||
|
BasePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
var Default *Config
|
var Default *Config
|
||||||
@ -27,11 +28,9 @@ func init() {
|
|||||||
Port: 8011,
|
Port: 8011,
|
||||||
CacheExpire: 60 * 5,
|
CacheExpire: 60 * 5,
|
||||||
LogLevel: "info",
|
LogLevel: "info",
|
||||||
|
BasePath: "/",
|
||||||
}
|
}
|
||||||
err := godotenv.Load()
|
_ = godotenv.Load()
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if os.Getenv("PORT") != "" {
|
if os.Getenv("PORT") != "" {
|
||||||
atoi, err := strconv.Atoi(os.Getenv("PORT"))
|
atoi, err := strconv.Atoi(os.Getenv("PORT"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -69,4 +68,10 @@ func init() {
|
|||||||
if os.Getenv("LOG_LEVEL") != "" {
|
if os.Getenv("LOG_LEVEL") != "" {
|
||||||
Default.LogLevel = os.Getenv("LOG_LEVEL")
|
Default.LogLevel = os.Getenv("LOG_LEVEL")
|
||||||
}
|
}
|
||||||
|
if os.Getenv("BASE_PATH") != "" {
|
||||||
|
Default.BasePath = os.Getenv("BASE_PATH")
|
||||||
|
if Default.BasePath[len(Default.BasePath)-1] != '/' {
|
||||||
|
Default.BasePath += "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8011:8011"
|
- "8011:8011"
|
||||||
volumes:
|
volumes:
|
||||||
- ./templates:/app/templates
|
|
||||||
- ./logs:/app/logs
|
- ./logs:/app/logs
|
||||||
|
# - ./templates:/app/templates
|
||||||
# environment:
|
# environment:
|
||||||
# - PORT=8011
|
# - PORT=8011
|
||||||
# - META_TEMPLATE=template_meta.yaml
|
# - META_TEMPLATE=template_meta.yaml
|
||||||
|
17
go.mod
17
go.mod
@ -4,21 +4,30 @@ go 1.21
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
|
github.com/glebarez/sqlite v1.9.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
go.uber.org/zap v1.25.0
|
go.uber.org/zap v1.26.0
|
||||||
|
golang.org/x/text v0.13.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
gorm.io/gorm v1.25.4
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic v1.10.1 // indirect
|
github.com/bytedance/sonic v1.10.1 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.15.4 // indirect
|
github.com/go-playground/validator/v10 v10.15.4 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
@ -26,6 +35,7 @@ require (
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
@ -33,6 +43,9 @@ require (
|
|||||||
golang.org/x/crypto v0.13.0 // indirect
|
golang.org/x/crypto v0.13.0 // indirect
|
||||||
golang.org/x/net v0.15.0 // indirect
|
golang.org/x/net v0.15.0 // indirect
|
||||||
golang.org/x/sys v0.12.0 // indirect
|
golang.org/x/sys v0.12.0 // indirect
|
||||||
golang.org/x/text v0.13.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
|
modernc.org/libc v1.22.5 // indirect
|
||||||
|
modernc.org/mathutil v1.5.0 // indirect
|
||||||
|
modernc.org/memory v1.5.0 // indirect
|
||||||
|
modernc.org/sqlite v1.23.1 // indirect
|
||||||
)
|
)
|
||||||
|
37
go.sum
37
go.sum
@ -1,5 +1,3 @@
|
|||||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
|
||||||
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc=
|
github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc=
|
||||||
@ -13,12 +11,18 @@ github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLI
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
|
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||||
|
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||||
|
github.com/glebarez/sqlite v1.9.0 h1:Aj6bPA12ZEx5GbSF6XADmCkYXlljPNUY+Zf1EQxynXs=
|
||||||
|
github.com/glebarez/sqlite v1.9.0/go.mod h1:YBYCoyupOao60lzp1MVBLEjZfgkq0tdB1voAQ09K9zw=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
@ -30,9 +34,18 @@ github.com/go-playground/validator/v10 v10.15.4/go.mod h1:9iXMNT7sEkjXb0I+enO7QX
|
|||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||||
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
@ -54,6 +67,9 @@ github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6
|
|||||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
@ -73,8 +89,8 @@ go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
|||||||
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
|
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||||
go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=
|
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
|
golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
|
||||||
golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
@ -88,7 +104,6 @@ golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
|||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
@ -98,5 +113,15 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw=
|
||||||
|
gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
|
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||||
|
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||||
|
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||||
|
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
|
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||||
|
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||||
|
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||||
|
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
5
main.go
5
main.go
@ -12,6 +12,7 @@ import (
|
|||||||
"sub2clash/config"
|
"sub2clash/config"
|
||||||
"sub2clash/logger"
|
"sub2clash/logger"
|
||||||
"sub2clash/utils"
|
"sub2clash/utils"
|
||||||
|
"sub2clash/utils/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed templates/template_meta.yaml
|
//go:embed templates/template_meta.yaml
|
||||||
@ -53,6 +54,10 @@ func init() {
|
|||||||
if err := writeTemplate(config.Default.ClashTemplate, templateClash); err != nil {
|
if err := writeTemplate(config.Default.ClashTemplate, templateClash); err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
err := database.ConnectDB()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
29
model/clash.go
Normal file
29
model/clash.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type ClashType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Clash ClashType = 1 + iota
|
||||||
|
ClashMeta
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetSupportProxyTypes(clashType ClashType) map[string]bool {
|
||||||
|
if clashType == Clash {
|
||||||
|
return map[string]bool{
|
||||||
|
"ss": true,
|
||||||
|
"ssr": true,
|
||||||
|
"vmess": true,
|
||||||
|
"trojan": true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if clashType == ClashMeta {
|
||||||
|
return map[string]bool{
|
||||||
|
"ss": true,
|
||||||
|
"ssr": true,
|
||||||
|
"vmess": true,
|
||||||
|
"trojan": true,
|
||||||
|
"vless": true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
13
model/rule_provider.go
Normal file
13
model/rule_provider.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type RuleProvider struct {
|
||||||
|
Type string `yaml:"type,omitempty"`
|
||||||
|
Behavior string `yaml:"behavior,omitempty"`
|
||||||
|
Url string `yaml:"url,omitempty"`
|
||||||
|
Path string `yaml:"path,omitempty"`
|
||||||
|
Interval int `yaml:"interval,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Payload struct {
|
||||||
|
Rules []string `yaml:"payload,omitempty"`
|
||||||
|
}
|
7
model/short_link.go
Normal file
7
model/short_link.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type ShortLink struct {
|
||||||
|
Hash string `gorm:"primary_key"`
|
||||||
|
Url string
|
||||||
|
LastRequestTime int64
|
||||||
|
}
|
12
model/sub.go
12
model/sub.go
@ -12,15 +12,3 @@ type Subscription struct {
|
|||||||
Rules []string `yaml:"rules,omitempty"`
|
Rules []string `yaml:"rules,omitempty"`
|
||||||
RuleProviders map[string]RuleProvider `yaml:"rule-providers,omitempty,omitempty"`
|
RuleProviders map[string]RuleProvider `yaml:"rule-providers,omitempty,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleProvider struct {
|
|
||||||
Type string `yaml:"type,omitempty"`
|
|
||||||
Behavior string `yaml:"behavior,omitempty"`
|
|
||||||
Url string `yaml:"url,omitempty"`
|
|
||||||
Path string `yaml:"path,omitempty"`
|
|
||||||
Interval int `yaml:"interval,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Payload struct {
|
|
||||||
Rules []string `yaml:"payload,omitempty"`
|
|
||||||
}
|
|
||||||
|
@ -14,6 +14,7 @@ func ParseShadowsocksR(proxy string) (model.Proxy, error) {
|
|||||||
return model.Proxy{}, fmt.Errorf("无效的 ssr Url")
|
return model.Proxy{}, fmt.Errorf("无效的 ssr Url")
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
|
proxy = strings.TrimPrefix(proxy, "ssr://")
|
||||||
if !strings.Contains(proxy, ":") {
|
if !strings.Contains(proxy, ":") {
|
||||||
proxy, err = DecodeBase64(strings.TrimPrefix(proxy, "ssr://"))
|
proxy, err = DecodeBase64(strings.TrimPrefix(proxy, "ssr://"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -21,7 +22,7 @@ func ParseShadowsocksR(proxy string) (model.Proxy, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 分割
|
// 分割
|
||||||
detailsAndParams := strings.SplitN(strings.TrimPrefix(proxy, "ssr://"), "/?", 2)
|
detailsAndParams := strings.SplitN(proxy, "/?", 2)
|
||||||
parts := strings.Split(detailsAndParams[0], ":")
|
parts := strings.Split(detailsAndParams[0], ":")
|
||||||
params, err := url.ParseQuery(detailsAndParams[1])
|
params, err := url.ParseQuery(detailsAndParams[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -32,7 +33,21 @@ func ParseShadowsocksR(proxy string) (model.Proxy, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return model.Proxy{}, err
|
return model.Proxy{}, err
|
||||||
}
|
}
|
||||||
|
var obfsParam string
|
||||||
|
var protoParam string
|
||||||
|
var remarks string
|
||||||
|
if params.Get("obfsparam") != "" {
|
||||||
|
obfsParam, err = DecodeBase64(params.Get("obfsparam"))
|
||||||
|
}
|
||||||
|
if params.Get("protoparam") != "" {
|
||||||
|
protoParam, err = DecodeBase64(params.Get("protoparam"))
|
||||||
|
}
|
||||||
|
if params.Get("remarks") != "" {
|
||||||
|
remarks, err = DecodeBase64(params.Get("remarks"))
|
||||||
|
}
|
||||||
|
|
||||||
result := model.Proxy{
|
result := model.Proxy{
|
||||||
|
Name: remarks,
|
||||||
Type: "ssr",
|
Type: "ssr",
|
||||||
Server: parts[0],
|
Server: parts[0],
|
||||||
Port: port,
|
Port: port,
|
||||||
@ -40,8 +55,13 @@ func ParseShadowsocksR(proxy string) (model.Proxy, error) {
|
|||||||
Cipher: parts[3],
|
Cipher: parts[3],
|
||||||
Obfs: parts[4],
|
Obfs: parts[4],
|
||||||
Password: parts[5],
|
Password: parts[5],
|
||||||
ObfsParam: params.Get("obfsparam"),
|
ObfsParam: obfsParam,
|
||||||
ProtocolParam: params.Get("protoparam"),
|
ProtocolParam: protoParam,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if result.Name == "" {
|
||||||
|
result.Name = result.Server
|
||||||
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,12 @@ proxy-groups:
|
|||||||
- name: 手动切换
|
- name: 手动切换
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- name: 微软服务
|
||||||
|
type: select
|
||||||
|
proxies:
|
||||||
|
- 节点选择
|
||||||
|
- 手动切换
|
||||||
|
- DIRECT
|
||||||
- name: 游戏平台
|
- name: 游戏平台
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
@ -48,14 +54,14 @@ proxy-groups:
|
|||||||
- 手动切换
|
- 手动切换
|
||||||
- DIRECT
|
- DIRECT
|
||||||
rules:
|
rules:
|
||||||
- GEOSITE,private,全球直连
|
- GEOSITE,private,全球直连,no-resolve
|
||||||
- GEOIP,private,全球直连
|
- GEOIP,private,全球直连
|
||||||
- GEOSITE,category-ads-all,广告拦截
|
- GEOSITE,category-ads-all,广告拦截
|
||||||
- GEOSITE,CN,全球直连
|
- GEOSITE,microsoft,微软服务
|
||||||
- GEOIP,CN,全球直连
|
|
||||||
- GEOSITE,biliintl,哔哩哔哩
|
|
||||||
- GEOSITE,bilibili,哔哩哔哩
|
- GEOSITE,bilibili,哔哩哔哩
|
||||||
- GEOSITE,bahamut,巴哈姆特
|
- GEOSITE,bahamut,巴哈姆特
|
||||||
- GEOSITE,category-games,游戏平台
|
- GEOSITE,category-games,游戏平台
|
||||||
- GEOSITE,geolocation-!cn,节点选择
|
- GEOSITE,geolocation-!cn,节点选择
|
||||||
|
- GEOSITE,CN,全球直连
|
||||||
|
- GEOIP,CN,全球直连
|
||||||
- MATCH,漏网之鱼
|
- MATCH,漏网之鱼
|
26
utils/database/database.go
Normal file
26
utils/database/database.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/glebarez/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"sub2clash/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DB *gorm.DB
|
||||||
|
|
||||||
|
func ConnectDB() error {
|
||||||
|
// 用上面的数据库连接初始化 gorm
|
||||||
|
db, err := gorm.Open(sqlite.Open("sub2clash.db"), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
DB = db
|
||||||
|
err = db.AutoMigrate(&model.ShortLink{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -29,12 +29,18 @@ func GetContryName(proxy model.Proxy) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AddProxy(
|
func AddProxy(
|
||||||
sub *model.Subscription, autotest bool, lazy bool, sortStrategy string,
|
sub *model.Subscription, autotest bool,
|
||||||
proxies ...model.Proxy,
|
lazy bool, sortStrategy string,
|
||||||
|
clashType model.ClashType, proxies ...model.Proxy,
|
||||||
) {
|
) {
|
||||||
newCountryGroupNames := make([]string, 0)
|
newCountryGroupNames := make([]string, 0)
|
||||||
|
proxyTypes := model.GetSupportProxyTypes(clashType)
|
||||||
|
|
||||||
// 添加节点
|
// 添加节点
|
||||||
for _, proxy := range proxies {
|
for _, proxy := range proxies {
|
||||||
|
if !proxyTypes[proxy.Type] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
sub.Proxies = append(sub.Proxies, proxy)
|
sub.Proxies = append(sub.Proxies, proxy)
|
||||||
haveProxyGroup := false
|
haveProxyGroup := false
|
||||||
countryName := GetContryName(proxy)
|
countryName := GetContryName(proxy)
|
||||||
|
13
utils/random_string.go
Normal file
13
utils/random_string.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "math/rand"
|
||||||
|
|
||||||
|
func RandomString(length int) string {
|
||||||
|
// 生成随机字符串
|
||||||
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
var result []byte
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
result = append(result, charset[rand.Intn(len(charset))])
|
||||||
|
}
|
||||||
|
return string(result)
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -19,7 +19,7 @@ func LoadSubscription(url string, refresh bool) ([]byte, error) {
|
|||||||
if refresh {
|
if refresh {
|
||||||
return FetchSubscriptionFromAPI(url)
|
return FetchSubscriptionFromAPI(url)
|
||||||
}
|
}
|
||||||
hash := md5.Sum([]byte(url))
|
hash := sha256.Sum224([]byte(url))
|
||||||
fileName := filepath.Join(subsDir, hex.EncodeToString(hash[:]))
|
fileName := filepath.Join(subsDir, hex.EncodeToString(hash[:]))
|
||||||
stat, err := os.Stat(fileName)
|
stat, err := os.Stat(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -49,7 +49,7 @@ func LoadSubscription(url string, refresh bool) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func FetchSubscriptionFromAPI(url string) ([]byte, error) {
|
func FetchSubscriptionFromAPI(url string) ([]byte, error) {
|
||||||
hash := md5.Sum([]byte(url))
|
hash := sha256.Sum224([]byte(url))
|
||||||
fileName := filepath.Join(subsDir, hex.EncodeToString(hash[:]))
|
fileName := filepath.Join(subsDir, hex.EncodeToString(hash[:]))
|
||||||
resp, err := Get(url)
|
resp, err := Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// LoadTemplate 加载模板
|
// LoadTemplate 加载模板
|
||||||
// template 模板文件名
|
// templates 模板文件名
|
||||||
func LoadTemplate(template string) ([]byte, error) {
|
func LoadTemplate(template string) ([]byte, error) {
|
||||||
tPath := filepath.Join("templates", template)
|
tPath := filepath.Join("templates", template)
|
||||||
if _, err := os.Stat(tPath); err == nil {
|
if _, err := os.Stat(tPath); err == nil {
|
||||||
|
9
validator/short_link.go
Normal file
9
validator/short_link.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
type ShortLinkGenValidator struct {
|
||||||
|
Url string `form:"url" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShortLinkGetValidator struct {
|
||||||
|
Hash string `form:"hash" binding:"required"`
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package validator
|
package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -11,7 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SubQuery struct {
|
type SubValidator struct {
|
||||||
Sub string `form:"sub" binding:""`
|
Sub string `form:"sub" binding:""`
|
||||||
Subs []string `form:"-" binding:""`
|
Subs []string `form:"-" binding:""`
|
||||||
Proxy string `form:"proxy" binding:""`
|
Proxy string `form:"proxy" binding:""`
|
||||||
@ -40,19 +40,22 @@ type RuleStruct struct {
|
|||||||
Prepend bool
|
Prepend bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseQuery(c *gin.Context) (SubQuery, error) {
|
func ParseQuery(c *gin.Context) (SubValidator, error) {
|
||||||
var query SubQuery
|
var query SubValidator
|
||||||
if err := c.ShouldBind(&query); err != nil {
|
if err := c.ShouldBind(&query); err != nil {
|
||||||
return SubQuery{}, errors.New("参数错误: " + err.Error())
|
return SubValidator{}, errors.New("参数错误: " + err.Error())
|
||||||
}
|
}
|
||||||
if query.Sub == "" && query.Proxy == "" {
|
if query.Sub == "" && query.Proxy == "" {
|
||||||
return SubQuery{}, errors.New("参数错误: sub 和 proxy 不能同时为空")
|
return SubValidator{}, errors.New("参数错误: sub 和 proxy 不能同时为空")
|
||||||
}
|
}
|
||||||
if query.Sub != "" {
|
if query.Sub != "" {
|
||||||
query.Subs = strings.Split(query.Sub, ",")
|
query.Subs = strings.Split(query.Sub, ",")
|
||||||
for i := range query.Subs {
|
for i := range query.Subs {
|
||||||
|
if !strings.HasPrefix(query.Subs[i], "http") {
|
||||||
|
return SubValidator{}, errors.New("参数错误: sub 格式错误")
|
||||||
|
}
|
||||||
if _, err := url.ParseRequestURI(query.Subs[i]); err != nil {
|
if _, err := url.ParseRequestURI(query.Subs[i]); err != nil {
|
||||||
return SubQuery{}, errors.New("参数错误: " + err.Error())
|
return SubValidator{}, errors.New("参数错误: " + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -67,7 +70,7 @@ func ParseQuery(c *gin.Context) (SubQuery, error) {
|
|||||||
uri, err := url.ParseRequestURI(query.Template)
|
uri, err := url.ParseRequestURI(query.Template)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(query.Template, string(os.PathSeparator)) {
|
if strings.Contains(query.Template, string(os.PathSeparator)) {
|
||||||
return SubQuery{}, err
|
return SubValidator{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
query.Template = uri.String()
|
query.Template = uri.String()
|
||||||
@ -79,16 +82,16 @@ func ParseQuery(c *gin.Context) (SubQuery, error) {
|
|||||||
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 SubValidator{}, errors.New("参数错误: ruleProvider 格式错误")
|
||||||
}
|
}
|
||||||
u := parts[1]
|
u := parts[1]
|
||||||
uri, err := url.ParseRequestURI(u)
|
uri, err := url.ParseRequestURI(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return SubQuery{}, errors.New("参数错误: " + err.Error())
|
return SubValidator{}, errors.New("参数错误: " + err.Error())
|
||||||
}
|
}
|
||||||
u = uri.String()
|
u = uri.String()
|
||||||
if len(parts) == 4 {
|
if len(parts) == 4 {
|
||||||
hash := md5.Sum([]byte(u))
|
hash := sha256.Sum224([]byte(u))
|
||||||
parts = append(parts, hex.EncodeToString(hash[:]))
|
parts = append(parts, hex.EncodeToString(hash[:]))
|
||||||
}
|
}
|
||||||
query.RuleProviders = append(
|
query.RuleProviders = append(
|
||||||
@ -101,6 +104,14 @@ func ParseQuery(c *gin.Context) (SubQuery, error) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// 校验 Rule-Provider 是否有重名
|
||||||
|
names := make(map[string]bool)
|
||||||
|
for _, ruleProvider := range query.RuleProviders {
|
||||||
|
if _, ok := names[ruleProvider.Name]; ok {
|
||||||
|
return SubValidator{}, errors.New("参数错误: Rule-Provider 名称重复")
|
||||||
|
}
|
||||||
|
names[ruleProvider.Name] = true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
query.RuleProviders = nil
|
query.RuleProviders = nil
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user