基本功能复刻完成
This commit is contained in:
@@ -2,9 +2,12 @@ package handler
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"xboard-go/internal/model"
|
||||
"xboard-go/internal/protocol"
|
||||
"xboard-go/internal/service"
|
||||
@@ -19,14 +22,25 @@ func ClientSubscribe(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
FulfillSubscription(c, user)
|
||||
}
|
||||
|
||||
func FulfillSubscription(c *gin.Context, user *model.User) {
|
||||
servers, err := service.AvailableServersForUser(user)
|
||||
if err != nil {
|
||||
Fail(c, 500, "failed to fetch servers")
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Filter servers
|
||||
types := c.Query("types")
|
||||
filter := c.Query("filter")
|
||||
servers = filterServers(servers, types, filter)
|
||||
|
||||
ua := c.GetHeader("User-Agent")
|
||||
flag := c.Query("flag")
|
||||
|
||||
// 2. Handle specialized configs
|
||||
if strings.Contains(ua, "Clash") || flag == "clash" {
|
||||
config, _ := protocol.GenerateClash(servers, *user)
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
@@ -41,14 +55,98 @@ func ClientSubscribe(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
links := make([]string, 0, len(servers))
|
||||
for _, server := range servers {
|
||||
links = append(links, "vmess://"+server.Name)
|
||||
// 3. General Subscription (Links)
|
||||
var finalLinks []string
|
||||
|
||||
// 3a. Add Info Nodes (match reference behavior)
|
||||
if service.MustGetBool("show_info_to_server_enable", true) {
|
||||
finalLinks = append(finalLinks, generateInfoNodes(user)...)
|
||||
}
|
||||
|
||||
// 3b. Add Actual Node Links
|
||||
nodeLinks := protocol.GenerateGeneralLinks(servers, *user)
|
||||
if nodeLinks != "" {
|
||||
finalLinks = append(finalLinks, strings.Split(nodeLinks, "\n")...)
|
||||
}
|
||||
|
||||
c.Header("Content-Type", "text/plain; charset=utf-8")
|
||||
c.Header("Subscription-Userinfo", subscriptionUserInfo(*user))
|
||||
c.String(http.StatusOK, base64.StdEncoding.EncodeToString([]byte(strings.Join(links, "\n"))))
|
||||
c.String(http.StatusOK, base64.StdEncoding.EncodeToString([]byte(strings.Join(finalLinks, "\n"))))
|
||||
}
|
||||
|
||||
func filterServers(servers []model.Server, types, filter string) []model.Server {
|
||||
var filtered []model.Server
|
||||
allowed := make(map[string]bool)
|
||||
if types != "" && types != "all" {
|
||||
for _, t := range strings.Split(types, ",") {
|
||||
allowed[strings.TrimSpace(t)] = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range servers {
|
||||
if len(allowed) > 0 && !allowed[s.Type] {
|
||||
continue
|
||||
}
|
||||
if filter != "" {
|
||||
if !strings.Contains(strings.ToLower(s.Name), strings.ToLower(filter)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
filtered = append(filtered, s)
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func generateInfoNodes(user *model.User) []string {
|
||||
var nodes []string
|
||||
|
||||
// Expire Info
|
||||
expireDate := "长期有效"
|
||||
if user.ExpiredAt != nil {
|
||||
expireDate = time.Unix(*user.ExpiredAt, 0).Format("2006-01-02")
|
||||
}
|
||||
nodes = append(nodes, buildInfoLink("套餐到期:"+expireDate))
|
||||
|
||||
// Traffic Info
|
||||
remaining := int64(user.TransferEnable) - (int64(user.U) + int64(user.D))
|
||||
if remaining < 0 {
|
||||
remaining = 0
|
||||
}
|
||||
nodes = append(nodes, buildInfoLink("剩余流量:"+formatTraffic(remaining)))
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
func buildInfoLink(name string) string {
|
||||
m := map[string]string{
|
||||
"v": "2",
|
||||
"ps": name,
|
||||
"add": "1.2.3.4",
|
||||
"port": "443",
|
||||
"id": "00000000-0000-0000-0000-000000000000",
|
||||
"aid": "0",
|
||||
"scy": "auto",
|
||||
"net": "tcp",
|
||||
"type": "none",
|
||||
"host": "",
|
||||
"path": "",
|
||||
"tls": "",
|
||||
}
|
||||
b, _ := json.Marshal(m)
|
||||
return "vmess://" + base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
func formatTraffic(bytes int64) string {
|
||||
const unit = 1024
|
||||
if bytes < unit {
|
||||
return fmt.Sprintf("%d B", bytes)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := bytes / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.2f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
|
||||
}
|
||||
|
||||
func ClientAppConfigV1(c *gin.Context) {
|
||||
|
||||
Reference in New Issue
Block a user