package service import ( "context" "fmt" "strings" "time" "github.com/prism/proxy/internal/country" "github.com/prism/proxy/internal/mihomoapi" "github.com/prism/proxy/internal/store" ) type OutboundMember struct { RuntimeName string `json:"runtime_name"` Name string `json:"name"` } type OutboundGroup struct { RuntimeName string `json:"runtime_name"` Name string `json:"name"` Type string `json:"type"` Source string `json:"source"` Now string `json:"now"` NowName string `json:"now_name"` Selectable bool `json:"selectable"` IsCountry bool `json:"is_country"` Members []OutboundMember `json:"members"` } type OutboundOverview struct { DefaultPolicy string `json:"default_policy"` Groups []OutboundGroup `json:"groups"` } func (a *App) OutboundOverview(ctx context.Context) (OutboundOverview, error) { settings, err := a.Store.GetSettings(ctx) if err != nil { return OutboundOverview{}, err } defaultPolicy := settings["default_policy"] if defaultPolicy == "" { defaultPolicy = "DIRECT" } base, secret, err := a.MihomoAPIBase(ctx) if err != nil { return OutboundOverview{}, err } live, err := mihomoapi.New(base, secret).ListProxiesCached(8 * time.Second) if err != nil { return OutboundOverview{}, fmt.Errorf("mihomo proxies: %w", err) } nodes, err := a.Store.ListProxyNodes(ctx, "", nil) if err != nil { return OutboundOverview{}, err } groups, err := a.Store.ListProxyGroups(ctx) if err != nil { return OutboundOverview{}, err } display := buildDisplayMap(nodes, groups) var outGroups []OutboundGroup seen := map[string]bool{} for _, g := range groups { if !g.Enabled { continue } state, ok := live[g.RuntimeName] if !ok || !mihomoapi.IsPolicyGroupType(state.Type) { continue } seen[g.RuntimeName] = true outGroups = append(outGroups, buildOutboundGroup(g.RuntimeName, g.Name, g.Source, state, display)) } for name, state := range live { if seen[name] || !mihomoapi.IsPolicyGroupType(state.Type) { continue } outGroups = append(outGroups, buildOutboundGroup(name, display[name], "runtime", state, display)) } return OutboundOverview{ DefaultPolicy: defaultPolicy, Groups: outGroups, }, nil } func (a *App) SelectOutboundGroup(ctx context.Context, groupRuntime, proxyRuntime string) error { base, secret, err := a.MihomoAPIBase(ctx) if err != nil { return err } if err := mihomoapi.New(base, secret).SelectProxy(groupRuntime, proxyRuntime); err != nil { return err } _ = a.Store.AddAuditLog(ctx, "select", "outbound", groupRuntime+" -> "+proxyRuntime) return nil } func (a *App) SetDefaultPolicy(ctx context.Context, policy string) error { if err := a.Store.SetSettings(ctx, map[string]string{"default_policy": policy}); err != nil { return err } return a.ReloadEngine(ctx) } func buildOutboundGroup(runtimeName, name, source string, state mihomoapi.ProxyState, display map[string]string) OutboundGroup { if name == "" { name = runtimeName } autoOnly := state.Type == "URLTest" || state.Type == "Fallback" members := make([]OutboundMember, 0, len(state.All)) for _, m := range state.All { if autoOnly && (m == "DIRECT" || m == "REJECT" || m == "REJECT-DROP") { continue } if m == "DIRECT" || m == "REJECT" || m == "REJECT-DROP" { members = append(members, OutboundMember{RuntimeName: m, Name: m}) continue } members = append(members, OutboundMember{ RuntimeName: m, Name: displayName(display, m), }) } return OutboundGroup{ RuntimeName: runtimeName, Name: name, Type: state.Type, Source: source, Now: state.Now, NowName: displayName(display, state.Now), Selectable: state.Type == "Selector", IsCountry: strings.HasPrefix(runtimeName, "country_"), Members: members, } } func buildDisplayMap(nodes []store.ProxyNode, groups []store.ProxyGroup) map[string]string { m := map[string]string{ "DIRECT": "DIRECT", "REJECT": "REJECT", } seen := map[string]bool{} for _, n := range nodes { m[n.RuntimeName] = n.Name if n.Country != "" && !seen[n.Country] { seen[n.Country] = true m[country.GroupRuntime(n.Country)] = country.Name(n.Country) + "(国家池)" } } for _, g := range groups { m[g.RuntimeName] = g.Name } return m } func displayName(display map[string]string, runtime string) string { if name, ok := display[runtime]; ok && name != "" { return name } return runtime }