summaryrefslogtreecommitdiff
path: root/internal/rules/loader.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/rules/loader.go')
-rw-r--r--internal/rules/loader.go210
1 files changed, 210 insertions, 0 deletions
diff --git a/internal/rules/loader.go b/internal/rules/loader.go
new file mode 100644
index 0000000..7bbbe6e
--- /dev/null
+++ b/internal/rules/loader.go
@@ -0,0 +1,210 @@
+package rules
+
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "vpnem/internal/models"
+)
+
+type Store struct {
+ dataDir string
+ connections *ConnectionStore
+}
+
+func NewStore(dataDir string) *Store {
+ s := &Store{
+ dataDir: dataDir,
+ connections: NewConnectionStore(dataDir),
+ }
+ _ = s.connections.Load()
+ return s
+}
+
+// Connections returns the connection store for recommendation logic.
+func (s *Store) Connections() *ConnectionStore {
+ return s.connections
+}
+
+func (s *Store) LoadServers() (*models.ServersResponse, error) {
+ data, err := os.ReadFile(filepath.Join(s.dataDir, "servers.json"))
+ if err != nil {
+ return nil, err
+ }
+ var resp models.ServersResponse
+ if err := json.Unmarshal(data, &resp); err != nil {
+ return nil, err
+ }
+ return &resp, nil
+}
+
+func (s *Store) LoadRuleSets() (*models.RuleSetManifest, error) {
+ data, err := os.ReadFile(filepath.Join(s.dataDir, "rulesets.json"))
+ if err != nil {
+ return nil, err
+ }
+ var manifest models.RuleSetManifest
+ if err := json.Unmarshal(data, &manifest); err != nil {
+ return nil, err
+ }
+ return &manifest, nil
+}
+
+func (s *Store) LoadVersion() (*models.VersionResponse, error) {
+ data, err := os.ReadFile(filepath.Join(s.dataDir, "version.json"))
+ if err != nil {
+ return nil, err
+ }
+ var ver models.VersionResponse
+ if err := json.Unmarshal(data, &ver); err != nil {
+ return nil, err
+ }
+ return &ver, nil
+}
+
+func (s *Store) LoadCatalogV2() (*models.CatalogV2, error) {
+ data, err := os.ReadFile(filepath.Join(s.dataDir, "catalog-v2.json"))
+ if err != nil {
+ return nil, err
+ }
+ var catalog models.CatalogV2
+ if err := json.Unmarshal(data, &catalog); err != nil {
+ return nil, err
+ }
+ return &catalog, nil
+}
+
+func (s *Store) LoadCatalogV2OrLegacy() (*models.CatalogV2, error) {
+ catalog, err := s.LoadCatalogV2()
+ if err == nil {
+ return catalog, nil
+ }
+ if !os.IsNotExist(err) {
+ return nil, err
+ }
+
+ servers, err := s.LoadServers()
+ if err != nil {
+ return nil, err
+ }
+ return legacyServersToCatalog(servers.Servers), nil
+}
+
+func (s *Store) LoadRoutingPolicy() (*models.RoutingPolicy, error) {
+ data, err := os.ReadFile(filepath.Join(s.dataDir, "routing-policy.json"))
+ if err != nil {
+ return nil, err
+ }
+ var policy models.RoutingPolicy
+ if err := json.Unmarshal(data, &policy); err != nil {
+ return nil, err
+ }
+ return &policy, nil
+}
+
+func (s *Store) RulesDir() string {
+ return filepath.Join(s.dataDir, "rules")
+}
+
+func (s *Store) ReleasesDir() string {
+ return filepath.Join(s.dataDir, "releases")
+}
+
+func (s *Store) DataDir() string {
+ return s.dataDir
+}
+
+func legacyServersToCatalog(servers []models.Server) *models.CatalogV2 {
+ nodesByID := make(map[string]*models.CatalogNode, len(servers))
+ order := make([]string, 0, len(servers))
+ for _, server := range servers {
+ nodeID := server.Tag
+ if existingID, protocolType, ok := splitLegacyTag(server.Tag); ok && existingID != "" {
+ nodeID = existingID
+ server.Type = protocolType
+ }
+
+ node := nodesByID[nodeID]
+ if node == nil {
+ node = &models.CatalogNode{
+ ID: nodeID,
+ Name: nodeID,
+ Region: server.Region,
+ Host: server.Server,
+ PublicHost: server.Server,
+ Status: "published",
+ }
+ nodesByID[nodeID] = node
+ order = append(order, nodeID)
+ }
+ node.Protocols = append(node.Protocols, legacyServerToCatalogProtocol(server))
+ }
+
+ nodes := make([]models.CatalogNode, 0, len(order))
+ for _, id := range order {
+ nodes = append(nodes, *nodesByID[id])
+ }
+ return &models.CatalogV2{
+ Version: "legacy-adapter",
+ Nodes: nodes,
+ }
+}
+
+func legacyServerToCatalogProtocol(server models.Server) models.CatalogProtocol {
+ protocolType := server.Type
+ if protocolType == "socks" {
+ protocolType = "socks5"
+ }
+ protocol := models.CatalogProtocol{
+ Type: protocolType,
+ Enabled: true,
+ Port: server.ServerPort,
+ TLS: server.TLS,
+ Extra: make(map[string]any),
+ }
+ switch server.Type {
+ case "vless", "vless-reality", "vmess":
+ protocol.Auth = &models.CatalogAuth{UUID: server.UUID}
+ protocol.Extra["legacy_tag"] = server.Tag
+ if server.Transport != nil {
+ protocol.Extra["transport_type"] = server.Transport.Type
+ if server.Transport.Path != "" {
+ protocol.Extra["path"] = server.Transport.Path
+ }
+ }
+ case "shadowsocks":
+ protocol.Auth = &models.CatalogAuth{Method: server.Method, Password: server.Password}
+ protocol.Extra["legacy_tag"] = server.Tag
+ case "hysteria2":
+ protocol.Auth = &models.CatalogAuth{Password: server.Password}
+ protocol.Extra["legacy_tag"] = server.Tag
+ if server.ObfsPassword != "" {
+ protocol.Extra["obfs_password"] = server.ObfsPassword
+ }
+ if server.UpMbps > 0 {
+ protocol.Extra["up_mbps"] = server.UpMbps
+ }
+ if server.DownMbps > 0 {
+ protocol.Extra["down_mbps"] = server.DownMbps
+ }
+ case "socks":
+ protocol.Extra["legacy_tag"] = server.Tag
+ protocol.Extra["udp_over_tcp"] = server.UDPOverTCP
+ }
+ if len(protocol.Extra) == 0 {
+ protocol.Extra = nil
+ }
+ return protocol
+}
+
+func splitLegacyTag(tag string) (nodeID, protocolType string, ok bool) {
+ for _, candidate := range []string{"vless-reality", "vless", "vmess", "shadowsocks", "hysteria2", "socks", "socks5"} {
+ suffix := "-" + candidate
+ if strings.HasSuffix(tag, suffix) && len(tag) > len(suffix) {
+ return strings.TrimSuffix(tag, suffix), candidate, true
+ }
+ }
+ return "", "", false
+}