package card import ( "bufio" "context" "fmt" "os" "strings" ) // FileProvider loads cards from a local TXT file (one card per line). // Supported formats: // 卡号|月|年|CVC // 卡号|月|年|CVC|姓名|国家|货币 // 卡号|月|年|CVC|姓名|国家|货币|地址|城市|州|邮编 // 卡号,月,年,CVC // 卡号,月,年,CVC,姓名,国家,货币,地址,城市,州,邮编 type FileProvider struct { pool *CardPool } // NewFileProvider creates a FileProvider by reading cards from a text file. func NewFileProvider(filePath string, defaultCountry, defaultCurrency string, poolCfg PoolConfig) (*FileProvider, error) { cards, err := parseCardFile(filePath, defaultCountry, defaultCurrency) if err != nil { return nil, err } if len(cards) == 0 { return nil, fmt.Errorf("no valid cards found in %s", filePath) } pool := NewCardPool(poolCfg) pool.AddCards(cards) return &FileProvider{pool: pool}, nil } // GetCard returns the next available card from the pool. func (p *FileProvider) GetCard(ctx context.Context) (*CardInfo, error) { return p.pool.GetCard() } // ReportResult reports the usage outcome. func (p *FileProvider) ReportResult(ctx context.Context, card *CardInfo, success bool) error { p.pool.ReportResult(card, success) return nil } // parseCardFile reads and parses a card file. func parseCardFile(filePath, defaultCountry, defaultCurrency string) ([]*CardInfo, error) { f, err := os.Open(filePath) if err != nil { return nil, fmt.Errorf("open card file: %w", err) } defer f.Close() var cards []*CardInfo scanner := bufio.NewScanner(f) lineNum := 0 for scanner.Scan() { lineNum++ line := strings.TrimSpace(scanner.Text()) // Skip empty lines and comments if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "//") { continue } card, err := parseLine(line, defaultCountry, defaultCurrency) if err != nil { return nil, fmt.Errorf("line %d: %w", lineNum, err) } cards = append(cards, card) } if err := scanner.Err(); err != nil { return nil, fmt.Errorf("read card file: %w", err) } return cards, nil } // parseLine parses a single card line. Supports | and , delimiters. func parseLine(line, defaultCountry, defaultCurrency string) (*CardInfo, error) { var parts []string if strings.Contains(line, "|") { parts = strings.Split(line, "|") } else { parts = strings.Split(line, ",") } // Trim all parts for i := range parts { parts[i] = strings.TrimSpace(parts[i]) } if len(parts) < 4 { return nil, fmt.Errorf("expected at least 4 fields (number|month|year|cvc), got %d", len(parts)) } card := &CardInfo{ Number: parts[0], ExpMonth: parts[1], ExpYear: parts[2], CVC: parts[3], Country: defaultCountry, Currency: defaultCurrency, } if len(parts) >= 5 && parts[4] != "" { card.Name = parts[4] } if len(parts) >= 6 && parts[5] != "" { card.Country = parts[5] } if len(parts) >= 7 && parts[6] != "" { card.Currency = parts[6] } if len(parts) >= 8 && parts[7] != "" { card.Address = parts[7] } if len(parts) >= 9 && parts[8] != "" { card.City = parts[8] } if len(parts) >= 10 && parts[9] != "" { card.State = parts[9] } if len(parts) >= 11 && parts[10] != "" { card.PostalCode = parts[10] } return card, nil }