64 lines
1.9 KiB
Go
64 lines
1.9 KiB
Go
// 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\.)?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
|
|
}
|