Compare commits

...

15 Commits

Author SHA1 Message Date
db00433931
Merge pull request #59 from timerzz/main
fix 修复Get函数重试结束后,返回nil,nil的bug
2025-03-24 01:12:06 +11:00
timerzz
5b3a12f00d fix 修复Get函数重试结束后,返回nil,nil的bug 2025-03-23 21:27:05 +08:00
d4d7010d8f
Merge pull request #56 from hausen1012/main
add:生成短链支持自定义id
2025-03-10 12:28:37 +11:00
hz
2331cd4d18 modify: 短链和密码修改为后端生成 2025-03-08 19:02:23 +08:00
hz
88d8653ab5 fix:修复ua标识等两个前端未正常回显问题 2025-03-07 17:41:42 +08:00
hz
cc0b73d7a4 add:生成短链支持自定义id 2025-03-07 17:30:28 +08:00
b57e4cf49f fix ua form 2025-03-05 19:24:48 +11:00
Nite07
cc80e237d6
Merge pull request #48 from 96368a/main
 增加socks5协议支持
2024-11-13 10:31:41 +08:00
96368a
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
16 changed files with 315 additions and 58 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

@ -32,16 +32,41 @@ func GenerateLinkHandler(c *gin.Context) {
return return
} }
hash, err := generateUniqueHash() var hash string
if err != nil { var password string
respondWithError(c, http.StatusInternalServerError, "生成短链接失败") var err error
return
if params.CustomID != "" {
// 检查自定义ID是否已存在
exists, err := database.CheckShortLinkHashExists(params.CustomID)
if err != nil {
respondWithError(c, http.StatusInternalServerError, "数据库错误")
return
}
if exists {
respondWithError(c, http.StatusBadRequest, "短链已存在")
return
}
hash = params.CustomID
password = params.Password
} else {
// 自动生成短链ID和密码
hash, err = generateUniqueHash()
if err != nil {
respondWithError(c, http.StatusInternalServerError, "生成短链接失败")
return
}
if params.Password == "" {
password = common.RandomString(8) // 生成8位随机密码
} else {
password = params.Password
}
} }
shortLink := model.ShortLink{ shortLink := model.ShortLink{
Hash: hash, Hash: hash,
Url: params.Url, Url: params.Url,
Password: params.Password, Password: password,
} }
if err := database.SaveShortLink(&shortLink); err != nil { if err := database.SaveShortLink(&shortLink); err != nil {
@ -49,10 +74,12 @@ func GenerateLinkHandler(c *gin.Context) {
return return
} }
if params.Password != "" { // 返回生成的短链ID和密码
hash += "?password=" + params.Password response := map[string]string{
"hash": hash,
"password": password,
} }
c.String(http.StatusOK, hash) c.JSON(http.StatusOK, response)
} }
func generateUniqueHash() (string, error) { func generateUniqueHash() (string, error) {
@ -74,11 +101,27 @@ func UpdateLinkHandler(c *gin.Context) {
respondWithError(c, http.StatusBadRequest, "参数错误: "+err.Error()) respondWithError(c, http.StatusBadRequest, "参数错误: "+err.Error())
return return
} }
// 先获取原有的短链接
existingLink, err := database.FindShortLinkByHash(params.Hash)
if err != nil {
respondWithError(c, http.StatusNotFound, "未找到短链接")
return
}
// 验证密码
if existingLink.Password != params.Password {
respondWithError(c, http.StatusUnauthorized, "密码错误")
return
}
// 更新URL但保持原密码不变
shortLink := model.ShortLink{ shortLink := model.ShortLink{
Hash: params.Hash, Hash: params.Hash,
Url: params.Url, Url: params.Url,
Password: params.Password, Password: existingLink.Password, // 保持原密码不变
} }
if err := database.SaveShortLink(&shortLink); err != nil { if err := database.SaveShortLink(&shortLink); err != nil {
respondWithError(c, http.StatusInternalServerError, "数据库错误") respondWithError(c, http.StatusInternalServerError, "数据库错误")
return return

View File

@ -79,7 +79,7 @@
<div class="form-group mb-3"> <div class="form-group mb-3">
<label for="user-agent">ua标识:</label> <label for="user-agent">ua标识:</label>
<textarea class="form-control" id="user-agent" name="user-agent" <textarea class="form-control" id="user-agent" name="user-agent"
placeholder="用于获取订阅的http请求中的user-agent标识(可选)" rows="1"></textarea> placeholder="用于获取订阅的http请求中的user-agent标识(可选)" rows="3"></textarea>
</div> </div>
<!-- Refresh --> <!-- Refresh -->
<div class="form-check mb-3"> <div class="form-check mb-3">
@ -147,24 +147,27 @@
<div class="form-group mb-5"> <div class="form-group mb-5">
<label for="apiLink">配置链接:</label> <label for="apiLink">配置链接:</label>
<div class="input-group mb-2"> <div class="input-group mb-2">
<input class="form-control" id="apiLink" type="text" placeholder="链接" /> <input class="form-control bg-light" id="apiLink" type="text" placeholder="链接" readonly style="cursor: not-allowed;" />
<button class="btn btn-primary" onclick="copyToClipboard('apiLink',this)" type="button"> <button class="btn btn-primary" onclick="copyToClipboard('apiLink',this)" type="button">
复制链接 复制链接
</button> </button>
</div> </div>
<div class="input-group"> <div class="input-group mb-2">
<input class="form-control" id="apiShortLink" type="text" placeholder="链接" /> <input class="form-control" id="customId" type="text" placeholder="短链ID可选" />
<input class="form-control" id="password" type="text" placeholder="密码" /> <input class="form-control" id="password" type="text" placeholder="密码(可选)" />
<button class="btn btn-primary" onclick="generateShortLink()" type="button"> <button class="btn btn-primary" onclick="generateShortLink()" type="button">
生成短链 生成短链
</button> </button>
<button class="btn btn-primary" onclick="updateShortLink()" type="button">
更新短链
</button>
<button class="btn btn-primary" onclick="copyToClipboard('apiShortLink',this)" type="button"> <button class="btn btn-primary" onclick="copyToClipboard('apiShortLink',this)" type="button">
复制短链 复制短链
</button> </button>
</div> </div>
<div class="input-group">
<input class="form-control bg-light" id="apiShortLink" type="text" placeholder="短链接" readonly style="cursor: not-allowed;" />
<button class="btn btn-primary" onclick="updateShortLink()" type="button">
更新短链
</button>
</div>
</div> </div>
<!-- footer--> <!-- footer-->

View File

@ -1,3 +1,15 @@
function setInputReadOnly(input, readonly) {
if (readonly) {
input.readOnly = true;
input.classList.add('bg-light');
input.style.cursor = 'not-allowed';
} else {
input.readOnly = false;
input.classList.remove('bg-light');
input.style.cursor = 'auto';
}
}
function clearExistingValues() { function clearExistingValues() {
// 清除简单输入框和复选框的值 // 清除简单输入框和复选框的值
document.getElementById("endpoint").value = "clash"; document.getElementById("endpoint").value = "clash";
@ -12,7 +24,23 @@ function clearExistingValues() {
document.getElementById("remove").value = ""; document.getElementById("remove").value = "";
document.getElementById("apiLink").value = ""; document.getElementById("apiLink").value = "";
document.getElementById("apiShortLink").value = ""; document.getElementById("apiShortLink").value = "";
document.getElementById("password").value = "";
// 恢复短链ID和密码输入框状态
const customIdInput = document.getElementById("customId");
const passwordInput = document.getElementById("password");
const generateButton = document.querySelector('button[onclick="generateShortLink()"]');
customIdInput.value = "";
setInputReadOnly(customIdInput, false);
passwordInput.value = "";
setInputReadOnly(passwordInput, false);
// 恢复生成短链按钮状态
generateButton.disabled = false;
generateButton.classList.remove('btn-secondary');
generateButton.classList.add('btn-primary');
document.getElementById("nodeList").checked = false; document.getElementById("nodeList").checked = false;
// 清除由 createRuleProvider, createReplace, 和 createRule 创建的所有额外输入组 // 清除由 createRuleProvider, createReplace, 和 createRule 创建的所有额外输入组
@ -69,7 +97,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;
@ -188,8 +216,32 @@ async function parseInputURL() {
try { try {
const response = await axios.get("./short?" + q.toString()); const response = await axios.get("./short?" + q.toString());
url = new URL(window.location.href + response.data); url = new URL(window.location.href + response.data);
document.querySelector("#apiShortLink").value = inputURL;
document.querySelector("#password").value = password; // 回显配置链接
const apiLinkInput = document.querySelector("#apiLink");
apiLinkInput.value = `${window.location.origin}${window.location.pathname}${response.data}`;
setInputReadOnly(apiLinkInput, true);
// 回显短链相关信息
const apiShortLinkInput = document.querySelector("#apiShortLink");
apiShortLinkInput.value = inputURL;
setInputReadOnly(apiShortLinkInput, true);
// 设置短链ID和密码并设置为只读
const customIdInput = document.querySelector("#customId");
const passwordInput = document.querySelector("#password");
const generateButton = document.querySelector('button[onclick="generateShortLink()"]');
customIdInput.value = hash;
setInputReadOnly(customIdInput, true);
passwordInput.value = password;
setInputReadOnly(passwordInput, true);
// 禁用生成短链按钮
generateButton.disabled = true;
generateButton.classList.add('btn-secondary');
generateButton.classList.remove('btn-primary');
} catch (error) { } catch (error) {
console.log(error); console.log(error);
alert("获取短链失败,请检查密码!"); alert("获取短链失败,请检查密码!");
@ -255,6 +307,17 @@ async function parseInputURL() {
); );
} }
if (params.has("userAgent")) {
document.getElementById("user-agent").value = decodeURIComponent(
params.get("userAgent")
);
}
if (params.has("ignoreCountryGroup")) {
document.getElementById("igcg").checked =
params.get("ignoreCountryGroup") === "true";
}
if (params.has("replace")) { if (params.has("replace")) {
parseAndFillReplaceParams(decodeURIComponent(params.get("replace"))); parseAndFillReplaceParams(decodeURIComponent(params.get("replace")));
} }
@ -428,21 +491,25 @@ function generateURL() {
return; return;
} }
apiLink.value = `${window.location.origin}${window.location.pathname}${uri}`; apiLink.value = `${window.location.origin}${window.location.pathname}${uri}`;
setInputReadOnly(apiLink, true);
} }
function generateShortLink() { function generateShortLink() {
const apiShortLink = document.getElementById("apiShortLink"); const apiShortLink = document.getElementById("apiShortLink");
const password = document.getElementById("password"); const password = document.getElementById("password");
const customId = document.getElementById("customId");
let uri = generateURI(); let uri = generateURI();
if (uri === "") { if (uri === "") {
return; return;
} }
axios axios
.post( .post(
"./short", "./short",
{ {
url: uri, url: uri,
password: password.value.trim(), password: password.value.trim(),
customId: customId.value.trim()
}, },
{ {
headers: { headers: {
@ -451,11 +518,20 @@ function generateShortLink() {
} }
) )
.then((response) => { .then((response) => {
apiShortLink.value = `${window.location.origin}${window.location.pathname}s/${response.data}`; // 设置返回的短链ID和密码
customId.value = response.data.hash;
password.value = response.data.password;
// 生成完整的短链接
const shortLink = `${window.location.origin}${window.location.pathname}s/${response.data.hash}?password=${response.data.password}`;
apiShortLink.value = shortLink;
}) })
.catch((error) => { .catch((error) => {
console.log(error); console.log(error);
alert("生成短链失败,请重试!"); if (error.response && error.response.data) {
alert(error.response.data);
} else {
alert("生成短链失败,请重试!");
}
}); });
} }
@ -468,7 +544,7 @@ function updateShortLink() {
hash = u.pathname.substring(u.pathname.lastIndexOf("/s/") + 3); hash = u.pathname.substring(u.pathname.lastIndexOf("/s/") + 3);
} }
if (password.value.trim() === "") { if (password.value.trim() === "") {
alert("请输入密码!"); alert("请输入密码进行验证");
return; return;
} }
let uri = generateURI(); let uri = generateURI();
@ -490,11 +566,17 @@ function updateShortLink() {
} }
) )
.then((response) => { .then((response) => {
alert("更新短链成功!"); alert(`短链 ${hash} 更新成功!`);
}) })
.catch((error) => { .catch((error) => {
console.log(error); console.log(error);
alert(error.response.data); if (error.response && error.response.status === 401) {
alert("密码错误,请输入正确的原密码!");
} else if (error.response && error.response.data) {
alert(error.response.data);
} else {
alert("更新短链失败,请重试!");
}
}); });
} }

View File

@ -2,6 +2,7 @@ package common
import ( import (
"errors" "errors"
"fmt"
"net/http" "net/http"
"time" "time"
@ -28,10 +29,12 @@ func Get(url string, options ...GetOption) (resp *http.Response, err error) {
for _, option := range options { for _, option := range options {
option(&getConfig) option(&getConfig)
} }
var req *http.Request
var get *http.Response
for haveTried < retryTimes { for haveTried < retryTimes {
client := &http.Client{} client := &http.Client{}
//client.Timeout = time.Second * 10 //client.Timeout = time.Second * 10
req, err := http.NewRequest("GET", url, nil) req, err = http.NewRequest("GET", url, nil)
if err != nil { if err != nil {
haveTried++ haveTried++
time.Sleep(retryDelay) time.Sleep(retryDelay)
@ -40,13 +43,12 @@ func Get(url string, options ...GetOption) (resp *http.Response, err error) {
if getConfig.userAgent != "" { if getConfig.userAgent != "" {
req.Header.Set("User-Agent", getConfig.userAgent) req.Header.Set("User-Agent", getConfig.userAgent)
} }
get, err := client.Do(req) get, err = client.Do(req)
if err != nil { if err != nil {
haveTried++ haveTried++
time.Sleep(retryDelay) time.Sleep(retryDelay)
continue continue
} else { } else {
if get != nil && get.ContentLength > config.Default.RequestMaxFileSize { if get != nil && get.ContentLength > config.Default.RequestMaxFileSize {
return nil, errors.New("文件过大") return nil, errors.New("文件过大")
} }
@ -54,5 +56,5 @@ func Get(url string, options ...GetOption) (resp *http.Response, err error) {
} }
} }
return nil, err return nil, fmt.Errorf("请求失败:%v", err)
} }

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()
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
}

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

View File

@ -3,6 +3,7 @@ package validator
type ShortLinkGenValidator struct { type ShortLinkGenValidator struct {
Url string `form:"url" binding:"required"` Url string `form:"url" binding:"required"`
Password string `form:"password"` Password string `form:"password"`
CustomID string `form:"customId"`
} }
type GetUrlValidator struct { type GetUrlValidator struct {