135 lines
3.2 KiB
Go
135 lines
3.2 KiB
Go
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
|
|
}
|