266 lines
7.5 KiB
Go
266 lines
7.5 KiB
Go
package hfb
|
||
|
||
import (
|
||
"fmt"
|
||
"regexp"
|
||
"strconv"
|
||
"strings"
|
||
)
|
||
|
||
var numberPattern = regexp.MustCompile(`[-+]?\d+(?:\.\d+)?`)
|
||
|
||
func ParseUploadText(text string) (ParsedUpload, error) {
|
||
values := make(map[string]string)
|
||
for lineNo, line := range strings.Split(text, "\n") {
|
||
line = strings.TrimSpace(line)
|
||
if line == "" {
|
||
continue
|
||
}
|
||
key, value, ok := splitKeyValue(line)
|
||
if !ok {
|
||
return ParsedUpload{}, fmt.Errorf("第 %d 行格式不正确,应为“字段:值”", lineNo+1)
|
||
}
|
||
key = normalizeKey(key)
|
||
if key == "" {
|
||
continue
|
||
}
|
||
values[key] = strings.TrimSpace(value)
|
||
}
|
||
|
||
account := ExternalAccountData{}
|
||
var err error
|
||
account.LoginMethod = firstValue(values, "上号方式", "登录方式")
|
||
account.Rank = firstValue(values, "段位")
|
||
account.Level, err = parseRequiredInt(values, "等级")
|
||
if err != nil {
|
||
return ParsedUpload{}, err
|
||
}
|
||
account.SafeSlots, err = parseRequiredInt(values, "保险格数", "保险箱格数")
|
||
if err != nil {
|
||
return ParsedUpload{}, err
|
||
}
|
||
account.DailyLossM, err = parseRequiredFloat(values, "每日消耗")
|
||
if err != nil {
|
||
return ParsedUpload{}, err
|
||
}
|
||
account.SecretKD, err = parseOptionalFloat(values, "绝密KD", "绝密kd")
|
||
if err != nil {
|
||
return ParsedUpload{}, err
|
||
}
|
||
account.Deposit, err = parseRequiredFloat(values, "押金")
|
||
if err != nil {
|
||
return ParsedUpload{}, err
|
||
}
|
||
banRaw := firstValue(values, "封禁记录")
|
||
account.BanRecord = normalizeBanFlag(banRaw)
|
||
account.CommonRegion = firstValue(values, "常用地区", "常用区域")
|
||
account.ContactPhone = firstValue(values, "联系电话", "手机号", "电话")
|
||
account.OwnerOnlineTime = firstValue(values, "号主在线时间", "在线时间")
|
||
account.Remark = composeRemark(firstValue(values, "备注", "说明"), banRaw)
|
||
|
||
account.Currency.HafuCoin, err = parseRequiredFloat(values, "哈夫币纯币", "哈夫币")
|
||
if err != nil {
|
||
return ParsedUpload{}, err
|
||
}
|
||
account.Currency.RecycleRatio, err = parseRequiredRatio(values, "回收比例")
|
||
if err != nil {
|
||
return ParsedUpload{}, err
|
||
}
|
||
account.Currency.RecycleRent, err = parseRequiredFloat(values, "回收租金")
|
||
if err != nil {
|
||
return ParsedUpload{}, err
|
||
}
|
||
|
||
account.DailyConsumption.Stamina, err = parseRequiredInt(values, "体力")
|
||
if err != nil {
|
||
return ParsedUpload{}, err
|
||
}
|
||
account.DailyConsumption.Weight, err = parseRequiredInt(values, "负重")
|
||
if err != nil {
|
||
return ParsedUpload{}, err
|
||
}
|
||
account.Inventory.AWMBullets, err = parseOptionalInt(values, "AWM子弹数量", "AWM子弹")
|
||
if err != nil {
|
||
return ParsedUpload{}, err
|
||
}
|
||
account.Inventory.Level6Helmets, err = parseOptionalInt(values, "6头数量", "六头数量", "6头")
|
||
if err != nil {
|
||
return ParsedUpload{}, err
|
||
}
|
||
account.Inventory.Level6Armor, err = parseOptionalInt(values, "6甲数量", "六甲数量", "6甲")
|
||
if err != nil {
|
||
return ParsedUpload{}, err
|
||
}
|
||
account.Inventory.Skins = parseSkins(firstValue(values, "皮肤"))
|
||
|
||
if strings.TrimSpace(account.LoginMethod) == "" {
|
||
return ParsedUpload{}, fmt.Errorf("缺少必填字段:上号方式")
|
||
}
|
||
if strings.TrimSpace(account.Rank) == "" {
|
||
return ParsedUpload{}, fmt.Errorf("缺少必填字段:段位")
|
||
}
|
||
|
||
return ParsedUpload{
|
||
UploaderName: firstValue(values, "上传人", "上传者", "上传账号", "上传管理员"),
|
||
Account: account,
|
||
}, nil
|
||
}
|
||
|
||
func splitKeyValue(line string) (string, string, bool) {
|
||
if idx := strings.Index(line, ":"); idx >= 0 {
|
||
return line[:idx], line[idx+len(":"):], true
|
||
}
|
||
if idx := strings.Index(line, ":"); idx >= 0 {
|
||
return line[:idx], line[idx+1:], true
|
||
}
|
||
return "", "", false
|
||
}
|
||
|
||
func normalizeKey(key string) string {
|
||
return strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(key), " ", ""))
|
||
}
|
||
|
||
func firstValue(values map[string]string, labels ...string) string {
|
||
for _, label := range labels {
|
||
if value, ok := values[normalizeKey(label)]; ok {
|
||
return strings.TrimSpace(value)
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func optionalDefault(value, fallback string) string {
|
||
if strings.TrimSpace(value) == "" {
|
||
return fallback
|
||
}
|
||
return strings.TrimSpace(value)
|
||
}
|
||
|
||
// normalizeBanFlag 只输出“有/无”,具体封禁历史放到备注。
|
||
func normalizeBanFlag(value string) string {
|
||
value = strings.TrimSpace(value)
|
||
if value == "" || isNoBanRecord(value) {
|
||
return "无"
|
||
}
|
||
return "有"
|
||
}
|
||
|
||
func isNoBanRecord(value string) bool {
|
||
value = strings.TrimSpace(value)
|
||
switch value {
|
||
case "无", "无封禁", "无封禁记录", "否", "没有", "none", "None", "NONE":
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
// composeRemark 合并原文备注与封禁历史详情。
|
||
func composeRemark(remark, banRaw string) string {
|
||
remark = strings.TrimSpace(remark)
|
||
banDetail := strings.TrimSpace(banRaw)
|
||
if banDetail == "" || isNoBanRecord(banDetail) {
|
||
return remark
|
||
}
|
||
// 如果原文已经是“有/无”,不当作详情追加
|
||
if banDetail == "有" || banDetail == "是" || banDetail == "有封禁" || banDetail == "有封禁记录" {
|
||
return remark
|
||
}
|
||
banLine := "封禁历史:" + banDetail
|
||
if remark == "" {
|
||
return banLine
|
||
}
|
||
return remark + "\n" + banLine
|
||
}
|
||
|
||
func parseRequiredInt(values map[string]string, labels ...string) (int, error) {
|
||
value := firstValue(values, labels...)
|
||
if value == "" {
|
||
return 0, fmt.Errorf("缺少必填字段:%s", labels[0])
|
||
}
|
||
return parseIntLabel(labels[0], value)
|
||
}
|
||
|
||
func parseOptionalInt(values map[string]string, labels ...string) (int, error) {
|
||
value := firstValue(values, labels...)
|
||
if value == "" {
|
||
return 0, nil
|
||
}
|
||
return parseIntLabel(labels[0], value)
|
||
}
|
||
|
||
func parseIntLabel(label, value string) (int, error) {
|
||
number, err := parseFloatText(value, false)
|
||
if err != nil {
|
||
return 0, fmt.Errorf("%s 数字格式不正确:%s", label, value)
|
||
}
|
||
return int(number), nil
|
||
}
|
||
|
||
func parseRequiredFloat(values map[string]string, labels ...string) (float64, error) {
|
||
value := firstValue(values, labels...)
|
||
if value == "" {
|
||
return 0, fmt.Errorf("缺少必填字段:%s", labels[0])
|
||
}
|
||
number, err := parseFloatText(value, false)
|
||
if err != nil {
|
||
return 0, fmt.Errorf("%s 数字格式不正确:%s", labels[0], value)
|
||
}
|
||
return number, nil
|
||
}
|
||
|
||
func parseOptionalFloat(values map[string]string, labels ...string) (float64, error) {
|
||
value := firstValue(values, labels...)
|
||
if value == "" {
|
||
return 0, nil
|
||
}
|
||
number, err := parseFloatText(value, false)
|
||
if err != nil {
|
||
return 0, fmt.Errorf("%s 数字格式不正确:%s", labels[0], value)
|
||
}
|
||
return number, nil
|
||
}
|
||
|
||
func parseRequiredRatio(values map[string]string, labels ...string) (float64, error) {
|
||
value := firstValue(values, labels...)
|
||
if value == "" {
|
||
return 0, fmt.Errorf("缺少必填字段:%s", labels[0])
|
||
}
|
||
number, err := parseFloatText(value, true)
|
||
if err != nil {
|
||
return 0, fmt.Errorf("%s 数字格式不正确:%s", labels[0], value)
|
||
}
|
||
return number, nil
|
||
}
|
||
|
||
func parseFloatText(value string, preferLast bool) (float64, error) {
|
||
value = strings.TrimSpace(strings.ReplaceAll(value, ",", ""))
|
||
matches := numberPattern.FindAllString(value, -1)
|
||
if len(matches) == 0 {
|
||
return 0, strconv.ErrSyntax
|
||
}
|
||
pick := matches[0]
|
||
if preferLast {
|
||
pick = matches[len(matches)-1]
|
||
}
|
||
return strconv.ParseFloat(pick, 64)
|
||
}
|
||
|
||
func parseSkins(value string) []string {
|
||
value = strings.TrimSpace(value)
|
||
if value == "" || value == "无" || strings.EqualFold(value, "none") {
|
||
return nil
|
||
}
|
||
parts := strings.FieldsFunc(value, func(r rune) bool {
|
||
return r == ',' || r == ',' || r == '、' || r == ';' || r == ';'
|
||
})
|
||
skins := make([]string, 0, len(parts))
|
||
for _, part := range parts {
|
||
part = strings.TrimSpace(part)
|
||
if part != "" && part != "无" {
|
||
skins = append(skins, part)
|
||
}
|
||
}
|
||
return skins
|
||
}
|