package app import ( "crypto/aes" "encoding/hex" "encoding/json" "errors" "fmt" "os" ) const ( defaultAPIBase = "https://dr6skssi3nxbk.cloudfront.net" defaultCDNURL = "https://sjljsla.lkkwip.cn" userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Mobile/15E148 Safari/604.1" xUserAgent = "BuildID=com.abc.Butterfly;SysType=ios;DevID=00000000000000000000000000000;Ver=1.0.0;DevType=iPhone;DeviceBrand=APPLE;DeviceModel=iPhone;SystemName=iOS;SystemVersion=18.7;Terminal=1;IsH5=1;Sid=00000000000000000000000000000000" ) type Config struct { adminUsername string adminPassword string adminUserExplicit bool adminPasswordExplicit bool settingsLoaded bool dataDir string outputDirSetting string APIBase string `json:"apiBase"` CDNURL string `json:"cdnURL"` Token string `json:"token"` AESKeyHex string `json:"aesKeyHex"` InterfaceKey string `json:"interfaceKey"` ParamKey string `json:"paramKey"` ParamIV string `json:"paramIV"` OutputDir string `json:"outputDir"` FFmpeg string `json:"ffmpeg"` Concurrency int `json:"concurrency"` RequestConcurrency int `json:"requestConcurrency"` RequestIntervalMS int `json:"requestIntervalMs"` MaxPagesPerSort int `json:"maxPagesPerSort"` PageSize int `json:"pageSize"` Retries int `json:"retries"` SkipBytes int64 `json:"skipBytes"` InsecureTLS bool `json:"insecureTLS"` ProxyURL string `json:"proxyURL,omitempty"` HuangguoAIURL string `json:"huangguoAIURL,omitempty"` HuangguoVideoURL string `json:"huangguoVideoURL,omitempty"` HuangdouURL string `json:"huangdouURL,omitempty"` HongguoURL string `json:"hongguoURL,omitempty"` } func defaultConfig() Config { return Config{ APIBase: defaultAPIBase, CDNURL: defaultCDNURL, OutputDir: defaultOutputDir(), FFmpeg: "ffmpeg", Concurrency: 2, RequestConcurrency: 2, RequestIntervalMS: 500, MaxPagesPerSort: 20, PageSize: 50, Retries: 3, SkipBytes: 512 * 1024, InsecureTLS: false, } } func defaultOutputDir() string { return "短剧下载" } func loadConfig(path string) Config { cfg := defaultConfig() if path != "" { b, err := os.ReadFile(path) if err != nil { fmt.Printf(" 读取配置失败,使用默认配置: %v\n", err) } else if err := json.Unmarshal(b, &cfg); err != nil { fmt.Printf(" 解析配置失败,使用默认配置: %v\n", err) cfg = defaultConfig() } } applyConfigEnvironment(&cfg) if cfg.APIBase == "" { cfg.APIBase = defaultAPIBase } if cfg.CDNURL == "" { cfg.CDNURL = defaultCDNURL } if cfg.OutputDir == "" { cfg.OutputDir = defaultOutputDir() } if cfg.FFmpeg == "" { cfg.FFmpeg = "ffmpeg" } if cfg.Concurrency <= 0 { cfg.Concurrency = 2 } if cfg.RequestConcurrency <= 0 { cfg.RequestConcurrency = 2 } if cfg.RequestIntervalMS <= 0 { cfg.RequestIntervalMS = 500 } if cfg.MaxPagesPerSort <= 0 { cfg.MaxPagesPerSort = 20 } if cfg.PageSize <= 0 || cfg.PageSize > 100 { cfg.PageSize = 50 } if cfg.Retries <= 0 { cfg.Retries = 3 } if cfg.SkipBytes <= 0 { cfg.SkipBytes = 512 * 1024 } return cfg } func applyConfigEnvironment(cfg *Config) { setIf := func(dst *string, key string) { if v := os.Getenv(key); v != "" { *dst = v } } setIf(&cfg.APIBase, "JUKU_API_BASE") setIf(&cfg.CDNURL, "JUKU_CDN_URL") setIf(&cfg.Token, "JUKU_TOKEN") setIf(&cfg.AESKeyHex, "JUKU_AES_KEY_HEX") setIf(&cfg.InterfaceKey, "JUKU_INTERFACE_KEY") setIf(&cfg.ParamKey, "JUKU_PARAM_KEY") setIf(&cfg.ParamIV, "JUKU_PARAM_IV") setIf(&cfg.HuangguoAIURL, "JUKU_HUANGGUO_AI_URL") setIf(&cfg.HuangguoVideoURL, "JUKU_HUANGGUO_VIDEO_URL") setIf(&cfg.HuangdouURL, "JUKU_HUANGDOU_URL") setIf(&cfg.HongguoURL, "JUKU_HONGGUO_URL") setIf(&cfg.ProxyURL, "JUKU_PROXY_URL") setIf(&cfg.OutputDir, "JUKU_OUTPUT_DIR") setIf(&cfg.FFmpeg, "JUKU_FFMPEG") } func (c Config) validate() error { if _, err := configuredProxy(c.ProxyURL); err != nil { return err } if c.ParamKey != "" && len(c.ParamKey) != aes.BlockSize || c.ParamIV != "" && len(c.ParamIV) != aes.BlockSize { return fmt.Errorf("paramKey/paramIV 必须是 %d 字节", aes.BlockSize) } if key, err := hex.DecodeString(c.AESKeyHex); c.AESKeyHex != "" && (err != nil || len(key) != aes.BlockSize) { return errors.New("aesKeyHex 必须是 16 字节 AES 密钥的 hex 编码") } for _, endpoint := range []string{c.APIBase, c.HuangguoAIURL, c.HuangguoVideoURL, c.HuangdouURL, c.HongguoURL} { if endpoint != "" && !isProviderHTTPMediaURL(endpoint) { return errors.New("站点地址必须是有效的 HTTP/HTTPS URL") } } return nil }