package notifier import ( "fmt" "sort" "strings" "naviwatcher/internal/scanner" ) // FormatDigest renders newly-found missing releases into a human-readable // Telegram message grouped by artist, with per-artist counts and a link to // the Web UI dashboard. It is deterministic: artists are sorted by label and // releases within an artist are sorted by title. // // artistNames maps an ArtistID to its human-readable display name. Names are // optional: if an ID is absent from the map (or the map itself is nil), the // raw ArtistID is used as the label so the digest remains informative. func FormatDigest(missing []scanner.MissingRelease, uiBaseURL string, artistNames map[string]string) string { if len(missing) == 0 { return "NaviWatcher: no new missing releases found." } type entry struct { title string } byArtist := make(map[string][]entry) order := make([]string, 0) for _, r := range missing { if _, ok := byArtist[r.ArtistID]; !ok { order = append(order, r.ArtistID) } byArtist[r.ArtistID] = append(byArtist[r.ArtistID], entry{title: r.Title}) } // Stable ordering by display label (name if known, else ID). sort.Slice(order, func(i, j int) bool { return artistLabel(order[i], artistNames) < artistLabel(order[j], artistNames) }) var b strings.Builder fmt.Fprintf(&b, "NaviWatcher: %d new missing release(s) found:\n\n", len(missing)) for _, artistID := range order { entries := byArtist[artistID] titles := make([]string, 0, len(entries)) for _, e := range entries { titles = append(titles, e.title) } sort.Strings(titles) fmt.Fprintf(&b, "%s (%d):\n", artistLabel(artistID, artistNames), len(titles)) for _, t := range titles { fmt.Fprintf(&b, " - %s\n", t) } b.WriteString("\n") } if uiBaseURL != "" { fmt.Fprintf(&b, "View details: %s\n", strings.TrimRight(uiBaseURL, "/")) } return strings.TrimRight(b.String(), "\n") } // artistLabel returns the human-readable display name for an artist ID when // available, otherwise the raw ID. A non-empty name takes precedence so // operators see recognizable artist names rather than opaque internal IDs. func artistLabel(artistID string, names map[string]string) string { if names != nil { if name, ok := names[artistID]; ok && name != "" { return name } } return artistID }