package moonblood import ( "regexp" "strconv" "strings" "git.kplus.net.ua/yevhen/resource-scraper/internal/releaseparser" ) var yearRegexp = regexp.MustCompile(`^(19|20)\d{2}$`) func Parse(raw string) releaseparser.ParsedRelease { result := releaseparser.ParsedRelease{ Raw: strings.TrimSpace(raw), Warnings: []string{}, UnknownTokens: []string{}, } tokens := split(raw) if len(tokens) < 5 { result.Warnings = append(result.Warnings, "too few tokens") result.Confidence = 0.2 return result } // Останній токен — група result.Group = tokens[len(tokens)-1] if result.Group != "MOONBLOOD" { result.Warnings = append(result.Warnings, "unexpected group") } // Передостанній — рік last := len(tokens) - 2 if yearRegexp.MatchString(tokens[last]) { result.Year, _ = strconv.Atoi(tokens[last]) last-- } // Рухаємось вліво по хвосту: format, source, bit_depth, resolution, language, edition. // Кожен слот бере токен лише зі свого словника (регістронезалежно). // Невідомий токен: репортимо лише якщо ліворуч ще є відомий хвостовий маркер // (токен між двома маркерами = сміття в хвості). Інакше це початок core. tailLoop: for last >= 0 { token := strings.ToUpper(tokens[last]) switch { case Profile.FormatTokens[token]: if result.Format == "" { result.Format = token } last-- case Profile.SourceTokens[token]: if result.Source == "" { result.Source = token } last-- case Profile.BitDepthTokens[token]: if result.BitDepth == "" { result.BitDepth = token } last-- case Profile.ResolutionTokens[token]: if result.Resolution == "" { result.Resolution = token } last-- case Profile.LanguageMarkers[token]: if result.Language == "" { result.Language = token } last-- case Profile.EditionTokens[token]: if result.Edition == "" { result.Edition = token } last-- default: if hasKnownTailToken(tokens[:last]) { result.UnknownTokens = append(result.UnknownTokens, tokens[last]) last-- continue } break tailLoop } } // Core = artist-title (все, що залишилось ліворуч) coreTokens := tokens[:last+1] result.Core = strings.Join(coreTokens, "-") if len(coreTokens) >= 2 { result.Artist = coreTokens[0] result.Title = strings.Join(coreTokens[1:], "-") } else if len(coreTokens) == 1 { result.Artist = coreTokens[0] result.Title = "" result.Warnings = append(result.Warnings, "title not found") } score(&result) return result } func split(raw string) []string { parts := strings.Split(strings.TrimSpace(raw), "-") out := make([]string, 0, len(parts)) for _, part := range parts { if part = strings.TrimSpace(part); part != "" { out = append(out, part) } } return out } // hasKnownTailToken reports whether any token is a known tail marker // (format, source, bit_depth, resolution, edition, language or a year). func hasKnownTailToken(tokens []string) bool { for _, tok := range tokens { up := strings.ToUpper(tok) if Profile.FormatTokens[up] || Profile.SourceTokens[up] || Profile.BitDepthTokens[up] || Profile.ResolutionTokens[up] || Profile.EditionTokens[up] || Profile.LanguageMarkers[up] { return true } if yearRegexp.MatchString(up) { return true } } return false } func score(result *releaseparser.ParsedRelease) { score := 0.0 if result.Group == "MOONBLOOD" { score += 0.25 } if result.Year != 0 { score += 0.20 } if result.Format != "" { score += 0.15 } if result.Source != "" { score += 0.15 } if result.Artist != "" { score += 0.15 } if len(result.Warnings) == 0 { score += 0.10 } if len(result.UnknownTokens) > 0 { score -= 0.10 } if score > 1 { score = 1 } if score < 0 { score = 0 } result.Confidence = score }