基本功能复刻完成
All checks were successful
build / build (api, amd64, linux) (push) Successful in -47s
build / build (api, arm64, linux) (push) Successful in -47s
build / build (api.exe, amd64, windows) (push) Successful in -48s

This commit is contained in:
CN-JS-HuiBai
2026-04-17 12:24:00 +08:00
parent 06da23fbbc
commit 981ee4f406
37 changed files with 11737 additions and 770 deletions

View File

@@ -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) {