mirror of
https://github.com/bestnite/sub2clash.git
synced 2025-07-05 04:12:34 +08:00
Compare commits
9 Commits
v0.0.10
...
v0.1.0-alp
Author | SHA1 | Date | |
---|---|---|---|
b57e4cf49f | |||
cc80e237d6 | |||
fefb4b895a | |||
66f214ae10 | |||
f7dc78aabc | |||
6bb2d16e4b | |||
98ef93c7bb | |||
6e09c44d17 | |||
42fd251eb5 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
@ -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
|
||||||
|
|
||||||
## 使用
|
## 使用
|
||||||
|
|
||||||
|
@ -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])
|
||||||
|
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
@ -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"
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
|
@ -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"`
|
||||||
|
@ -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()
|
||||||
if err != nil {
|
password, _ := link.User.Password()
|
||||||
return model.Proxy{}, &ParseError{
|
|
||||||
Type: ErrInvalidStruct,
|
|
||||||
Message: "missing method and password",
|
|
||||||
Raw: proxy,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if user == "" {
|
if password == "" {
|
||||||
return model.Proxy{}, &ParseError{
|
user, err := DecodeBase64(method)
|
||||||
Type: ErrInvalidStruct,
|
if err == nil {
|
||||||
Message: "missing method and password",
|
methodAndPass := strings.SplitN(user, ":", 2)
|
||||||
Raw: proxy,
|
if len(methodAndPass) == 2 {
|
||||||
|
method = methodAndPass[0]
|
||||||
|
password = methodAndPass[1]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
methodAndPass := strings.SplitN(user, ":", 2)
|
if isLikelyBase64(password) {
|
||||||
if len(methodAndPass) != 2 {
|
password, err = DecodeBase64(password)
|
||||||
return model.Proxy{}, &ParseError{
|
if err != nil {
|
||||||
Type: ErrInvalidStruct,
|
return model.Proxy{}, &ParseError{
|
||||||
Message: "missing method and password",
|
Type: ErrInvalidStruct,
|
||||||
Raw: proxy,
|
Message: "password decode error",
|
||||||
|
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
|
||||||
|
}
|
||||||
|
@ -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
79
parser/socks.go
Normal 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
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
Reference in New Issue
Block a user