package service import ( "crypto/md5" "encoding/json" "fmt" "strconv" "strings" "time" "xboard-go/internal/database" "xboard-go/internal/model" "github.com/google/uuid" ) const ( PluginRealNameVerification = "real_name_verification" PluginUserOnlineDevices = "user_online_devices" PluginUserAddIPv6 = "user_add_ipv6_subscription" ) func GetPlugin(code string) (*model.Plugin, bool) { var plugin model.Plugin if err := database.DB.Where("code = ?", code).First(&plugin).Error; err != nil { return nil, false } return &plugin, true } func IsPluginEnabled(code string) bool { plugin, ok := GetPlugin(code) return ok && plugin.IsEnabled } func GetPluginConfig(code string) map[string]any { plugin, ok := GetPlugin(code) if !ok || plugin.Config == nil || *plugin.Config == "" { return map[string]any{} } var cfg map[string]any if err := json.Unmarshal([]byte(*plugin.Config), &cfg); err != nil { return map[string]any{} } return cfg } func GetPluginConfigString(code, key, defaultValue string) string { cfg := GetPluginConfig(code) value, ok := cfg[key] if !ok { return defaultValue } if raw, ok := value.(string); ok && raw != "" { return raw } return defaultValue } func GetPluginConfigBool(code, key string, defaultValue bool) bool { cfg := GetPluginConfig(code) value, ok := cfg[key] if !ok { return defaultValue } switch typed := value.(type) { case bool: return typed case float64: return typed != 0 case string: return typed == "1" || typed == "true" default: return defaultValue } } func SyncIPv6ShadowAccount(user *model.User) bool { if user == nil || user.PlanID == nil { return false } var plan model.Plan if err := database.DB.First(&plan, *user.PlanID).Error; err != nil { return false } if !PluginPlanAllowed(&plan) { return false } ipv6Email := IPv6ShadowEmail(user.Email) var ipv6User model.User now := time.Now().Unix() if err := database.DB.Where("email = ?", ipv6Email).First(&ipv6User).Error; err != nil { ipv6User = *user ipv6User.ID = 0 ipv6User.Email = ipv6Email ipv6User.UUID = uuid.New().String() ipv6User.Token = fmt.Sprintf("%x", md5.Sum([]byte(time.Now().String()+ipv6Email)))[:16] ipv6User.U = 0 ipv6User.D = 0 ipv6User.T = 0 ipv6User.ParentID = &user.ID ipv6User.CreatedAt = now } ipv6User.Email = ipv6Email ipv6User.Password = user.Password ipv6User.U = 0 ipv6User.D = 0 ipv6User.T = 0 ipv6User.UpdatedAt = now if planID := parsePluginPositiveInt(GetPluginConfigString(PluginUserAddIPv6, "ipv6_plan_id", "0"), 0); planID > 0 { ipv6User.PlanID = &planID } if groupID := parsePluginPositiveInt(GetPluginConfigString(PluginUserAddIPv6, "ipv6_group_id", "0"), 0); groupID > 0 { ipv6User.GroupID = &groupID } return database.DB.Save(&ipv6User).Error == nil } func PluginPlanAllowed(plan *model.Plan) bool { if plan == nil { return false } for _, raw := range strings.Split(GetPluginConfigString(PluginUserAddIPv6, "allowed_plans", ""), ",") { if parsePluginPositiveInt(strings.TrimSpace(raw), 0) == plan.ID { return true } } referenceFlag := strings.ToLower(GetPluginConfigString(PluginUserAddIPv6, "reference_flag", "ipv6")) reference := "" if plan.Reference != nil { reference = strings.ToLower(*plan.Reference) } return referenceFlag != "" && strings.Contains(reference, referenceFlag) } func IPv6ShadowEmail(email string) string { suffix := GetPluginConfigString(PluginUserAddIPv6, "email_suffix", "-ipv6") parts := strings.SplitN(email, "@", 2) if len(parts) != 2 { return email } return parts[0] + suffix + "@" + parts[1] } func parsePluginPositiveInt(raw string, defaultValue int) int { value, err := strconv.Atoi(strings.TrimSpace(raw)) if err != nil || value <= 0 { return defaultValue } return value }