subjson and multidomain
This commit is contained in:
@@ -15,6 +15,7 @@ type APIHandler struct {
|
||||
service.ConfigService
|
||||
service.ClientService
|
||||
service.TlsService
|
||||
service.InDataService
|
||||
service.PanelService
|
||||
service.StatsService
|
||||
service.ServerService
|
||||
@@ -206,6 +207,10 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
inData, err := a.InDataService.GetAll()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -213,6 +218,7 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
|
||||
data["config"] = *config
|
||||
data["clients"] = clients
|
||||
data["tls"] = tlsConfigs
|
||||
data["inData"] = inData
|
||||
data["subURI"] = subURI
|
||||
data["onlines"] = onlines
|
||||
} else {
|
||||
|
||||
@@ -60,6 +60,7 @@ func InitDB(dbPath string) error {
|
||||
err = db.AutoMigrate(
|
||||
&model.Setting{},
|
||||
&model.Tls{},
|
||||
&model.InboundData{},
|
||||
&model.User{},
|
||||
&model.Stats{},
|
||||
&model.Client{},
|
||||
|
||||
@@ -16,6 +16,13 @@ type Tls struct {
|
||||
Client json.RawMessage `json:"client" form:"client"`
|
||||
}
|
||||
|
||||
type InboundData struct {
|
||||
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Tag string `json:"tag" form:"tag"`
|
||||
Addrs json.RawMessage `json:"addrs" form:"addrs"`
|
||||
OutJson json.RawMessage `json:"outJson" form:"outJson"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Username string `json:"username" form:"username"`
|
||||
|
||||
@@ -18,6 +18,7 @@ var LastUpdate int64
|
||||
type ConfigService struct {
|
||||
ClientService
|
||||
TlsService
|
||||
InDataService
|
||||
singbox.Controller
|
||||
SettingService
|
||||
}
|
||||
@@ -80,7 +81,7 @@ func (s *ConfigService) GetConfig() (*SingBoxConfig, error) {
|
||||
|
||||
func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) error {
|
||||
var err error
|
||||
var clientChanges, tlsChanges, settingChanges, configChanges []model.Changes
|
||||
var clientChanges, tlsChanges, inChanges, settingChanges, configChanges []model.Changes
|
||||
if _, ok := changes["clients"]; ok {
|
||||
err = json.Unmarshal([]byte(changes["clients"]), &clientChanges)
|
||||
if err != nil {
|
||||
@@ -93,6 +94,12 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, ok := changes["inData"]; ok {
|
||||
err = json.Unmarshal([]byte(changes["inData"]), &inChanges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, ok := changes["settings"]; ok {
|
||||
err = json.Unmarshal([]byte(changes["settings"]), &settingChanges)
|
||||
if err != nil {
|
||||
@@ -128,6 +135,12 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(inChanges) > 0 {
|
||||
err = s.InDataService.Save(tx, inChanges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(settingChanges) > 0 {
|
||||
err = s.SettingService.Save(tx, settingChanges)
|
||||
if err != nil {
|
||||
@@ -185,14 +198,19 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
|
||||
|
||||
// Log changes
|
||||
dt := time.Now().Unix()
|
||||
allChanges := append(append(clientChanges, settingChanges...), append(configChanges, tlsChanges...)...)
|
||||
for index := range allChanges {
|
||||
allChanges[index].DateTime = dt
|
||||
allChanges[index].Actor = loginUser
|
||||
}
|
||||
err = tx.Model(model.Changes{}).Create(&allChanges).Error
|
||||
if err != nil {
|
||||
return err
|
||||
allChanges := append(clientChanges, settingChanges...)
|
||||
allChanges = append(allChanges, configChanges...)
|
||||
allChanges = append(allChanges, tlsChanges...)
|
||||
allChanges = append(allChanges, inChanges...)
|
||||
if len(allChanges) > 0 {
|
||||
for index := range allChanges {
|
||||
allChanges[index].DateTime = dt
|
||||
allChanges[index].Actor = loginUser
|
||||
}
|
||||
err = tx.Model(model.Changes{}).Create(&allChanges).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
LastUpdate = dt
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"s-ui/database"
|
||||
"s-ui/database/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type InDataService struct {
|
||||
}
|
||||
|
||||
func (s *InDataService) GetAll() ([]model.InboundData, error) {
|
||||
db := database.GetDB()
|
||||
inData := []model.InboundData{}
|
||||
err := db.Model(model.InboundData{}).Scan(&inData).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return inData, nil
|
||||
}
|
||||
|
||||
func (s *InDataService) Save(tx *gorm.DB, changes []model.Changes) error {
|
||||
var err error
|
||||
for _, change := range changes {
|
||||
inData := model.InboundData{}
|
||||
err = json.Unmarshal(change.Obj, &inData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch change.Action {
|
||||
case "new":
|
||||
err = tx.Create(&inData).Error
|
||||
case "del":
|
||||
err = tx.Where("id = ?", change.Index).Delete(model.InboundData{}).Error
|
||||
default:
|
||||
err = tx.Save(inData).Error
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -36,6 +36,7 @@ var defaultValueMap = map[string]string{
|
||||
"subEncode": "true",
|
||||
"subShowInfo": "false",
|
||||
"subURI": "",
|
||||
"subJsonExt": "",
|
||||
}
|
||||
|
||||
type SettingService struct {
|
||||
@@ -65,7 +66,7 @@ func (s *SettingService) GetAllSetting() (*map[string]string, error) {
|
||||
}
|
||||
|
||||
// Due to security principles
|
||||
delete(allSetting, "webSecret")
|
||||
delete(allSetting, "secret")
|
||||
|
||||
return &allSetting, nil
|
||||
}
|
||||
@@ -347,6 +348,10 @@ func (s *SettingService) Save(tx *gorm.DB, changes []model.Changes) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubJsonExt() (string, error) {
|
||||
return s.getString("subJsonExt")
|
||||
}
|
||||
|
||||
func (s *SettingService) fileExists(path string) error {
|
||||
_, err := os.Stat(path)
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"s-ui/database"
|
||||
"s-ui/database/model"
|
||||
"s-ui/service"
|
||||
"s-ui/util"
|
||||
)
|
||||
|
||||
const defaultJson = `
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"mtu": 9000,
|
||||
"auto_route": true,
|
||||
"strict_route": false,
|
||||
"sniff": true,
|
||||
"endpoint_independent_nat": false,
|
||||
"stack": "system",
|
||||
"platform": {
|
||||
"http_proxy": {
|
||||
"enabled": true,
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 2080
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "mixed",
|
||||
"listen": "127.0.0.1",
|
||||
"listen_port": 2080,
|
||||
"sniff": true,
|
||||
"users": []
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
type JsonService struct {
|
||||
service.SettingService
|
||||
LinkService
|
||||
}
|
||||
|
||||
func (j *JsonService) GetJson(subId string, format string) (*string, error) {
|
||||
var jsonConfig map[string]interface{}
|
||||
|
||||
client, inDatas, err := j.getData(subId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
outbounds, outTags, err := j.getOutbounds(client.Config, inDatas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
links := j.LinkService.GetLinks(&client.Links, "external", "")
|
||||
for index, link := range links {
|
||||
json, tag, err := util.GetOutbound(link, index)
|
||||
if err == nil && len(tag) > 0 {
|
||||
*outbounds = append(*outbounds, *json)
|
||||
*outTags = append(*outTags, tag)
|
||||
}
|
||||
}
|
||||
|
||||
j.addDefaultOutbounds(outbounds, outTags)
|
||||
|
||||
err = json.Unmarshal([]byte(defaultJson), &jsonConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
jsonConfig["outbounds"] = outbounds
|
||||
|
||||
// Add other objects from settings
|
||||
j.addOthers(&jsonConfig)
|
||||
|
||||
result, _ := json.MarshalIndent(jsonConfig, " ", " ")
|
||||
resultStr := string(result)
|
||||
return &resultStr, nil
|
||||
}
|
||||
|
||||
func (j *JsonService) getData(subId string) (*model.Client, *[]model.InboundData, error) {
|
||||
db := database.GetDB()
|
||||
client := &model.Client{}
|
||||
err := db.Model(model.Client{}).Where("enable = true and name = ?", subId).First(client).Error
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var inbounds []string
|
||||
err = json.Unmarshal(client.Inbounds, &inbounds)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
inDatas := &[]model.InboundData{}
|
||||
err = db.Model(model.InboundData{}).Where("tag in ?", inbounds).Find(&inDatas).Error
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return client, inDatas, nil
|
||||
}
|
||||
|
||||
func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inDatas *[]model.InboundData) (*[]map[string]interface{}, *[]string, error) {
|
||||
var outbounds []map[string]interface{}
|
||||
var configs map[string]interface{}
|
||||
var outTags []string
|
||||
|
||||
err := json.Unmarshal(clientConfig, &configs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, inData := range *inDatas {
|
||||
if len(inData.OutJson) < 5 {
|
||||
continue
|
||||
}
|
||||
var outbound map[string]interface{}
|
||||
err = json.Unmarshal(inData.OutJson, &outbound)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
protocol, _ := outbound["type"].(string)
|
||||
config, _ := configs[protocol].(map[string]interface{})
|
||||
for key, value := range config {
|
||||
if key != "alterId" && key != "name" && key != "username" {
|
||||
outbound[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
var addrs []map[string]interface{}
|
||||
err = json.Unmarshal(inData.Addrs, &addrs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
tag := outbound["tag"].(string)
|
||||
if len(addrs) == 0 {
|
||||
outTags = append(outTags, tag)
|
||||
outbounds = append(outbounds, outbound)
|
||||
} else {
|
||||
for index, addr := range addrs {
|
||||
// Copy original config
|
||||
newOut := make(map[string]interface{}, len(outbound))
|
||||
for key, value := range outbound {
|
||||
newOut[key] = value
|
||||
}
|
||||
// Change and push copied config
|
||||
newOut["server"] = addr["server"].(string)
|
||||
port := addr["server_port"].(float64)
|
||||
newOut["server_port"] = int(port)
|
||||
newTag := fmt.Sprintf("%d.%s", index+1, tag)
|
||||
outTags = append(outTags, newTag)
|
||||
newOut["tag"] = newTag
|
||||
outbounds = append(outbounds, newOut)
|
||||
}
|
||||
}
|
||||
}
|
||||
return &outbounds, &outTags, nil
|
||||
}
|
||||
|
||||
func (j *JsonService) addDefaultOutbounds(outbounds *[]map[string]interface{}, outTags *[]string) {
|
||||
outbound := []map[string]interface{}{
|
||||
{
|
||||
"outbounds": append([]string{"auto", "direct"}, *outTags...),
|
||||
"tag": "proxy",
|
||||
"type": "selector",
|
||||
},
|
||||
{
|
||||
"tag": "auto",
|
||||
"type": "urltest",
|
||||
"outbounds": outTags,
|
||||
"url": "http://www.gstatic.com/generate_204",
|
||||
"interval": "10m",
|
||||
"tolerance": 50,
|
||||
},
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct",
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns-out",
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block",
|
||||
},
|
||||
}
|
||||
*outbounds = append(outbound, *outbounds...)
|
||||
}
|
||||
|
||||
func (j *JsonService) addOthers(jsonConfig *map[string]interface{}) error {
|
||||
rules := []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": []interface{}{
|
||||
map[string]interface{}{
|
||||
"port": 53,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"protocol": "dns",
|
||||
},
|
||||
},
|
||||
"outbound": "dns-out",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"clash_mode": "Direct",
|
||||
"outbound": "direct",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"clash_mode": "Global",
|
||||
"outbound": "proxy",
|
||||
},
|
||||
}
|
||||
route := map[string]interface{}{
|
||||
"auto_detect_interface": true,
|
||||
"final": "proxy",
|
||||
"rules": rules,
|
||||
}
|
||||
|
||||
othersStr, err := j.SettingService.GetSubJsonExt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(othersStr) == 0 {
|
||||
(*jsonConfig)["route"] = route
|
||||
return nil
|
||||
}
|
||||
var othersJson map[string]interface{}
|
||||
err = json.Unmarshal([]byte(othersStr), &othersJson)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := othersJson["log"]; ok {
|
||||
(*jsonConfig)["log"] = othersJson["log"]
|
||||
}
|
||||
if _, ok := othersJson["dns"]; ok {
|
||||
(*jsonConfig)["dns"] = othersJson["dns"]
|
||||
}
|
||||
if _, ok := othersJson["experimental"]; ok {
|
||||
(*jsonConfig)["experimental"] = othersJson["lexperimentalog"]
|
||||
}
|
||||
if _, ok := othersJson["rule_set"]; ok {
|
||||
route["rule_set"] = othersJson["rule_set"]
|
||||
}
|
||||
if settingRules, ok := othersJson["rules"].([]interface{}); ok {
|
||||
route["rules"] = append(rules, settingRules...)
|
||||
}
|
||||
(*jsonConfig)["route"] = route
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"s-ui/logger"
|
||||
"s-ui/util"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Link struct {
|
||||
Type string `json:"type"`
|
||||
Remark string `json:"remark"`
|
||||
Uri string `json:"uri"`
|
||||
}
|
||||
|
||||
type LinkService struct {
|
||||
}
|
||||
|
||||
func (s *LinkService) GetLinks(linkJson *json.RawMessage, types string, clientInfo string) []string {
|
||||
links := []Link{}
|
||||
var result []string
|
||||
err := json.Unmarshal(*linkJson, &links)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, link := range links {
|
||||
switch link.Type {
|
||||
case "external":
|
||||
result = append(result, link.Uri)
|
||||
case "sub":
|
||||
result = append(result, s.getExternalSub(link.Uri)...)
|
||||
case "local":
|
||||
if types == "all" {
|
||||
result = append(result, s.addClientInfo(link.Uri, clientInfo))
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *LinkService) addClientInfo(uri string, clientInfo string) string {
|
||||
protocol := strings.Split(uri, "://")
|
||||
if len(protocol) < 2 {
|
||||
return uri
|
||||
}
|
||||
switch protocol[0] {
|
||||
case "vmess":
|
||||
var vmessJson map[string]interface{}
|
||||
config, err := util.B64StrToByte(protocol[1])
|
||||
if err != nil {
|
||||
logger.Warning("sub: Error decoding vmess content:", err)
|
||||
return uri
|
||||
}
|
||||
err = json.Unmarshal(config, &vmessJson)
|
||||
if err != nil {
|
||||
logger.Warning("sub: Error decoding vmess content:", err)
|
||||
return uri
|
||||
}
|
||||
vmessJson["ps"] = vmessJson["ps"].(string) + clientInfo
|
||||
result, err := json.MarshalIndent(vmessJson, "", " ")
|
||||
if err != nil {
|
||||
logger.Warning("sub: Error decoding vmess + clientInfo content:", err)
|
||||
return uri
|
||||
}
|
||||
return "vmess://" + util.ByteToB64Str(result)
|
||||
default:
|
||||
return uri + clientInfo
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LinkService) getExternalSub(url string) []string {
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
// Make the HTTP request
|
||||
response, err := client.Get(url)
|
||||
if err != nil {
|
||||
logger.Warning("sub: Error making HTTP request:", err)
|
||||
return nil
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
// Read the response body
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
logger.Warning("sub: Error reading response body:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert if the content is Base64 encoded
|
||||
links := util.StrOrBase64Encoded(string(body))
|
||||
return strings.Split(links, "\n")
|
||||
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
type SubHandler struct {
|
||||
service.SettingService
|
||||
SubService
|
||||
JsonService
|
||||
}
|
||||
|
||||
func NewSubHandler(g *gin.RouterGroup) {
|
||||
@@ -23,17 +24,28 @@ func (s *SubHandler) initRouter(g *gin.RouterGroup) {
|
||||
|
||||
func (s *SubHandler) subs(c *gin.Context) {
|
||||
subId := c.Param("subid")
|
||||
result, headers, err := s.SubService.GetSubs(subId)
|
||||
if err != nil || result == nil {
|
||||
logger.Error(err)
|
||||
c.String(400, "Error!")
|
||||
format, isFormat := c.GetQuery("format")
|
||||
if isFormat {
|
||||
result, err := s.JsonService.GetJson(subId, format)
|
||||
if err != nil || result == nil {
|
||||
logger.Error(err)
|
||||
c.String(400, "Error!")
|
||||
} else {
|
||||
c.String(200, *result)
|
||||
}
|
||||
} else {
|
||||
result, headers, err := s.SubService.GetSubs(subId)
|
||||
if err != nil || result == nil {
|
||||
logger.Error(err)
|
||||
c.String(400, "Error!")
|
||||
} else {
|
||||
|
||||
// Add headers
|
||||
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
|
||||
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
|
||||
c.Writer.Header().Set("Profile-Title", headers[2])
|
||||
// Add headers
|
||||
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
|
||||
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
|
||||
c.Writer.Header().Set("Profile-Title", headers[2])
|
||||
|
||||
c.String(200, *result)
|
||||
c.String(200, *result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-102
@@ -1,15 +1,10 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"s-ui/database"
|
||||
"s-ui/database/model"
|
||||
"s-ui/logger"
|
||||
"s-ui/service"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -17,12 +12,7 @@ import (
|
||||
|
||||
type SubService struct {
|
||||
service.SettingService
|
||||
}
|
||||
|
||||
type Link struct {
|
||||
Type string `json:"type"`
|
||||
Remark string `json:"remark"`
|
||||
Uri string `json:"uri"`
|
||||
LinkService
|
||||
}
|
||||
|
||||
func (s *SubService) GetSubs(subId string) (*string, []string, error) {
|
||||
@@ -35,29 +25,14 @@ func (s *SubService) GetSubs(subId string) (*string, []string, error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
links := []Link{}
|
||||
err = json.Unmarshal([]byte(client.Links), &links)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
clientInfo := ""
|
||||
subShowInfo, _ := s.SettingService.GetSubShowInfo()
|
||||
if subShowInfo {
|
||||
clientInfo = s.getClientInfo(client)
|
||||
}
|
||||
|
||||
var result string
|
||||
for _, link := range links {
|
||||
switch link.Type {
|
||||
case "external":
|
||||
result += fmt.Sprintln(link.Uri)
|
||||
case "sub":
|
||||
result += s.getExternalSub(link.Uri)
|
||||
case "local":
|
||||
result += fmt.Sprintln(s.addClientInfo(link.Uri, clientInfo))
|
||||
}
|
||||
}
|
||||
linksArray := s.LinkService.GetLinks(&client.Links, "all", clientInfo)
|
||||
result := strings.Join(linksArray, "\n")
|
||||
|
||||
var headers []string
|
||||
updateInterval, _ := s.SettingService.GetSubUpdates()
|
||||
@@ -90,80 +65,6 @@ func (s *SubService) getClientInfo(c *model.Client) string {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SubService) addClientInfo(uri string, clientInfo string) string {
|
||||
protocol := strings.Split(uri, "://")
|
||||
if len(protocol) < 2 {
|
||||
return uri
|
||||
}
|
||||
switch protocol[0] {
|
||||
case "vmess":
|
||||
var vmessJson map[string]interface{}
|
||||
config, err := base64.StdEncoding.DecodeString(protocol[1])
|
||||
if err != nil {
|
||||
logger.Warning("sub: Error decoding vmess content:", err)
|
||||
return uri
|
||||
}
|
||||
err = json.Unmarshal(config, &vmessJson)
|
||||
if err != nil {
|
||||
logger.Warning("sub: Error decoding vmess content:", err)
|
||||
return uri
|
||||
}
|
||||
vmessJson["ps"] = vmessJson["ps"].(string) + clientInfo
|
||||
result, err := json.MarshalIndent(vmessJson, "", " ")
|
||||
if err != nil {
|
||||
logger.Warning("sub: Error decoding vmess + clientInfo content:", err)
|
||||
return uri
|
||||
}
|
||||
return "vmess://" + base64.StdEncoding.EncodeToString(result)
|
||||
default:
|
||||
return uri + clientInfo
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SubService) getExternalSub(url string) string {
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
// Make the HTTP request
|
||||
response, err := client.Get(url)
|
||||
if err != nil {
|
||||
logger.Warning("sub: Error making HTTP request:", err)
|
||||
return ""
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
// Read the response body
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
logger.Warning("sub: Error reading response body:", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
// Check if the content is Base64 encoded
|
||||
isBase64 := s.isBase64Encoded(string(body))
|
||||
if isBase64 {
|
||||
// Decode Base64 content
|
||||
decodedText, err := base64.StdEncoding.DecodeString(string(body))
|
||||
if err != nil {
|
||||
logger.Warning("sub: Error decoding Base64 content:", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(decodedText)
|
||||
} else {
|
||||
return string(body)
|
||||
}
|
||||
}
|
||||
|
||||
// Function to check if a string is Base64 encoded
|
||||
func (s *SubService) isBase64Encoded(str string) bool {
|
||||
_, err := base64.StdEncoding.DecodeString(str)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (s *SubService) formatTraffic(trafficBytes int64) string {
|
||||
if trafficBytes < 1024 {
|
||||
return fmt.Sprintf("%.2fB", float64(trafficBytes)/float64(1))
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package util
|
||||
|
||||
import "encoding/base64"
|
||||
|
||||
// Function to return decoded bytes if a string is Base64 encoded
|
||||
func StrOrBase64Encoded(str string) string {
|
||||
decoded, err := base64.StdEncoding.DecodeString(str)
|
||||
if err == nil {
|
||||
return string(decoded)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func B64StrToByte(str string) ([]byte, error) {
|
||||
return base64.StdEncoding.DecodeString(str)
|
||||
}
|
||||
|
||||
func ByteToB64Str(b []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"s-ui/util/common"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetOutbound(uri string, i int) (*map[string]interface{}, string, error) {
|
||||
u, err := url.Parse(uri)
|
||||
if err == nil {
|
||||
switch u.Scheme {
|
||||
case "vmess":
|
||||
return vmess(u.Host, i)
|
||||
case "vless":
|
||||
return vless(u, i)
|
||||
case "trojan":
|
||||
return trojan(u, i)
|
||||
case "hy", "hysteria":
|
||||
return hy(u, i)
|
||||
case "hy2", "hysteria2":
|
||||
return hy2(u, i)
|
||||
case "tuic":
|
||||
return tuic(u, i)
|
||||
case "ss", "shadowsocks":
|
||||
return ss(u, i)
|
||||
}
|
||||
}
|
||||
return nil, "", common.NewError("Unsupported link format")
|
||||
}
|
||||
|
||||
func vmess(data string, i int) (*map[string]interface{}, string, error) {
|
||||
dataByte, err := B64StrToByte(data)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
var dataJson map[string]interface{}
|
||||
err = json.Unmarshal(dataByte, &dataJson)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
transport := map[string]interface{}{}
|
||||
tp_net, _ := dataJson["net"].(string)
|
||||
tp_type, _ := dataJson["type"].(string)
|
||||
tp_host, _ := dataJson["host"].(string)
|
||||
tp_path, _ := dataJson["path"].(string)
|
||||
switch strings.ToLower(tp_net) {
|
||||
case "tcp", "":
|
||||
if tp_type == "http" {
|
||||
transport["type"] = tp_type
|
||||
if len(tp_host) > 0 {
|
||||
transport["host"] = strings.Split(tp_host, ",")
|
||||
}
|
||||
transport["path"] = tp_path
|
||||
}
|
||||
case "http", "h2":
|
||||
transport["type"] = "http"
|
||||
if len(tp_host) > 0 {
|
||||
transport["host"] = strings.Split(tp_host, ",")
|
||||
}
|
||||
transport["path"] = tp_path
|
||||
case "ws":
|
||||
transport["type"] = tp_net
|
||||
transport["path"] = tp_path
|
||||
transport["early_data_header_name"] = "Sec-WebSocket-Protocol"
|
||||
if len(tp_host) > 0 {
|
||||
transport["headers"] = map[string]interface{}{
|
||||
"Host": tp_host,
|
||||
}
|
||||
}
|
||||
case "quic":
|
||||
transport["type"] = tp_net
|
||||
case "grpc":
|
||||
transport["type"] = tp_net
|
||||
transport["service_name"] = tp_path
|
||||
case "httpupgrade":
|
||||
transport["type"] = tp_net
|
||||
transport["path"] = tp_path
|
||||
transport["host"] = tp_host
|
||||
default:
|
||||
return nil, "", common.NewError("Invalid vmess")
|
||||
}
|
||||
tls := map[string]interface{}{}
|
||||
vmess_tls, _ := dataJson["tls"].(string)
|
||||
if vmess_tls == "tls" {
|
||||
tls["enabled"] = true
|
||||
tls_sni, _ := dataJson["sni"].(string)
|
||||
tls_alpn, _ := dataJson["alpn"].(string)
|
||||
_, tls_insecure := dataJson["allowInsecure"]
|
||||
tls_fp, _ := dataJson["fp"].(string)
|
||||
if len(tls_sni) > 0 {
|
||||
tls["server_name"] = tls_sni
|
||||
}
|
||||
if len(tls_alpn) > 0 {
|
||||
tls["alpn"] = strings.Split(tls_alpn, ",")
|
||||
}
|
||||
if tls_insecure {
|
||||
tls["insecure"] = true
|
||||
}
|
||||
if len(tls_fp) > 0 {
|
||||
tls["utls"] = map[string]interface{}{
|
||||
"enabled": true,
|
||||
"fingerprint": tls_fp,
|
||||
}
|
||||
}
|
||||
}
|
||||
tag, _ := dataJson["ps"].(string)
|
||||
tag = fmt.Sprintf("%d.%s", i, tag)
|
||||
alter_id, ok := dataJson["aid"].(int)
|
||||
if !ok {
|
||||
alter_id = 0
|
||||
}
|
||||
vmess := map[string]interface{}{
|
||||
"type": "vmess",
|
||||
"tag": tag,
|
||||
"server": dataJson["add"],
|
||||
"server_port": dataJson["port"],
|
||||
"uuid": dataJson["id"],
|
||||
"security": "auto",
|
||||
"alter_id": alter_id,
|
||||
"tls": tls,
|
||||
"transport": transport,
|
||||
}
|
||||
return &vmess, tag, err
|
||||
}
|
||||
|
||||
func vless(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||
query, _ := url.ParseQuery(u.RawQuery)
|
||||
security := query.Get("security")
|
||||
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||
port := 80
|
||||
if len(portStr) > 0 {
|
||||
port, _ = strconv.Atoi(portStr)
|
||||
} else {
|
||||
if security == "tls" || security == "reality" {
|
||||
port = 443
|
||||
}
|
||||
}
|
||||
tp_type := query.Get("type")
|
||||
tag := fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||
vless := map[string]interface{}{
|
||||
"type": "vless",
|
||||
"tag": tag,
|
||||
"server": host,
|
||||
"server_port": port,
|
||||
"uuid": u.User.Username(),
|
||||
"flow": query.Get("flow"),
|
||||
"tls": getTls(security, &query),
|
||||
"transport": getTransport(tp_type, &query),
|
||||
}
|
||||
return &vless, tag, nil
|
||||
}
|
||||
|
||||
func trojan(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||
query, _ := url.ParseQuery(u.RawQuery)
|
||||
security := query.Get("security")
|
||||
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||
port := 80
|
||||
if len(portStr) > 0 {
|
||||
port, _ = strconv.Atoi(portStr)
|
||||
} else {
|
||||
if security == "tls" || security == "reality" {
|
||||
port = 443
|
||||
}
|
||||
}
|
||||
tp_type := query.Get("type")
|
||||
tag := fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||
trojan := map[string]interface{}{
|
||||
"type": "trojan",
|
||||
"tag": tag,
|
||||
"server": host,
|
||||
"server_port": port,
|
||||
"password": u.User.Username(),
|
||||
"tls": getTls(security, &query),
|
||||
"transport": getTransport(tp_type, &query),
|
||||
}
|
||||
return &trojan, tag, nil
|
||||
}
|
||||
|
||||
func hy(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||
query, _ := url.ParseQuery(u.RawQuery)
|
||||
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||
port := 443
|
||||
if len(portStr) > 0 {
|
||||
port, _ = strconv.Atoi(portStr)
|
||||
}
|
||||
|
||||
tls := map[string]interface{}{
|
||||
"enabled": true,
|
||||
"server_name": query.Get("peer"),
|
||||
}
|
||||
alpn := query.Get("alpn")
|
||||
insecure := query.Get("insecure")
|
||||
if len(alpn) > 0 {
|
||||
tls["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
if insecure == "1" || insecure == "true" {
|
||||
tls["insecure"] = true
|
||||
}
|
||||
|
||||
tag := fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||
hy := map[string]interface{}{
|
||||
"type": "hysteria",
|
||||
"tag": tag,
|
||||
"server": host,
|
||||
"server_port": port,
|
||||
"obfs": query.Get("obfsParam"),
|
||||
"auth_str": query.Get("auth"),
|
||||
"tls": tls,
|
||||
}
|
||||
down, _ := strconv.Atoi(query.Get("downmbps"))
|
||||
up, _ := strconv.Atoi(query.Get("upmbps"))
|
||||
recv_window_conn, _ := strconv.Atoi(query.Get("recv_window_conn"))
|
||||
recv_window, _ := strconv.Atoi(query.Get("recv_window"))
|
||||
if down > 0 {
|
||||
hy["down_mbps"] = down
|
||||
}
|
||||
if up > 0 {
|
||||
hy["up_mbps"] = up
|
||||
}
|
||||
if recv_window_conn > 0 {
|
||||
hy["recv_window_conn"] = recv_window_conn
|
||||
}
|
||||
if recv_window > 0 {
|
||||
hy["recv_window"] = recv_window
|
||||
}
|
||||
return &hy, tag, nil
|
||||
}
|
||||
|
||||
func hy2(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||
query, _ := url.ParseQuery(u.RawQuery)
|
||||
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||
port := 443
|
||||
if len(portStr) > 0 {
|
||||
port, _ = strconv.Atoi(portStr)
|
||||
}
|
||||
|
||||
tls := map[string]interface{}{
|
||||
"enabled": true,
|
||||
"server_name": query.Get("sni"),
|
||||
}
|
||||
alpn := query.Get("alpn")
|
||||
insecure := query.Get("insecure")
|
||||
if len(alpn) > 0 {
|
||||
tls["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
if insecure == "1" || insecure == "true" {
|
||||
tls["insecure"] = true
|
||||
}
|
||||
|
||||
tag := fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||
hy2 := map[string]interface{}{
|
||||
"type": "hysteria2",
|
||||
"tag": tag,
|
||||
"server": host,
|
||||
"server_port": port,
|
||||
"password": u.User.Username(),
|
||||
"tls": tls,
|
||||
}
|
||||
down, _ := strconv.Atoi(query.Get("downmbps"))
|
||||
up, _ := strconv.Atoi(query.Get("upmbps"))
|
||||
obfs := query.Get("obfs")
|
||||
if down > 0 {
|
||||
hy2["down_mbps"] = down
|
||||
}
|
||||
if up > 0 {
|
||||
hy2["up_mbps"] = up
|
||||
}
|
||||
if obfs == "salamander" {
|
||||
hy2["obfs"] = map[string]interface{}{
|
||||
"type": "salamander",
|
||||
"password": query.Get("obfs-password"),
|
||||
}
|
||||
}
|
||||
return &hy2, tag, nil
|
||||
}
|
||||
|
||||
func tuic(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||
query, _ := url.ParseQuery(u.RawQuery)
|
||||
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||
port := 443
|
||||
if len(portStr) > 0 {
|
||||
port, _ = strconv.Atoi(portStr)
|
||||
}
|
||||
|
||||
tls := map[string]interface{}{
|
||||
"enabled": true,
|
||||
"server_name": query.Get("sni"),
|
||||
}
|
||||
alpn := query.Get("alpn")
|
||||
insecure := query.Get("allow_insecure")
|
||||
disable_sni := query.Get("disable_sni")
|
||||
if len(alpn) > 0 {
|
||||
tls["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
if insecure == "1" || insecure == "true" {
|
||||
tls["insecure"] = true
|
||||
}
|
||||
if disable_sni == "1" || disable_sni == "true" {
|
||||
tls["disable_sni"] = true
|
||||
}
|
||||
|
||||
tag := fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||
password, _ := u.User.Password()
|
||||
tuic := map[string]interface{}{
|
||||
"type": "tuic",
|
||||
"tag": tag,
|
||||
"server": host,
|
||||
"server_port": port,
|
||||
"uuid": u.User.Username(),
|
||||
"password": password,
|
||||
"congestion_control": query.Get("congestion_control"),
|
||||
"udp_relay_mode": query.Get("udp_relay_mode"),
|
||||
"tls": tls,
|
||||
}
|
||||
return &tuic, tag, nil
|
||||
}
|
||||
|
||||
func ss(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||
query, _ := url.ParseQuery(u.RawQuery)
|
||||
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||
port := 443
|
||||
if len(portStr) > 0 {
|
||||
port, _ = strconv.Atoi(portStr)
|
||||
}
|
||||
method := u.User.Username()
|
||||
password, ok := u.User.Password()
|
||||
if !ok {
|
||||
decrypted := StrOrBase64Encoded(method)
|
||||
decrypted_arr := strings.Split(decrypted, ":")
|
||||
if len(decrypted_arr) > 1 {
|
||||
method = decrypted_arr[0]
|
||||
password = strings.Join(decrypted_arr[1:], ":")
|
||||
} else {
|
||||
return nil, "", common.NewError("Unsupported shadowsocks")
|
||||
}
|
||||
}
|
||||
|
||||
tag := fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||
ss := map[string]interface{}{
|
||||
"type": "shadowsocks",
|
||||
"tag": tag,
|
||||
"server": host,
|
||||
"server_port": port,
|
||||
"method": method,
|
||||
"password": password,
|
||||
}
|
||||
|
||||
v2ray_type := query.Get("type")
|
||||
if len(v2ray_type) > 0 {
|
||||
pl_arr := []string{}
|
||||
host_header := query.Get("host")
|
||||
if query.Get("security") == "tls" {
|
||||
pl_arr = append(pl_arr, "tls")
|
||||
}
|
||||
if v2ray_type == "quic" {
|
||||
pl_arr = append(pl_arr, "mode=quic")
|
||||
}
|
||||
if len(host_header) > 0 {
|
||||
pl_arr = append(pl_arr, "host="+host_header)
|
||||
}
|
||||
ss["plugin"] = "v2ray-plugin"
|
||||
ss["plugin_opts"] = strings.Join(pl_arr, ";")
|
||||
}
|
||||
plugin := query.Get("plugin")
|
||||
if len(plugin) > 0 {
|
||||
pl_arr := strings.Split(plugin, ";")
|
||||
if len(pl_arr) > 0 {
|
||||
ss["plugin"] = pl_arr[0]
|
||||
ss["plugin_opts"] = strings.Join(pl_arr[1:], ";")
|
||||
}
|
||||
}
|
||||
return &ss, tag, nil
|
||||
}
|
||||
|
||||
func getTransport(tp_type string, q *url.Values) *map[string]interface{} {
|
||||
transport := map[string]interface{}{}
|
||||
tp_host := q.Get("host")
|
||||
tp_path := q.Get("path")
|
||||
switch strings.ToLower(tp_type) {
|
||||
case "tcp", "":
|
||||
if q.Get("headerType") == "http" {
|
||||
transport["type"] = "http"
|
||||
if len(tp_host) > 0 {
|
||||
transport["host"] = strings.Split(tp_host, ",")
|
||||
}
|
||||
transport["path"] = tp_path
|
||||
}
|
||||
case "http", "h2":
|
||||
transport["type"] = "http"
|
||||
if len(tp_host) > 0 {
|
||||
transport["host"] = strings.Split(tp_host, ",")
|
||||
}
|
||||
transport["path"] = tp_path
|
||||
case "ws":
|
||||
transport["type"] = "ws"
|
||||
transport["path"] = tp_path
|
||||
if len(tp_host) > 0 {
|
||||
transport["headers"] = map[string]interface{}{
|
||||
"Host": tp_host,
|
||||
}
|
||||
}
|
||||
case "quic":
|
||||
transport["type"] = "quic"
|
||||
case "grpc":
|
||||
transport["type"] = "grpc"
|
||||
transport["service_name"] = q.Get("serviceName")
|
||||
case "httpupgrade":
|
||||
transport["type"] = "httpupgrade"
|
||||
transport["path"] = tp_path
|
||||
transport["host"] = tp_host
|
||||
}
|
||||
return &transport
|
||||
}
|
||||
|
||||
func getTls(security string, q *url.Values) *map[string]interface{} {
|
||||
tls := map[string]interface{}{}
|
||||
tls_fp := q.Get("fp")
|
||||
tls_sni := q.Get("sni")
|
||||
tls_insecure := q.Get("allowInsecure")
|
||||
tls_alpn := q.Get("alpn")
|
||||
switch security {
|
||||
case "tls":
|
||||
tls["enabled"] = true
|
||||
case "reality":
|
||||
tls["enabled"] = true
|
||||
tls["reality"] = map[string]interface{}{
|
||||
"enabled": true,
|
||||
"public_key": q.Get("pbk"),
|
||||
"short_id": q.Get("sid"),
|
||||
}
|
||||
}
|
||||
if len(tls_sni) > 0 {
|
||||
tls["server_name"] = tls_sni
|
||||
}
|
||||
if len(tls_alpn) > 0 {
|
||||
tls["alpn"] = strings.Split(tls_alpn, ",")
|
||||
}
|
||||
if tls_insecure == "1" || tls_insecure == "true" {
|
||||
tls["insecure"] = true
|
||||
}
|
||||
if len(tls_fp) > 0 {
|
||||
tls["utls"] = map[string]interface{}{
|
||||
"enabled": true,
|
||||
"fingerprint": tls_fp,
|
||||
}
|
||||
}
|
||||
return &tls
|
||||
}
|
||||
Reference in New Issue
Block a user