Compare commits
12 Commits
v0.0.9
...
v0.1.0-alp
Author | SHA1 | Date |
---|---|---|
Nite07 | 3d3b4e0bea | |
Nite07 | b86aa2559a | |
Nite07 | 4a2fa21a0a | |
Nite07 | 3b8352a34f | |
Nite07 | ac4ad3c8aa | |
Nite07 | ebc91d8aad | |
Nite07 | 48dece2a51 | |
Nite07 | aa9e102a81 | |
Nite07 | faaf5c366a | |
Nite07 | 4384e56cc6 | |
Nite07 | abbd7b8b19 | |
Nite07 | b687acb94c |
|
@ -1,66 +0,0 @@
|
|||
name: Build and Push Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
tags:
|
||||
- "*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKER_HUB_USERNAME }}/sub2clash
|
||||
ghcr.io/${{ github.repository }}
|
||||
|
||||
- name: Prepare args
|
||||
id: prep
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == 'workflow_dispatch' ]]; then
|
||||
VERSION="${{ github.sha }}"
|
||||
fi
|
||||
if [[ "$GITHUB_REF" == 'refs/heads/dev' ]]; then
|
||||
VERSION="${{ github.sha }}"
|
||||
fi
|
||||
if [[ "$GITHUB_REF" == 'refs/tags/'* ]]; then
|
||||
VERSION=$TAG_NAME
|
||||
fi
|
||||
echo "version=$VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push Docker image to GHCR and Docker Hub
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
build-args: version=${{ env.version }}
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm,linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
|
@ -1,31 +0,0 @@
|
|||
name: Build and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.21.5
|
||||
|
||||
- name: Run goreleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --rm-dist --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -1,7 +1,6 @@
|
|||
.idea
|
||||
dist
|
||||
subs
|
||||
test
|
||||
logs
|
||||
sub2clash.db
|
||||
.env
|
||||
|
|
|
@ -8,10 +8,17 @@ builds:
|
|||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
ldflags:
|
||||
- -s -w -X sub2clash/config.Version={{ .Version }}
|
||||
no_unique_dist_dir: true
|
||||
binary: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}"
|
||||
- -s -w -X sub2clash/constant.Version={{ .Version }}
|
||||
flags:
|
||||
- -trimpath
|
||||
archives:
|
||||
- format: binary
|
||||
- format: tar.gz
|
||||
format_overrides:
|
||||
- format: zip
|
||||
goos: windows
|
||||
wrap_in_directory: true
|
||||
files:
|
||||
- LICENSE
|
||||
- README.md
|
||||
- templates
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
when:
|
||||
- event: tag
|
||||
ref: refs/tags/v*
|
||||
|
||||
steps:
|
||||
- name: prepare-tags
|
||||
image: bash
|
||||
commands:
|
||||
- |
|
||||
echo "${CI_COMMIT_TAG##v}" > .tags
|
||||
if ! echo "${CI_COMMIT_TAG}" | grep -Eq "beta|alpha"; then
|
||||
echo "latest" > .tags
|
||||
fi
|
||||
- name: build
|
||||
image: woodpeckerci/plugin-docker-buildx
|
||||
settings:
|
||||
platforms: linux/amd64
|
||||
repo: nite07/sub2clash
|
||||
username: nite07
|
||||
password:
|
||||
from_secret: docker_password
|
|
@ -0,0 +1,20 @@
|
|||
when:
|
||||
- event: tag
|
||||
ref: refs/tags/v*
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: goreleaser/goreleaser:latest
|
||||
commands:
|
||||
- goreleaser release --clean --skip publish
|
||||
- mkdir release
|
||||
- mv dist/*.tar.gz release/
|
||||
- mv dist/*.zip release/
|
||||
- name: release
|
||||
image: woodpeckerci/plugin-gitea-release
|
||||
settings:
|
||||
files: release/*
|
||||
api_key:
|
||||
from_secret: forgejo_token
|
||||
base-url: https://git.nite07.com
|
||||
draft: true
|
19
Dockerfile
19
Dockerfile
|
@ -1,27 +1,12 @@
|
|||
# 使用官方 Golang 镜像作为构建环境
|
||||
FROM golang:1.21-alpine as builder
|
||||
LABEL authors="nite07"
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 复制源代码到工作目录
|
||||
COPY . .
|
||||
RUN go mod download
|
||||
|
||||
# 获取参数
|
||||
ARG version
|
||||
|
||||
# 使用 -ldflags 参数进行编译
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X sub2clash/config.Version=${version}" -o sub2clash main.go
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w -X sub2clash/constant.Version=${version}" -o sub2clash .
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 从 builder 镜像中复制出编译好的二进制文件
|
||||
COPY --from=builder /app/sub2clash /app/sub2clash
|
||||
|
||||
# 设置容器的默认启动命令
|
||||
ENTRYPOINT ["/app/sub2clash"]
|
||||
ENTRYPOINT ["/app/sub2clash"]
|
|
@ -9,10 +9,10 @@ import (
|
|||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2clash/common"
|
||||
"sub2clash/logger"
|
||||
"sub2clash/model"
|
||||
"sub2clash/parser"
|
||||
"sub2clash/utils"
|
||||
"sub2clash/validator"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
@ -32,7 +32,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
|||
template = query.Template
|
||||
}
|
||||
if strings.HasPrefix(template, "http") {
|
||||
templateBytes, err = utils.LoadSubscription(template, query.Refresh)
|
||||
templateBytes, err = common.LoadSubscription(template, query.Refresh)
|
||||
if err != nil {
|
||||
logger.Logger.Debug(
|
||||
"load template failed", zap.String("template", template), zap.Error(err),
|
||||
|
@ -44,7 +44,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
|||
if err != nil {
|
||||
return nil, errors.New("加载模板失败: " + err.Error())
|
||||
}
|
||||
templateBytes, err = utils.LoadTemplate(unescape)
|
||||
templateBytes, err = common.LoadTemplate(unescape)
|
||||
if err != nil {
|
||||
logger.Logger.Debug(
|
||||
"load template failed", zap.String("template", template), zap.Error(err),
|
||||
|
@ -61,7 +61,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
|||
var proxyList []model.Proxy
|
||||
// 加载订阅
|
||||
for i := range query.Subs {
|
||||
data, err := utils.LoadSubscription(query.Subs[i], query.Refresh)
|
||||
data, err := common.LoadSubscription(query.Subs[i], query.Refresh)
|
||||
subName := ""
|
||||
if strings.Contains(query.Subs[i], "#") {
|
||||
subName = query.Subs[i][strings.LastIndex(query.Subs[i], "#")+1:]
|
||||
|
@ -76,9 +76,9 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
|||
err = yaml.Unmarshal(data, &sub)
|
||||
var newProxies []model.Proxy
|
||||
if err != nil {
|
||||
reg, _ := regexp.Compile("(ssr|ss|vmess|trojan|vless|hysteria)://")
|
||||
reg, _ := regexp.Compile("(ssr|ss|vmess|trojan|vless|hysteria|hy2|hysteria2)://")
|
||||
if reg.Match(data) {
|
||||
p := utils.ParseProxy(strings.Split(string(data), "\n")...)
|
||||
p := common.ParseProxy(strings.Split(string(data), "\n")...)
|
||||
newProxies = p
|
||||
} else {
|
||||
// 如果无法直接解析,尝试Base64解码
|
||||
|
@ -91,7 +91,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
|||
)
|
||||
return nil, errors.New("加载订阅失败: " + err.Error())
|
||||
}
|
||||
p := utils.ParseProxy(strings.Split(base64, "\n")...)
|
||||
p := common.ParseProxy(strings.Split(base64, "\n")...)
|
||||
newProxies = p
|
||||
}
|
||||
} else {
|
||||
|
@ -106,7 +106,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
|||
}
|
||||
// 添加自定义节点
|
||||
if len(query.Proxies) != 0 {
|
||||
proxyList = append(proxyList, utils.ParseProxy(query.Proxies...)...)
|
||||
proxyList = append(proxyList, common.ParseProxy(query.Proxies...)...)
|
||||
}
|
||||
// 给节点添加订阅名称
|
||||
for i := range proxyList {
|
||||
|
@ -118,7 +118,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
|||
proxies := make(map[string]*model.Proxy)
|
||||
newProxies := make([]model.Proxy, 0, len(proxyList))
|
||||
for i := range proxyList {
|
||||
key := proxyList[i].Server + ":" + strconv.Itoa(proxyList[i].Port) + ":" + proxyList[i].Type
|
||||
key := proxyList[i].Server + strconv.Itoa(proxyList[i].Port) + proxyList[i].Type + proxyList[i].UUID + proxyList[i].Password
|
||||
if _, exist := proxies[key]; !exist {
|
||||
proxies[key] = &proxyList[i]
|
||||
newProxies = append(newProxies, proxyList[i])
|
||||
|
@ -181,7 +181,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
|||
}
|
||||
// 将新增节点都添加到临时变量 t 中,防止策略组排序错乱
|
||||
var t = &model.Subscription{}
|
||||
utils.AddProxy(t, query.AutoTest, query.Lazy, clashType, proxyList...)
|
||||
common.AddProxy(t, query.AutoTest, query.Lazy, clashType, proxyList...)
|
||||
// 排序策略组
|
||||
switch query.Sort {
|
||||
case "sizeasc":
|
||||
|
@ -200,9 +200,9 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
|||
// 处理自定义规则
|
||||
for _, v := range query.Rules {
|
||||
if v.Prepend {
|
||||
utils.PrependRules(temp, v.Rule)
|
||||
common.PrependRules(temp, v.Rule)
|
||||
} else {
|
||||
utils.AppendRules(temp, v.Rule)
|
||||
common.AppendRules(temp, v.Rule)
|
||||
}
|
||||
}
|
||||
// 处理自定义 ruleProvider
|
||||
|
@ -217,11 +217,11 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
|||
Interval: 3600,
|
||||
}
|
||||
if v.Prepend {
|
||||
utils.PrependRuleProvider(
|
||||
common.PrependRuleProvider(
|
||||
temp, v.Name, v.Group, provider,
|
||||
)
|
||||
} else {
|
||||
utils.AppenddRuleProvider(
|
||||
common.AppenddRuleProvider(
|
||||
temp, v.Name, v.Group, provider,
|
||||
)
|
||||
}
|
||||
|
@ -266,12 +266,16 @@ func MergeSubAndTemplate(temp *model.Subscription, sub *model.Subscription, igcg
|
|||
case "all":
|
||||
newProxies = append(newProxies, proxyNames...)
|
||||
case "countries":
|
||||
newProxies = append(newProxies, countryGroupNames...)
|
||||
if !igcg {
|
||||
newProxies = append(newProxies, countryGroupNames...)
|
||||
}
|
||||
default:
|
||||
if len(key) == 2 {
|
||||
newProxies = append(
|
||||
newProxies, countryGroupMap[utils.GetContryName(key)].Proxies...,
|
||||
)
|
||||
if !igcg {
|
||||
if len(key) == 2 {
|
||||
newProxies = append(
|
||||
newProxies, countryGroupMap[common.GetContryName(key)].Proxies...,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -6,11 +6,11 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2clash/common"
|
||||
"sub2clash/common/database"
|
||||
"sub2clash/config"
|
||||
"sub2clash/logger"
|
||||
"sub2clash/model"
|
||||
"sub2clash/utils"
|
||||
"sub2clash/utils/database"
|
||||
"sub2clash/validator"
|
||||
"time"
|
||||
|
||||
|
@ -30,7 +30,7 @@ func ShortLinkGenHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
// 生成hash
|
||||
hash := utils.RandomString(config.Default.ShortLinkLength)
|
||||
hash := common.RandomString(config.Default.ShortLinkLength)
|
||||
var item model.ShortLink
|
||||
result := database.FindShortLinkByUrl(params.Url, &item)
|
||||
if result.Error == nil {
|
||||
|
@ -51,7 +51,7 @@ func ShortLinkGenHandler(c *gin.Context) {
|
|||
// 如果记录存在则重新生成hash,直到记录不存在
|
||||
result = database.FindShortLinkByHash(hash, &item)
|
||||
for result.Error == nil {
|
||||
hash = utils.RandomString(config.Default.ShortLinkLength)
|
||||
hash = common.RandomString(config.Default.ShortLinkLength)
|
||||
result = database.FindShortLinkByHash(hash, &item)
|
||||
}
|
||||
// 创建记录
|
||||
|
@ -116,7 +116,7 @@ func ShortLinkGetConfigHandler(c *gin.Context) {
|
|||
// 更新最后访问时间
|
||||
shortLink.LastRequestTime = time.Now().Unix()
|
||||
database.SaveShortLink(&shortLink)
|
||||
get, err := utils.Get("http://localhost:" + strconv.Itoa(config.Default.Port) + "/" + shortLink.Url)
|
||||
get, err := common.Get("http://localhost:" + strconv.Itoa(config.Default.Port) + "/" + shortLink.Url)
|
||||
if err != nil {
|
||||
logger.Logger.Debug("get short link data failed", zap.Error(err))
|
||||
c.String(500, "请求错误: "+err.Error())
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
"sub2clash/api/handler"
|
||||
"sub2clash/config"
|
||||
"sub2clash/constant"
|
||||
"sub2clash/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
@ -32,9 +32,9 @@ func SetRoute(r *gin.Engine) {
|
|||
)
|
||||
r.GET(
|
||||
"/", func(c *gin.Context) {
|
||||
version := config.Version
|
||||
if len(config.Version) > 7 {
|
||||
version = config.Version[:7]
|
||||
version := constant.Version
|
||||
if len(constant.Version) > 7 {
|
||||
version = constant.Version[:7]
|
||||
}
|
||||
c.HTML(
|
||||
200, "index.html", gin.H{
|
||||
|
|
|
@ -2,9 +2,9 @@ package database
|
|||
|
||||
import (
|
||||
"path/filepath"
|
||||
"sub2clash/common"
|
||||
"sub2clash/logger"
|
||||
"sub2clash/model"
|
||||
"sub2clash/utils"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"go.uber.org/zap"
|
||||
|
@ -15,7 +15,7 @@ var DB *gorm.DB
|
|||
|
||||
func ConnectDB() error {
|
||||
// 用上面的数据库连接初始化 gorm
|
||||
err := utils.MKDir("data")
|
||||
err := common.MKDir("data")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package utils
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
|
@ -1,4 +1,4 @@
|
|||
package utils
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
|
@ -1,7 +1,8 @@
|
|||
package utils
|
||||
package common
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sub2clash/constant"
|
||||
"sub2clash/logger"
|
||||
"sub2clash/model"
|
||||
"sub2clash/parser"
|
||||
|
@ -106,25 +107,25 @@ func ParseProxy(proxies ...string) []model.Proxy {
|
|||
var proxyItem model.Proxy
|
||||
var err error
|
||||
// 解析节点
|
||||
if strings.HasPrefix(proxy, "ss://") {
|
||||
proxyItem, err = parser.ParseSS(proxy)
|
||||
if strings.HasPrefix(proxy, constant.ShadowsocksPrefix) {
|
||||
proxyItem, err = parser.ParseShadowsocks(proxy)
|
||||
}
|
||||
if strings.HasPrefix(proxy, "trojan://") {
|
||||
if strings.HasPrefix(proxy, constant.TrojanPrefix) {
|
||||
proxyItem, err = parser.ParseTrojan(proxy)
|
||||
}
|
||||
if strings.HasPrefix(proxy, "vmess://") {
|
||||
if strings.HasPrefix(proxy, constant.VMessPrefix) {
|
||||
proxyItem, err = parser.ParseVmess(proxy)
|
||||
}
|
||||
if strings.HasPrefix(proxy, "vless://") {
|
||||
if strings.HasPrefix(proxy, constant.VLESSPrefix) {
|
||||
proxyItem, err = parser.ParseVless(proxy)
|
||||
}
|
||||
if strings.HasPrefix(proxy, "ssr://") {
|
||||
if strings.HasPrefix(proxy, constant.ShadowsocksRPrefix) {
|
||||
proxyItem, err = parser.ParseShadowsocksR(proxy)
|
||||
}
|
||||
if strings.HasPrefix(proxy, "hysteria2://") || strings.HasPrefix(proxy, "hy2://") {
|
||||
if strings.HasPrefix(proxy, constant.Hysteria2Prefix1) || strings.HasPrefix(proxy, constant.Hysteria2Prefix2) {
|
||||
proxyItem, err = parser.ParseHysteria2(proxy)
|
||||
}
|
||||
if strings.HasPrefix(proxy, "hysteria://") {
|
||||
if strings.HasPrefix(proxy, constant.HysteriaPrefix) {
|
||||
proxyItem, err = parser.ParseHysteria(proxy)
|
||||
}
|
||||
if err == nil {
|
|
@ -1,4 +1,4 @@
|
|||
package utils
|
||||
package common
|
||||
|
||||
import "math/rand"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package utils
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -1,4 +1,4 @@
|
|||
package utils
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
|
@ -1,4 +1,4 @@
|
|||
package utils
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
|
@ -1,4 +1,4 @@
|
|||
package utils
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
|
@ -3,7 +3,6 @@ package config
|
|||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
|
@ -22,18 +21,8 @@ type Config struct {
|
|||
}
|
||||
|
||||
var Default *Config
|
||||
var Version string
|
||||
var Dev string
|
||||
|
||||
func init() {
|
||||
reg := regexp.MustCompile(`^v\d+\.\d+\.\d+$`)
|
||||
if reg.MatchString(Version) {
|
||||
Dev = "false"
|
||||
} else {
|
||||
Dev = "true"
|
||||
}
|
||||
}
|
||||
|
||||
func LoadConfig() error {
|
||||
Default = &Config{
|
||||
MetaTemplate: "template_meta.yaml",
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package constant
|
||||
|
||||
const (
|
||||
HysteriaPrefix string = "hysteria://"
|
||||
Hysteria2Prefix1 string = "hysteria2://"
|
||||
Hysteria2Prefix2 string = "hy2://"
|
||||
ShadowsocksPrefix string = "ss://"
|
||||
ShadowsocksRPrefix string = "ssr://"
|
||||
TrojanPrefix string = "trojan://"
|
||||
VLESSPrefix string = "vless://"
|
||||
VMessPrefix string = "vmess://"
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
package constant
|
||||
|
||||
var Version = "dev"
|
|
@ -4,7 +4,7 @@ services:
|
|||
sub2clash:
|
||||
container_name: sub2clash
|
||||
restart: unless-stopped
|
||||
image: ghcr.io/nitezs/sub2clash:latest
|
||||
image: nite07/sub2clash:latest
|
||||
ports:
|
||||
- "8011:8011"
|
||||
volumes:
|
||||
|
|
6
go.mod
6
go.mod
|
@ -26,16 +26,15 @@ require (
|
|||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // 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/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/knz/go-libedit v1.10.1 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
|
@ -47,11 +46,8 @@ require (
|
|||
golang.org/x/net v0.22.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||
modernc.org/libc v1.44.0 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.7.2 // indirect
|
||||
modernc.org/sqlite v1.29.4 // indirect
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
modernc.org/token v1.1.0 // indirect
|
||||
)
|
||||
|
|
81
go.sum
81
go.sum
|
@ -1,14 +1,11 @@
|
|||
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.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc=
|
||||
github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||
github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA=
|
||||
github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
|
||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
|
||||
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
|
@ -17,20 +14,14 @@ 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/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/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
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-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
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/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.9.0 h1:Aj6bPA12ZEx5GbSF6XADmCkYXlljPNUY+Zf1EQxynXs=
|
||||
github.com/glebarez/sqlite v1.9.0/go.mod h1:YBYCoyupOao60lzp1MVBLEjZfgkq0tdB1voAQ09K9zw=
|
||||
github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
|
||||
github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
|
@ -39,27 +30,17 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
|||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.15.4 h1:zMXza4EpOdooxPel5xDqXEdXG5r+WggpvnAKMsalBjs=
|
||||
github.com/go-playground/validator/v10 v10.15.4/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
|
||||
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
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/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
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=
|
||||
|
@ -69,18 +50,11 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA
|
|||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1 h1:0pHpWtx9vcvC0xGZqEQlQdfSQs7WRlAjuPvk3fOZDCo=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
@ -88,15 +62,14 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
|||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
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/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=
|
||||
|
@ -107,51 +80,35 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
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.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
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/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
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/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
||||
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
|
@ -159,26 +116,26 @@ 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
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=
|
||||
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/cc/v4 v4.19.3 h1:vE9kmJqUcyvNOf8F2Hn8od14SOMq34BiqcZ2tMzLk5c=
|
||||
modernc.org/cc/v4 v4.19.3/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.10.1 h1:qi+3luLv0LR5UkLmZyKXZxIC4K/136vcAUoYYeGSS+g=
|
||||
modernc.org/ccgo/v4 v4.10.1/go.mod h1:9YDnb1IIvHymh899K5a++jza0JIWygZPTc5dlh7xvhQ=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/libc v1.44.0 h1:71bbnKgb0mCg7GOOI/PHlzz7Bv6obELGNKnIEeowX8c=
|
||||
modernc.org/libc v1.44.0/go.mod h1:RRqfGVjvILF5AdNP3RPCiihj7+Dn2pIBrdlU60lA9vs=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.29.4 h1:mbvQTJ3Tl5Vz+wLA6z8hdBFSeNQ0XXQ+KVwn8NkUliw=
|
||||
modernc.org/sqlite v1.29.4/go.mod h1:MjUIBKZ+tU/lqjNLbVAAMjsQPdWdA/ciwdhsT9kBwk8=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
|
|
22
main.go
22
main.go
|
@ -5,10 +5,10 @@ import (
|
|||
"io"
|
||||
"strconv"
|
||||
"sub2clash/api"
|
||||
"sub2clash/common"
|
||||
"sub2clash/common/database"
|
||||
"sub2clash/config"
|
||||
"sub2clash/logger"
|
||||
"sub2clash/utils"
|
||||
"sub2clash/utils/database"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
@ -23,7 +23,7 @@ var templateClash string
|
|||
func init() {
|
||||
var err error
|
||||
// 创建文件夹
|
||||
err = utils.MkEssentialDir()
|
||||
err = common.MkEssentialDir()
|
||||
if err != nil {
|
||||
logger.Logger.Panic("create essential dir failed", zap.Error(err))
|
||||
}
|
||||
|
@ -34,22 +34,8 @@ func init() {
|
|||
if err != nil {
|
||||
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.WriteDefalutTemplate(templateMeta, templateClash)
|
||||
err = common.WriteDefalutTemplate(templateMeta, templateClash)
|
||||
if err != nil {
|
||||
logger.Logger.Panic("write default template failed", zap.Error(err))
|
||||
}
|
||||
|
|
|
@ -6,108 +6,29 @@ import (
|
|||
)
|
||||
|
||||
type ProxyGroup struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Type string `yaml:"type,omitempty"`
|
||||
Proxies []string `yaml:"proxies,omitempty"`
|
||||
IsCountryGrop bool `yaml:"-"`
|
||||
Url string `yaml:"url,omitempty"`
|
||||
Interval int `yaml:"interval,omitempty"`
|
||||
Tolerance int `yaml:"tolerance,omitempty"`
|
||||
Lazy bool `yaml:"lazy"`
|
||||
Size int `yaml:"-"`
|
||||
DisableUDP bool `yaml:"disable-udp,omitempty"`
|
||||
Strategy string `yaml:"strategy,omitempty"`
|
||||
Icon string `yaml:"icon,omitempty"`
|
||||
}
|
||||
|
||||
type SelectProxyGroup struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Type string `yaml:"type,omitempty"`
|
||||
Proxies []string `yaml:"proxies,omitempty"`
|
||||
DisableUDP bool `yaml:"disable-udp,omitempty"`
|
||||
Icon string `yaml:"icon,omitempty"`
|
||||
}
|
||||
|
||||
type UrlTestProxyGroup struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Type string `yaml:"type,omitempty"`
|
||||
Proxies []string `yaml:"proxies,omitempty"`
|
||||
Url string `yaml:"url,omitempty"`
|
||||
Interval int `yaml:"interval,omitempty"`
|
||||
Tolerance int `yaml:"tolerance,omitempty"`
|
||||
Lazy bool `yaml:"lazy"`
|
||||
DisableUDP bool `yaml:"disable-udp,omitempty"`
|
||||
Icon string `yaml:"icon,omitempty"`
|
||||
}
|
||||
|
||||
type LoadBalanceProxyGroup struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Type string `yaml:"type,omitempty"`
|
||||
Proxies []string `yaml:"proxies,omitempty"`
|
||||
DisableUDP bool `yaml:"disable-udp,omitempty"`
|
||||
Url string `yaml:"url,omitempty"`
|
||||
Interval int `yaml:"interval,omitempty"`
|
||||
Lazy bool `yaml:"lazy"`
|
||||
Strategy string `yaml:"strategy,omitempty"`
|
||||
Icon string `yaml:"icon,omitempty"`
|
||||
}
|
||||
|
||||
type RelayProxyGroup struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Type string `yaml:"type,omitempty"`
|
||||
Proxies []string `yaml:"proxies,omitempty"`
|
||||
Icon string `yaml:"icon,omitempty"`
|
||||
}
|
||||
|
||||
func (p ProxyGroup) MarshalYAML() (interface{}, error) {
|
||||
switch p.Type {
|
||||
case "select":
|
||||
return SelectProxyGroup{
|
||||
Name: p.Name,
|
||||
Type: p.Type,
|
||||
Proxies: p.Proxies,
|
||||
DisableUDP: p.DisableUDP,
|
||||
Icon: p.Icon,
|
||||
}, nil
|
||||
case "url-test", "fallback":
|
||||
return UrlTestProxyGroup{
|
||||
Name: p.Name,
|
||||
Type: p.Type,
|
||||
Proxies: p.Proxies,
|
||||
Url: p.Url,
|
||||
Interval: p.Interval,
|
||||
Tolerance: p.Tolerance,
|
||||
Lazy: p.Lazy,
|
||||
DisableUDP: p.DisableUDP,
|
||||
Icon: p.Icon,
|
||||
}, nil
|
||||
case "load-balance":
|
||||
return LoadBalanceProxyGroup{
|
||||
Name: p.Name,
|
||||
Type: p.Type,
|
||||
Proxies: p.Proxies,
|
||||
DisableUDP: p.DisableUDP,
|
||||
Url: p.Url,
|
||||
Interval: p.Interval,
|
||||
Lazy: p.Lazy,
|
||||
Strategy: p.Strategy,
|
||||
Icon: p.Icon,
|
||||
}, nil
|
||||
case "relay":
|
||||
return RelayProxyGroup{
|
||||
Name: p.Name,
|
||||
Type: p.Type,
|
||||
Proxies: p.Proxies,
|
||||
Icon: p.Icon,
|
||||
}, nil
|
||||
default:
|
||||
return SelectProxyGroup{
|
||||
Name: p.Name,
|
||||
Type: p.Type,
|
||||
Proxies: p.Proxies,
|
||||
Icon: p.Icon,
|
||||
}, nil
|
||||
}
|
||||
Type string `yaml:"type,omitempty"`
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Proxies []string `yaml:"proxies,omitempty"`
|
||||
IsCountryGrop bool `yaml:"-"` // 是否是国家分组
|
||||
Url string `yaml:"url,omitempty"`
|
||||
Interval int `yaml:"interval,omitempty"`
|
||||
Tolerance int `yaml:"tolerance,omitempty"`
|
||||
Lazy bool `yaml:"lazy"`
|
||||
Size int `yaml:"-"` // 代理数量
|
||||
DisableUDP bool `yaml:"disable-udp,omitempty"`
|
||||
Strategy string `yaml:"strategy,omitempty"`
|
||||
Icon string `yaml:"icon,omitempty"`
|
||||
Timeout int `yaml:"timeout,omitempty"`
|
||||
Use []string `yaml:"use,omitempty"`
|
||||
InterfaceName string `yaml:"interface-name,omitempty"`
|
||||
RoutingMark int `yaml:"routing-mark,omitempty"`
|
||||
IncludeAll bool `yaml:"include-all,omitempty"`
|
||||
IncludeAllProxies bool `yaml:"include-all-proxies,omitempty"`
|
||||
IncludeAllProviders bool `yaml:"include-all-providers,omitempty"`
|
||||
Filter string `yaml:"filter,omitempty"`
|
||||
ExcludeFilter string `yaml:"exclude-filter,omitempty"`
|
||||
ExpectedStatus int `yaml:"expected-status,omitempty"`
|
||||
Hidden bool `yaml:"hidden,omitempty"`
|
||||
}
|
||||
|
||||
type ProxyGroupsSortByName []ProxyGroup
|
||||
|
|
13
model/sub.go
13
model/sub.go
|
@ -1,18 +1,5 @@
|
|||
package model
|
||||
|
||||
// type Subscription struct {
|
||||
// Port int `yaml:"port,omitempty"`
|
||||
// SocksPort int `yaml:"socks-port,omitempty"`
|
||||
// AllowLan bool `yaml:"allow-lan"`
|
||||
// Mode string `yaml:"mode,omitempty"`
|
||||
// LogLevel string `yaml:"logger-level,omitempty"`
|
||||
// ExternalController string `yaml:"external-controller,omitempty"`
|
||||
// Proxies []Proxy `yaml:"proxies,omitempty"`
|
||||
// ProxyGroups []ProxyGroup `yaml:"proxy-groups,omitempty"`
|
||||
// Rules []string `yaml:"rules,omitempty"`
|
||||
// RuleProviders map[string]RuleProvider `yaml:"rule-providers,omitempty,omitempty"`
|
||||
// }
|
||||
|
||||
type NodeList struct {
|
||||
Proxies []Proxy `yaml:"proxies,omitempty"`
|
||||
}
|
||||
|
|
|
@ -7,6 +7,11 @@ import (
|
|||
|
||||
func DecodeBase64(s string) (string, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
// url safe
|
||||
if strings.Contains(s, "-") || strings.Contains(s, "_") {
|
||||
s = strings.Replace(s, "-", "+", -1)
|
||||
s = strings.Replace(s, "_", "/", -1)
|
||||
}
|
||||
if len(s)%4 != 0 {
|
||||
s += strings.Repeat("=", 4-len(s)%4)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package parser
|
||||
|
||||
type ParseError struct {
|
||||
Type ParseErrorType
|
||||
Message string
|
||||
Raw string
|
||||
}
|
||||
|
||||
type ParseErrorType string
|
||||
|
||||
const (
|
||||
ErrInvalidPrefix ParseErrorType = "invalid url prefix"
|
||||
ErrInvalidStruct ParseErrorType = "invalid struct"
|
||||
ErrInvalidPort ParseErrorType = "invalid port number"
|
||||
ErrCannotParseParams ParseErrorType = "cannot parse query parameters"
|
||||
ErrInvalidBase64 ParseErrorType = "invalid base64"
|
||||
)
|
||||
|
||||
func (e *ParseError) Error() string {
|
||||
if e.Message != "" {
|
||||
return string(e.Type) + ": " + e.Message + " \"" + e.Raw + "\""
|
||||
}
|
||||
return string(e.Type)
|
||||
}
|
|
@ -1,79 +1,86 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2clash/constant"
|
||||
"sub2clash/model"
|
||||
)
|
||||
|
||||
//hysteria://host:port?protocol=udp&auth=123456&peer=sni.domain&insecure=1&upmbps=100&downmbps=100&alpn=hysteria&obfs=xplus&obfsParam=123456#remarks
|
||||
//
|
||||
//- host: hostname or IP address of the server to connect to (required)
|
||||
//- port: port of the server to connect to (required)
|
||||
//- protocol: protocol to use ("udp", "wechat-video", "faketcp") (optional, default: "udp")
|
||||
//- auth: authentication payload (string) (optional)
|
||||
//- peer: SNI for TLS (optional)
|
||||
//- insecure: ignore certificate errors (optional)
|
||||
//- upmbps: upstream bandwidth in Mbps (required)
|
||||
//- downmbps: downstream bandwidth in Mbps (required)
|
||||
//- alpn: QUIC ALPN (optional)
|
||||
//- obfs: Obfuscation mode (optional, empty or "xplus")
|
||||
//- obfsParam: Obfuscation password (optional)
|
||||
//- remarks: remarks (optional)
|
||||
|
||||
func ParseHysteria(proxy string) (model.Proxy, error) {
|
||||
// 判断是否以 hysteria:// 开头
|
||||
if !strings.HasPrefix(proxy, "hysteria://") {
|
||||
return model.Proxy{}, errors.New("invalid hysteria Url")
|
||||
if !strings.HasPrefix(proxy, constant.HysteriaPrefix) {
|
||||
return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy}
|
||||
}
|
||||
// 分割
|
||||
parts := strings.SplitN(strings.TrimPrefix(proxy, "hysteria://"), "?", 2)
|
||||
serverInfo := strings.SplitN(parts[0], ":", 2)
|
||||
|
||||
proxy = strings.TrimPrefix(proxy, constant.HysteriaPrefix)
|
||||
urlParts := strings.SplitN(proxy, "?", 2)
|
||||
if len(urlParts) != 2 {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Message: "missing character '?' in url",
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
|
||||
serverInfo := strings.SplitN(urlParts[0], ":", 2)
|
||||
if len(serverInfo) != 2 {
|
||||
return model.Proxy{}, errors.New("invalid hysteria Url")
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Message: "missing server host or port",
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
params, err := url.ParseQuery(parts[1])
|
||||
server, portStr := serverInfo[0], serverInfo[1]
|
||||
|
||||
port, err := ParsePort(portStr)
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid hysteria Url")
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidPort,
|
||||
Message: err.Error(),
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
host := serverInfo[0]
|
||||
port, err := strconv.Atoi(serverInfo[1])
|
||||
|
||||
params, err := url.ParseQuery(urlParts[1])
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid hysteria Url")
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrCannotParseParams,
|
||||
Raw: proxy,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
protocol := params.Get("protocol")
|
||||
auth := params.Get("auth")
|
||||
peer := params.Get("peer")
|
||||
insecure := params.Get("insecure")
|
||||
upmbps := params.Get("upmbps")
|
||||
downmbps := params.Get("downmbps")
|
||||
alpn := params.Get("alpn")
|
||||
obfs := params.Get("obfs")
|
||||
obfsParam := params.Get("obfsParam")
|
||||
remarks := ""
|
||||
if strings.Contains(parts[1], "#") {
|
||||
r := strings.Split(parts[1], "#")
|
||||
remarks = r[len(r)-1]
|
||||
} else {
|
||||
remarks = serverInfo[0] + ":" + serverInfo[1]
|
||||
|
||||
protocol, auth, insecure, upmbps, downmbps, obfs, alpnStr := params.Get("protocol"), params.Get("auth"), params.Get("insecure"), params.Get("upmbps"), params.Get("downmbps"), params.Get("obfs"), params.Get("alpn")
|
||||
insecureBool, err := strconv.ParseBool(insecure)
|
||||
if err != nil {
|
||||
insecureBool = false
|
||||
}
|
||||
// 返回结果
|
||||
|
||||
var alpn []string
|
||||
alpnStr = strings.TrimSpace(alpnStr)
|
||||
if alpnStr != "" {
|
||||
alpn = strings.Split(alpnStr, ",")
|
||||
}
|
||||
|
||||
remarks := server + ":" + portStr
|
||||
if params.Get("remarks") != "" {
|
||||
remarks = params.Get("remarks")
|
||||
}
|
||||
|
||||
result := model.Proxy{
|
||||
Type: "hysteria",
|
||||
Name: remarks,
|
||||
Server: host,
|
||||
Server: server,
|
||||
Port: port,
|
||||
Up: upmbps,
|
||||
Down: downmbps,
|
||||
Auth: auth,
|
||||
Obfs: obfs,
|
||||
Sni: peer,
|
||||
SkipCertVerify: insecure == "1",
|
||||
Alpn: strings.Split(alpn, ","),
|
||||
ObfsParam: obfsParam,
|
||||
Alpn: alpn,
|
||||
Protocol: protocol,
|
||||
AllowInsecure: insecureBool,
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -1,50 +1,89 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2clash/constant"
|
||||
"sub2clash/model"
|
||||
)
|
||||
|
||||
// hysteria2://letmein@example.com/?insecure=1&obfs=salamander&obfs-password=gawrgura&pinSHA256=deadbeef&sni=real.example.com
|
||||
|
||||
func ParseHysteria2(proxy string) (model.Proxy, error) {
|
||||
// 判断是否以 hysteria2:// 开头
|
||||
if !strings.HasPrefix(proxy, "hysteria2://") && !strings.HasPrefix(proxy, "hy2://") {
|
||||
return model.Proxy{}, errors.New("invalid hysteria2 Url")
|
||||
if !strings.HasPrefix(proxy, constant.Hysteria2Prefix1) &&
|
||||
!strings.HasPrefix(proxy, constant.Hysteria2Prefix2) {
|
||||
return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy}
|
||||
}
|
||||
// 分割
|
||||
parts := strings.SplitN(strings.TrimPrefix(proxy, "hysteria2://"), "@", 2)
|
||||
// 分割
|
||||
serverInfo := strings.SplitN(parts[1], "/?", 2)
|
||||
|
||||
proxy = strings.TrimPrefix(proxy, constant.Hysteria2Prefix1)
|
||||
proxy = strings.TrimPrefix(proxy, constant.Hysteria2Prefix2)
|
||||
urlParts := strings.SplitN(proxy, "@", 2)
|
||||
if len(urlParts) != 2 {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Message: "missing character '@' in url",
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
password := urlParts[0]
|
||||
|
||||
serverInfo := strings.SplitN(urlParts[1], "/?", 2)
|
||||
if len(serverInfo) != 2 {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Message: "missing params in url",
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
paramStr := serverInfo[1]
|
||||
|
||||
serverAndPort := strings.SplitN(serverInfo[0], ":", 2)
|
||||
var server string
|
||||
var portStr string
|
||||
if len(serverAndPort) == 1 {
|
||||
serverAndPort = append(serverAndPort, "443")
|
||||
} else if len(serverAndPort) != 2 {
|
||||
return model.Proxy{}, errors.New("invalid hysteria2 Url")
|
||||
portStr = "443"
|
||||
} else if len(serverAndPort) == 2 {
|
||||
server, portStr = serverAndPort[0], serverAndPort[1]
|
||||
} else {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Message: "missing server host or port",
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
params, err := url.ParseQuery(serverInfo[1])
|
||||
|
||||
port, err := ParsePort(portStr)
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid hysteria2 Url")
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidPort,
|
||||
Message: err.Error(),
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
// 获取端口
|
||||
port, err := strconv.Atoi(serverAndPort[1])
|
||||
|
||||
params, err := url.ParseQuery(paramStr)
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid hysteria2 Url")
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrCannotParseParams,
|
||||
Raw: proxy,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
// 返回结果
|
||||
|
||||
remarks, network, obfs, obfsPassword, pinSHA256, insecure, sni := params.Get("name"), params.Get("network"), params.Get("obfs"), params.Get("obfs-password"), params.Get("pinSHA256"), params.Get("insecure"), params.Get("sni")
|
||||
enableTLS := pinSHA256 != ""
|
||||
insecureBool := insecure == "1"
|
||||
|
||||
result := model.Proxy{
|
||||
Type: "hysteria2",
|
||||
Name: params.Get("name"),
|
||||
Server: serverAndPort[0],
|
||||
Name: remarks,
|
||||
Server: server,
|
||||
Port: port,
|
||||
Password: parts[0],
|
||||
Obfs: params.Get("obfs"),
|
||||
ObfsParam: params.Get("obfs-password"),
|
||||
Sni: params.Get("sni"),
|
||||
SkipCertVerify: params.Get("insecure") == "1",
|
||||
Password: password,
|
||||
Obfs: obfs,
|
||||
ObfsParam: obfsPassword,
|
||||
Sni: sni,
|
||||
SkipCertVerify: insecureBool,
|
||||
TLS: enableTLS,
|
||||
Network: network,
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func ParsePort(portStr string) (int, error) {
|
||||
port, err := strconv.Atoi(portStr)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if port < 1 || port > 65535 {
|
||||
return 0, errors.New("invaild port range")
|
||||
}
|
||||
return port, nil
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"sub2clash/constant"
|
||||
"sub2clash/model"
|
||||
)
|
||||
|
||||
func ParseShadowsocks(proxy string) (model.Proxy, error) {
|
||||
if !strings.HasPrefix(proxy, constant.ShadowsocksPrefix) {
|
||||
return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy}
|
||||
}
|
||||
|
||||
proxy = strings.TrimPrefix(proxy, constant.ShadowsocksPrefix)
|
||||
urlParts := strings.SplitN(proxy, "@", 2)
|
||||
if len(urlParts) != 2 {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Message: "missing character '@' in url",
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
|
||||
var serverAndPort []string
|
||||
if !strings.Contains(urlParts[0], ":") {
|
||||
decoded, err := DecodeBase64(urlParts[0])
|
||||
if err != nil {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Message: "invalid base64 encoded",
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
urlParts[0] = decoded
|
||||
}
|
||||
credentials := strings.SplitN(urlParts[0], ":", 2)
|
||||
if len(credentials) != 2 {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Message: "missing server host or port",
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
method, password := credentials[0], credentials[1]
|
||||
|
||||
serverInfo := strings.SplitN(urlParts[1], "#", 2)
|
||||
serverAndPort = strings.SplitN(serverInfo[0], ":", 2)
|
||||
server, portStr := serverAndPort[0], serverAndPort[1]
|
||||
if len(serverInfo) != 2 {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Message: "missing server host or port",
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
port, err := ParsePort(portStr)
|
||||
if err != nil {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidPort,
|
||||
Message: err.Error(),
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
|
||||
var remarks string
|
||||
if len(serverInfo) == 2 {
|
||||
unescape, err := url.QueryUnescape(serverInfo[1])
|
||||
if err != nil {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Message: "cannot unescape remarks",
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
remarks = strings.TrimSpace(unescape)
|
||||
} else {
|
||||
remarks = strings.TrimSpace(server + ":" + portStr)
|
||||
}
|
||||
|
||||
result := model.Proxy{
|
||||
Type: "ss",
|
||||
Cipher: method,
|
||||
Password: password,
|
||||
Server: server,
|
||||
Port: port,
|
||||
Name: remarks,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2clash/constant"
|
||||
"sub2clash/model"
|
||||
)
|
||||
|
||||
func ParseShadowsocksR(proxy string) (model.Proxy, error) {
|
||||
if !strings.HasPrefix(proxy, constant.ShadowsocksRPrefix) {
|
||||
return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy}
|
||||
}
|
||||
|
||||
proxy = strings.TrimPrefix(proxy, constant.ShadowsocksRPrefix)
|
||||
proxy, err := DecodeBase64(proxy)
|
||||
if err != nil {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidBase64,
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
serverInfoAndParams := strings.SplitN(proxy, "/?", 2)
|
||||
parts := strings.Split(serverInfoAndParams[0], ":")
|
||||
server := parts[0]
|
||||
protocol := parts[2]
|
||||
method := parts[3]
|
||||
obfs := parts[4]
|
||||
password := parts[5]
|
||||
port, err := ParsePort(parts[1])
|
||||
if err != nil {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidPort,
|
||||
Message: err.Error(),
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
|
||||
var obfsParam string
|
||||
var protoParam string
|
||||
var remarks string
|
||||
if len(serverInfoAndParams) == 2 {
|
||||
params, err := url.ParseQuery(serverInfoAndParams[1])
|
||||
if err != nil {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrCannotParseParams,
|
||||
Raw: proxy,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
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"))
|
||||
} else {
|
||||
remarks = server + ":" + strconv.Itoa(port)
|
||||
}
|
||||
if err != nil {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Raw: proxy,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result := model.Proxy{
|
||||
Name: remarks,
|
||||
Type: "ssr",
|
||||
Server: server,
|
||||
Port: port,
|
||||
Protocol: protocol,
|
||||
Cipher: method,
|
||||
Obfs: obfs,
|
||||
Password: password,
|
||||
ObfsParam: obfsParam,
|
||||
ProtocolParam: protoParam,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
67
parser/ss.go
67
parser/ss.go
|
@ -1,67 +0,0 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2clash/model"
|
||||
)
|
||||
|
||||
// ParseSS 解析 SS(Shadowsocks)Url
|
||||
func ParseSS(proxy string) (model.Proxy, error) {
|
||||
// 判断是否以 ss:// 开头
|
||||
if !strings.HasPrefix(proxy, "ss://") {
|
||||
return model.Proxy{}, errors.New("invalid ss Url")
|
||||
}
|
||||
// 分割
|
||||
parts := strings.SplitN(strings.TrimPrefix(proxy, "ss://"), "@", 2)
|
||||
if len(parts) != 2 {
|
||||
return model.Proxy{}, errors.New("invalid ss Url")
|
||||
}
|
||||
if !strings.Contains(parts[0], ":") {
|
||||
// 解码
|
||||
decoded, err := DecodeBase64(parts[0])
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid ss Url" + err.Error())
|
||||
}
|
||||
parts[0] = decoded
|
||||
}
|
||||
credentials := strings.SplitN(parts[0], ":", 2)
|
||||
if len(credentials) != 2 {
|
||||
return model.Proxy{}, errors.New("invalid ss Url")
|
||||
}
|
||||
// 分割
|
||||
serverInfo := strings.SplitN(parts[1], "#", 2)
|
||||
serverAndPort := strings.SplitN(serverInfo[0], ":", 2)
|
||||
if len(serverAndPort) != 2 {
|
||||
return model.Proxy{}, errors.New("invalid ss Url")
|
||||
}
|
||||
// 转换端口字符串为数字
|
||||
port, err := strconv.Atoi(strings.TrimSpace(serverAndPort[1]))
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid ss Url" + err.Error())
|
||||
}
|
||||
// 返回结果
|
||||
result := model.Proxy{
|
||||
Type: "ss",
|
||||
Cipher: strings.TrimSpace(credentials[0]),
|
||||
Password: strings.TrimSpace(credentials[1]),
|
||||
Server: strings.TrimSpace(serverAndPort[0]),
|
||||
Port: port,
|
||||
UDP: true,
|
||||
Name: serverAndPort[0],
|
||||
}
|
||||
// 如果有节点名称
|
||||
if len(serverInfo) == 2 {
|
||||
unescape, err := url.QueryUnescape(serverInfo[1])
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid ss Url" + err.Error())
|
||||
}
|
||||
result.Name = strings.TrimSpace(unescape)
|
||||
} else {
|
||||
result.Name = strings.TrimSpace(serverAndPort[0])
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2clash/model"
|
||||
)
|
||||
|
||||
func ParseShadowsocksR(proxy string) (model.Proxy, error) {
|
||||
// 判断是否以 ssr:// 开头
|
||||
if !strings.HasPrefix(proxy, "ssr://") {
|
||||
return model.Proxy{}, fmt.Errorf("invalid ssr Url")
|
||||
}
|
||||
var err error
|
||||
proxy = strings.TrimPrefix(proxy, "ssr://")
|
||||
if !strings.Contains(proxy, ":") {
|
||||
proxy, err = DecodeBase64(strings.TrimPrefix(proxy, "ssr://"))
|
||||
if err != nil {
|
||||
return model.Proxy{}, err
|
||||
}
|
||||
}
|
||||
// 分割
|
||||
detailsAndParams := strings.SplitN(proxy, "/?", 2)
|
||||
parts := strings.Split(detailsAndParams[0], ":")
|
||||
params, err := url.ParseQuery(detailsAndParams[1])
|
||||
if err != nil {
|
||||
return model.Proxy{}, err
|
||||
}
|
||||
// 处理端口
|
||||
port, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
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"))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return model.Proxy{}, err
|
||||
}
|
||||
|
||||
result := model.Proxy{
|
||||
Name: remarks,
|
||||
Type: "ssr",
|
||||
Server: parts[0],
|
||||
Port: port,
|
||||
Protocol: parts[2],
|
||||
Cipher: parts[3],
|
||||
Obfs: parts[4],
|
||||
Password: parts[5],
|
||||
ObfsParam: obfsParam,
|
||||
ProtocolParam: protoParam,
|
||||
}
|
||||
|
||||
if result.Name == "" {
|
||||
result.Name = result.Server
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
137
parser/trojan.go
137
parser/trojan.go
|
@ -1,53 +1,134 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2clash/constant"
|
||||
"sub2clash/model"
|
||||
)
|
||||
|
||||
// ParseTrojan 解析给定的Trojan代理URL并返回Proxy结构。
|
||||
func ParseTrojan(proxy string) (model.Proxy, error) {
|
||||
// 判断是否以 trojan:// 开头
|
||||
if !strings.HasPrefix(proxy, "trojan://") {
|
||||
return model.Proxy{}, fmt.Errorf("invalid trojan Url")
|
||||
if !strings.HasPrefix(proxy, constant.TrojanPrefix) {
|
||||
return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy}
|
||||
}
|
||||
// 分割
|
||||
parts := strings.SplitN(strings.TrimPrefix(proxy, "trojan://"), "@", 2)
|
||||
if len(parts) != 2 {
|
||||
return model.Proxy{}, fmt.Errorf("invalid trojan Url")
|
||||
|
||||
proxy = strings.TrimPrefix(proxy, constant.TrojanPrefix)
|
||||
urlParts := strings.SplitN(proxy, "@", 2)
|
||||
if len(urlParts) != 2 {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Message: "missing character '@' in url",
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
// 分割
|
||||
serverInfo := strings.SplitN(parts[1], "#", 2)
|
||||
password := strings.TrimSpace(urlParts[0])
|
||||
|
||||
serverInfo := strings.SplitN(urlParts[1], "#", 2)
|
||||
serverAndPortAndParams := strings.SplitN(serverInfo[0], "?", 2)
|
||||
if len(serverAndPortAndParams) != 2 {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Message: "missing character '?' in url",
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
|
||||
serverAndPort := strings.SplitN(serverAndPortAndParams[0], ":", 2)
|
||||
if len(serverAndPort) != 2 {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Message: "missing server host or port",
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
server, portStr := serverAndPort[0], serverAndPort[1]
|
||||
|
||||
params, err := url.ParseQuery(serverAndPortAndParams[1])
|
||||
if err != nil {
|
||||
return model.Proxy{}, err
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrCannotParseParams,
|
||||
Raw: proxy,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
if len(serverAndPort) != 2 {
|
||||
return model.Proxy{}, fmt.Errorf("invalid trojan")
|
||||
}
|
||||
// 处理端口
|
||||
port, err := strconv.Atoi(strings.TrimSpace(serverAndPort[1]))
|
||||
|
||||
port, err := ParsePort(portStr)
|
||||
if err != nil {
|
||||
return model.Proxy{}, err
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidPort,
|
||||
Message: err.Error(),
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
// 返回结果
|
||||
|
||||
remarks := ""
|
||||
if len(serverInfo) == 2 {
|
||||
remarks, _ = url.QueryUnescape(strings.TrimSpace(serverInfo[1]))
|
||||
} else {
|
||||
remarks = serverAndPort[0]
|
||||
}
|
||||
|
||||
network, security, alpnStr, sni, pbk, sid, fp, path, host, serviceName := params.Get("type"), params.Get("security"), params.Get("alpn"), params.Get("sni"), params.Get("pbk"), params.Get("sid"), params.Get("fp"), params.Get("path"), params.Get("host"), params.Get("serviceName")
|
||||
|
||||
var alpn []string
|
||||
if strings.Contains(alpnStr, ",") {
|
||||
alpn = strings.Split(alpnStr, ",")
|
||||
} else {
|
||||
alpn = nil
|
||||
}
|
||||
|
||||
// enableUTLS := fp != ""
|
||||
|
||||
// 构建Proxy结构体
|
||||
result := model.Proxy{
|
||||
Type: "trojan",
|
||||
Server: strings.TrimSpace(serverAndPort[0]),
|
||||
Server: server,
|
||||
Port: port,
|
||||
UDP: true,
|
||||
Password: strings.TrimSpace(parts[0]),
|
||||
Sni: params.Get("sni"),
|
||||
Password: password,
|
||||
Name: remarks,
|
||||
Network: network,
|
||||
}
|
||||
// 如果有节点名称
|
||||
if len(serverInfo) == 2 {
|
||||
result.Name, _ = url.QueryUnescape(strings.TrimSpace(serverInfo[1]))
|
||||
} else {
|
||||
result.Name = serverAndPort[0]
|
||||
|
||||
if security == "xtls" || security == "tls" {
|
||||
result.Alpn = alpn
|
||||
result.Sni = sni
|
||||
result.TLS = true
|
||||
}
|
||||
|
||||
if security == "reality" {
|
||||
result.TLS = true
|
||||
result.Sni = sni
|
||||
result.RealityOpts = model.RealityOptions{
|
||||
PublicKey: pbk,
|
||||
ShortID: sid,
|
||||
}
|
||||
result.Fingerprint = fp
|
||||
}
|
||||
|
||||
if network == "ws" {
|
||||
result.Network = "ws"
|
||||
result.WSOpts = model.WSOptions{
|
||||
Path: path,
|
||||
Headers: map[string]string{
|
||||
"Host": host,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// if network == "http" {
|
||||
// // 未查到相关支持文档
|
||||
// }
|
||||
|
||||
// if network == "quic" {
|
||||
// // 未查到相关支持文档
|
||||
// }
|
||||
|
||||
if network == "grpc" {
|
||||
result.GrpcOpts = model.GrpcOptions{
|
||||
GrpcServiceName: serviceName,
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
188
parser/vless.go
188
parser/vless.go
|
@ -1,92 +1,156 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2clash/constant"
|
||||
"sub2clash/model"
|
||||
)
|
||||
|
||||
func ParseVless(proxy string) (model.Proxy, error) {
|
||||
// 判断是否以 vless:// 开头
|
||||
if !strings.HasPrefix(proxy, "vless://") {
|
||||
return model.Proxy{}, fmt.Errorf("invalid vless Url")
|
||||
if !strings.HasPrefix(proxy, constant.VLESSPrefix) {
|
||||
return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy}
|
||||
}
|
||||
// 分割
|
||||
parts := strings.SplitN(strings.TrimPrefix(proxy, "vless://"), "@", 2)
|
||||
if len(parts) != 2 {
|
||||
return model.Proxy{}, fmt.Errorf("invalid vless Url")
|
||||
|
||||
urlParts := strings.SplitN(strings.TrimPrefix(proxy, constant.VLESSPrefix), "@", 2)
|
||||
if len(urlParts) != 2 {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Message: "missing character '@' in url",
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
// 分割
|
||||
serverInfo := strings.SplitN(parts[1], "#", 2)
|
||||
|
||||
serverInfo := strings.SplitN(urlParts[1], "#", 2)
|
||||
serverAndPortAndParams := strings.SplitN(serverInfo[0], "?", 2)
|
||||
if len(serverAndPortAndParams) != 2 {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Message: "missing character '?' in url",
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
|
||||
serverAndPort := strings.SplitN(serverAndPortAndParams[0], ":", 2)
|
||||
if len(serverAndPort) != 2 {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidStruct,
|
||||
Message: "missing server host or port",
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
server, portStr := serverAndPort[0], serverAndPort[1]
|
||||
port, err := ParsePort(portStr)
|
||||
if err != nil {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidPort,
|
||||
Message: err.Error(),
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
|
||||
params, err := url.ParseQuery(serverAndPortAndParams[1])
|
||||
if err != nil {
|
||||
return model.Proxy{}, err
|
||||
}
|
||||
if len(serverAndPort) != 2 {
|
||||
return model.Proxy{}, fmt.Errorf("invalid vless")
|
||||
}
|
||||
// 处理端口
|
||||
port, err := strconv.Atoi(strings.TrimSpace(serverAndPort[1]))
|
||||
if err != nil {
|
||||
return model.Proxy{}, err
|
||||
}
|
||||
// 返回结果
|
||||
result := model.Proxy{
|
||||
Type: "vless",
|
||||
Server: strings.TrimSpace(serverAndPort[0]),
|
||||
Port: port,
|
||||
UUID: strings.TrimSpace(parts[0]),
|
||||
UDP: true,
|
||||
Sni: params.Get("sni"),
|
||||
Network: params.Get("type"),
|
||||
Flow: params.Get("flow"),
|
||||
ClientFingerprint: params.Get("fp"),
|
||||
Servername: params.Get("sni"),
|
||||
}
|
||||
if params.Get("alpn") != "" {
|
||||
result.Alpn = strings.Split(params.Get("alpn"), ",")
|
||||
}
|
||||
if params.Get("security") == "reality" {
|
||||
result.TLS = true
|
||||
result.RealityOpts = model.RealityOptions{
|
||||
PublicKey: params.Get("pbk"),
|
||||
ShortID: params.Get("sid"),
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrCannotParseParams,
|
||||
Raw: proxy,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
if params.Get("type") == "ws" {
|
||||
result.TLS = true
|
||||
result.WSOpts = model.WSOptions{
|
||||
Path: params.Get("path"),
|
||||
Headers: map[string]string{
|
||||
"Host": params.Get("host"),
|
||||
},
|
||||
}
|
||||
}
|
||||
if params.Get("type") == "grpc" {
|
||||
result.TLS = true
|
||||
result.GrpcOpts = model.GrpcOptions{
|
||||
GrpcServiceName: params.Get("serviceName"),
|
||||
}
|
||||
}
|
||||
// 如果有节点名称
|
||||
|
||||
remarks := ""
|
||||
if len(serverInfo) == 2 {
|
||||
if strings.Contains(serverInfo[1], "|") {
|
||||
result.Name = strings.SplitN(serverInfo[1], "|", 2)[1]
|
||||
remarks = strings.SplitN(serverInfo[1], "|", 2)[1]
|
||||
} else {
|
||||
result.Name, err = url.QueryUnescape(serverInfo[1])
|
||||
remarks, err = url.QueryUnescape(serverInfo[1])
|
||||
if err != nil {
|
||||
return model.Proxy{}, err
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrCannotParseParams,
|
||||
Raw: proxy,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.Name, err = url.QueryUnescape(serverAndPort[0])
|
||||
remarks, err = url.QueryUnescape(server)
|
||||
if err != nil {
|
||||
return model.Proxy{}, err
|
||||
}
|
||||
}
|
||||
|
||||
uuid := strings.TrimSpace(urlParts[0])
|
||||
flow, security, alpnStr, sni, insecure, fp, pbk, sid, path, host, serviceName, _type := params.Get("flow"), params.Get("security"), params.Get("alpn"), params.Get("sni"), params.Get("allowInsecure"), params.Get("fp"), params.Get("pbk"), params.Get("sid"), params.Get("path"), params.Get("host"), params.Get("serviceName"), params.Get("type")
|
||||
|
||||
// enableUTLS := fp != ""
|
||||
insecureBool := insecure == "1"
|
||||
var alpn []string
|
||||
if strings.Contains(alpnStr, ",") {
|
||||
alpn = strings.Split(alpnStr, ",")
|
||||
} else {
|
||||
alpn = nil
|
||||
}
|
||||
|
||||
result := model.Proxy{
|
||||
Type: "vless",
|
||||
Server: server,
|
||||
Name: remarks,
|
||||
Port: port,
|
||||
UUID: uuid,
|
||||
Flow: flow,
|
||||
}
|
||||
|
||||
if security == "tls" {
|
||||
result.TLS = true
|
||||
result.Alpn = alpn
|
||||
result.Sni = sni
|
||||
result.AllowInsecure = insecureBool
|
||||
result.Fingerprint = fp
|
||||
}
|
||||
|
||||
if security == "reality" {
|
||||
result.TLS = true
|
||||
result.RealityOpts = model.RealityOptions{
|
||||
PublicKey: pbk,
|
||||
ShortID: sid,
|
||||
}
|
||||
}
|
||||
|
||||
if _type == "ws" {
|
||||
result.Network = "ws"
|
||||
result.WSOpts = model.WSOptions{
|
||||
Path: path,
|
||||
}
|
||||
if host != "" {
|
||||
result.WSOpts.Headers = make(map[string]string)
|
||||
result.WSOpts.Headers["Host"] = host
|
||||
}
|
||||
}
|
||||
|
||||
// if _type == "quic" {
|
||||
// // 未查到相关支持文档
|
||||
// }
|
||||
|
||||
if _type == "grpc" {
|
||||
result.Network = "grpc"
|
||||
result.Servername = serviceName
|
||||
}
|
||||
|
||||
if _type == "http" {
|
||||
hosts, err := url.QueryUnescape(host)
|
||||
if err != nil {
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrCannotParseParams,
|
||||
Raw: proxy,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
result.Network = "http"
|
||||
result.HTTPOpts = model.HTTPOptions{
|
||||
Headers: map[string][]string{"Host": strings.Split(hosts, ",")},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
107
parser/vmess.go
107
parser/vmess.go
|
@ -2,83 +2,96 @@ package parser
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2clash/constant"
|
||||
"sub2clash/model"
|
||||
)
|
||||
|
||||
func ParseVmess(proxy string) (model.Proxy, error) {
|
||||
// 判断是否以 vmess:// 开头
|
||||
if !strings.HasPrefix(proxy, "vmess://") {
|
||||
return model.Proxy{}, errors.New("invalid vmess url")
|
||||
if !strings.HasPrefix(proxy, constant.VMessPrefix) {
|
||||
return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy}
|
||||
}
|
||||
// 解码
|
||||
base64, err := DecodeBase64(strings.TrimPrefix(proxy, "vmess://"))
|
||||
|
||||
proxy = strings.TrimPrefix(proxy, constant.VMessPrefix)
|
||||
base64, err := DecodeBase64(proxy)
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid vmess url" + err.Error())
|
||||
return model.Proxy{}, &ParseError{Type: ErrInvalidBase64, Raw: proxy, Message: err.Error()}
|
||||
}
|
||||
// 解析
|
||||
|
||||
var vmess model.VmessJson
|
||||
err = json.Unmarshal([]byte(base64), &vmess)
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid vmess url" + err.Error())
|
||||
return model.Proxy{}, &ParseError{Type: ErrInvalidStruct, Raw: proxy, Message: err.Error()}
|
||||
}
|
||||
// 解析端口
|
||||
port := 0
|
||||
|
||||
var port int
|
||||
switch vmess.Port.(type) {
|
||||
case string:
|
||||
port, err = strconv.Atoi(vmess.Port.(string))
|
||||
port, err = ParsePort(vmess.Port.(string))
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid vmess url" + err.Error())
|
||||
return model.Proxy{}, &ParseError{
|
||||
Type: ErrInvalidPort,
|
||||
Message: err.Error(),
|
||||
Raw: proxy,
|
||||
}
|
||||
}
|
||||
case float64:
|
||||
port = int(vmess.Port.(float64))
|
||||
}
|
||||
// 解析Aid
|
||||
|
||||
aid := 0
|
||||
switch vmess.Aid.(type) {
|
||||
case string:
|
||||
aid, err = strconv.Atoi(vmess.Aid.(string))
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid vmess url" + err.Error())
|
||||
return model.Proxy{}, &ParseError{Type: ErrInvalidStruct, Raw: proxy, Message: err.Error()}
|
||||
}
|
||||
case float64:
|
||||
aid = int(vmess.Aid.(float64))
|
||||
}
|
||||
// 设置默认值
|
||||
|
||||
if vmess.Scy == "" {
|
||||
vmess.Scy = "auto"
|
||||
}
|
||||
if vmess.Net == "ws" && vmess.Path == "" {
|
||||
vmess.Path = "/"
|
||||
}
|
||||
if vmess.Net == "ws" && vmess.Host == "" {
|
||||
vmess.Host = vmess.Add
|
||||
}
|
||||
|
||||
name, err := url.QueryUnescape(vmess.Ps)
|
||||
if err != nil {
|
||||
name = vmess.Ps
|
||||
}
|
||||
// 返回结果
|
||||
|
||||
result := model.Proxy{
|
||||
Name: name,
|
||||
Type: "vmess",
|
||||
Server: vmess.Add,
|
||||
Port: port,
|
||||
UUID: vmess.Id,
|
||||
AlterID: aid,
|
||||
Cipher: vmess.Scy,
|
||||
UDP: true,
|
||||
TLS: vmess.Tls == "tls",
|
||||
Fingerprint: vmess.Fp,
|
||||
ClientFingerprint: "chrome",
|
||||
SkipCertVerify: true,
|
||||
Servername: vmess.Add,
|
||||
Network: vmess.Net,
|
||||
Name: name,
|
||||
Type: "vmess",
|
||||
Server: vmess.Add,
|
||||
Port: port,
|
||||
UUID: vmess.Id,
|
||||
AlterID: aid,
|
||||
Cipher: vmess.Scy,
|
||||
}
|
||||
|
||||
if vmess.Tls == "tls" {
|
||||
var alpn []string
|
||||
if strings.Contains(vmess.Alpn, ",") {
|
||||
alpn = strings.Split(vmess.Alpn, ",")
|
||||
} else {
|
||||
alpn = nil
|
||||
}
|
||||
result.TLS = true
|
||||
result.Fingerprint = vmess.Fp
|
||||
result.Alpn = alpn
|
||||
result.Servername = vmess.Sni
|
||||
}
|
||||
|
||||
if vmess.Net == "ws" {
|
||||
if vmess.Path == "" {
|
||||
vmess.Path = "/"
|
||||
}
|
||||
if vmess.Host == "" {
|
||||
vmess.Host = vmess.Add
|
||||
}
|
||||
result.Network = "ws"
|
||||
result.WSOpts = model.WSOptions{
|
||||
Path: vmess.Path,
|
||||
Headers: map[string]string{
|
||||
|
@ -86,5 +99,25 @@ func ParseVmess(proxy string) (model.Proxy, error) {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
// if vmess.Net == "quic" {
|
||||
// // 未查到相关支持文档
|
||||
// }
|
||||
|
||||
if vmess.Net == "grpc" {
|
||||
result.GrpcOpts = model.GrpcOptions{
|
||||
GrpcServiceName: vmess.Path,
|
||||
}
|
||||
result.Network = "grpc"
|
||||
}
|
||||
|
||||
if vmess.Net == "h2" {
|
||||
result.HTTP2Opts = model.HTTP2Options{
|
||||
Host: strings.Split(vmess.Host, ","),
|
||||
Path: vmess.Path,
|
||||
}
|
||||
result.Network = "h2"
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
// res, err := parser.ParseTrojan("trojan://Abse64hhjewrs@test.com:8443?type=ws&path=%2Fx&host=test.com&security=tls&fp=&alpn=http%2F1.1&sni=test.com#test")
|
||||
// if err != nil {
|
||||
// t.Log(err.Error())
|
||||
// t.Fail()
|
||||
// }
|
||||
// bytes, err := yaml.Marshal(res)
|
||||
// if err != nil {
|
||||
// t.Log(err.Error())
|
||||
// t.Fail()
|
||||
// }
|
||||
// t.Log(string(bytes))
|
||||
t.Log(strings.SplitN("123456", "/?", 2))
|
||||
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue