187 lines
4.9 KiB
Go
187 lines
4.9 KiB
Go
package proxy
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/rand"
|
|
"net"
|
|
"net/url"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/zu1k/proxypool/pkg/tool"
|
|
)
|
|
|
|
var (
|
|
ErrorNotSSRLink = errors.New("not a correct ssr link")
|
|
ErrorPasswordParseFail = errors.New("password parse failed")
|
|
ErrorPathNotComplete = errors.New("path not complete")
|
|
ErrorMissingQuery = errors.New("link missing query")
|
|
ErrorProtocolParamParseFail = errors.New("protocol param parse failed")
|
|
ErrorObfsParamParseFail = errors.New("obfs param parse failed")
|
|
)
|
|
|
|
type ShadowsocksR struct {
|
|
Base
|
|
Password string `yaml:"password" json:"password"`
|
|
Cipher string `yaml:"cipher" json:"cipher"`
|
|
Protocol string `yaml:"protocol" json:"protocol"`
|
|
ProtocolParam string `yaml:"protocol-param,omitempty" json:"protocol_param,omitempty"`
|
|
Obfs string `yaml:"obfs" json:"obfs"`
|
|
ObfsParam string `yaml:"obfs-param,omitempty" json:"obfs_param,omitempty"`
|
|
Group string `yaml:"group,omitempty" json:"group,omitempty"`
|
|
}
|
|
|
|
func (ssr ShadowsocksR) Identifier() string {
|
|
return net.JoinHostPort(ssr.Server, strconv.Itoa(ssr.Port)) + ssr.Password + ssr.ProtocolParam
|
|
}
|
|
|
|
func (ssr ShadowsocksR) String() string {
|
|
data, err := json.Marshal(ssr)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return string(data)
|
|
}
|
|
|
|
func (ssr ShadowsocksR) ToClash() string {
|
|
data, err := json.Marshal(ssr)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return "- " + string(data)
|
|
}
|
|
|
|
func (ssr ShadowsocksR) ToSurge() string {
|
|
return ""
|
|
}
|
|
|
|
func (ssr ShadowsocksR) Clone() Proxy {
|
|
return &ssr
|
|
}
|
|
|
|
// https://github.com/HMBSbige/ShadowsocksR-Windows/wiki/SSR-QRcode-scheme
|
|
func (ssr ShadowsocksR) Link() (link string) {
|
|
payload := fmt.Sprintf("%s:%d:%s:%s:%s:%s",
|
|
ssr.Server, ssr.Port, ssr.Protocol, ssr.Cipher, ssr.Obfs, tool.Base64EncodeString(ssr.Password, true))
|
|
query := url.Values{}
|
|
query.Add("obfsparam", tool.Base64EncodeString(ssr.ObfsParam, true))
|
|
query.Add("protoparam", tool.Base64EncodeString(ssr.ProtocolParam, true))
|
|
query.Add("remarks", tool.Base64EncodeString(ssr.Name, true))
|
|
query.Add("group", tool.Base64EncodeString("proxy.tgbot.co", true))
|
|
payload = tool.Base64EncodeString(fmt.Sprintf("%s/?%s", payload, query.Encode()), true)
|
|
return fmt.Sprintf("ssr://%s", payload)
|
|
}
|
|
|
|
func ParseSSRLink(link string) (*ShadowsocksR, error) {
|
|
if !strings.HasPrefix(link, "ssr") {
|
|
return nil, ErrorNotSSRLink
|
|
}
|
|
|
|
ssrmix := strings.SplitN(link, "://", 2)
|
|
if len(ssrmix) < 2 {
|
|
return nil, ErrorNotSSRLink
|
|
}
|
|
linkPayloadBase64 := ssrmix[1]
|
|
payload, err := tool.Base64DecodeString(linkPayloadBase64)
|
|
if err != nil {
|
|
return nil, ErrorMissingQuery
|
|
}
|
|
|
|
infoPayload := strings.SplitN(payload, "/?", 2)
|
|
if len(infoPayload) < 2 {
|
|
return nil, ErrorNotSSRLink
|
|
}
|
|
ssrpath := strings.Split(infoPayload[0], ":")
|
|
if len(ssrpath) < 6 {
|
|
return nil, ErrorPathNotComplete
|
|
}
|
|
// base info
|
|
server := strings.ToLower(ssrpath[0])
|
|
port, _ := strconv.Atoi(ssrpath[1])
|
|
protocol := strings.ToLower(ssrpath[2])
|
|
cipher := strings.ToLower(ssrpath[3])
|
|
obfs := strings.ToLower(ssrpath[4])
|
|
password, err := tool.Base64DecodeString(ssrpath[5])
|
|
if err != nil {
|
|
return nil, ErrorPasswordParseFail
|
|
}
|
|
|
|
moreInfo, _ := url.ParseQuery(infoPayload[1])
|
|
|
|
// remarks
|
|
remarks := moreInfo.Get("remarks")
|
|
remarks, err = tool.Base64DecodeString(remarks)
|
|
if err != nil {
|
|
remarks = ""
|
|
err = nil
|
|
}
|
|
if strings.ContainsAny(remarks, "\t\r\n ") {
|
|
remarks = strings.ReplaceAll(remarks, "\t", "")
|
|
remarks = strings.ReplaceAll(remarks, "\r", "")
|
|
remarks = strings.ReplaceAll(remarks, "\n", "")
|
|
remarks = strings.ReplaceAll(remarks, " ", "")
|
|
}
|
|
|
|
// protocol param
|
|
protocolParam, err := tool.Base64DecodeString(moreInfo.Get("protoparam"))
|
|
if err != nil {
|
|
return nil, ErrorProtocolParamParseFail
|
|
}
|
|
if tool.ContainChineseChar(protocolParam) {
|
|
protocolParam = ""
|
|
}
|
|
if strings.HasSuffix(protocol, "_compatible") {
|
|
protocol = strings.ReplaceAll(protocol, "_compatible", "")
|
|
}
|
|
|
|
// obfs param
|
|
obfsParam, err := tool.Base64DecodeString(moreInfo.Get("obfsparam"))
|
|
if err != nil {
|
|
return nil, ErrorObfsParamParseFail
|
|
}
|
|
if tool.ContainChineseChar(obfsParam) {
|
|
obfsParam = ""
|
|
}
|
|
if strings.HasSuffix(obfs, "_compatible") {
|
|
obfs = strings.ReplaceAll(obfs, "_compatible", "")
|
|
}
|
|
|
|
//group, err := tool.Base64DecodeString(moreInfo.Get("group"))
|
|
//if err != nil {
|
|
// group = ""
|
|
//}
|
|
group := ""
|
|
|
|
return &ShadowsocksR{
|
|
Base: Base{
|
|
Name: remarks + "_" + strconv.Itoa(rand.Int()),
|
|
Server: server,
|
|
Port: port,
|
|
Type: "ssr",
|
|
},
|
|
Password: password,
|
|
Cipher: cipher,
|
|
Protocol: protocol,
|
|
ProtocolParam: protocolParam,
|
|
Obfs: obfs,
|
|
ObfsParam: obfsParam,
|
|
Group: group,
|
|
}, nil
|
|
}
|
|
|
|
var (
|
|
ssrPlainRe = regexp.MustCompile("ssr://([A-Za-z0-9+/_-])+")
|
|
)
|
|
|
|
func GrepSSRLinkFromString(text string) []string {
|
|
results := make([]string, 0)
|
|
texts := strings.Split(text, "ssr://")
|
|
for _, text := range texts {
|
|
results = append(results, ssrPlainRe.FindAllString("ssr://"+text, -1)...)
|
|
}
|
|
return results
|
|
}
|