mirror of
https://github.com/nitezs/sub2sing-box.git
synced 2024-12-23 14:04:41 -05:00
init
This commit is contained in:
commit
c75126e368
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.vscode
|
||||
dist
|
87
cmd/url.go
Normal file
87
cmd/url.go
Normal file
@ -0,0 +1,87 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sub2sing-box/model"
|
||||
"sub2sing-box/parser"
|
||||
. "sub2sing-box/util"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func Url(cmd *cobra.Command, args []string) {
|
||||
proxyList := make([]model.Proxy, 0)
|
||||
if cmd.Flag("url").Changed {
|
||||
urls, _ := cmd.Flags().GetStringSlice("url")
|
||||
for _, url := range urls {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
proxy, err := DecodeBase64(string(data))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
proxies := strings.Split(proxy, "\n")
|
||||
for _, p := range proxies {
|
||||
if strings.HasPrefix(p, "ss://") {
|
||||
proxy, err := parser.ParseShadowsocks(p)
|
||||
if err != nil {
|
||||
fmt.Println(proxy)
|
||||
}
|
||||
proxyList = append(proxyList, proxy)
|
||||
} else if strings.HasPrefix(p, "vmess://") {
|
||||
proxy, err := parser.ParseVmess(p)
|
||||
if err != nil {
|
||||
fmt.Println(proxy)
|
||||
}
|
||||
proxyList = append(proxyList, proxy)
|
||||
} else if strings.HasPrefix(p, "trojan://") {
|
||||
proxy, err := parser.ParseTrojan(p)
|
||||
if err != nil {
|
||||
fmt.Println(proxy)
|
||||
}
|
||||
proxyList = append(proxyList, proxy)
|
||||
} else if strings.HasPrefix(p, "vless://") {
|
||||
proxy, err := parser.ParseVless(p)
|
||||
if err != nil {
|
||||
fmt.Println(proxy)
|
||||
}
|
||||
proxyList = append(proxyList, proxy)
|
||||
} else if strings.HasPrefix(p, "hysteria://") {
|
||||
proxy, err := parser.ParseHysteria(p)
|
||||
if err != nil {
|
||||
fmt.Println(proxy)
|
||||
}
|
||||
proxyList = append(proxyList, proxy)
|
||||
} else if strings.HasPrefix(p, "hy2://") || strings.HasPrefix(p, "hysteria2://") {
|
||||
proxy, err := parser.ParseHysteria2(p)
|
||||
if err != nil {
|
||||
fmt.Println(proxy)
|
||||
}
|
||||
proxyList = append(proxyList, proxy)
|
||||
}
|
||||
}
|
||||
}
|
||||
result, err := json.Marshal(proxyList)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
} else {
|
||||
fmt.Println(string(result))
|
||||
}
|
||||
} else {
|
||||
fmt.Println("No URLs provided")
|
||||
}
|
||||
}
|
10
go.mod
Normal file
10
go.mod
Normal file
@ -0,0 +1,10 @@
|
||||
module sub2sing-box
|
||||
|
||||
go 1.21.5
|
||||
|
||||
require github.com/spf13/cobra v1.8.0
|
||||
|
||||
require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
)
|
10
go.sum
Normal file
10
go.sum
Normal file
@ -0,0 +1,10 @@
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
23
main.go
Normal file
23
main.go
Normal file
@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sub2sing-box/cmd"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "process",
|
||||
Run: cmd.Url,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.Flags().StringSliceP("url", "u", []string{}, "URLs to process")
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
20
model/hysteria.go
Normal file
20
model/hysteria.go
Normal file
@ -0,0 +1,20 @@
|
||||
package model
|
||||
|
||||
type Hysteria struct {
|
||||
Type string `json:"type"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Server string `json:"server"`
|
||||
ServerPort uint16 `json:"server_port"`
|
||||
Up string `json:"up,omitempty"`
|
||||
UpMbps int `json:"up_mbps,omitempty"`
|
||||
Down string `json:"down,omitempty"`
|
||||
DownMbps int `json:"down_mbps,omitempty"`
|
||||
Obfs string `json:"obfs,omitempty"`
|
||||
Auth []byte `json:"auth,omitempty"`
|
||||
AuthString string `json:"auth_str,omitempty"`
|
||||
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
|
||||
ReceiveWindow uint64 `json:"recv_window,omitempty"`
|
||||
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
|
||||
Network string `json:"network,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
}
|
46
model/hysteria2.go
Normal file
46
model/hysteria2.go
Normal file
@ -0,0 +1,46 @@
|
||||
package model
|
||||
|
||||
type Hysteria2Obfs struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
type Hysteria2 struct {
|
||||
Type string `json:"type"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Server string `json:"server"`
|
||||
ServerPort uint16 `json:"server_port"`
|
||||
UpMbps int `json:"up_mbps,omitempty"`
|
||||
DownMbps int `json:"down_mbps,omitempty"`
|
||||
Obfs *Hysteria2Obfs `json:"obfs,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Network string `json:"network,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
BrutalDebug bool `json:"brutal_debug,omitempty"`
|
||||
}
|
||||
|
||||
// func (h *Hysteria2OutboundOptions) MarshalJSON() ([]byte, error) {
|
||||
// val := reflect.ValueOf(h)
|
||||
// out := make(map[string]interface{})
|
||||
// typ := val.Type()
|
||||
// for i := 0; i < val.NumField(); i++ {
|
||||
// field := val.Field(i)
|
||||
// fieldType := typ.Field(i)
|
||||
// if field.Kind() == reflect.Struct {
|
||||
// for j := 0; j < field.NumField(); j++ {
|
||||
// subField := field.Field(j)
|
||||
// subFieldType := fieldType.Type.Field(j)
|
||||
// jsonTag := subFieldType.Tag.Get("json")
|
||||
// if jsonTag != "" && jsonTag != "-" {
|
||||
// out[jsonTag] = subField.Interface()
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// jsonTag := fieldType.Tag.Get("json")
|
||||
// if jsonTag != "" && jsonTag != "-" {
|
||||
// out[jsonTag] = field.Interface()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return json.Marshal(out)
|
||||
// }
|
17
model/multiplex.go
Normal file
17
model/multiplex.go
Normal file
@ -0,0 +1,17 @@
|
||||
package model
|
||||
|
||||
type OutboundMultiplexOptions struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
MaxConnections int `json:"max_connections,omitempty"`
|
||||
MinStreams int `json:"min_streams,omitempty"`
|
||||
MaxStreams int `json:"max_streams,omitempty"`
|
||||
Padding bool `json:"padding,omitempty"`
|
||||
Brutal *BrutalOptions `json:"brutal,omitempty"`
|
||||
}
|
||||
|
||||
type BrutalOptions struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
UpMbps int `json:"up_mbps,omitempty"`
|
||||
DownMbps int `json:"down_mbps,omitempty"`
|
||||
}
|
79
model/proxy.go
Normal file
79
model/proxy.go
Normal file
@ -0,0 +1,79 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type Proxy struct {
|
||||
Type string `json:"type"`
|
||||
Shadowsocks `json:"-"`
|
||||
VMess `json:"-"`
|
||||
VLESS `json:"-"`
|
||||
Trojan `json:"-"`
|
||||
TUIC `json:"-"`
|
||||
Hysteria `json:"-"`
|
||||
Hysteria2 `json:"-"`
|
||||
}
|
||||
|
||||
func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
switch p.Type {
|
||||
case "shadowsocks":
|
||||
return json.Marshal(&struct {
|
||||
Type string `json:"type"`
|
||||
Shadowsocks
|
||||
}{
|
||||
Type: p.Type,
|
||||
Shadowsocks: p.Shadowsocks,
|
||||
})
|
||||
case "vmess":
|
||||
return json.Marshal(&struct {
|
||||
Type string `json:"type"`
|
||||
VMess
|
||||
}{
|
||||
Type: p.Type,
|
||||
VMess: p.VMess,
|
||||
})
|
||||
case "vless":
|
||||
return json.Marshal(&struct {
|
||||
Type string `json:"type"`
|
||||
VLESS
|
||||
}{
|
||||
Type: p.Type,
|
||||
VLESS: p.VLESS,
|
||||
})
|
||||
case "trojan":
|
||||
return json.Marshal(&struct {
|
||||
Type string `json:"type"`
|
||||
Trojan
|
||||
}{
|
||||
Type: p.Type,
|
||||
Trojan: p.Trojan,
|
||||
})
|
||||
case "tuic":
|
||||
return json.Marshal(&struct {
|
||||
Type string `json:"type"`
|
||||
TUIC
|
||||
}{
|
||||
Type: p.Type,
|
||||
TUIC: p.TUIC,
|
||||
})
|
||||
case "hysteria":
|
||||
return json.Marshal(&struct {
|
||||
Type string `json:"type"`
|
||||
Hysteria
|
||||
}{
|
||||
Type: p.Type,
|
||||
Hysteria: p.Hysteria,
|
||||
})
|
||||
case "hysteria2":
|
||||
return json.Marshal(&struct {
|
||||
Type string `json:"type"`
|
||||
Hysteria2
|
||||
}{
|
||||
Type: p.Type,
|
||||
Hysteria2: p.Hysteria2,
|
||||
})
|
||||
default:
|
||||
return json.Marshal(p)
|
||||
}
|
||||
}
|
16
model/shadowsocks.go
Normal file
16
model/shadowsocks.go
Normal file
@ -0,0 +1,16 @@
|
||||
package model
|
||||
|
||||
type NetworkList string
|
||||
|
||||
type Shadowsocks struct {
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Server string `json:"server"`
|
||||
ServerPort uint16 `json:"server_port"`
|
||||
Method string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
Plugin string `json:"plugin,omitempty"`
|
||||
PluginOptions string `json:"plugin_opts,omitempty"`
|
||||
Network string `json:"network,omitempty"`
|
||||
UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"`
|
||||
Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"`
|
||||
}
|
36
model/tls.go
Normal file
36
model/tls.go
Normal file
@ -0,0 +1,36 @@
|
||||
package model
|
||||
|
||||
type OutboundTLSOptions struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
DisableSNI bool `json:"disable_sni,omitempty"`
|
||||
ServerName string `json:"server_name,omitempty"`
|
||||
Insecure bool `json:"insecure,omitempty"`
|
||||
ALPN []string `json:"alpn,omitempty"`
|
||||
MinVersion string `json:"min_version,omitempty"`
|
||||
MaxVersion string `json:"max_version,omitempty"`
|
||||
CipherSuites []string `json:"cipher_suites,omitempty"`
|
||||
Certificate []string `json:"certificate,omitempty"`
|
||||
CertificatePath string `json:"certificate_path,omitempty"`
|
||||
ECH *OutboundECHOptions `json:"ech,omitempty"`
|
||||
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
|
||||
Reality *OutboundRealityOptions `json:"reality,omitempty"`
|
||||
}
|
||||
|
||||
type OutboundECHOptions struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"`
|
||||
DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"`
|
||||
Config []string `json:"config,omitempty"`
|
||||
ConfigPath string `json:"config_path,omitempty"`
|
||||
}
|
||||
|
||||
type OutboundUTLSOptions struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
Fingerprint string `json:"fingerprint,omitempty"`
|
||||
}
|
||||
|
||||
type OutboundRealityOptions struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
PublicKey string `json:"public_key,omitempty"`
|
||||
ShortID string `json:"short_id,omitempty"`
|
||||
}
|
13
model/trojan.go
Normal file
13
model/trojan.go
Normal file
@ -0,0 +1,13 @@
|
||||
package model
|
||||
|
||||
type Trojan struct {
|
||||
Type string `json:"type"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Server string `json:"server"`
|
||||
ServerPort uint16 `json:"server_port"`
|
||||
Password string `json:"password"`
|
||||
Network string `json:"network,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
}
|
17
model/tuic.go
Normal file
17
model/tuic.go
Normal file
@ -0,0 +1,17 @@
|
||||
package model
|
||||
|
||||
type TUIC struct {
|
||||
Type string `json:"type"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Server string `json:"server"`
|
||||
ServerPort uint16 `json:"server_port"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
CongestionControl string `json:"congestion_control,omitempty"`
|
||||
UDPRelayMode string `json:"udp_relay_mode,omitempty"`
|
||||
UDPOverStream bool `json:"udp_over_stream,omitempty"`
|
||||
ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"`
|
||||
Heartbeat Duration `json:"heartbeat,omitempty"`
|
||||
Network string `json:"network,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
}
|
5
model/types.go
Normal file
5
model/types.go
Normal file
@ -0,0 +1,5 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Duration time.Duration
|
6
model/udp_over_tcp.go
Normal file
6
model/udp_over_tcp.go
Normal file
@ -0,0 +1,6 @@
|
||||
package model
|
||||
|
||||
type UDPOverTCPOptions struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
Version uint8 `json:"version,omitempty"`
|
||||
}
|
85
model/v2ray_transport.go
Normal file
85
model/v2ray_transport.go
Normal file
@ -0,0 +1,85 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type V2RayTransportOptions struct {
|
||||
Type string `json:"type"`
|
||||
HTTPOptions V2RayHTTPOptions `json:"-"`
|
||||
WebsocketOptions V2RayWebsocketOptions `json:"-"`
|
||||
QUICOptions V2RayQUICOptions `json:"-"`
|
||||
GRPCOptions V2RayGRPCOptions `json:"-"`
|
||||
HTTPUpgradeOptions V2RayHTTPUpgradeOptions `json:"-"`
|
||||
}
|
||||
|
||||
func (o *V2RayTransportOptions) MarshalJSON() ([]byte, error) {
|
||||
switch o.Type {
|
||||
case "ws":
|
||||
return json.Marshal(&struct {
|
||||
Type string `json:"type"`
|
||||
*V2RayWebsocketOptions
|
||||
}{
|
||||
Type: o.Type,
|
||||
V2RayWebsocketOptions: &o.WebsocketOptions,
|
||||
})
|
||||
case "quic":
|
||||
return json.Marshal(&struct {
|
||||
Type string `json:"type"`
|
||||
*V2RayQUICOptions
|
||||
}{
|
||||
Type: o.Type,
|
||||
V2RayQUICOptions: &o.QUICOptions,
|
||||
})
|
||||
case "grpc":
|
||||
return json.Marshal(&struct {
|
||||
Type string `json:"type"`
|
||||
*V2RayGRPCOptions
|
||||
}{
|
||||
Type: o.Type,
|
||||
V2RayGRPCOptions: &o.GRPCOptions,
|
||||
})
|
||||
case "http":
|
||||
return json.Marshal(&struct {
|
||||
Type string `json:"type"`
|
||||
*V2RayHTTPOptions
|
||||
}{
|
||||
Type: o.Type,
|
||||
V2RayHTTPOptions: &o.HTTPOptions,
|
||||
})
|
||||
default:
|
||||
return json.Marshal(&struct{}{})
|
||||
}
|
||||
}
|
||||
|
||||
type V2RayHTTPOptions struct {
|
||||
Host []string `json:"host,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Method string `json:"method,omitempty"`
|
||||
Headers map[string]string `json:"headers,omitempty"`
|
||||
IdleTimeout Duration `json:"idle_timeout,omitempty"`
|
||||
PingTimeout Duration `json:"ping_timeout,omitempty"`
|
||||
}
|
||||
|
||||
type V2RayWebsocketOptions struct {
|
||||
Path string `json:"path,omitempty"`
|
||||
Headers map[string]string `json:"headers,omitempty"`
|
||||
MaxEarlyData uint32 `json:"max_early_data,omitempty"`
|
||||
EarlyDataHeaderName string `json:"early_data_header_name,omitempty"`
|
||||
}
|
||||
|
||||
type V2RayQUICOptions struct{}
|
||||
|
||||
type V2RayGRPCOptions struct {
|
||||
ServiceName string `json:"service_name,omitempty"`
|
||||
IdleTimeout Duration `json:"idle_timeout,omitempty"`
|
||||
PingTimeout Duration `json:"ping_timeout,omitempty"`
|
||||
PermitWithoutStream bool `json:"permit_without_stream,omitempty"`
|
||||
ForceLite bool `json:"-"` // for test
|
||||
}
|
||||
|
||||
type V2RayHTTPUpgradeOptions struct {
|
||||
Host string `json:"host,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Headers map[string]string `json:"headers,omitempty"`
|
||||
}
|
15
model/vless.go
Normal file
15
model/vless.go
Normal file
@ -0,0 +1,15 @@
|
||||
package model
|
||||
|
||||
type VLESS struct {
|
||||
Type string `json:"type"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Server string `json:"server"`
|
||||
ServerPort uint16 `json:"server_port"`
|
||||
UUID string `json:"uuid"`
|
||||
Flow string `json:"flow,omitempty"`
|
||||
Network string `json:"network,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
PacketEncoding *string `json:"packet_encoding,omitempty"`
|
||||
}
|
36
model/vmess.go
Normal file
36
model/vmess.go
Normal file
@ -0,0 +1,36 @@
|
||||
package model
|
||||
|
||||
type VmessJson struct {
|
||||
V string `json:"v"`
|
||||
Ps string `json:"ps"`
|
||||
Add string `json:"add"`
|
||||
Port interface{} `json:"port"`
|
||||
Id string `json:"id"`
|
||||
Aid interface{} `json:"aid"`
|
||||
Scy string `json:"scy"`
|
||||
Net string `json:"net"`
|
||||
Type string `json:"type"`
|
||||
Host string `json:"host"`
|
||||
Path string `json:"path"`
|
||||
Tls string `json:"tls"`
|
||||
Sni string `json:"sni"`
|
||||
Alpn string `json:"alpn"`
|
||||
Fp string `json:"fp"`
|
||||
}
|
||||
|
||||
type VMess struct {
|
||||
Type string `json:"type"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Server string `json:"server"`
|
||||
ServerPort uint16 `json:"server_port"`
|
||||
UUID string `json:"uuid"`
|
||||
Security string `json:"security"`
|
||||
AlterId int `json:"alter_id,omitempty"`
|
||||
GlobalPadding bool `json:"global_padding,omitempty"`
|
||||
AuthenticatedLength bool `json:"authenticated_length,omitempty"`
|
||||
Network string `json:"network,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
PacketEncoding string `json:"packet_encoding,omitempty"`
|
||||
Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
}
|
83
parser/hysteria.go
Normal file
83
parser/hysteria.go
Normal file
@ -0,0 +1,83 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2sing-box/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) {
|
||||
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]
|
||||
}
|
||||
insecureBool, err := strconv.ParseBool(insecure)
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid hysteria Url")
|
||||
}
|
||||
result := model.Proxy{
|
||||
Type: "hysteria",
|
||||
Hysteria: model.Hysteria{
|
||||
Tag: remarks,
|
||||
Server: host,
|
||||
ServerPort: uint16(port),
|
||||
Up: upmbps,
|
||||
Down: downmbps,
|
||||
Auth: []byte(auth),
|
||||
Obfs: obfs,
|
||||
Network: protocol,
|
||||
TLS: &model.OutboundTLSOptions{
|
||||
Enabled: true,
|
||||
Insecure: insecureBool,
|
||||
ALPN: strings.Split(alpn, ","),
|
||||
},
|
||||
},
|
||||
}
|
||||
return result, nil
|
||||
}
|
62
parser/hysteria2.go
Normal file
62
parser/hysteria2.go
Normal file
@ -0,0 +1,62 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2sing-box/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) {
|
||||
if !strings.HasPrefix(proxy, "hysteria2://") && !strings.HasPrefix(proxy, "hy2://") {
|
||||
return model.Proxy{}, errors.New("invalid hysteria2 Url")
|
||||
}
|
||||
parts := strings.SplitN(strings.TrimPrefix(proxy, "hysteria2://"), "@", 2)
|
||||
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")
|
||||
}
|
||||
remarks := params.Get("name")
|
||||
certificate := make([]string, 0)
|
||||
if params.Get("pinSHA256") != "" {
|
||||
certificate = append(certificate, params.Get("pinSHA256"))
|
||||
}
|
||||
server := serverAndPort[0]
|
||||
password := parts[0]
|
||||
network := params.Get("network")
|
||||
result := model.Proxy{
|
||||
Type: "hysteria2",
|
||||
Hysteria2: model.Hysteria2{
|
||||
Tag: remarks,
|
||||
Server: server,
|
||||
ServerPort: uint16(port),
|
||||
Password: password,
|
||||
Obfs: &model.Hysteria2Obfs{
|
||||
Type: params.Get("obfs"),
|
||||
Password: params.Get("obfs-password"),
|
||||
},
|
||||
TLS: &model.OutboundTLSOptions{
|
||||
Enabled: params.Get("pinSHA256") != "",
|
||||
Insecure: params.Get("insecure") == "1",
|
||||
ServerName: params.Get("sni"),
|
||||
Certificate: certificate,
|
||||
},
|
||||
Network: network,
|
||||
},
|
||||
}
|
||||
return result, nil
|
||||
}
|
64
parser/shadowsocks.go
Normal file
64
parser/shadowsocks.go
Normal file
@ -0,0 +1,64 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2sing-box/model"
|
||||
. "sub2sing-box/util"
|
||||
)
|
||||
|
||||
func ParseShadowsocks(proxy string) (model.Proxy, error) {
|
||||
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())
|
||||
}
|
||||
remarks := ""
|
||||
if len(serverInfo) == 2 {
|
||||
unescape, err := url.QueryUnescape(serverInfo[1])
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid ss Url" + err.Error())
|
||||
}
|
||||
remarks = strings.TrimSpace(unescape)
|
||||
} else {
|
||||
remarks = strings.TrimSpace(serverAndPort[0])
|
||||
}
|
||||
method := credentials[0]
|
||||
password := credentials[1]
|
||||
server := strings.TrimSpace(serverAndPort[0])
|
||||
result := model.Proxy{
|
||||
Type: "shadowsocks",
|
||||
Shadowsocks: model.Shadowsocks{
|
||||
Tag: remarks,
|
||||
Method: method,
|
||||
Password: password,
|
||||
Server: server,
|
||||
ServerPort: uint16(port),
|
||||
},
|
||||
}
|
||||
return result, nil
|
||||
}
|
56
parser/trojan.go
Normal file
56
parser/trojan.go
Normal file
@ -0,0 +1,56 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2sing-box/model"
|
||||
)
|
||||
|
||||
func ParseTrojan(proxy string) (model.Proxy, error) {
|
||||
if !strings.HasPrefix(proxy, "trojan://") {
|
||||
return model.Proxy{}, fmt.Errorf("invalid trojan Url")
|
||||
}
|
||||
parts := strings.SplitN(strings.TrimPrefix(proxy, "trojan://"), "@", 2)
|
||||
if len(parts) != 2 {
|
||||
return model.Proxy{}, fmt.Errorf("invalid trojan Url")
|
||||
}
|
||||
serverInfo := strings.SplitN(parts[1], "#", 2)
|
||||
serverAndPortAndParams := strings.SplitN(serverInfo[0], "?", 2)
|
||||
serverAndPort := strings.SplitN(serverAndPortAndParams[0], ":", 2)
|
||||
params, err := url.ParseQuery(serverAndPortAndParams[1])
|
||||
if err != nil {
|
||||
return model.Proxy{}, err
|
||||
}
|
||||
if len(serverAndPort) != 2 {
|
||||
return model.Proxy{}, fmt.Errorf("invalid trojan")
|
||||
}
|
||||
port, err := strconv.Atoi(strings.TrimSpace(serverAndPort[1]))
|
||||
if err != nil {
|
||||
return model.Proxy{}, err
|
||||
}
|
||||
remarks := ""
|
||||
if len(serverInfo) == 2 {
|
||||
remarks, _ = url.QueryUnescape(strings.TrimSpace(serverInfo[1]))
|
||||
} else {
|
||||
remarks = serverAndPort[0]
|
||||
}
|
||||
server := strings.TrimSpace(serverAndPort[0])
|
||||
password := strings.TrimSpace(parts[0])
|
||||
result := model.Proxy{
|
||||
Type: "trojan",
|
||||
Trojan: model.Trojan{
|
||||
Tag: remarks,
|
||||
Server: server,
|
||||
ServerPort: uint16(port),
|
||||
TLS: &model.OutboundTLSOptions{
|
||||
Enabled: true,
|
||||
ServerName: params.Get("sni"),
|
||||
},
|
||||
Password: password,
|
||||
Network: "tcp",
|
||||
},
|
||||
}
|
||||
return result, nil
|
||||
}
|
89
parser/vless.go
Normal file
89
parser/vless.go
Normal file
@ -0,0 +1,89 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2sing-box/model"
|
||||
)
|
||||
|
||||
func ParseVless(proxy string) (model.Proxy, error) {
|
||||
if !strings.HasPrefix(proxy, "vless://") {
|
||||
return model.Proxy{}, fmt.Errorf("invalid vless Url")
|
||||
}
|
||||
parts := strings.SplitN(strings.TrimPrefix(proxy, "vless://"), "@", 2)
|
||||
if len(parts) != 2 {
|
||||
return model.Proxy{}, fmt.Errorf("invalid vless Url")
|
||||
}
|
||||
serverInfo := strings.SplitN(parts[1], "#", 2)
|
||||
serverAndPortAndParams := strings.SplitN(serverInfo[0], "?", 2)
|
||||
serverAndPort := strings.SplitN(serverAndPortAndParams[0], ":", 2)
|
||||
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
|
||||
}
|
||||
remarks := ""
|
||||
if len(serverInfo) == 2 {
|
||||
if strings.Contains(serverInfo[1], "|") {
|
||||
remarks = strings.SplitN(serverInfo[1], "|", 2)[1]
|
||||
} else {
|
||||
remarks, err = url.QueryUnescape(serverInfo[1])
|
||||
if err != nil {
|
||||
return model.Proxy{}, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
remarks, err = url.QueryUnescape(serverAndPort[0])
|
||||
if err != nil {
|
||||
return model.Proxy{}, err
|
||||
}
|
||||
}
|
||||
server := strings.TrimSpace(serverAndPort[0])
|
||||
uuid := strings.TrimSpace(parts[0])
|
||||
network := params.Get("type")
|
||||
result := model.Proxy{
|
||||
Type: "vless",
|
||||
VLESS: model.VLESS{
|
||||
Tag: remarks,
|
||||
Server: server,
|
||||
ServerPort: uint16(port),
|
||||
UUID: uuid,
|
||||
Network: network,
|
||||
TLS: &model.OutboundTLSOptions{
|
||||
Enabled: params.Get("security") == "reality",
|
||||
ServerName: params.Get("sni"),
|
||||
UTLS: &model.OutboundUTLSOptions{
|
||||
Enabled: params.Get("fp") != "",
|
||||
Fingerprint: params.Get("fp"),
|
||||
},
|
||||
Reality: &model.OutboundRealityOptions{
|
||||
Enabled: params.Get("pbk") != "",
|
||||
PublicKey: params.Get("pbk"),
|
||||
ShortID: params.Get("sid"),
|
||||
},
|
||||
ALPN: strings.Split(params.Get("alpn"), ","),
|
||||
},
|
||||
Transport: &model.V2RayTransportOptions{
|
||||
WebsocketOptions: model.V2RayWebsocketOptions{
|
||||
Path: params.Get("path"),
|
||||
Headers: map[string]string{
|
||||
"Host": params.Get("host"),
|
||||
},
|
||||
},
|
||||
GRPCOptions: model.V2RayGRPCOptions{
|
||||
ServiceName: params.Get("serviceName"),
|
||||
},
|
||||
},
|
||||
Flow: params.Get("flow"),
|
||||
},
|
||||
}
|
||||
return result, nil
|
||||
}
|
133
parser/vmess.go
Normal file
133
parser/vmess.go
Normal file
@ -0,0 +1,133 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sub2sing-box/model"
|
||||
. "sub2sing-box/util"
|
||||
)
|
||||
|
||||
func ParseVmess(proxy string) (model.Proxy, error) {
|
||||
if !strings.HasPrefix(proxy, "vmess://") {
|
||||
return model.Proxy{}, errors.New("invalid vmess url")
|
||||
}
|
||||
base64, err := DecodeBase64(strings.TrimPrefix(proxy, "vmess://"))
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid vmess url" + 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())
|
||||
}
|
||||
port := 0
|
||||
switch vmess.Port.(type) {
|
||||
case string:
|
||||
port, err = strconv.Atoi(vmess.Port.(string))
|
||||
if err != nil {
|
||||
return model.Proxy{}, errors.New("invalid vmess url" + err.Error())
|
||||
}
|
||||
case float64:
|
||||
port = int(vmess.Port.(float64))
|
||||
}
|
||||
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())
|
||||
}
|
||||
case float64:
|
||||
aid = int(vmess.Aid.(float64))
|
||||
}
|
||||
if vmess.Scy == "" {
|
||||
vmess.Scy = "auto"
|
||||
}
|
||||
|
||||
name, err := url.QueryUnescape(vmess.Ps)
|
||||
if err != nil {
|
||||
name = vmess.Ps
|
||||
}
|
||||
|
||||
result := model.Proxy{
|
||||
Type: "vmess",
|
||||
VMess: model.VMess{
|
||||
Tag: name,
|
||||
Server: vmess.Add,
|
||||
ServerPort: uint16(port),
|
||||
UUID: vmess.Id,
|
||||
AlterId: aid,
|
||||
Security: vmess.Scy,
|
||||
Network: vmess.Net,
|
||||
},
|
||||
}
|
||||
|
||||
if vmess.Tls == "tls" {
|
||||
tls := model.OutboundTLSOptions{
|
||||
Enabled: true,
|
||||
UTLS: &model.OutboundUTLSOptions{
|
||||
Fingerprint: vmess.Fp,
|
||||
},
|
||||
ALPN: strings.Split(vmess.Alpn, ","),
|
||||
}
|
||||
result.VMess.TLS = &tls
|
||||
}
|
||||
|
||||
if vmess.Net == "ws" {
|
||||
if vmess.Path == "" {
|
||||
vmess.Path = "/"
|
||||
}
|
||||
if vmess.Host == "" {
|
||||
vmess.Host = vmess.Add
|
||||
}
|
||||
ws := model.V2RayWebsocketOptions{
|
||||
Path: vmess.Path,
|
||||
Headers: map[string]string{
|
||||
"Host": vmess.Host,
|
||||
},
|
||||
}
|
||||
transport := model.V2RayTransportOptions{
|
||||
Type: "ws",
|
||||
WebsocketOptions: ws,
|
||||
}
|
||||
result.VMess.Transport = &transport
|
||||
}
|
||||
|
||||
if vmess.Net == "quic" {
|
||||
quic := model.V2RayQUICOptions{}
|
||||
transport := model.V2RayTransportOptions{
|
||||
Type: "quic",
|
||||
QUICOptions: quic,
|
||||
}
|
||||
result.VMess.Transport = &transport
|
||||
}
|
||||
|
||||
if vmess.Net == "grpc" {
|
||||
grpc := model.V2RayGRPCOptions{
|
||||
ServiceName: vmess.Path,
|
||||
PermitWithoutStream: true,
|
||||
}
|
||||
transport := model.V2RayTransportOptions{
|
||||
Type: "grpc",
|
||||
GRPCOptions: grpc,
|
||||
}
|
||||
result.VMess.Transport = &transport
|
||||
}
|
||||
|
||||
if vmess.Net == "h2" {
|
||||
httpOps := model.V2RayHTTPOptions{
|
||||
Host: strings.Split(vmess.Host, ","),
|
||||
Path: vmess.Path,
|
||||
}
|
||||
transport := model.V2RayTransportOptions{
|
||||
Type: "http",
|
||||
HTTPOptions: httpOps,
|
||||
}
|
||||
result.VMess.Transport = &transport
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
18
util/base64.go
Normal file
18
util/base64.go
Normal file
@ -0,0 +1,18 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func DecodeBase64(s string) (string, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
if len(s)%4 != 0 {
|
||||
s += strings.Repeat("=", 4-len(s)%4)
|
||||
}
|
||||
decodeStr, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(decodeStr), nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user