// Package detector provides music link detection in text. package detector import ( "regexp" "musiclink/internal/services" ) // patterns match music service URLs. // We keep patterns per service for easier maintenance. var patterns = []*regexp.Regexp{ regexp.MustCompile(`https?://(?:open\.)?spotify\.com/(?:track|album|artist|playlist)/[a-zA-Z0-9]+`), regexp.MustCompile(`https?://spoti\.fi/[a-zA-Z0-9]+`), regexp.MustCompile(`https?://(?:www\.)?(?:youtube\.com/watch\?v=|youtu\.be/|music\.youtube\.com/watch\?v=)[a-zA-Z0-9_-]{11}`), regexp.MustCompile(`https?://(?:(?:music|geo\.music)\.)?apple\.com/[a-z]{2}/(?:album|artist|playlist|song)/[^\s]+`), regexp.MustCompile(`https?://(?:www\.)?deezer\.com/(?:[a-z]{2}/)?(?:track|album|artist|playlist)/\d+`), regexp.MustCompile(`https?://(?:www\.)?soundcloud\.com/[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+`), regexp.MustCompile(`https?://(?:www\.)?tidal\.com/(?:browse/)?(?:track|album|artist|playlist)/\d+`), regexp.MustCompile(`https?://(?:www\.)?qobuz\.com/[a-z]{2}-[a-z]{2}/(?:album|track|artist|playlist)/[^\s]+`), regexp.MustCompile(`https?://[a-zA-Z0-9_-]+\.bandcamp\.com/(?:track|album)/[a-zA-Z0-9_-]+`), } // Detector finds music links in text. type Detector struct{} // New creates a new Detector. func New() *Detector { return &Detector{} } // Detect finds all music links in the given text. func (d *Detector) Detect(text string) []services.DetectedLink { matches := findMatches(text) if len(matches) == 0 { return nil } links := make([]services.DetectedLink, len(matches)) for i, match := range matches { links[i] = services.DetectedLink{ URL: match, RawMatch: match, } } return links } func findMatches(text string) []string { var matches []string seen := make(map[string]struct{}) for _, re := range patterns { for _, match := range re.FindAllString(text, -1) { if _, ok := seen[match]; ok { continue } seen[match] = struct{}{} matches = append(matches, match) } } return matches }