mirror of
https://github.com/nitezs/sub2clash.git
synced 2024-12-23 22:04:41 -05:00
commit
3309a0c208
13
.vscode/launch.json
vendored
Normal file
13
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceFolder}/main.go",
|
||||
"output": "${workspaceFolder}/dist/main.exe",
|
||||
"buildFlags": "-ldflags '-X sub2clash/config.Dev=true -X sub2clash/config.Version=dev'"
|
||||
}
|
||||
]
|
||||
}
|
@ -3,19 +3,19 @@
|
||||
获取 Clash/Clash.Meta 配置链接
|
||||
|
||||
| Query 参数 | 类型 | 是否必须 | 默认值 | 说明 |
|
||||
|--------------|--------|-------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|--------------|--------|-------------------|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| sub | string | sub/proxy 至少有一项存在 | - | 订阅链接,可以在链接结尾加上`#名称`,来给订阅中的节点加上统一前缀(可以输入多个,用 `,` 分隔) |
|
||||
| proxy | string | sub/proxy 至少有一项存在 | - | 节点分享链接(可以输入多个,用 `,` 分隔) |
|
||||
| refresh | bool | 否 | `false` | 强制刷新配置(默认缓存 5 分钟) |
|
||||
| template | string | 否 | - | 外部模板链接或内部模板名称 |
|
||||
| ruleProvider | string | 否 | - | 格式 `[Behavior,Url,Group,Prepend,Name],[Behavior,Url,Group,Prepend,Name]...`,其中 `Group` 是该规则集使用的策略组名,`Prepend` 为 bool 类型,如果为 `true` 规则将被添加到规则列表顶部,否则添加到规则列表底部(会调整到MATCH规则之前) |
|
||||
| rule | string | 否 | - | 格式 `[Rule,Prepend],[Rule,Prepend]...`,其中 `Prepend` 为 bool 类型,如果为 `true` 规则将被添加到规则列表顶部,否则添加到规则列表底部(会调整到MATCH规则之前) |
|
||||
| ruleProvider | string | 否 | - | 格式 `[Behavior,Url,Group,Prepend,Name],[Behavior,Url,Group,Prepend,Name]...`,其中 `Group` 是该规则集使用的策略组名,`Prepend` 为 bool 类型,如果为 `true` 规则将被添加到规则列表顶部,否则添加到规则列表底部(会调整到 MATCH 规则之前) |
|
||||
| rule | string | 否 | - | 格式 `[Rule,Prepend],[Rule,Prepend]...`,其中 `Prepend` 为 bool 类型,如果为 `true` 规则将被添加到规则列表顶部,否则添加到规则列表底部(会调整到 MATCH 规则之前) |
|
||||
| autoTest | bool | 否 | `false` | 国家策略组是否自动测速 |
|
||||
| lazy | bool | 否 | `false` | 自动测速是否启用 lazy |
|
||||
| sort | string | 否 | `nameasc` | 国家策略组排序策略,可选值 `nameasc`、`namedesc`、`sizeasc`、`sizedesc` |
|
||||
| replace | string | 否 | - | 通过正则表达式重命名节点,格式 `[<ReplaceKey>,<ReplaceTo>],[<ReplaceKey>,<ReplaceTo>]...` |
|
||||
| remove | string | 否 | - | 通过正则表达式删除节点 |
|
||||
| nodeList | bool | 否 | `false` | 只输出节点 | |
|
||||
| nodeList | bool | 否 | `false` | 只输出节点 |
|
||||
|
||||
# `/short`
|
||||
|
||||
|
13
README.md
13
README.md
@ -1,18 +1,21 @@
|
||||
# sub2clash
|
||||
|
||||
将订阅链接转换为 Clash、Clash.Meta 配置
|
||||
[预览](https://www.nite07.com/sub)
|
||||
|
||||
## 特性
|
||||
|
||||
- 开箱即用的规则、策略组配置
|
||||
- 自动根据节点名称按国家划分策略组
|
||||
- 支持多订阅合并
|
||||
- 支持添加自定义 Rule Provider、Rule
|
||||
- 支持多种协议
|
||||
- Shadowsocks
|
||||
- ShadowsocksR
|
||||
- Vmess
|
||||
- Vless
|
||||
- Vless (Clash.Meta)
|
||||
- Trojan
|
||||
- Hysteria2 (Clash.Meta)
|
||||
|
||||
## 使用
|
||||
|
||||
@ -26,10 +29,10 @@
|
||||
可以通过编辑 .env 文件来修改默认配置,docker 直接添加环境变量
|
||||
|
||||
| 变量名 | 说明 | 默认值 |
|
||||
|-----------------------|-----------------------------------------------------------|-----------------------|
|
||||
|-----------------------|----------------------------------------|-----------------------|
|
||||
| PORT | 端口 | `8011` |
|
||||
| META_TEMPLATE | meta 模板文件名 | `template_meta.yaml` |
|
||||
| CLASH_TEMPLATE | clash 模板文件名 | `template_clash.yaml` |
|
||||
| META_TEMPLATE | 默认 meta 模板文件名 | `template_meta.yaml` |
|
||||
| CLASH_TEMPLATE | 默认 clash 模板文件名 | `template_clash.yaml` |
|
||||
| REQUEST_RETRY_TIMES | Get 请求重试次数 | `3` |
|
||||
| REQUEST_MAX_FILE_SIZE | Get 请求订阅文件最大大小(byte) | `1048576` |
|
||||
| CACHE_EXPIRE | 订阅缓存时间(秒) | `300` |
|
||||
@ -38,7 +41,7 @@
|
||||
|
||||
### API
|
||||
|
||||
[API文档](./API_README.md)
|
||||
[API 文档](./API_README.md)
|
||||
|
||||
### 模板
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"gopkg.in/yaml.v3"
|
||||
"net/http"
|
||||
"sub2clash/config"
|
||||
"sub2clash/model"
|
||||
"sub2clash/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func SubmodHandler(c *gin.Context) {
|
||||
|
@ -4,8 +4,6 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"go.uber.org/zap"
|
||||
"gopkg.in/yaml.v3"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sort"
|
||||
@ -16,6 +14,9 @@ import (
|
||||
"sub2clash/parser"
|
||||
"sub2clash/utils"
|
||||
"sub2clash/validator"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func BuildSub(clashType model.ClashType, query validator.SubValidator, template string) (
|
||||
@ -30,9 +31,8 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
||||
if query.Template != "" {
|
||||
template = query.Template
|
||||
}
|
||||
_, err = url.ParseRequestURI(template)
|
||||
if err != nil {
|
||||
templateBytes, err = utils.LoadTemplate(template)
|
||||
if strings.HasPrefix(template, "http") {
|
||||
templateBytes, err = utils.LoadSubscription(template, query.Refresh)
|
||||
if err != nil {
|
||||
logger.Logger.Debug(
|
||||
"load template failed", zap.String("template", template), zap.Error(err),
|
||||
@ -40,7 +40,11 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
||||
return nil, errors.New("加载模板失败: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
templateBytes, err = utils.LoadSubscription(template, query.Refresh)
|
||||
unescape, err := url.QueryUnescape(template)
|
||||
if err != nil {
|
||||
return nil, errors.New("加载模板失败: " + err.Error())
|
||||
}
|
||||
templateBytes, err = utils.LoadTemplate(unescape)
|
||||
if err != nil {
|
||||
logger.Logger.Debug(
|
||||
"load template failed", zap.String("template", template), zap.Error(err),
|
||||
@ -72,7 +76,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
||||
err = yaml.Unmarshal(data, &sub)
|
||||
newProxies := make([]model.Proxy, 0)
|
||||
if err != nil {
|
||||
reg, _ := regexp.Compile("(ssr|ss|vmess|trojan|vless)://")
|
||||
reg, _ := regexp.Compile("(ssr|ss|vmess|trojan|vless|hysteria)://")
|
||||
if reg.Match(data) {
|
||||
p := utils.ParseProxy(strings.Split(string(data), "\n")...)
|
||||
newProxies = p
|
||||
|
@ -2,12 +2,13 @@ package controller
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gopkg.in/yaml.v3"
|
||||
"net/http"
|
||||
"sub2clash/config"
|
||||
"sub2clash/model"
|
||||
"sub2clash/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func SubHandler(c *gin.Context) {
|
||||
|
@ -2,9 +2,6 @@ package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@ -16,6 +13,10 @@ import (
|
||||
"sub2clash/utils/database"
|
||||
"sub2clash/validator"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func ShortLinkGenHandler(c *gin.Context) {
|
||||
|
@ -2,13 +2,14 @@ package api
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"github.com/gin-gonic/gin"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"sub2clash/api/controller"
|
||||
"sub2clash/config"
|
||||
"sub2clash/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
//go:embed static
|
||||
|
@ -1,8 +1,8 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>sub2clash</title>
|
||||
<!-- Bootstrap CSS -->
|
||||
<link
|
||||
@ -34,9 +34,9 @@
|
||||
width: 25px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<div class="container mt-5">
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<div class="container mt-5">
|
||||
<div class="mb-4">
|
||||
<h2>sub2clash</h2>
|
||||
<span class="text-muted fst-italic"
|
||||
@ -46,6 +46,8 @@
|
||||
target="_blank"
|
||||
>使用文档</a
|
||||
></span
|
||||
><br><span class="text-muted fst-italic"
|
||||
>注意:本程序非纯前端程序,输入的订阅将被后端缓存,请确保您信任当前站点</span
|
||||
>
|
||||
</div>
|
||||
|
||||
@ -117,7 +119,9 @@
|
||||
name="refresh"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label class="form-check-label" for="refresh">强制重新获取订阅</label>
|
||||
<label class="form-check-label" for="refresh"
|
||||
>强制重新获取订阅</label
|
||||
>
|
||||
</div>
|
||||
<!-- Node List -->
|
||||
<div class="form-check mb-3">
|
||||
@ -127,7 +131,9 @@
|
||||
name="nodeList"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label class="form-check-label" for="nodeList">输出为 Node List</label>
|
||||
<label class="form-check-label" for="nodeList"
|
||||
>输出为 Node List</label
|
||||
>
|
||||
</div>
|
||||
<!-- Auto Test -->
|
||||
<div class="form-check mb-3">
|
||||
@ -143,7 +149,12 @@
|
||||
</div>
|
||||
<!-- Lazy -->
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" id="lazy" name="lazy" type="checkbox" />
|
||||
<input
|
||||
class="form-check-input"
|
||||
id="lazy"
|
||||
name="lazy"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label class="form-check-label" for="lazy"
|
||||
>自动测速启用 lazy 模式</label
|
||||
>
|
||||
@ -207,8 +218,17 @@
|
||||
<div class="form-group mb-5">
|
||||
<label for="apiLink">配置链接:</label>
|
||||
<div class="input-group mb-2">
|
||||
<input class="form-control" id="apiLink" readonly type="text" />
|
||||
<button class="btn btn-primary" onclick="generateURL()" type="button">
|
||||
<input
|
||||
class="form-control"
|
||||
id="apiLink"
|
||||
readonly
|
||||
type="text"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick="generateURL()"
|
||||
type="button"
|
||||
>
|
||||
生成链接
|
||||
</button>
|
||||
<button
|
||||
@ -220,7 +240,12 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input class="form-control" id="apiShortLink" readonly type="text" />
|
||||
<input
|
||||
class="form-control"
|
||||
id="apiShortLink"
|
||||
readonly
|
||||
type="text"
|
||||
/>
|
||||
<input
|
||||
class="form-control"
|
||||
id="password"
|
||||
@ -247,12 +272,14 @@
|
||||
<footer>
|
||||
<p class="text-center">
|
||||
Powered by
|
||||
<a class="link-primary" href="https://github.com/nitezs/sub2clash"
|
||||
<a
|
||||
class="link-primary"
|
||||
href="https://github.com/nitezs/sub2clash"
|
||||
>sub2clash</a
|
||||
>
|
||||
</p>
|
||||
<p class="text-center">Version {{.Version}}</p>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -114,7 +114,11 @@ function generateURI() {
|
||||
let prepend = rules[i * 3 + 1].value;
|
||||
let group = rules[i * 3 + 2].value;
|
||||
// 是否存在空值
|
||||
if (rule.trim() === "" || prepend.trim() === "" || group.trim() === "") {
|
||||
if (
|
||||
rule.trim() === "" ||
|
||||
prepend.trim() === "" ||
|
||||
group.trim() === ""
|
||||
) {
|
||||
alert("Rule 中存在空值,请检查后重试!");
|
||||
return "";
|
||||
}
|
||||
@ -189,14 +193,16 @@ function parseInputURL() {
|
||||
}
|
||||
|
||||
if (params.has("sub")) {
|
||||
document.getElementById("sub").value = decodeURIComponent(params.get("sub"))
|
||||
document.getElementById("sub").value = decodeURIComponent(
|
||||
params.get("sub")
|
||||
)
|
||||
.split(",")
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
if (params.has("proxy")) {
|
||||
document.getElementById("proxy").value = decodeURIComponent(
|
||||
params.get("proxy"),
|
||||
params.get("proxy")
|
||||
)
|
||||
.split(",")
|
||||
.join("\n");
|
||||
@ -218,7 +224,7 @@ function parseInputURL() {
|
||||
|
||||
if (params.has("template")) {
|
||||
document.getElementById("template").value = decodeURIComponent(
|
||||
params.get("template"),
|
||||
params.get("template")
|
||||
);
|
||||
}
|
||||
|
||||
@ -228,7 +234,7 @@ function parseInputURL() {
|
||||
|
||||
if (params.has("remove")) {
|
||||
document.getElementById("remove").value = decodeURIComponent(
|
||||
params.get("remove"),
|
||||
params.get("remove")
|
||||
);
|
||||
}
|
||||
|
||||
@ -238,7 +244,7 @@ function parseInputURL() {
|
||||
|
||||
if (params.has("ruleProvider")) {
|
||||
parseAndFillRuleProviderParams(
|
||||
decodeURIComponent(params.get("ruleProvider")),
|
||||
decodeURIComponent(params.get("ruleProvider"))
|
||||
);
|
||||
}
|
||||
|
||||
@ -403,7 +409,7 @@ function generateShortLink() {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
apiShortLink.value = `${window.location.origin}${window.location.pathname}s/${response.data}`;
|
||||
|
@ -2,9 +2,10 @@ package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/joho/godotenv"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: '3'
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
sub2clash:
|
||||
|
@ -1,11 +1,12 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
var (
|
||||
|
5
main.go
5
main.go
@ -2,8 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"strconv"
|
||||
"sub2clash/api"
|
||||
@ -11,6 +9,9 @@ import (
|
||||
"sub2clash/logger"
|
||||
"sub2clash/utils"
|
||||
"sub2clash/utils/database"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
//go:embed templates/template_meta.yaml
|
||||
|
@ -1,11 +1,12 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"strconv"
|
||||
"sub2clash/logger"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func ZapLogger() gin.HandlerFunc {
|
||||
|
@ -23,6 +23,7 @@ func GetSupportProxyTypes(clashType ClashType) map[string]bool {
|
||||
"vmess": true,
|
||||
"trojan": true,
|
||||
"vless": true,
|
||||
"hysteria2": true,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -45,6 +45,11 @@ type Proxy struct {
|
||||
UDPOverTCP bool `yaml:"udp-over-tcp,omitempty"`
|
||||
UDPOverTCPVersion int `yaml:"udp-over-tcp-version,omitempty"`
|
||||
SubName string `yaml:"-"`
|
||||
Up string `yaml:"up,omitempty"`
|
||||
Down string `yaml:"down,omitempty"`
|
||||
CustomCA string `yaml:"ca,omitempty"`
|
||||
CustomCAString string `yaml:"ca-str,omitempty"`
|
||||
CWND int `yaml:"cwnd,omitempty"`
|
||||
}
|
||||
|
||||
func (p Proxy) MarshalYAML() (interface{}, error) {
|
||||
@ -59,6 +64,8 @@ func (p Proxy) MarshalYAML() (interface{}, error) {
|
||||
return ProxyToVless(p), nil
|
||||
case "trojan":
|
||||
return ProxyToTrojan(p), nil
|
||||
case "hysteria2":
|
||||
return ProxyToHysteria2(p), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
41
model/proxy_hysteria2.go
Normal file
41
model/proxy_hysteria2.go
Normal file
@ -0,0 +1,41 @@
|
||||
package model
|
||||
|
||||
type Hysteria2 struct {
|
||||
Type string `yaml:"type"`
|
||||
Name string `yaml:"name"`
|
||||
Server string `yaml:"server"`
|
||||
Port int `yaml:"port"`
|
||||
Up string `yaml:"up,omitempty"`
|
||||
Down string `yaml:"down,omitempty"`
|
||||
Password string `yaml:"password,omitempty"`
|
||||
Obfs string `yaml:"obfs,omitempty"`
|
||||
ObfsPassword string `yaml:"obfs-password,omitempty"`
|
||||
SNI string `yaml:"sni,omitempty"`
|
||||
SkipCertVerify bool `yaml:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `yaml:"fingerprint,omitempty"`
|
||||
ALPN []string `yaml:"alpn,omitempty"`
|
||||
CustomCA string `yaml:"ca,omitempty"`
|
||||
CustomCAString string `yaml:"ca-str,omitempty"`
|
||||
CWND int `yaml:"cwnd,omitempty"`
|
||||
}
|
||||
|
||||
func ProxyToHysteria2(p Proxy) Hysteria2 {
|
||||
return Hysteria2{
|
||||
Type: "hysteria2",
|
||||
Name: p.Name,
|
||||
Server: p.Server,
|
||||
Port: p.Port,
|
||||
Up: p.Up,
|
||||
Down: p.Down,
|
||||
Password: p.Password,
|
||||
Obfs: p.Obfs,
|
||||
ObfsPassword: p.ObfsParam,
|
||||
SNI: p.Sni,
|
||||
SkipCertVerify: p.SkipCertVerify,
|
||||
Fingerprint: p.Fingerprint,
|
||||
ALPN: p.Alpn,
|
||||
CustomCA: p.CustomCA,
|
||||
CustomCAString: p.CustomCAString,
|
||||
CWND: p.CWND,
|
||||
}
|
||||
}
|
@ -16,8 +16,8 @@ type GrpcOptions struct {
|
||||
}
|
||||
|
||||
type RealityOptions struct {
|
||||
PublicKey string `proxy:"public-key"`
|
||||
ShortID string `proxy:"short-id"`
|
||||
PublicKey string `yaml:"public-key"`
|
||||
ShortID string `yaml:"short-id,omitempty"`
|
||||
}
|
||||
|
||||
type WSOptions struct {
|
||||
|
51
parser/hysteria2.go
Normal file
51
parser/hysteria2.go
Normal file
@ -0,0 +1,51 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2clash/model"
|
||||
)
|
||||
|
||||
func ParseHysteria2(proxy string) (model.Proxy, error) {
|
||||
// 判断是否以 hysteria2:// 开头
|
||||
if !strings.HasPrefix(proxy, "hysteria2://") {
|
||||
return model.Proxy{}, errors.New("invalid hysteria2 Url")
|
||||
}
|
||||
// 分割
|
||||
parts := strings.SplitN(strings.TrimPrefix(proxy, "hysteria2://"), "@", 2)
|
||||
if len(parts) != 2 {
|
||||
return model.Proxy{}, errors.New("invalid hysteria2 Url")
|
||||
}
|
||||
// 分割
|
||||
serverInfo := strings.SplitN(parts[1], "/?", 2)
|
||||
serverAndPort := strings.SplitN(serverInfo[0], ":", 2)
|
||||
if len(serverAndPort) == 1 {
|
||||
serverAndPort = append(serverAndPort, "443")
|
||||
} else if len(serverAndPort) != 2 {
|
||||
return model.Proxy{}, errors.New("invalid hysteria2 Url")
|
||||
}
|
||||
params, err := url.ParseQuery(serverInfo[1])
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid hysteria2 Url")
|
||||
}
|
||||
// 获取端口
|
||||
port, err := strconv.Atoi(serverAndPort[1])
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid hysteria2 Url")
|
||||
}
|
||||
// 返回结果
|
||||
result := model.Proxy{
|
||||
Type: "hysteria2",
|
||||
Name: params.Get("name"),
|
||||
Server: serverAndPort[0],
|
||||
Port: port,
|
||||
Password: parts[0],
|
||||
Obfs: params.Get("obfs"),
|
||||
ObfsParam: params.Get("obfs-password"),
|
||||
Sni: params.Get("sni"),
|
||||
SkipCertVerify: params.Get("insecure") == "1",
|
||||
}
|
||||
return result, nil
|
||||
}
|
@ -43,9 +43,9 @@ func ParseVless(proxy string) (model.Proxy, error) {
|
||||
UDP: true,
|
||||
Sni: params.Get("sni"),
|
||||
Network: params.Get("type"),
|
||||
TLS: params.Get("security") == "tls",
|
||||
TLS: params.Get("security") == "reality",
|
||||
Flow: params.Get("flow"),
|
||||
Fingerprint: params.Get("fp"),
|
||||
ClientFingerprint: params.Get("fp"),
|
||||
Servername: params.Get("sni"),
|
||||
RealityOpts: model.RealityOptions{
|
||||
PublicKey: params.Get("pbk"),
|
||||
|
@ -1,13 +1,14 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"github.com/glebarez/sqlite"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
"path/filepath"
|
||||
"sub2clash/logger"
|
||||
"sub2clash/model"
|
||||
"sub2clash/utils"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var DB *gorm.DB
|
||||
|
@ -1,11 +1,12 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
"sub2clash/logger"
|
||||
"sub2clash/model"
|
||||
"sub2clash/parser"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func GetContryName(countryKey string) string {
|
||||
@ -126,6 +127,9 @@ func ParseProxy(proxies ...string) []model.Proxy {
|
||||
if strings.HasPrefix(proxy, "ssr://") {
|
||||
proxyItem, err = parser.ParseShadowsocksR(proxy)
|
||||
}
|
||||
if strings.HasPrefix(proxy, "hysteria2://") {
|
||||
proxyItem, err = parser.ParseHysteria2(proxy)
|
||||
}
|
||||
if err == nil {
|
||||
result = append(result, proxyItem)
|
||||
} else {
|
||||
|
@ -4,11 +4,11 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SubValidator struct {
|
||||
@ -72,14 +72,14 @@ func ParseQuery(c *gin.Context) (SubValidator, error) {
|
||||
query.Proxies = nil
|
||||
}
|
||||
if query.Template != "" {
|
||||
if strings.HasPrefix(query.Template, "http") {
|
||||
uri, err := url.ParseRequestURI(query.Template)
|
||||
if err != nil {
|
||||
if strings.Contains(query.Template, string(os.PathSeparator)) {
|
||||
return SubValidator{}, err
|
||||
}
|
||||
}
|
||||
query.Template = uri.String()
|
||||
}
|
||||
}
|
||||
if query.RuleProvider != "" {
|
||||
reg := regexp.MustCompile(`\[(.*?)\]`)
|
||||
ruleProviders := reg.FindAllStringSubmatch(query.RuleProvider, -1)
|
||||
|
Loading…
Reference in New Issue
Block a user