package config import ( "fmt" "os" "strconv" "gopkg.in/yaml.v3" "gorm.io/gorm" ) type Config struct { Proxy ProxyConfig `yaml:"proxy"` Email EmailConfig `yaml:"email"` Card CardConfig `yaml:"card"` Stripe StripeConfig `yaml:"stripe"` Captcha CaptchaConfig `yaml:"captcha"` Account AccountConfig `yaml:"account"` Team TeamConfig `yaml:"team"` Output OutputConfig `yaml:"output"` Batch BatchConfig `yaml:"batch"` } type BatchConfig struct { Count int `yaml:"count"` // number of accounts to create (0 = auto-detect from card codes) } type CaptchaConfig struct { Provider string `yaml:"provider"` // "hcaptchasolver" APIKey string `yaml:"api_key"` Proxy string `yaml:"proxy"` // fixed HTTP proxy for captcha solver } type ProxyConfig struct { URL string `yaml:"url"` B2Proxy B2ProxyConfig `yaml:"b2proxy"` } type B2ProxyConfig struct { Enabled bool `yaml:"enabled"` APIBase string `yaml:"api_base"` // e.g. http://global.rrp.b2proxy.com:8089 Zone string `yaml:"zone"` // e.g. "custom" PType int `yaml:"ptype"` // e.g. 1 Proto string `yaml:"proto"` // "socks5" or "http" SessTime int `yaml:"sess_time"` // sticky session minutes, default 5 } type EmailConfig struct { Provider string `yaml:"provider"` MailGateway MailGatewayConfig `yaml:"mailgateway"` Outlook OutlookEmailConfig `yaml:"outlook"` } type OutlookEmailConfig struct { AccountsFile string `yaml:"accounts_file"` // path to file with email----password----client_id----refresh_token POP3Server string `yaml:"pop3_server"` // default: outlook.office365.com POP3Port int `yaml:"pop3_port"` // default: 995 } type MailGatewayConfig struct { BaseURL string `yaml:"base_url"` APIKey string `yaml:"api_key"` Provider string `yaml:"provider"` // e.g. "gptmail" } type CardConfig struct { Provider string `yaml:"provider"` // "static", "file", "api" Static StaticCardConfig `yaml:"static"` File FileCardConfig `yaml:"file"` API APICardConfig `yaml:"api"` Pool CardPoolConfig `yaml:"pool"` } type StaticCardConfig struct { Cards []CardEntry `yaml:"cards"` } type FileCardConfig struct { Path string `yaml:"path"` // path to card TXT file DefaultCountry string `yaml:"default_country"` // default country code e.g. JP DefaultCurrency string `yaml:"default_currency"` // default currency e.g. JPY } type APICardConfig struct { BaseURL string `yaml:"base_url"` // e.g. https://yyl.ncet.top Codes []string `yaml:"codes"` // redeem codes list CodesFile string `yaml:"codes_file"` // or path to file with one code per line DefaultName string `yaml:"default_name"` // default cardholder name DefaultCountry string `yaml:"default_country"` // default country code e.g. US DefaultCurrency string `yaml:"default_currency"` // default currency e.g. USD DefaultAddress string `yaml:"default_address"` // default billing address DefaultCity string `yaml:"default_city"` // default billing city DefaultState string `yaml:"default_state"` // default billing state DefaultPostalCode string `yaml:"default_postal_code"` // default billing ZIP code } type CardEntry struct { Number string `yaml:"number"` ExpMonth string `yaml:"exp_month"` ExpYear string `yaml:"exp_year"` CVC string `yaml:"cvc"` Name string `yaml:"name"` Country string `yaml:"country"` Currency string `yaml:"currency"` Address string `yaml:"address"` City string `yaml:"city"` State string `yaml:"state"` PostalCode string `yaml:"postal_code"` } type CardPoolConfig struct { TTLMinutes int `yaml:"ttl_minutes"` // card validity in minutes (default: 60) MultiBind bool `yaml:"multi_bind"` // allow reusing cards multiple times MaxBinds int `yaml:"max_binds"` // max uses per card (0 = unlimited when multi_bind=true) } type StripeConfig struct { BuildHash string `yaml:"build_hash"` TagVersion string `yaml:"tag_version"` StripeVersion string `yaml:"stripe_version"` FingerprintDir string `yaml:"fingerprint_dir"` // directory with browser fingerprint JSON files Aimizy AimizyConfig `yaml:"aimizy"` } type AimizyConfig struct { Enabled bool `yaml:"enabled"` BaseURL string `yaml:"base_url"` // default: https://team.aimizy.com } type AccountConfig struct { PasswordLength int `yaml:"password_length"` Locale string `yaml:"locale"` } type TeamConfig struct { Enabled bool `yaml:"enabled"` WorkspacePrefix string `yaml:"workspace_prefix"` SeatQuantity int `yaml:"seat_quantity"` Coupon string `yaml:"coupon"` InviteCount int `yaml:"invite_count"` // number of members to invite (0 = skip) } type OutputConfig struct { Dir string `yaml:"dir"` } // ExpectedCountry returns the country code associated with the current card configuration. // Falls back to "US" if no country is configured. func (c *CardConfig) ExpectedCountry() string { switch c.Provider { case "static", "": if len(c.Static.Cards) > 0 && c.Static.Cards[0].Country != "" { return c.Static.Cards[0].Country } case "api": if c.API.DefaultCountry != "" { return c.API.DefaultCountry } case "file": if c.File.DefaultCountry != "" { return c.File.DefaultCountry } } return "US" } func Load(path string) (*Config, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read config file: %w", err) } cfg := &Config{} if err := yaml.Unmarshal(data, cfg); err != nil { return nil, fmt.Errorf("parse config: %w", err) } // Outlook email defaults (YAML-only provider) if cfg.Email.Outlook.POP3Server == "" { cfg.Email.Outlook.POP3Server = "outlook.office365.com" } if cfg.Email.Outlook.POP3Port == 0 { cfg.Email.Outlook.POP3Port = 995 } applyDefaults(cfg) return cfg, nil } // configRow mirrors internal/db.SystemConfig without importing internal/. type configRow struct { Key string Value string } // LoadFromDB builds a Config by reading the system_configs table. // It applies the same defaults as Load() for any missing values. func LoadFromDB(d *gorm.DB) (*Config, error) { var rows []configRow if err := d.Table("system_configs").Select("key, value").Find(&rows).Error; err != nil { return nil, fmt.Errorf("query configs: %w", err) } m := make(map[string]string, len(rows)) for _, r := range rows { m[r.Key] = r.Value } cfg := &Config{} // Proxy cfg.Proxy.URL = m["proxy.url"] cfg.Proxy.B2Proxy.Enabled = m["proxy.b2proxy.enabled"] == "true" cfg.Proxy.B2Proxy.APIBase = m["proxy.b2proxy.api_base"] cfg.Proxy.B2Proxy.Zone = m["proxy.b2proxy.zone"] cfg.Proxy.B2Proxy.Proto = m["proxy.b2proxy.proto"] cfg.Proxy.B2Proxy.SessTime, _ = strconv.Atoi(m["proxy.b2proxy.sess_time"]) // Email cfg.Email.Provider = "mailgateway" cfg.Email.MailGateway.BaseURL = m["email.gateway.base_url"] cfg.Email.MailGateway.APIKey = m["email.gateway.api_key"] cfg.Email.MailGateway.Provider = m["email.gateway.provider"] // Card — DB mode uses DBCardProvider, not static/file/api cfg.Card.Provider = "db" cfg.Card.API.BaseURL = m["card.api_base_url"] cfg.Card.API.DefaultName = m["card.default_name"] cfg.Card.API.DefaultCountry = m["card.default_country"] cfg.Card.API.DefaultCurrency = m["card.default_currency"] cfg.Card.API.DefaultAddress = m["card.default_address"] cfg.Card.API.DefaultCity = m["card.default_city"] cfg.Card.API.DefaultState = m["card.default_state"] cfg.Card.API.DefaultPostalCode = m["card.default_postal_code"] cfg.Card.Pool.MaxBinds, _ = strconv.Atoi(m["card.max_binds"]) // Stripe cfg.Stripe.BuildHash = m["stripe.build_hash"] cfg.Stripe.TagVersion = m["stripe.tag_version"] cfg.Stripe.FingerprintDir = m["stripe.fingerprint_dir"] // Captcha cfg.Captcha.Provider = m["captcha.provider"] cfg.Captcha.APIKey = m["captcha.api_key"] cfg.Captcha.Proxy = m["captcha.proxy"] // Account cfg.Account.PasswordLength, _ = strconv.Atoi(m["account.password_length"]) cfg.Account.Locale = m["account.locale"] // Team cfg.Team.Enabled = m["team.enabled"] == "true" cfg.Team.WorkspacePrefix = m["team.workspace_prefix"] cfg.Team.SeatQuantity, _ = strconv.Atoi(m["team.seat_quantity"]) cfg.Team.Coupon = m["team.coupon"] cfg.Team.InviteCount, _ = strconv.Atoi(m["team.invite_count"]) // Output cfg.Output.Dir = "./output" // Apply same defaults as Load() applyDefaults(cfg) return cfg, nil } func applyDefaults(cfg *Config) { if cfg.Stripe.BuildHash == "" { cfg.Stripe.BuildHash = "ede17ac9fd" } if cfg.Stripe.TagVersion == "" { cfg.Stripe.TagVersion = "4.5.43" } if cfg.Stripe.StripeVersion == "" { cfg.Stripe.StripeVersion = "2025-03-31.basil" } if cfg.Account.PasswordLength == 0 { cfg.Account.PasswordLength = 16 } if cfg.Account.Locale == "" { cfg.Account.Locale = "en-GB" } if cfg.Team.WorkspacePrefix == "" { cfg.Team.WorkspacePrefix = "Team" } if cfg.Team.SeatQuantity == 0 { cfg.Team.SeatQuantity = 5 } if cfg.Team.Coupon == "" { cfg.Team.Coupon = "team-1-month-free" } if cfg.Output.Dir == "" { cfg.Output.Dir = "./output" } if cfg.Email.MailGateway.Provider == "" { cfg.Email.MailGateway.Provider = "gptmail" } if cfg.Captcha.Provider == "" { cfg.Captcha.Provider = "hcaptchasolver" } if cfg.Stripe.FingerprintDir == "" { cfg.Stripe.FingerprintDir = "./fingerprints" } if cfg.Proxy.B2Proxy.APIBase == "" { cfg.Proxy.B2Proxy.APIBase = "http://global.rrp.b2proxy.com:8089" } if cfg.Proxy.B2Proxy.Zone == "" { cfg.Proxy.B2Proxy.Zone = "custom" } if cfg.Proxy.B2Proxy.PType == 0 { cfg.Proxy.B2Proxy.PType = 1 } if cfg.Proxy.B2Proxy.Proto == "" { cfg.Proxy.B2Proxy.Proto = "socks5" } if cfg.Proxy.B2Proxy.SessTime == 0 { cfg.Proxy.B2Proxy.SessTime = 5 } if cfg.Stripe.Aimizy.BaseURL == "" { cfg.Stripe.Aimizy.BaseURL = "https://team.aimizy.com" } if cfg.Card.Pool.MaxBinds == 0 { cfg.Card.Pool.MaxBinds = 1 } }