9 Commits

Author SHA1 Message Date
b57e4cf49f fix ua form 2025-03-05 19:24:48 +11:00
cc80e237d6 Merge pull request #48 from 96368a/main
 增加socks5协议支持
2024-11-13 10:31:41 +08:00
fefb4b895a 增加socks5协议支持 2024-11-13 09:21:31 +08:00
66f214ae10 try to fix ss parser 2024-11-06 18:43:48 +08:00
f7dc78aabc 🐛 2024-10-19 15:39:20 +08:00
6bb2d16e4b 🐛 #46 2024-10-09 11:08:24 +08:00
98ef93c7bb 🐛 #42 2024-10-08 10:05:13 +08:00
6e09c44d17 🐛 #43 2024-10-08 10:01:09 +08:00
42fd251eb5 #42 2024-10-07 16:34:56 +08:00
12 changed files with 155 additions and 29 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

View File

@ -1,7 +1,5 @@
# sub2clash # sub2clash
> Sing-box 用户?看看另一个项目 [sub2sing-box](https://github.com/nitezs/sub2sing-box)
将订阅链接转换为 Clash、Clash.Meta 配置 将订阅链接转换为 Clash、Clash.Meta 配置
[预览](https://www.nite07.com/sub) [预览](https://www.nite07.com/sub)
@ -19,6 +17,7 @@
- Trojan - Trojan
- Hysteria Clash.Meta - Hysteria Clash.Meta
- Hysteria2 Clash.Meta - Hysteria2 Clash.Meta
- Socks5
## 使用 ## 使用

View File

@ -120,6 +120,9 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
newProxies := make([]model.Proxy, 0, len(proxyList)) newProxies := make([]model.Proxy, 0, len(proxyList))
for i := range proxyList { for i := range proxyList {
key := proxyList[i].Server + strconv.Itoa(proxyList[i].Port) + proxyList[i].Type + proxyList[i].UUID + proxyList[i].Password key := proxyList[i].Server + strconv.Itoa(proxyList[i].Port) + proxyList[i].Type + proxyList[i].UUID + proxyList[i].Password
if proxyList[i].Network == "ws" {
key += proxyList[i].WSOpts.Path + proxyList[i].WSOpts.Headers["Host"]
}
if _, exist := proxies[key]; !exist { if _, exist := proxies[key]; !exist {
proxies[key] = &proxyList[i] proxies[key] = &proxyList[i]
newProxies = append(newProxies, proxyList[i]) newProxies = append(newProxies, proxyList[i])

View File

@ -69,7 +69,7 @@ function generateURI() {
// 获取订阅user-agent标识 // 获取订阅user-agent标识
const userAgent = document.getElementById("user-agent").value; const userAgent = document.getElementById("user-agent").value;
queryParams.push(`userAgent=${userAgent}`) queryParams.push(`userAgent=${encodeURIComponent(userAgent)}`);
// 获取复选框的值 // 获取复选框的值
const refresh = document.getElementById("refresh").checked; const refresh = document.getElementById("refresh").checked;

View File

@ -127,6 +127,9 @@ func ParseProxy(proxies ...string) []model.Proxy {
if strings.HasPrefix(proxy, constant.HysteriaPrefix) { if strings.HasPrefix(proxy, constant.HysteriaPrefix) {
proxyItem, err = parser.ParseHysteria(proxy) proxyItem, err = parser.ParseHysteria(proxy)
} }
if strings.HasPrefix(proxy, constant.SocksPrefix) {
proxyItem, err = parser.ParseSocks(proxy)
}
if err == nil { if err == nil {
result = append(result, proxyItem) result = append(result, proxyItem)
} else { } else {

View File

@ -9,4 +9,5 @@ const (
TrojanPrefix string = "trojan://" TrojanPrefix string = "trojan://"
VLESSPrefix string = "vless://" VLESSPrefix string = "vless://"
VMessPrefix string = "vmess://" VMessPrefix string = "vmess://"
SocksPrefix string = "socks"
) )

View File

@ -14,6 +14,7 @@ func GetSupportProxyTypes(clashType ClashType) map[string]bool {
"ssr": true, "ssr": true,
"vmess": true, "vmess": true,
"trojan": true, "trojan": true,
"socks5": true,
} }
} }
if clashType == ClashMeta { if clashType == ClashMeta {
@ -25,6 +26,7 @@ func GetSupportProxyTypes(clashType ClashType) map[string]bool {
"vless": true, "vless": true,
"hysteria": true, "hysteria": true,
"hysteria2": true, "hysteria2": true,
"socks5": true,
} }
} }
return nil return nil

View File

@ -10,6 +10,7 @@ type Proxy struct {
Port int `yaml:"port,omitempty"` Port int `yaml:"port,omitempty"`
Type string `yaml:"type,omitempty"` Type string `yaml:"type,omitempty"`
Cipher string `yaml:"cipher,omitempty"` Cipher string `yaml:"cipher,omitempty"`
Username string `yaml:"username,omitempty"`
Password string `yaml:"password,omitempty"` Password string `yaml:"password,omitempty"`
UDP bool `yaml:"udp,omitempty"` UDP bool `yaml:"udp,omitempty"`
UUID string `yaml:"uuid,omitempty"` UUID string `yaml:"uuid,omitempty"`

View File

@ -13,7 +13,22 @@ func ParseShadowsocks(proxy string) (model.Proxy, error) {
if !strings.HasPrefix(proxy, constant.ShadowsocksPrefix) { if !strings.HasPrefix(proxy, constant.ShadowsocksPrefix) {
return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy}
} }
if !strings.Contains(proxy, "@") {
s := strings.SplitN(proxy, "#", 2)
d, err := DecodeBase64(strings.TrimPrefix(s[0], "ss://"))
if err != nil {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "url parse error",
Raw: proxy,
}
}
if len(s) == 2 {
proxy = "ss://" + d + "#" + s[1]
} else {
proxy = "ss://" + d
}
}
link, err := url.Parse(proxy) link, err := url.Parse(proxy)
if err != nil { if err != nil {
return model.Proxy{}, &ParseError{ return model.Proxy{}, &ParseError{
@ -48,32 +63,29 @@ func ParseShadowsocks(proxy string) (model.Proxy, error) {
} }
} }
user, err := DecodeBase64(link.User.Username()) method := link.User.Username()
password, _ := link.User.Password()
if password == "" {
user, err := DecodeBase64(method)
if err == nil {
methodAndPass := strings.SplitN(user, ":", 2)
if len(methodAndPass) == 2 {
method = methodAndPass[0]
password = methodAndPass[1]
}
}
}
if isLikelyBase64(password) {
password, err = DecodeBase64(password)
if err != nil { if err != nil {
return model.Proxy{}, &ParseError{ return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct, Type: ErrInvalidStruct,
Message: "missing method and password", Message: "password decode error",
Raw: proxy, Raw: proxy,
} }
} }
if user == "" {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "missing method and password",
Raw: proxy,
} }
}
methodAndPass := strings.SplitN(user, ":", 2)
if len(methodAndPass) != 2 {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "missing method and password",
Raw: proxy,
}
}
method := methodAndPass[0]
password := methodAndPass[1]
remarks := link.Fragment remarks := link.Fragment
if remarks == "" { if remarks == "" {
@ -92,3 +104,17 @@ func ParseShadowsocks(proxy string) (model.Proxy, error) {
return result, nil return result, nil
} }
func isLikelyBase64(s string) bool {
if len(s)%4 == 0 && strings.HasSuffix(s, "=") && !strings.Contains(strings.TrimSuffix(s, "="), "=") {
s = strings.TrimSuffix(s, "=")
chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
for _, c := range s {
if !strings.ContainsRune(chars, c) {
return false
}
}
return true
}
return false
}

View File

@ -28,7 +28,14 @@ func ParseShadowsocksR(proxy string) (model.Proxy, error) {
protocol := parts[2] protocol := parts[2]
method := parts[3] method := parts[3]
obfs := parts[4] obfs := parts[4]
password := parts[5] password, err := DecodeBase64(parts[5])
if err != nil {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Raw: proxy,
Message: err.Error(),
}
}
port, err := ParsePort(parts[1]) port, err := ParsePort(parts[1])
if err != nil { if err != nil {
return model.Proxy{}, &ParseError{ return model.Proxy{}, &ParseError{

79
parser/socks.go Normal file
View File

@ -0,0 +1,79 @@
package parser
import (
"fmt"
"github.com/nitezs/sub2clash/constant"
"github.com/nitezs/sub2clash/model"
"net/url"
"strings"
)
func ParseSocks(proxy string) (model.Proxy, error) {
if !strings.HasPrefix(proxy, constant.SocksPrefix) {
return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy}
}
link, err := url.Parse(proxy)
if err != nil {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "url parse error",
Raw: proxy,
}
}
server := link.Hostname()
if server == "" {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "missing server host",
Raw: proxy,
}
}
portStr := link.Port()
if portStr == "" {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "missing server port",
Raw: proxy,
}
}
port, err := ParsePort(portStr)
if err != nil {
return model.Proxy{}, &ParseError{
Type: ErrInvalidPort,
Raw: portStr,
}
}
remarks := link.Fragment
if remarks == "" {
remarks = fmt.Sprintf("%s:%s", server, portStr)
}
remarks = strings.TrimSpace(remarks)
encodeStr := link.User.Username()
var username, password string
if encodeStr != "" {
decodeStr, err := DecodeBase64(encodeStr)
splitStr := strings.Split(decodeStr, ":")
if err != nil {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "url parse error",
Raw: proxy,
}
}
username = splitStr[0]
if len(splitStr) == 2 {
password = splitStr[1]
}
}
return model.Proxy{
Type: "socks5",
Name: remarks,
Server: server,
Port: port,
Username: username,
Password: password,
}, nil
}

View File

@ -104,6 +104,11 @@ func ParseVless(proxy string) (model.Proxy, error) {
} }
if _type == "http" { if _type == "http" {
result.HTTPOpts = model.HTTPOptions{}
result.HTTPOpts.Headers = map[string][]string{}
result.HTTPOpts.Path = strings.Split(path, ",")
hosts, err := url.QueryUnescape(host) hosts, err := url.QueryUnescape(host)
if err != nil { if err != nil {
return model.Proxy{}, &ParseError{ return model.Proxy{}, &ParseError{
@ -113,10 +118,9 @@ func ParseVless(proxy string) (model.Proxy, error) {
} }
} }
result.Network = "http" result.Network = "http"
result.HTTPOpts = model.HTTPOptions{ if hosts != "" {
Headers: map[string][]string{"Host": strings.Split(hosts, ",")}, result.HTTPOpts.Headers["host"] = strings.Split(host, ",")
} }
} }
return result, nil return result, nil