separate frontend repository

This commit is contained in:
Alireza Ahmadi
2025-01-28 23:57:53 +01:00
parent f4b1b09362
commit 7faa28a89d
194 changed files with 48 additions and 19256 deletions
+277
View File
@@ -0,0 +1,277 @@
package sub
import (
"encoding/json"
"fmt"
"s-ui/database"
"s-ui/database/model"
"s-ui/service"
"s-ui/util"
)
const defaultJson = `
{
"inbounds": [
{
"type": "tun",
"address": [
"172.19.0.1/30",
"fdfe:dcba:9876::1/126"
],
"mtu": 9000,
"auto_route": true,
"strict_route": false,
"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,
"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.Inbound, 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 clientInbounds []uint
err = json.Unmarshal(client.Inbounds, &clientInbounds)
if err != nil {
return nil, nil, err
}
var inbounds []*model.Inbound
err = db.Model(model.Inbound{}).Where("id in ?", clientInbounds).Find(&inbounds).Error
if err != nil {
return nil, nil, err
}
return client, inbounds, nil
}
func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inbounds []*model.Inbound) (*[]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 inbounds {
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" {
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 {
// For mixed protocol, use separated socks and http
if protocol == "mixed" {
outbound["tag"] = tag
j.pushMixed(&outbounds, &outTags, outbound)
} else {
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)
// Override TLS
outTls, _ := newOut["tls"].(map[string]interface{})
if addrTls, ok := addr["tls"].(map[string]interface{}); ok {
for key, value := range addrTls {
outTls[key] = value
}
}
newOut["tls"] = outTls
remark, _ := addr["remark"].(string)
newTag := fmt.Sprintf("%d.%s%s", index+1, tag, remark)
newOut["tag"] = newTag
// For mixed protocol, use separated socks and http
if protocol == "mixed" {
j.pushMixed(&outbounds, &outTags, newOut)
} else {
outTags = append(outTags, 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",
},
}
*outbounds = append(outbound, *outbounds...)
}
func (j *JsonService) addOthers(jsonConfig *map[string]interface{}) error {
rules := []interface{}{
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["inbounds"]; ok {
(*jsonConfig)["inbounds"] = othersJson["inbounds"]
}
if _, ok := othersJson["experimental"]; ok {
(*jsonConfig)["experimental"] = othersJson["experimental"]
}
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
}
func (j *JsonService) pushMixed(outbounds *[]map[string]interface{}, outTags *[]string, out map[string]interface{}) {
socksOut := make(map[string]interface{}, 1)
httpOut := make(map[string]interface{}, 1)
for key, value := range out {
socksOut[key] = value
httpOut[key] = value
}
socksTag := fmt.Sprintf("%s-socks", out["tag"])
httpTag := fmt.Sprintf("%s-http", out["tag"])
socksOut["type"] = "socks"
httpOut["type"] = "http"
socksOut["tag"] = socksTag
httpOut["tag"] = httpTag
*outbounds = append(*outbounds, socksOut, httpOut)
*outTags = append(*outTags, socksTag, httpTag)
}
+103
View File
@@ -0,0 +1,103 @@
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 {
if len(clientInfo) == 0 {
return uri
}
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")
}
+154
View File
@@ -0,0 +1,154 @@
package sub
import (
"context"
"crypto/tls"
"io"
"net"
"net/http"
"s-ui/config"
"s-ui/logger"
"s-ui/middleware"
"s-ui/network"
"s-ui/service"
"strconv"
"github.com/gin-gonic/gin"
)
type Server struct {
httpServer *http.Server
listener net.Listener
ctx context.Context
cancel context.CancelFunc
service.SettingService
}
func NewServer() *Server {
ctx, cancel := context.WithCancel(context.Background())
return &Server{
ctx: ctx,
cancel: cancel,
}
}
func (s *Server) initRouter() (*gin.Engine, error) {
if config.IsDebug() {
gin.SetMode(gin.DebugMode)
} else {
gin.DefaultWriter = io.Discard
gin.DefaultErrorWriter = io.Discard
gin.SetMode(gin.ReleaseMode)
}
engine := gin.Default()
subPath, err := s.SettingService.GetSubPath()
if err != nil {
return nil, err
}
subDomain, err := s.SettingService.GetSubDomain()
if err != nil {
return nil, err
}
if subDomain != "" {
engine.Use(middleware.DomainValidator(subDomain))
}
g := engine.Group(subPath)
NewSubHandler(g)
return engine, nil
}
func (s *Server) Start() (err error) {
//This is an anonymous function, no function name
defer func() {
if err != nil {
s.Stop()
}
}()
engine, err := s.initRouter()
if err != nil {
return err
}
certFile, err := s.SettingService.GetSubCertFile()
if err != nil {
return err
}
keyFile, err := s.SettingService.GetSubKeyFile()
if err != nil {
return err
}
listen, err := s.SettingService.GetSubListen()
if err != nil {
return err
}
port, err := s.SettingService.GetSubPort()
if err != nil {
return err
}
listenAddr := net.JoinHostPort(listen, strconv.Itoa(port))
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
return err
}
if certFile != "" || keyFile != "" {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
listener.Close()
return err
}
c := &tls.Config{
Certificates: []tls.Certificate{cert},
}
listener = network.NewAutoHttpsListener(listener)
listener = tls.NewListener(listener, c)
}
if certFile != "" || keyFile != "" {
logger.Info("Sub server run https on", listener.Addr())
} else {
logger.Info("Sub server run http on", listener.Addr())
}
s.listener = listener
s.httpServer = &http.Server{
Handler: engine,
}
go func() {
s.httpServer.Serve(listener)
}()
return nil
}
func (s *Server) Stop() error {
s.cancel()
var err error
if s.httpServer != nil {
err = s.httpServer.Shutdown(s.ctx)
if err != nil {
return err
}
}
if s.listener != nil {
err = s.listener.Close()
if err != nil {
return err
}
}
return nil
}
func (s *Server) GetCtx() context.Context {
return s.ctx
}
+51
View File
@@ -0,0 +1,51 @@
package sub
import (
"s-ui/logger"
"s-ui/service"
"github.com/gin-gonic/gin"
)
type SubHandler struct {
service.SettingService
SubService
JsonService
}
func NewSubHandler(g *gin.RouterGroup) {
a := &SubHandler{}
a.initRouter(g)
}
func (s *SubHandler) initRouter(g *gin.RouterGroup) {
g.GET("/:subid", s.subs)
}
func (s *SubHandler) subs(c *gin.Context) {
subId := c.Param("subid")
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])
c.String(200, *result)
}
}
}
+82
View File
@@ -0,0 +1,82 @@
package sub
import (
"encoding/base64"
"fmt"
"s-ui/database"
"s-ui/database/model"
"s-ui/service"
"strings"
"time"
)
type SubService struct {
service.SettingService
LinkService
}
func (s *SubService) GetSubs(subId string) (*string, []string, error) {
var err 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
}
clientInfo := ""
subShowInfo, _ := s.SettingService.GetSubShowInfo()
if subShowInfo {
clientInfo = s.getClientInfo(client)
}
linksArray := s.LinkService.GetLinks(&client.Links, "all", clientInfo)
result := strings.Join(linksArray, "\n")
var headers []string
updateInterval, _ := s.SettingService.GetSubUpdates()
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", client.Up, client.Down, client.Volume, client.Expiry))
headers = append(headers, fmt.Sprintf("%d", updateInterval))
headers = append(headers, subId)
subEncode, _ := s.SettingService.GetSubEncode()
if subEncode {
result = base64.StdEncoding.EncodeToString([]byte(result))
}
return &result, headers, nil
}
func (s *SubService) getClientInfo(c *model.Client) string {
now := time.Now().Unix()
var result []string
if vol := c.Volume - (c.Up + c.Down); vol > 0 {
result = append(result, fmt.Sprintf("%s%s", s.formatTraffic(vol), "📊"))
}
if c.Expiry > 0 {
result = append(result, fmt.Sprintf("%d%s⏳", (c.Expiry-now)/86400, "Days"))
}
if len(result) > 0 {
return " " + strings.Join(result, " ")
} else {
return " ♾"
}
}
func (s *SubService) formatTraffic(trafficBytes int64) string {
if trafficBytes < 1024 {
return fmt.Sprintf("%.2fB", float64(trafficBytes)/float64(1))
} else if trafficBytes < (1024 * 1024) {
return fmt.Sprintf("%.2fKB", float64(trafficBytes)/float64(1024))
} else if trafficBytes < (1024 * 1024 * 1024) {
return fmt.Sprintf("%.2fMB", float64(trafficBytes)/float64(1024*1024))
} else if trafficBytes < (1024 * 1024 * 1024 * 1024) {
return fmt.Sprintf("%.2fGB", float64(trafficBytes)/float64(1024*1024*1024))
} else if trafficBytes < (1024 * 1024 * 1024 * 1024 * 1024) {
return fmt.Sprintf("%.2fTB", float64(trafficBytes)/float64(1024*1024*1024*1024))
} else {
return fmt.Sprintf("%.2fEB", float64(trafficBytes)/float64(1024*1024*1024*1024*1024))
}
}