1
0
mirror of https://github.com/nitezs/sub2sing-box.git synced 2024-12-23 14:04:41 -05:00
This commit is contained in:
Nite07 2024-03-11 03:13:42 +08:00
commit c75126e368
25 changed files with 1028 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.vscode
dist

87
cmd/url.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,5 @@
package model
import "time"
type Duration time.Duration

6
model/udp_over_tcp.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}