mirror of
https://github.com/Dvorinka/SpotifyRecAlg.git
synced 2026-06-04 04:23:02 +00:00
first commit
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
package spotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
uriPattern = regexp.MustCompile(`(?i)^spotify:(track|album|playlist|artist):([A-Za-z0-9]+)$`)
|
||||
idPattern = regexp.MustCompile(`^[A-Za-z0-9]{10,}$`)
|
||||
pathIDPattern = regexp.MustCompile(`^[A-Za-z0-9]+$`)
|
||||
)
|
||||
|
||||
type ParsedSource struct {
|
||||
Type string
|
||||
ID string
|
||||
URL string
|
||||
}
|
||||
|
||||
func ParseSource(sourceType, value string) (ParsedSource, error) {
|
||||
sourceType = strings.ToLower(strings.TrimSpace(sourceType))
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return ParsedSource{}, errors.New("source value is required")
|
||||
}
|
||||
|
||||
if sourceType == "" || sourceType == "url" {
|
||||
parsed, err := ParseURL(value)
|
||||
if err == nil {
|
||||
return parsed, nil
|
||||
}
|
||||
if sourceType == "url" {
|
||||
return ParsedSource{}, err
|
||||
}
|
||||
}
|
||||
if sourceType == "" {
|
||||
sourceType = "track"
|
||||
}
|
||||
if !validSpotifyType(sourceType) {
|
||||
return ParsedSource{}, errors.New("source type must be track, album, playlist, artist, or url")
|
||||
}
|
||||
if parsed, err := ParseURL(value); err == nil {
|
||||
if parsed.Type != sourceType {
|
||||
return ParsedSource{}, errors.New("source URL type does not match requested type")
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
if !idPattern.MatchString(value) {
|
||||
return ParsedSource{}, errors.New("source value must be a Spotify ID, URI, or open.spotify.com URL")
|
||||
}
|
||||
return ParsedSource{Type: sourceType, ID: value, URL: "https://open.spotify.com/" + sourceType + "/" + value}, nil
|
||||
}
|
||||
|
||||
func ParseURL(raw string) (ParsedSource, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return ParsedSource{}, errors.New("url is required")
|
||||
}
|
||||
|
||||
if match := uriPattern.FindStringSubmatch(raw); len(match) == 3 {
|
||||
return ParsedSource{Type: strings.ToLower(match[1]), ID: match[2], URL: "https://open.spotify.com/" + strings.ToLower(match[1]) + "/" + match[2]}, nil
|
||||
}
|
||||
|
||||
parsedURL, err := parseURLWithDefaultScheme(raw)
|
||||
if err == nil {
|
||||
if value := parsedURL.Query().Get("uri"); value != "" {
|
||||
return ParseURL(value)
|
||||
}
|
||||
|
||||
host := spotifyHost(parsedURL.Host)
|
||||
switch host {
|
||||
case "open.spotify.com", "play.spotify.com":
|
||||
if parsed, ok := parseSpotifyPath(parsedURL.Path); ok {
|
||||
parsed.URL = canonicalURL(parsed.Type, parsed.ID)
|
||||
return parsed, nil
|
||||
}
|
||||
case "embed.spotify.com":
|
||||
if parsed, ok := parseSpotifyPath(parsedURL.Path); ok {
|
||||
parsed.URL = canonicalURL(parsed.Type, parsed.ID)
|
||||
return parsed, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ParsedSource{}, errors.New("unsupported Spotify URL")
|
||||
}
|
||||
|
||||
func parseURLWithDefaultScheme(raw string) (*url.URL, error) {
|
||||
if strings.Contains(raw, "://") {
|
||||
return url.Parse(raw)
|
||||
}
|
||||
lower := strings.ToLower(raw)
|
||||
if strings.HasPrefix(lower, "open.spotify.com/") ||
|
||||
strings.HasPrefix(lower, "play.spotify.com/") ||
|
||||
strings.HasPrefix(lower, "embed.spotify.com/") {
|
||||
return url.Parse("https://" + raw)
|
||||
}
|
||||
return url.Parse(raw)
|
||||
}
|
||||
|
||||
func spotifyHost(host string) string {
|
||||
host = strings.ToLower(strings.TrimSpace(host))
|
||||
host = strings.TrimPrefix(host, "www.")
|
||||
return host
|
||||
}
|
||||
|
||||
func parseSpotifyPath(path string) (ParsedSource, bool) {
|
||||
parts := pathSegments(path)
|
||||
if len(parts) == 0 {
|
||||
return ParsedSource{}, false
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(parts[0]), "intl-") {
|
||||
parts = parts[1:]
|
||||
}
|
||||
if len(parts) > 0 && strings.EqualFold(parts[0], "embed") {
|
||||
parts = parts[1:]
|
||||
}
|
||||
if len(parts) >= 4 && strings.EqualFold(parts[0], "user") && strings.EqualFold(parts[2], "playlist") && pathIDPattern.MatchString(parts[3]) {
|
||||
return ParsedSource{Type: "playlist", ID: parts[3]}, true
|
||||
}
|
||||
itemType := strings.ToLower(parts[0])
|
||||
if len(parts) >= 2 && validSpotifyType(itemType) && pathIDPattern.MatchString(parts[1]) {
|
||||
return ParsedSource{Type: itemType, ID: parts[1]}, true
|
||||
}
|
||||
return ParsedSource{}, false
|
||||
}
|
||||
|
||||
func pathSegments(path string) []string {
|
||||
rawParts := strings.Split(path, "/")
|
||||
parts := make([]string, 0, len(rawParts))
|
||||
for _, part := range rawParts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part != "" {
|
||||
parts = append(parts, part)
|
||||
}
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
func canonicalURL(itemType, id string) string {
|
||||
return "https://open.spotify.com/" + itemType + "/" + id
|
||||
}
|
||||
|
||||
func validSpotifyType(value string) bool {
|
||||
switch value {
|
||||
case "track", "album", "playlist", "artist":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user