This commit is contained in:
admin8800
2026-05-10 06:41:44 +00:00
parent d104b6e40d
commit 3eb70ee9ed
252 changed files with 42215 additions and 2 deletions
+393
View File
@@ -0,0 +1,393 @@
package sub
import (
"strings"
"github.com/alireza0/s-ui/logger"
"github.com/alireza0/s-ui/service"
"github.com/alireza0/s-ui/util"
"gopkg.in/yaml.v3"
)
type ClashService struct {
service.SettingService
JsonService
LinkService
}
const basicClashConfig = `mixed-port: 7890
allow-lan: false
mode: rule
log-level: info
external-controller: 127.0.0.1:9090
tun:
enable: true
stack: system
auto-route: true
auto-detect-interface: true
dns-hijack:
- any:53
dns:
enable: true
ipv6: false
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
default-nameserver:
- 8.8.8.8
- 1.1.1.1
nameserver:
- https://doh.pub/dns-query
- https://1.0.0.1/dns-query
fallback:
- tcp://9.9.9.9:53
fake-ip-filter:
- "*.lan"
- localhost
- "*.local"
rules:
- GEOIP,Private,DIRECT
- MATCH,Proxy
`
const ProxyGroups = `- name: Proxy
type: select
proxies: []
- name: Auto
type: url-test
proxies: []
url: http://www.gstatic.com/generate_204
interval: 300
tolerance: 50
`
func (s *ClashService) GetClash(subId string) (*string, []string, error) {
client, inDatas, err := s.getData(subId)
if err != nil {
return nil, nil, err
}
outbounds, outTags, err := s.getOutbounds(client.Config, inDatas)
if err != nil {
return nil, nil, err
}
links := s.LinkService.GetLinks(&client.Links, "external", "")
tagNumEnable := 0
if len(links) > 1 {
tagNumEnable = 1
}
for index, link := range links {
json, tag, err := util.GetOutbound(link, (index+1)*tagNumEnable)
if err == nil && len(tag) > 0 {
*outbounds = append(*outbounds, *json)
*outTags = append(*outTags, tag)
}
}
basicConfig, err := s.getClashConfig()
if err != nil || len(basicConfig) == 0 {
basicConfig = basicClashConfig
}
resultStr, err := s.ConvertToClashMeta(outbounds, basicConfig)
if err != nil {
return nil, nil, err
}
updateInterval, _ := s.SettingService.GetSubUpdates()
headers := util.GetHeaders(client, updateInterval)
return &resultStr, headers, nil
}
func (s *ClashService) getClashConfig() (string, error) {
subClashExt, err := s.SettingService.GetSubClashExt()
if err != nil {
return "", err
}
return subClashExt, nil
}
func (s *ClashService) ConvertToClashMeta(outbounds *[]map[string]interface{}, basicConfig string) (string, error) {
var proxies []interface{}
proxyTags := make([]string, 0)
for _, obMap := range *outbounds {
t, _ := obMap["type"].(string)
if t == "selector" || t == "urltest" || t == "direct" {
continue
}
proxy := make(map[string]interface{})
proxy["name"] = obMap["tag"]
proxy["type"] = t
server, _ := obMap["server"].(string)
if len(server) > 0 && strings.Contains(server, ":") && !strings.Contains(server, ".") && !(strings.HasPrefix(server, "[") && strings.HasSuffix(server, "]")) {
server = "'[" + server + "]'"
}
proxy["server"] = server
proxy["port"] = obMap["server_port"]
switch t {
case "vmess", "vless", "tuic":
proxy["uuid"] = obMap["uuid"]
if t == "vmess" {
if alterId, ok := obMap["alter_id"].(float64); ok {
proxy["alterId"] = int(alterId)
} else {
proxy["alterId"] = 0
}
proxy["cipher"] = "auto"
}
if t == "vless" {
if flow, ok := obMap["flow"].(string); ok {
proxy["flow"] = flow
}
}
if t == "tuic" {
proxy["password"] = obMap["password"]
if congestion_control, ok := obMap["congestion_control"].(string); ok {
proxy["congestion-controller"] = congestion_control
}
}
case "trojan":
proxy["password"] = obMap["password"]
case "socks", "http":
if t == "socks" {
proxy["type"] = "socks5"
}
proxy["username"] = obMap["username"]
proxy["password"] = obMap["password"]
case "hysteria", "hysteria2":
if _, ok := obMap["up_mbps"].(float64); ok {
proxy["up"] = obMap["up_mbps"]
}
if _, ok := obMap["down_mbps"].(float64); ok {
proxy["down"] = obMap["down_mbps"]
}
if t == "hysteria" {
proxy["auth-str"] = obMap["auth_str"]
if obfs, ok := obMap["obfs"].(string); ok {
proxy["obfs"] = obfs
}
} else {
proxy["password"] = obMap["password"]
if obfs, ok := obMap["obfs"].(map[string]interface{}); ok {
proxy["obfs"] = obfs["type"]
proxy["obfs-password"] = obfs["password"]
}
}
if portLists, ok := obMap["server_ports"].([]interface{}); ok {
var ports []string
for _, portList := range portLists {
portRange, _ := portList.(string)
ports = append(ports, strings.ReplaceAll(portRange, ":", "-"))
}
proxy["ports"] = strings.Join(ports, ",")
}
case "anytls":
proxy["password"] = obMap["password"]
if tls, ok := obMap["tls"].(map[string]interface{}); ok {
proxy["sni"] = tls["server_name"]
proxy["skip-cert-verify"] = tls["insecure"]
}
case "shadowsocks":
proxy["type"] = "ss"
proxy["cipher"] = obMap["method"]
proxy["password"] = obMap["password"]
if network, ok := obMap["network"].(string); ok && network != "tcp" {
proxy["udp"] = true
}
if uot, ok := obMap["udp_over_tcp"].(bool); ok && uot {
proxy["udp-over-tcp"] = true
}
default:
continue
}
// TLS params
tls, isTls := obMap["tls"].(map[string]interface{})
if isTls {
tlsEnabled, ok := tls["enabled"].(bool)
if ok && !tlsEnabled {
isTls = false
}
}
if isTls {
proxy["tls"] = tls["enabled"]
// ALPN if exists
if alpn, ok := tls["alpn"].([]interface{}); ok {
proxy["alpn"] = alpn
}
// Add reality if exists
if reality, ok := tls["reality"].(map[string]interface{}); ok && reality["enabled"].(bool) {
reality_opts := make(map[string]interface{})
if pbk, ok := reality["public_key"].(string); ok {
reality_opts["public-key"] = pbk
}
if sid, ok := reality["short_id"].(string); ok {
reality_opts["short-id"] = sid
}
proxy["reality-opts"] = reality_opts
}
if utls, ok := tls["utls"].(map[string]interface{}); ok {
if enabled, ok := utls["enabled"].(bool); ok && enabled {
if fp, ok := utls["fingerprint"].(string); ok {
proxy["client-fingerprint"] = fp
}
}
}
if sni, ok := tls["server_name"].(string); ok {
if t == "vless" || t == "vmess" {
proxy["servername"] = sni
} else {
proxy["sni"] = sni
}
}
if insecure, ok := tls["insecure"].(bool); ok && insecure {
proxy["skip-cert-verify"] = insecure
}
// ech outbounds
if ech, ok := tls["ech"].(map[string]interface{}); ok && ech["enabled"].(bool) {
ech_config, _ := ech["config"].([]interface{})
ech_string := ""
for i := 1; i < len(ech_config)-1; i++ {
ech_string += ech_config[i].(string)
}
proxy["ech-opts"] = map[string]interface{}{
"enable": true,
"config": ech_string,
}
}
}
// Transport if exist
if transport, ok := obMap["transport"].(map[string]interface{}); ok {
tt, _ := transport["type"].(string)
switch tt {
case "http":
httpOpts := make(map[string]interface{})
if path, ok := transport["path"].([]interface{}); ok {
httpOpts["path"] = path[0]
} else if path, ok := transport["path"].(string); ok {
httpOpts["path"] = path
}
if host, ok := transport["host"].([]interface{}); ok {
httpOpts["host"] = host[0]
}
if isTls {
proxy["network"] = "h2"
proxy["h2-opts"] = httpOpts
} else {
proxy["network"] = "http"
proxy["http-opts"] = map[string]interface{}{"path": []interface{}{httpOpts["path"]}, "host": httpOpts["host"]}
}
case "ws", "httpupgrade":
proxy["network"] = "ws"
wsOpts := make(map[string]interface{})
if path, ok := transport["path"].(string); ok {
wsOpts["path"] = path
}
if headers, ok := transport["headers"].([]interface{}); ok {
wsOpts["headers"] = headers
}
if ed, ok := transport["early_data_header_name"].(string); ok {
wsOpts["early-data-header-name"] = ed
}
if tt == "httpupgrade" {
wsOpts["v2ray-http-upgrade"] = true
}
proxy["ws-opts"] = wsOpts
case "grpc":
proxy["network"] = "grpc"
grpcOpts := make(map[string]interface{})
if service_name, ok := transport["service_name"].(string); ok {
grpcOpts["grpc-service-name"] = service_name
}
proxy["grpc-opts"] = grpcOpts
}
}
// Multiplex
if mux, ok := obMap["multiplex"].(map[string]interface{}); ok {
if enabled, ok := mux["enabled"].(bool); ok && enabled {
smux := make(map[string]interface{})
smux["enabled"] = true
if protocol, ok := mux["protocol"].(string); ok {
smux["protocol"] = protocol
}
if _, ok := mux["max_connections"].(float64); ok {
smux["max-connections"] = mux["max_connections"]
}
if _, ok := mux["min_streams"].(float64); ok {
smux["min-streams"] = mux["min_streams"]
}
if _, ok := mux["max_streams"].(float64); ok {
smux["max-streams"] = mux["max_streams"]
}
if _, ok := mux["padding"].(bool); ok {
smux["padding"] = mux["padding"]
}
if brutal, ok := mux["brutal"].(map[string]interface{}); ok {
if enabled, ok := brutal["enabled"].(bool); ok && enabled {
brutalOpts := make(map[string]interface{})
brutalOpts["enabled"] = true
if _, ok := brutal["up_mbps"].(float64); ok {
brutalOpts["up"] = brutal["up_mbps"]
}
if _, ok := brutal["down_mbps"].(float64); ok {
brutalOpts["down"] = brutal["down_mbps"]
}
smux["brutal-opts"] = brutalOpts
}
}
proxy["smux"] = smux
}
}
proxies = append(proxies, proxy)
proxyTags = append(proxyTags, obMap["tag"].(string))
}
var proxyGroups []map[string]interface{}
err := yaml.Unmarshal([]byte(ProxyGroups), &proxyGroups)
if err != nil {
logger.Error(err.Error())
}
proxyGroups[1]["proxies"] = proxyTags
proxyGroups[0]["proxies"] = append([]string{proxyGroups[1]["name"].(string)}, proxyTags...)
// Merge proxies and proxy groups if exist
var output map[string]interface{}
err = yaml.Unmarshal([]byte(basicConfig), &output)
if err != nil {
logger.Error(err.Error())
}
if p, ok := output["proxies"].([]interface{}); ok {
output["proxies"] = append(p, proxies...)
} else {
output["proxies"] = proxies
}
if pg, ok := output["proxy-groups"].([]interface{}); ok {
output["proxy-groups"] = append(pg, proxyGroups[0], proxyGroups[1])
} else {
output["proxy-groups"] = proxyGroups
}
result, err := yaml.Marshal(output)
if err != nil {
return "", err
}
return string(result), nil
}
+326
View File
@@ -0,0 +1,326 @@
package sub
import (
"encoding/json"
"fmt"
"strings"
"github.com/alireza0/s-ui/database"
"github.com/alireza0/s-ui/database/model"
"github.com/alireza0/s-ui/service"
"github.com/alireza0/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, []string, error) {
var jsonConfig map[string]interface{}
client, inDatas, err := j.getData(subId)
if err != nil {
return nil, nil, err
}
outbounds, outTags, err := j.getOutbounds(client.Config, inDatas)
if err != nil {
return nil, nil, err
}
links := j.LinkService.GetLinks(&client.Links, "external", "")
tagNumEnable := 0
if len(links) > 1 {
tagNumEnable = 1
}
for index, link := range links {
json, tag, err := util.GetOutbound(link, (index+1)*tagNumEnable)
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, nil, err
}
jsonConfig["outbounds"] = outbounds
// Add other objects from settings
j.addOthers(&jsonConfig)
result, _ := json.MarshalIndent(jsonConfig, "", " ")
resultStr := string(result)
updateInterval, _ := j.SettingService.GetSubUpdates()
headers := util.GetHeaders(client, updateInterval)
return &resultStr, headers, 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{}).Preload("Tls").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)
// Shadowsocks
if protocol == "shadowsocks" {
var userPass []string
var inbOptions map[string]interface{}
err = json.Unmarshal(inData.Options, &inbOptions)
if err != nil {
return nil, nil, err
}
method, _ := inbOptions["method"].(string)
if strings.HasPrefix(method, "2022") {
inbPass, _ := inbOptions["password"].(string)
userPass = append(userPass, inbPass)
}
var pass string
if method == "2022-blake3-aes-128-gcm" {
pass, _ = configs["shadowsocks16"].(map[string]interface{})["password"].(string)
} else {
pass, _ = configs["shadowsocks"].(map[string]interface{})["password"].(string)
}
userPass = append(userPass, pass)
outbound["password"] = strings.Join(userPass, ":")
} else { // Other protocols
config, _ := configs[protocol].(map[string]interface{})
for key, value := range config {
if key == "name" || key == "alterId" || (key == "flow" && inData.TlsId == 0) {
continue
}
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
if addrTls, ok := addr["tls"].(map[string]interface{}); ok {
outTls, _ := newOut["tls"].(map[string]interface{})
if outTls == nil {
outTls = make(map[string]interface{})
}
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_start := []interface{}{
map[string]interface{}{
"action": "sniff",
},
map[string]interface{}{
"clash_mode": "Direct",
"action": "route",
"outbound": "direct",
},
}
rules_end := []interface{}{
map[string]interface{}{
"clash_mode": "Global",
"action": "route",
"outbound": "proxy",
},
}
route := map[string]interface{}{
"auto_detect_interface": true,
"final": "proxy",
"rules": rules_start,
}
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 {
rules := append(rules_start, settingRules...)
route["rules"] = append(rules, rules_end...)
}
if defaultDomainResolver, ok := othersJson["default_domain_resolver"].(string); ok {
route["default_domain_resolver"] = defaultDomainResolver
}
(*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)
}
+74
View File
@@ -0,0 +1,74 @@
package sub
import (
"encoding/json"
"strings"
"github.com/alireza0/s-ui/logger"
"github.com/alireza0/s-ui/util"
)
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":
subLinks := util.GetExternalLink(link.Uri)
result = append(result, strings.Split(subLinks, "\n")...)
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
}
}
+162
View File
@@ -0,0 +1,162 @@
package sub
import (
"context"
"crypto/tls"
"io"
"net"
"net/http"
"strconv"
"time"
"github.com/alireza0/s-ui/config"
"github.com/alireza0/s-ui/logger"
"github.com/alireza0/s-ui/middleware"
"github.com/alireza0/s-ui/network"
"github.com/alireza0/s-ui/service"
"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 {
var err error
if s.httpServer != nil {
shutdownCtx, cancelShutdown := context.WithTimeout(context.Background(), 30*time.Second)
err = s.httpServer.Shutdown(shutdownCtx)
cancelShutdown()
if err != nil {
s.cancel()
if s.listener != nil {
_ = s.listener.Close()
}
return err
}
} else if s.listener != nil {
err = s.listener.Close()
if err != nil {
s.cancel()
return err
}
}
s.cancel()
return nil
}
func (s *Server) GetCtx() context.Context {
return s.ctx
}
+78
View File
@@ -0,0 +1,78 @@
package sub
import (
"github.com/alireza0/s-ui/logger"
"github.com/alireza0/s-ui/service"
"github.com/gin-gonic/gin"
)
type SubHandler struct {
service.SettingService
SubService
JsonService
ClashService
}
func NewSubHandler(g *gin.RouterGroup) {
a := &SubHandler{}
a.initRouter(g)
}
func (s *SubHandler) initRouter(g *gin.RouterGroup) {
g.GET("/:subid", s.subs)
g.HEAD("/:subid", s.subHeaders)
}
func (s *SubHandler) subs(c *gin.Context) {
var headers []string
var result *string
var err error
subId := c.Param("subid")
format, isFormat := c.GetQuery("format")
if isFormat {
switch format {
case "json":
result, headers, err = s.JsonService.GetJson(subId, format)
case "clash":
result, headers, err = s.ClashService.GetClash(subId)
}
if err != nil || result == nil {
logger.Error(err)
c.String(400, "Error!")
return
}
} else {
result, headers, err = s.SubService.GetSubs(subId)
if err != nil || result == nil {
logger.Error(err)
c.String(400, "Error!")
return
}
}
s.addHeaders(c, headers)
c.String(200, *result)
}
func (s *SubHandler) subHeaders(c *gin.Context) {
subId := c.Param("subid")
client, err := s.SubService.getClientBySubId(subId)
if err != nil {
logger.Error(err)
c.String(400, "Error!")
return
}
headers := s.SubService.getClientHeaders(client)
s.addHeaders(c, headers)
c.Status(200)
}
func (s *SubHandler) addHeaders(c *gin.Context, headers []string) {
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])
}
+93
View File
@@ -0,0 +1,93 @@
package sub
import (
"encoding/base64"
"fmt"
"strings"
"time"
"github.com/alireza0/s-ui/database"
"github.com/alireza0/s-ui/database/model"
"github.com/alireza0/s-ui/service"
"github.com/alireza0/s-ui/util"
)
type SubService struct {
service.SettingService
LinkService
}
func (s *SubService) GetSubs(subId string) (*string, []string, error) {
var err error
client, err := s.getClientBySubId(subId)
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")
headers := s.getClientHeaders(client)
subEncode, _ := s.SettingService.GetSubEncode()
if subEncode {
result = base64.StdEncoding.EncodeToString([]byte(result))
}
return &result, headers, nil
}
func (j *SubService) getClientBySubId(subId string) (*model.Client, 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, err
}
return client, nil
}
func (s *SubService) getClientHeaders(client *model.Client) []string {
updateInterval, _ := s.SettingService.GetSubUpdates()
return util.GetHeaders(client, updateInterval)
}
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))
}
}