From 8d06ab3175371598b2f97738737db82d31dcc642 Mon Sep 17 00:00:00 2001
From: Nite07 <130290347+nitezs@users.noreply.github.com>
Date: Thu, 21 Sep 2023 09:08:02 +0800
Subject: [PATCH] Dev (#2)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
fix: 修复当订阅链接有多个 clash 配置时丢失节点的问题
update: 增加检测更新
modify: 修改数据库路径
modify: 修改短链生成逻辑
modify: 统一输出信息
---
.github/workflows/docker.yml | 6 +
.github/workflows/go.yml | 46 +---
.gitignore | 3 +-
.goreleaser.yaml | 4 +-
Dockerfile | 6 +-
README.md | 1 +
api/controller/default.go | 34 ++-
api/controller/short_link.go | 48 +++-
api/route.go | 7 +-
api/templates/index.html | 448 ++++++++++++++++++++------------
config/config.go | 23 +-
docker-compose.yml | 3 +-
logger/logger.go | 19 +-
main.go | 75 +++---
model/github.go | 12 +
model/proxy_group.go | 1 -
parser/ss.go | 8 +-
parser/ssr.go | 2 +-
parser/trojan.go | 6 +-
parser/vless.go | 6 +-
parser/vmess.go | 2 +-
utils/check_update.go | 33 +++
utils/database/database.go | 36 ++-
utils/mkdir.go | 30 +++
utils/os.go | 16 --
utils/proxy.go | 25 +-
utils/write_default_template.go | 37 +++
27 files changed, 588 insertions(+), 349 deletions(-)
create mode 100644 model/github.go
create mode 100644 utils/check_update.go
create mode 100644 utils/mkdir.go
delete mode 100644 utils/os.go
create mode 100644 utils/write_default_template.go
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 5dcd8de..8d8e079 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -47,6 +47,9 @@ jobs:
with:
context: .
file: ./Dockerfile
+ build-args: |
+ dev=true
+ version=${{ github.sha }}
push: true
tags: ghcr.io/${{ github.repository }}:${{ steps.set_tag.outputs.tag }}
@@ -56,6 +59,9 @@ jobs:
with:
context: .
file: ./Dockerfile
+ build-args: |
+ dev=false
+ version=${{ steps.set_tag.outputs.tag }}
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ steps.set_tag.outputs.tag }}
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 9570789..8c99d22 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -23,12 +23,10 @@ jobs:
- name: Build
run: |
- LDFLAGS="-s -w"
+ LDFLAGS="-s -w -X config.Version=${{ github.ref_name }}"
# Linux
- CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags="$LDFLAGS" -o output/sub2clash-linux-386 main.go
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="$LDFLAGS" -o output/sub2clash-linux-amd64 main.go
- CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags="$LDFLAGS" -o output/sub2clash-linux-arm main.go
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="$LDFLAGS" -o output/sub2clash-linux-arm64 main.go
# Darwin
@@ -36,9 +34,7 @@ jobs:
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="$LDFLAGS" -o output/sub2clash-darwin-arm64 main.go
# Windows
- CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags="$LDFLAGS" -o output/sub2clash-windows-386.exe main.go
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="$LDFLAGS" -o output/sub2clash-windows-amd64.exe main.go
- CGO_ENABLED=0 GOOS=windows GOARCH=arm go build -ldflags="$LDFLAGS" -o output/sub2clash-windows-arm.exe main.go
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags="$LDFLAGS" -o output/sub2clash-windows-arm64.exe main.go
- name: Create Release
@@ -52,16 +48,6 @@ jobs:
draft: false
prerelease: false
- - name: Upload Release Asset (Linux 386)
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./output/sub2clash-linux-386
- asset_name: sub2clash-linux-386
- asset_content_type: application/octet-stream
-
- name: Upload Release Asset (Linux amd64)
uses: actions/upload-release-asset@v1
env:
@@ -72,16 +58,6 @@ jobs:
asset_name: sub2clash-linux-amd64
asset_content_type: application/octet-stream
- - name: Upload Release Asset (Linux arm)
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./output/sub2clash-linux-arm
- asset_name: sub2clash-linux-arm
- asset_content_type: application/octet-stream
-
- name: Upload Release Asset (Linux arm64)
uses: actions/upload-release-asset@v1
env:
@@ -112,16 +88,6 @@ jobs:
asset_name: sub2clash-darwin-arm64
asset_content_type: application/octet-stream
- - name: Upload Release Asset (Windows 386)
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./output/sub2clash-windows-386.exe
- asset_name: sub2clash-windows-386.exe
- asset_content_type: application/octet-stream
-
- name: Upload Release Asset (Windows amd64)
uses: actions/upload-release-asset@v1
env:
@@ -132,16 +98,6 @@ jobs:
asset_name: sub2clash-windows-amd64.exe
asset_content_type: application/octet-stream
- - name: Upload Release Asset (Windows arm)
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./output/sub2clash-windows-arm.exe
- asset_name: sub2clash-windows-arm.exe
- asset_content_type: application/octet-stream
-
- name: Upload Release Asset (Windows arm64)
uses: actions/upload-release-asset@v1
env:
diff --git a/.gitignore b/.gitignore
index 4081dca..7f8d47b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,5 @@ dist
subs
test
logs
-sub2clash.db
\ No newline at end of file
+sub2clash.db
+.env
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 17bb435..16b8440 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -10,10 +10,8 @@ builds:
- darwin
goarch:
- amd64
- - arm
- arm64
- - 386
ldflags:
- - -s -w
+ - -s -w -X sub2clash/config.Version={{ .Version }}
no_unique_dist_dir: true
binary: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}"
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 9f1a96a..755bc46 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -9,8 +9,12 @@ WORKDIR /app
COPY . .
RUN go mod download
+# 获取参数
+ARG version
+ARG dev
+
# 使用 -ldflags 参数进行编译
-RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o sub2clash main.go
+RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X sub2clash/config.Version=${version} -X sub2clash/config.Dev=${dev}" -o sub2clash main.go
FROM alpine:latest
diff --git a/README.md b/README.md
index 0867c09..ba8b5d5 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@
| REQUEST_MAX_FILE_SIZE | Get 请求订阅文件最大大小(byte) | `1048576` |
| CACHE_EXPIRE | 订阅缓存时间(秒) | `300` |
| LOG_LEVEL | 日志等级,可选值 `debug`,`info`,`warn`,`error` | `info` |
+| SHORT_LINK_LENGTH | 短链长度 | `6` |
### API
diff --git a/api/controller/default.go b/api/controller/default.go
index c45f72d..0b58d97 100644
--- a/api/controller/default.go
+++ b/api/controller/default.go
@@ -7,6 +7,7 @@ import (
"gopkg.in/yaml.v3"
"net/url"
"regexp"
+ "sort"
"strings"
"sub2clash/model"
"sub2clash/parser"
@@ -43,6 +44,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
if err != nil {
return nil, errors.New("解析模板失败: " + err.Error())
}
+ var proxyList []model.Proxy
// 加载订阅
for i := range query.Subs {
data, err := utils.LoadSubscription(query.Subs[i], query.Refresh)
@@ -50,31 +52,49 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
return nil, errors.New("加载订阅失败: " + err.Error())
}
// 解析订阅
- var proxyList []model.Proxy
+
err = yaml.Unmarshal(data, &sub)
if err != nil {
reg, _ := regexp.Compile("(ssr|ss|vmess|trojan|http|https)://")
if reg.Match(data) {
- proxyList = utils.ParseProxy(strings.Split(string(data), "\n")...)
+ p := utils.ParseProxy(strings.Split(string(data), "\n")...)
+ proxyList = append(proxyList, p...)
} else {
// 如果无法直接解析,尝试Base64解码
base64, err := parser.DecodeBase64(string(data))
if err != nil {
return nil, errors.New("加载订阅失败: " + err.Error())
}
- proxyList = utils.ParseProxy(strings.Split(base64, "\n")...)
+ p := utils.ParseProxy(strings.Split(base64, "\n")...)
+ proxyList = append(proxyList, p...)
}
} else {
- proxyList = sub.Proxies
+ proxyList = append(proxyList, sub.Proxies...)
}
- utils.AddProxy(sub, query.AutoTest, query.Lazy, query.Sort, clashType, proxyList...)
}
+ // 将新增节点都添加到临时变量 t 中,防止策略组排序错乱
+ var t = &model.Subscription{}
+ utils.AddProxy(t, query.AutoTest, query.Lazy, clashType, proxyList...)
// 处理自定义代理
utils.AddProxy(
- sub, query.AutoTest, query.Lazy, query.Sort, clashType,
+ t, query.AutoTest, query.Lazy, clashType,
utils.ParseProxy(query.Proxies...)...,
)
- MergeSubAndTemplate(temp, sub)
+ // 排序策略组
+ switch query.Sort {
+ case "sizeasc":
+ sort.Sort(model.ProxyGroupsSortBySize(t.ProxyGroups))
+ case "sizedesc":
+ sort.Sort(sort.Reverse(model.ProxyGroupsSortBySize(t.ProxyGroups)))
+ case "nameasc":
+ sort.Sort(model.ProxyGroupsSortByName(t.ProxyGroups))
+ case "namedesc":
+ sort.Sort(sort.Reverse(model.ProxyGroupsSortByName(t.ProxyGroups)))
+ default:
+ sort.Sort(model.ProxyGroupsSortByName(t.ProxyGroups))
+ }
+ // 合并新节点和模板
+ MergeSubAndTemplate(temp, t)
// 处理自定义规则
for _, v := range query.Rules {
if v.Prepend {
diff --git a/api/controller/short_link.go b/api/controller/short_link.go
index 4f5872d..1eea508 100644
--- a/api/controller/short_link.go
+++ b/api/controller/short_link.go
@@ -1,12 +1,14 @@
package controller
import (
- "crypto/sha256"
- "encoding/hex"
+ "errors"
"github.com/gin-gonic/gin"
+ "gorm.io/gorm"
"net/http"
+ "strings"
"sub2clash/config"
"sub2clash/model"
+ "sub2clash/utils"
"sub2clash/utils/database"
"sub2clash/validator"
"time"
@@ -18,12 +20,32 @@ func ShortLinkGenHandler(c *gin.Context) {
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[:])
+ if strings.TrimSpace(params.Url) == "" {
+ c.String(400, "参数错误")
+ return
+ }
+ // 生成hash
+ hash := utils.RandomString(config.Default.ShortLinkLength)
// 存入数据库
- database.DB.FirstOrCreate(
+ var item model.ShortLink
+ result := database.FindShortLinkByUrl(params.Url, &item)
+ if result.Error == nil {
+ c.String(200, item.Hash)
+ return
+ } else {
+ if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
+ c.String(500, "数据库错误: "+result.Error.Error())
+ return
+ }
+ }
+ // 如果记录存在则重新生成hash,直到记录不存在
+ result = database.FindShortLinkByHash(hash, &item)
+ for result.Error == nil {
+ hash = utils.RandomString(config.Default.ShortLinkLength)
+ result = database.FindShortLinkByHash(hash, &item)
+ }
+ // 创建记录
+ database.FirstOrCreateShortLink(
&model.ShortLink{
Hash: hash,
Url: params.Url,
@@ -37,17 +59,21 @@ func ShortLinkGenHandler(c *gin.Context) {
func ShortLinkGetHandler(c *gin.Context) {
// 获取动态路由
hash := c.Param("hash")
+ if strings.TrimSpace(hash) == "" {
+ c.String(400, "参数错误")
+ return
+ }
// 查询数据库
var shortLink model.ShortLink
- result := database.DB.Where("hash = ?", hash).First(&shortLink)
- // 更新最后访问时间
- shortLink.LastRequestTime = time.Now().Unix()
- database.DB.Save(&shortLink)
+ result := database.FindShortLinkByHash(hash, &shortLink)
// 重定向
if result.Error != nil {
c.String(404, "未找到短链接")
return
}
+ // 更新最后访问时间
+ shortLink.LastRequestTime = time.Now().Unix()
+ database.SaveShortLink(&shortLink)
uri := config.Default.BasePath + shortLink.Url
c.Redirect(http.StatusTemporaryRedirect, uri)
}
diff --git a/api/route.go b/api/route.go
index 51dc3aa..17d54b4 100644
--- a/api/route.go
+++ b/api/route.go
@@ -5,6 +5,7 @@ import (
"github.com/gin-gonic/gin"
"html/template"
"sub2clash/api/controller"
+ "sub2clash/config"
"sub2clash/middleware"
)
@@ -17,7 +18,11 @@ func SetRoute(r *gin.Engine) {
r.SetHTMLTemplate(template.Must(template.New("").ParseFS(templates, "templates/*")))
r.GET(
"/", func(c *gin.Context) {
- c.HTML(200, "index.html", nil)
+ c.HTML(
+ 200, "index.html", gin.H{
+ "Version": config.Version,
+ },
+ )
},
)
r.GET(
diff --git a/api/templates/index.html b/api/templates/index.html
index 8c63925..4b3bf5e 100644
--- a/api/templates/index.html
+++ b/api/templates/index.html
@@ -1,163 +1,240 @@
-
+
-
-
-
-
+
+
+
sub2clash
-
+
-
+
-
+
-
-
-
-
+
+
+
sub2clash
-
通用订阅链接转 Clash(Meta) 配置工具 使用文档
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
+ });
+ }
+
+
diff --git a/config/config.go b/config/config.go
index d7f7978..2371992 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,6 +1,7 @@
package config
import (
+ "errors"
"github.com/joho/godotenv"
"os"
"strconv"
@@ -15,11 +16,14 @@ type Config struct {
CacheExpire int64
LogLevel string
BasePath string
+ ShortLinkLength int
}
var Default *Config
+var Version string
+var Dev string
-func init() {
+func LoadConfig() error {
Default = &Config{
MetaTemplate: "template_meta.yaml",
ClashTemplate: "template_clash.yaml",
@@ -29,12 +33,13 @@ func init() {
CacheExpire: 60 * 5,
LogLevel: "info",
BasePath: "/",
+ ShortLinkLength: 6,
}
_ = godotenv.Load()
if os.Getenv("PORT") != "" {
atoi, err := strconv.Atoi(os.Getenv("PORT"))
if err != nil {
- panic("PORT invalid")
+ return errors.New("PORT invalid")
}
Default.Port = atoi
}
@@ -47,21 +52,21 @@ func init() {
if os.Getenv("REQUEST_RETRY_TIMES") != "" {
atoi, err := strconv.Atoi(os.Getenv("REQUEST_RETRY_TIMES"))
if err != nil {
- panic("REQUEST_RETRY_TIMES invalid")
+ return errors.New("REQUEST_RETRY_TIMES invalid")
}
Default.RequestRetryTimes = atoi
}
if os.Getenv("REQUEST_MAX_FILE_SIZE") != "" {
atoi, err := strconv.Atoi(os.Getenv("REQUEST_MAX_FILE_SIZE"))
if err != nil {
- panic("REQUEST_MAX_FILE_SIZE invalid")
+ return errors.New("REQUEST_MAX_FILE_SIZE invalid")
}
Default.RequestMaxFileSize = int64(atoi)
}
if os.Getenv("CACHE_EXPIRE") != "" {
atoi, err := strconv.Atoi(os.Getenv("CACHE_EXPIRE"))
if err != nil {
- panic("CACHE_EXPIRE invalid")
+ return errors.New("CACHE_EXPIRE invalid")
}
Default.CacheExpire = int64(atoi)
}
@@ -74,4 +79,12 @@ func init() {
Default.BasePath += "/"
}
}
+ if os.Getenv("SHORT_LINK_LENGTH") != "" {
+ atoi, err := strconv.Atoi(os.Getenv("SHORT_LINK_LENGTH"))
+ if err != nil {
+ return errors.New("SHORT_LINK_LENGTH invalid")
+ }
+ Default.ShortLinkLength = atoi
+ }
+ return nil
}
diff --git a/docker-compose.yml b/docker-compose.yml
index 8a3b07a..9eadcb6 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -9,7 +9,8 @@ services:
- "8011:8011"
volumes:
- ./logs:/app/logs
- # - ./templates:/app/templates
+ - ./templates:/app/templates
+ - ./data:/app/data
# environment:
# - PORT=8011
# - META_TEMPLATE=template_meta.yaml
diff --git a/logger/logger.go b/logger/logger.go
index e141cf9..55abcf3 100644
--- a/logger/logger.go
+++ b/logger/logger.go
@@ -4,18 +4,18 @@ import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"path/filepath"
- "sub2clash/config"
- "sub2clash/utils"
"sync"
"time"
)
var (
- Logger *zap.Logger
- lock sync.Mutex
+ Logger *zap.Logger
+ lock sync.Mutex
+ logLevel string
)
-func init() {
+func InitLogger(level string) {
+ logLevel = level
buildLogger()
go rotateLogs()
}
@@ -24,7 +24,7 @@ func buildLogger() {
lock.Lock()
defer lock.Unlock()
var level zapcore.Level
- switch config.Default.LogLevel {
+ switch logLevel {
case "error":
level = zap.ErrorLevel
case "debug":
@@ -36,10 +36,6 @@ func buildLogger() {
default:
level = zap.InfoLevel
}
- err := utils.MKDir("logs")
- if err != nil {
- panic("创建日志失败" + err.Error())
- }
zapConfig := zap.NewProductionConfig()
zapConfig.Encoding = "console"
zapConfig.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
@@ -47,9 +43,10 @@ func buildLogger() {
zapConfig.OutputPaths = []string{"stdout", getLogFileName("info")}
zapConfig.ErrorOutputPaths = []string{"stderr", getLogFileName("error")}
zapConfig.Level = zap.NewAtomicLevelAt(level)
+ var err error
Logger, err = zapConfig.Build()
if err != nil {
- panic("创建日志失败" + err.Error())
+ panic("log failed" + err.Error())
}
}
diff --git a/main.go b/main.go
index 3bf78a3..72e5f58 100644
--- a/main.go
+++ b/main.go
@@ -5,8 +5,6 @@ import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"io"
- "os"
- "path/filepath"
"strconv"
"sub2clash/api"
"sub2clash/config"
@@ -21,43 +19,44 @@ var templateMeta string
//go:embed templates/template_clash.yaml
var templateClash string
-func writeTemplate(path string, template string) error {
- tPath := filepath.Join(
- "templates", path,
- )
- if _, err := os.Stat(tPath); os.IsNotExist(err) {
- file, err := os.Create(tPath)
- if err != nil {
- return err
- }
- defer func(file *os.File) {
- _ = file.Close()
- }(file)
- _, err = file.WriteString(template)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
func init() {
- if err := utils.MKDir("subs"); err != nil {
- os.Exit(1)
- }
- if err := utils.MKDir("templates"); err != nil {
- os.Exit(1)
- }
- if err := writeTemplate(config.Default.MetaTemplate, templateMeta); err != nil {
- os.Exit(1)
- }
- if err := writeTemplate(config.Default.ClashTemplate, templateClash); err != nil {
- os.Exit(1)
- }
- err := database.ConnectDB()
+ // 加载配置
+ err := config.LoadConfig()
+ // 初始化日志
+ logger.InitLogger(config.Default.LogLevel)
if err != nil {
- panic(err)
+ logger.Logger.Panic("load config failed", zap.Error(err))
}
+ // 检查更新
+ if config.Dev != "true" {
+ go func() {
+ update, newVersion, err := utils.CheckUpdate()
+ if err != nil {
+ logger.Logger.Warn("check update failed", zap.Error(err))
+ }
+ if update {
+ logger.Logger.Info("new version is available", zap.String("version", newVersion))
+ }
+ }()
+ } else {
+ logger.Logger.Info("running in dev mode")
+ }
+ // 创建文件夹
+ err = utils.MkEssentialDir()
+ if err != nil {
+ logger.Logger.Panic("create essential dir failed", zap.Error(err))
+ }
+ // 写入默认模板
+ err = utils.WriteDefalutTemplate(templateMeta, templateClash)
+ if err != nil {
+ logger.Logger.Panic("write default template failed", zap.Error(err))
+ }
+ // 连接数据库
+ err = database.ConnectDB()
+ if err != nil {
+ logger.Logger.Panic("database connect failed", zap.Error(err))
+ }
+ logger.Logger.Info("database connect success")
}
func main() {
@@ -69,10 +68,10 @@ func main() {
r := gin.Default()
// 设置路由
api.SetRoute(r)
- logger.Logger.Info("Server is running at http://localhost:" + strconv.Itoa(config.Default.Port))
+ logger.Logger.Info("server is running at http://localhost:" + strconv.Itoa(config.Default.Port))
err := r.Run(":" + strconv.Itoa(config.Default.Port))
if err != nil {
- logger.Logger.Error("Server run error", zap.Error(err))
+ logger.Logger.Error("server running failed", zap.Error(err))
return
}
}
diff --git a/model/github.go b/model/github.go
new file mode 100644
index 0000000..eef70db
--- /dev/null
+++ b/model/github.go
@@ -0,0 +1,12 @@
+package model
+
+type Tags []struct {
+ Name string `json:"name"`
+ ZipballUrl string `json:"zipball_url"`
+ TarballUrl string `json:"tarball_url"`
+ Commit struct {
+ Sha string `json:"sha"`
+ Url string `json:"url"`
+ }
+ NodeId string `json:"node_id"`
+}
diff --git a/model/proxy_group.go b/model/proxy_group.go
index 26179c6..14ba7cf 100644
--- a/model/proxy_group.go
+++ b/model/proxy_group.go
@@ -39,7 +39,6 @@ func (p ProxyGroupsSortByName) Less(i, j int) bool {
bestMatch, _, _ := matcher.Match(language.Make("zh"))
// 使用最佳匹配的语言进行排序
c := collate.New(bestMatch)
-
return c.CompareString(p[i].Name, p[j].Name) < 0
}
diff --git a/parser/ss.go b/parser/ss.go
index eebeae5..a654fdf 100644
--- a/parser/ss.go
+++ b/parser/ss.go
@@ -12,12 +12,12 @@ import (
func ParseSS(proxy string) (model.Proxy, error) {
// 判断是否以 ss:// 开头
if !strings.HasPrefix(proxy, "ss://") {
- return model.Proxy{}, fmt.Errorf("无效的 ss Url")
+ return model.Proxy{}, fmt.Errorf("invalid ss Url")
}
// 分割
parts := strings.SplitN(strings.TrimPrefix(proxy, "ss://"), "@", 2)
if len(parts) != 2 {
- return model.Proxy{}, fmt.Errorf("无效的 ss Url")
+ return model.Proxy{}, fmt.Errorf("invalid ss Url")
}
if !strings.Contains(parts[0], ":") {
// 解码
@@ -29,13 +29,13 @@ func ParseSS(proxy string) (model.Proxy, error) {
}
credentials := strings.SplitN(parts[0], ":", 2)
if len(credentials) != 2 {
- return model.Proxy{}, fmt.Errorf("无效的 ss 凭证")
+ return model.Proxy{}, fmt.Errorf("invalid ss Url")
}
// 分割
serverInfo := strings.SplitN(parts[1], "#", 2)
serverAndPort := strings.SplitN(serverInfo[0], ":", 2)
if len(serverAndPort) != 2 {
- return model.Proxy{}, fmt.Errorf("无效的 ss 服务器和端口")
+ return model.Proxy{}, fmt.Errorf("invalid ss Url")
}
// 转换端口字符串为数字
port, err := strconv.Atoi(strings.TrimSpace(serverAndPort[1]))
diff --git a/parser/ssr.go b/parser/ssr.go
index 0c6f749..afc9b79 100644
--- a/parser/ssr.go
+++ b/parser/ssr.go
@@ -11,7 +11,7 @@ import (
func ParseShadowsocksR(proxy string) (model.Proxy, error) {
// 判断是否以 ssr:// 开头
if !strings.HasPrefix(proxy, "ssr://") {
- return model.Proxy{}, fmt.Errorf("无效的 ssr Url")
+ return model.Proxy{}, fmt.Errorf("invalid ssr Url")
}
var err error
proxy = strings.TrimPrefix(proxy, "ssr://")
diff --git a/parser/trojan.go b/parser/trojan.go
index c8c2d2c..d16398d 100644
--- a/parser/trojan.go
+++ b/parser/trojan.go
@@ -11,12 +11,12 @@ import (
func ParseTrojan(proxy string) (model.Proxy, error) {
// 判断是否以 trojan:// 开头
if !strings.HasPrefix(proxy, "trojan://") {
- return model.Proxy{}, fmt.Errorf("无效的 trojan Url")
+ return model.Proxy{}, fmt.Errorf("invalid trojan Url")
}
// 分割
parts := strings.SplitN(strings.TrimPrefix(proxy, "trojan://"), "@", 2)
if len(parts) != 2 {
- return model.Proxy{}, fmt.Errorf("无效的 trojan Url")
+ return model.Proxy{}, fmt.Errorf("invalid trojan Url")
}
// 分割
serverInfo := strings.SplitN(parts[1], "#", 2)
@@ -27,7 +27,7 @@ func ParseTrojan(proxy string) (model.Proxy, error) {
return model.Proxy{}, err
}
if len(serverAndPort) != 2 {
- return model.Proxy{}, fmt.Errorf("无效的 trojan 服务器和端口")
+ return model.Proxy{}, fmt.Errorf("invalid trojan")
}
// 处理端口
port, err := strconv.Atoi(strings.TrimSpace(serverAndPort[1]))
diff --git a/parser/vless.go b/parser/vless.go
index 6740855..5e56467 100644
--- a/parser/vless.go
+++ b/parser/vless.go
@@ -11,12 +11,12 @@ import (
func ParseVless(proxy string) (model.Proxy, error) {
// 判断是否以 vless:// 开头
if !strings.HasPrefix(proxy, "vless://") {
- return model.Proxy{}, fmt.Errorf("无效的 vless Url")
+ return model.Proxy{}, fmt.Errorf("invalid vless Url")
}
// 分割
parts := strings.SplitN(strings.TrimPrefix(proxy, "vless://"), "@", 2)
if len(parts) != 2 {
- return model.Proxy{}, fmt.Errorf("无效的 vless Url")
+ return model.Proxy{}, fmt.Errorf("invalid vless Url")
}
// 分割
serverInfo := strings.SplitN(parts[1], "#", 2)
@@ -27,7 +27,7 @@ func ParseVless(proxy string) (model.Proxy, error) {
return model.Proxy{}, err
}
if len(serverAndPort) != 2 {
- return model.Proxy{}, fmt.Errorf("无效的 vless 服务器和端口")
+ return model.Proxy{}, fmt.Errorf("invalid vless")
}
// 处理端口
port, err := strconv.Atoi(strings.TrimSpace(serverAndPort[1]))
diff --git a/parser/vmess.go b/parser/vmess.go
index 0dcd268..60b846c 100644
--- a/parser/vmess.go
+++ b/parser/vmess.go
@@ -12,7 +12,7 @@ import (
func ParseVmess(proxy string) (model.Proxy, error) {
// 判断是否以 vmess:// 开头
if !strings.HasPrefix(proxy, "vmess://") {
- return model.Proxy{}, fmt.Errorf("无效的 vmess Url")
+ return model.Proxy{}, fmt.Errorf("invalid vmess Url")
}
// 解码
base64, err := DecodeBase64(strings.TrimPrefix(proxy, "vmess://"))
diff --git a/utils/check_update.go b/utils/check_update.go
new file mode 100644
index 0000000..bd6cf53
--- /dev/null
+++ b/utils/check_update.go
@@ -0,0 +1,33 @@
+package utils
+
+import (
+ "encoding/json"
+ "errors"
+ "io"
+ "sub2clash/config"
+ "sub2clash/model"
+)
+
+func CheckUpdate() (bool, string, error) {
+ get, err := Get("https://api.github.com/repos/nitezs/sub2clash/tags")
+ if err != nil {
+ return false, "", errors.New("get version info failed" + err.Error())
+
+ }
+ var version model.Tags
+ all, err := io.ReadAll(get.Body)
+ if err != nil {
+ return false, "", errors.New("get version info failed" + err.Error())
+
+ }
+ err = json.Unmarshal(all, &version)
+ if err != nil {
+ return false, "", errors.New("get version info failed" + err.Error())
+
+ }
+ if version[0].Name == config.Version {
+ return false, "", nil
+ } else {
+ return true, version[0].Name, nil
+ }
+}
diff --git a/utils/database/database.go b/utils/database/database.go
index 3740ca9..2c5c02c 100644
--- a/utils/database/database.go
+++ b/utils/database/database.go
@@ -2,17 +2,29 @@ package database
import (
"github.com/glebarez/sqlite"
+ "go.uber.org/zap"
"gorm.io/gorm"
+ "path/filepath"
+ "sub2clash/logger"
"sub2clash/model"
+ "sub2clash/utils"
)
var DB *gorm.DB
func ConnectDB() error {
// 用上面的数据库连接初始化 gorm
- db, err := gorm.Open(sqlite.Open("sub2clash.db"), &gorm.Config{})
+ err := utils.MKDir("data")
if err != nil {
- panic(err)
+ return err
+ }
+ db, err := gorm.Open(
+ sqlite.Open(filepath.Join("data", "sub2clash.db")), &gorm.Config{
+ Logger: nil,
+ },
+ )
+ if err != nil {
+ return err
}
if err != nil {
return err
@@ -24,3 +36,23 @@ func ConnectDB() error {
}
return nil
}
+
+func FindShortLinkByUrl(url string, shortLink *model.ShortLink) *gorm.DB {
+ logger.Logger.Debug("find short link by url", zap.String("url", url))
+ return DB.Where("url = ?", url).First(&shortLink)
+}
+
+func FindShortLinkByHash(hash string, shortLink *model.ShortLink) *gorm.DB {
+ logger.Logger.Debug("find short link by hash", zap.String("hash", hash))
+ return DB.Where("hash = ?", hash).First(&shortLink)
+}
+
+func SaveShortLink(shortLink *model.ShortLink) {
+ logger.Logger.Debug("save short link", zap.String("hash", shortLink.Hash))
+ DB.Save(shortLink)
+}
+
+func FirstOrCreateShortLink(shortLink *model.ShortLink) {
+ logger.Logger.Debug("first or create short link", zap.String("hash", shortLink.Hash))
+ DB.FirstOrCreate(shortLink)
+}
diff --git a/utils/mkdir.go b/utils/mkdir.go
new file mode 100644
index 0000000..45d276a
--- /dev/null
+++ b/utils/mkdir.go
@@ -0,0 +1,30 @@
+package utils
+
+import (
+ "errors"
+ "os"
+)
+
+func MKDir(dir string) error {
+ if _, err := os.Stat(dir); os.IsNotExist(err) {
+ err := os.MkdirAll(dir, os.ModePerm)
+ if err != nil {
+
+ return err
+ }
+ }
+ return nil
+}
+
+func MkEssentialDir() error {
+ if err := MKDir("subs"); err != nil {
+ return errors.New("create subs dir failed" + err.Error())
+ }
+ if err := MKDir("templates"); err != nil {
+ return errors.New("create templates dir failed" + err.Error())
+ }
+ if err := MKDir("logs"); err != nil {
+ return errors.New("create logs dir failed" + err.Error())
+ }
+ return nil
+}
diff --git a/utils/os.go b/utils/os.go
deleted file mode 100644
index e318160..0000000
--- a/utils/os.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package utils
-
-import (
- "os"
-)
-
-func MKDir(dir string) error {
- if _, err := os.Stat(dir); os.IsNotExist(err) {
- err := os.MkdirAll(dir, os.ModePerm)
- if err != nil {
-
- return err
- }
- }
- return nil
-}
diff --git a/utils/proxy.go b/utils/proxy.go
index 4623bde..c51b720 100644
--- a/utils/proxy.go
+++ b/utils/proxy.go
@@ -1,7 +1,6 @@
package utils
import (
- "sort"
"strings"
"sub2clash/model"
"sub2clash/parser"
@@ -30,12 +29,10 @@ func GetContryName(proxy model.Proxy) string {
func AddProxy(
sub *model.Subscription, autotest bool,
- lazy bool, sortStrategy string,
- clashType model.ClashType, proxies ...model.Proxy,
+ lazy bool, clashType model.ClashType, proxies ...model.Proxy,
) {
newCountryGroupNames := make([]string, 0)
proxyTypes := model.GetSupportProxyTypes(clashType)
-
// 添加节点
for _, proxy := range proxies {
if !proxyTypes[proxy.Type] {
@@ -85,26 +82,6 @@ func AddProxy(
newCountryGroupNames = append(newCountryGroupNames, countryName)
}
}
- // 统计国家策略组数量
- countryGroupCount := 0
- for i := range sub.ProxyGroups {
- if sub.ProxyGroups[i].IsCountryGrop {
- countryGroupCount++
- }
- }
- // 对国家策略组进行排序
- switch sortStrategy {
- case "sizeasc":
- sort.Sort(model.ProxyGroupsSortBySize(sub.ProxyGroups[:countryGroupCount]))
- case "sizedesc":
- sort.Sort(sort.Reverse(model.ProxyGroupsSortBySize(sub.ProxyGroups[:countryGroupCount])))
- case "nameasc":
- sort.Sort(model.ProxyGroupsSortByName(sub.ProxyGroups[:countryGroupCount]))
- case "namedesc":
- sort.Sort(sort.Reverse(model.ProxyGroupsSortByName(sub.ProxyGroups[:countryGroupCount])))
- default:
- sort.Sort(model.ProxyGroupsSortByName(sub.ProxyGroups[:countryGroupCount]))
- }
}
func ParseProxy(proxies ...string) []model.Proxy {
diff --git a/utils/write_default_template.go b/utils/write_default_template.go
new file mode 100644
index 0000000..07a60ae
--- /dev/null
+++ b/utils/write_default_template.go
@@ -0,0 +1,37 @@
+package utils
+
+import (
+ "os"
+ "path/filepath"
+ "sub2clash/config"
+)
+
+func writeTemplate(path string, template string) error {
+ tPath := filepath.Join(
+ "templates", path,
+ )
+ if _, err := os.Stat(tPath); os.IsNotExist(err) {
+ file, err := os.Create(tPath)
+ if err != nil {
+ return err
+ }
+ defer func(file *os.File) {
+ _ = file.Close()
+ }(file)
+ _, err = file.WriteString(template)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func WriteDefalutTemplate(templateMeta string, templateClash string) error {
+ if err := writeTemplate(config.Default.MetaTemplate, templateMeta); err != nil {
+ return err
+ }
+ if err := writeTemplate(config.Default.ClashTemplate, templateClash); err != nil {
+ return err
+ }
+ return nil
+}