mirror of
https://github.com/nitezs/sub2clash.git
synced 2024-12-24 13:24:42 -05:00
commit
c159f2b417
@ -15,6 +15,7 @@
|
|||||||
- Vmess
|
- Vmess
|
||||||
- Vless (Clash.Meta)
|
- Vless (Clash.Meta)
|
||||||
- Trojan
|
- Trojan
|
||||||
|
- Hysteria (Clash.Meta)
|
||||||
- Hysteria2 (Clash.Meta)
|
- Hysteria2 (Clash.Meta)
|
||||||
|
|
||||||
## 使用
|
## 使用
|
||||||
|
@ -173,9 +173,11 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
|||||||
names := make(map[string]int)
|
names := make(map[string]int)
|
||||||
for i := range proxyList {
|
for i := range proxyList {
|
||||||
if _, exist := names[proxyList[i].Name]; exist {
|
if _, exist := names[proxyList[i].Name]; exist {
|
||||||
|
names[proxyList[i].Name] = names[proxyList[i].Name] + 1
|
||||||
proxyList[i].Name = proxyList[i].Name + " " + strconv.Itoa(names[proxyList[i].Name])
|
proxyList[i].Name = proxyList[i].Name + " " + strconv.Itoa(names[proxyList[i].Name])
|
||||||
|
} else {
|
||||||
|
names[proxyList[i].Name] = 0
|
||||||
}
|
}
|
||||||
names[proxyList[i].Name] = names[proxyList[i].Name] + 1
|
|
||||||
}
|
}
|
||||||
// trim
|
// trim
|
||||||
for i := range proxyList {
|
for i := range proxyList {
|
||||||
|
@ -23,6 +23,7 @@ func GetSupportProxyTypes(clashType ClashType) map[string]bool {
|
|||||||
"vmess": true,
|
"vmess": true,
|
||||||
"trojan": true,
|
"trojan": true,
|
||||||
"vless": true,
|
"vless": true,
|
||||||
|
"hysteria": true,
|
||||||
"hysteria2": true,
|
"hysteria2": true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,12 @@ type Proxy struct {
|
|||||||
CustomCA string `yaml:"ca,omitempty"`
|
CustomCA string `yaml:"ca,omitempty"`
|
||||||
CustomCAString string `yaml:"ca-str,omitempty"`
|
CustomCAString string `yaml:"ca-str,omitempty"`
|
||||||
CWND int `yaml:"cwnd,omitempty"`
|
CWND int `yaml:"cwnd,omitempty"`
|
||||||
|
Auth string `yaml:"auth,omitempty"`
|
||||||
|
ReceiveWindowConn int `yaml:"recv-window-conn,omitempty"`
|
||||||
|
ReceiveWindow int `yaml:"recv-window,omitempty"`
|
||||||
|
DisableMTUDiscovery bool `yaml:"disable-mtu-discovery,omitempty"`
|
||||||
|
FastOpen bool `yaml:"fast-open,omitempty"`
|
||||||
|
HopInterval int `yaml:"hop-interval,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Proxy) MarshalYAML() (interface{}, error) {
|
func (p Proxy) MarshalYAML() (interface{}, error) {
|
||||||
@ -64,6 +70,8 @@ func (p Proxy) MarshalYAML() (interface{}, error) {
|
|||||||
return ProxyToVless(p), nil
|
return ProxyToVless(p), nil
|
||||||
case "trojan":
|
case "trojan":
|
||||||
return ProxyToTrojan(p), nil
|
return ProxyToTrojan(p), nil
|
||||||
|
case "hysteria":
|
||||||
|
return ProxyToHysteria(p), nil
|
||||||
case "hysteria2":
|
case "hysteria2":
|
||||||
return ProxyToHysteria2(p), nil
|
return ProxyToHysteria2(p), nil
|
||||||
}
|
}
|
||||||
|
53
model/proxy_hysteria.go
Normal file
53
model/proxy_hysteria.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type Hysteria struct {
|
||||||
|
Type string `yaml:"type"`
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Server string `yaml:"server"`
|
||||||
|
Port int `yaml:"port,omitempty"`
|
||||||
|
Ports string `yaml:"ports,omitempty"`
|
||||||
|
Protocol string `yaml:"protocol,omitempty"`
|
||||||
|
ObfsProtocol string `yaml:"obfs-protocol,omitempty"` // compatible with Stash
|
||||||
|
Up string `yaml:"up"`
|
||||||
|
UpSpeed int `yaml:"up-speed,omitempty"` // compatible with Stash
|
||||||
|
Down string `yaml:"down"`
|
||||||
|
DownSpeed int `yaml:"down-speed,omitempty"` // compatible with Stash
|
||||||
|
Auth string `yaml:"auth,omitempty"`
|
||||||
|
AuthString string `yaml:"auth-str,omitempty"`
|
||||||
|
Obfs string `yaml:"obfs,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"`
|
||||||
|
ReceiveWindowConn int `yaml:"recv-window-conn,omitempty"`
|
||||||
|
ReceiveWindow int `yaml:"recv-window,omitempty"`
|
||||||
|
DisableMTUDiscovery bool `yaml:"disable-mtu-discovery,omitempty"`
|
||||||
|
FastOpen bool `yaml:"fast-open,omitempty"`
|
||||||
|
HopInterval int `yaml:"hop-interval,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProxyToHysteria(p Proxy) Hysteria {
|
||||||
|
return Hysteria{
|
||||||
|
Type: "hysteria",
|
||||||
|
Name: p.Name,
|
||||||
|
Server: p.Server,
|
||||||
|
Port: p.Port,
|
||||||
|
Up: p.Up,
|
||||||
|
Down: p.Down,
|
||||||
|
Auth: p.Auth,
|
||||||
|
Obfs: p.Obfs,
|
||||||
|
SNI: p.Sni,
|
||||||
|
SkipCertVerify: p.SkipCertVerify,
|
||||||
|
Fingerprint: p.Fingerprint,
|
||||||
|
ALPN: p.Alpn,
|
||||||
|
CustomCA: p.CustomCA,
|
||||||
|
CustomCAString: p.CustomCAString,
|
||||||
|
ReceiveWindowConn: p.ReceiveWindowConn,
|
||||||
|
ReceiveWindow: p.ReceiveWindow,
|
||||||
|
DisableMTUDiscovery: p.DisableMTUDiscovery,
|
||||||
|
FastOpen: p.FastOpen,
|
||||||
|
HopInterval: p.HopInterval,
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,18 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
type HTTPOptions struct {
|
type HTTPOptions struct {
|
||||||
Method string `proxy:"method,omitempty"`
|
Method string `yaml:"method,omitempty"`
|
||||||
Path []string `proxy:"path,omitempty"`
|
Path []string `yaml:"path,omitempty"`
|
||||||
Headers map[string][]string `proxy:"headers,omitempty"`
|
Headers map[string][]string `yaml:"headers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTP2Options struct {
|
type HTTP2Options struct {
|
||||||
Host []string `proxy:"host,omitempty"`
|
Host []string `yaml:"host,omitempty"`
|
||||||
Path string `proxy:"path,omitempty"`
|
Path string `yaml:"path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GrpcOptions struct {
|
type GrpcOptions struct {
|
||||||
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
|
GrpcServiceName string `yaml:"grpc-service-name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RealityOptions struct {
|
type RealityOptions struct {
|
||||||
@ -21,10 +21,10 @@ type RealityOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WSOptions struct {
|
type WSOptions struct {
|
||||||
Path string `proxy:"path,omitempty"`
|
Path string `yaml:"path,omitempty"`
|
||||||
Headers map[string]string `proxy:"headers,omitempty"`
|
Headers map[string]string `yaml:"headers,omitempty"`
|
||||||
MaxEarlyData int `proxy:"max-early-data,omitempty"`
|
MaxEarlyData int `yaml:"max-early-data,omitempty"`
|
||||||
EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"`
|
EarlyDataHeaderName string `yaml:"early-data-header-name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VmessJson struct {
|
type VmessJson struct {
|
||||||
|
79
parser/hysteria.go
Normal file
79
parser/hysteria.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"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")
|
||||||
|
}
|
||||||
|
// 分割
|
||||||
|
parts := strings.SplitN(strings.TrimPrefix(proxy, "hysteria://"), "?", 2)
|
||||||
|
serverInfo := strings.SplitN(parts[0], ":", 2)
|
||||||
|
if len(serverInfo) != 2 {
|
||||||
|
return model.Proxy{}, errors.New("invalid hysteria Url")
|
||||||
|
}
|
||||||
|
params, err := url.ParseQuery(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return model.Proxy{}, errors.New("invalid hysteria Url")
|
||||||
|
}
|
||||||
|
host := serverInfo[0]
|
||||||
|
port, err := strconv.Atoi(serverInfo[1])
|
||||||
|
if err != nil {
|
||||||
|
return model.Proxy{}, errors.New("invalid hysteria Url")
|
||||||
|
}
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
// 返回结果
|
||||||
|
result := model.Proxy{
|
||||||
|
Type: "hysteria",
|
||||||
|
Name: remarks,
|
||||||
|
Server: host,
|
||||||
|
Port: port,
|
||||||
|
Up: upmbps,
|
||||||
|
Down: downmbps,
|
||||||
|
Auth: auth,
|
||||||
|
Obfs: obfs,
|
||||||
|
Sni: peer,
|
||||||
|
SkipCertVerify: insecure == "1",
|
||||||
|
Alpn: strings.Split(alpn, ","),
|
||||||
|
ObfsParam: obfsParam,
|
||||||
|
Protocol: protocol,
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
@ -8,16 +8,15 @@ import (
|
|||||||
"sub2clash/model"
|
"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) {
|
func ParseHysteria2(proxy string) (model.Proxy, error) {
|
||||||
// 判断是否以 hysteria2:// 开头
|
// 判断是否以 hysteria2:// 开头
|
||||||
if !strings.HasPrefix(proxy, "hysteria2://") {
|
if !strings.HasPrefix(proxy, "hysteria2://") && !strings.HasPrefix(proxy, "hy2://") {
|
||||||
return model.Proxy{}, errors.New("invalid hysteria2 Url")
|
return model.Proxy{}, errors.New("invalid hysteria2 Url")
|
||||||
}
|
}
|
||||||
// 分割
|
// 分割
|
||||||
parts := strings.SplitN(strings.TrimPrefix(proxy, "hysteria2://"), "@", 2)
|
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)
|
serverInfo := strings.SplitN(parts[1], "/?", 2)
|
||||||
serverAndPort := strings.SplitN(serverInfo[0], ":", 2)
|
serverAndPort := strings.SplitN(serverInfo[0], ":", 2)
|
||||||
|
@ -72,10 +72,16 @@ func ParseVless(proxy string) (model.Proxy, error) {
|
|||||||
if strings.Contains(serverInfo[1], "|") {
|
if strings.Contains(serverInfo[1], "|") {
|
||||||
result.Name = strings.SplitN(serverInfo[1], "|", 2)[1]
|
result.Name = strings.SplitN(serverInfo[1], "|", 2)[1]
|
||||||
} else {
|
} else {
|
||||||
result.Name = serverInfo[1]
|
result.Name, err = url.QueryUnescape(serverInfo[1])
|
||||||
|
if err != nil {
|
||||||
|
return model.Proxy{}, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result.Name = serverAndPort[0]
|
result.Name, err = url.QueryUnescape(serverAndPort[0])
|
||||||
|
if err != nil {
|
||||||
|
return model.Proxy{}, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package parser
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sub2clash/model"
|
"sub2clash/model"
|
||||||
@ -56,9 +57,13 @@ func ParseVmess(proxy string) (model.Proxy, error) {
|
|||||||
if vmess.Net == "ws" && vmess.Host == "" {
|
if vmess.Net == "ws" && vmess.Host == "" {
|
||||||
vmess.Host = vmess.Add
|
vmess.Host = vmess.Add
|
||||||
}
|
}
|
||||||
|
name, err := url.QueryUnescape(vmess.Ps)
|
||||||
|
if err != nil {
|
||||||
|
name = vmess.Ps
|
||||||
|
}
|
||||||
// 返回结果
|
// 返回结果
|
||||||
result := model.Proxy{
|
result := model.Proxy{
|
||||||
Name: vmess.Ps,
|
Name: name,
|
||||||
Type: "vmess",
|
Type: "vmess",
|
||||||
Server: vmess.Add,
|
Server: vmess.Add,
|
||||||
Port: port,
|
Port: port,
|
||||||
|
@ -127,9 +127,12 @@ func ParseProxy(proxies ...string) []model.Proxy {
|
|||||||
if strings.HasPrefix(proxy, "ssr://") {
|
if strings.HasPrefix(proxy, "ssr://") {
|
||||||
proxyItem, err = parser.ParseShadowsocksR(proxy)
|
proxyItem, err = parser.ParseShadowsocksR(proxy)
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(proxy, "hysteria2://") {
|
if strings.HasPrefix(proxy, "hysteria2://") || strings.HasPrefix(proxy, "hy2://") {
|
||||||
proxyItem, err = parser.ParseHysteria2(proxy)
|
proxyItem, err = parser.ParseHysteria2(proxy)
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(proxy, "hysteria://") {
|
||||||
|
proxyItem, err = parser.ParseHysteria(proxy)
|
||||||
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
result = append(result, proxyItem)
|
result = append(result, proxyItem)
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user