initial Go port of streamrip

This commit is contained in:
2026-04-19 21:11:38 +02:00
commit 97e8b758b3
32 changed files with 7008 additions and 0 deletions

202
internal/urlparse/parse.go Normal file
View File

@@ -0,0 +1,202 @@
package urlparse
import (
"net/url"
"regexp"
"strings"
)
type URLKind string
const (
KindGeneric URLKind = "generic"
KindDeezerDynamic URLKind = "deezer_dynamic"
KindSoundcloud URLKind = "soundcloud"
)
type ParsedURL struct {
OriginalURL string
Source string
MediaType string
ID string
Kind URLKind
}
var deezerDynamicRe = regexp.MustCompile(`^https?://dzr\.page\.link/`)
func Parse(raw string) *ParsedURL {
if deezerDynamicRe.MatchString(raw) {
return &ParsedURL{
OriginalURL: raw,
Source: "deezer",
Kind: KindDeezerDynamic,
}
}
u, err := url.Parse(raw)
if err != nil || u.Host == "" {
return nil
}
host := normalizeHost(u.Host)
path := strings.Trim(u.EscapedPath(), "/")
parts := splitParts(path)
switch {
case isQobuzHost(host):
return parseQobuz(raw, parts)
case isTidalHost(host):
return parseTidal(raw, parts)
case isDeezerHost(host):
return parseDeezer(raw, parts)
case host == "soundcloud.com":
return parseSoundcloud(raw, parts)
default:
return nil
}
}
func parseQobuz(raw string, parts []string) *ParsedURL {
if len(parts) < 2 {
return nil
}
if isLocaleToken(parts[0]) {
parts = parts[1:]
}
if len(parts) < 2 {
return nil
}
mediaType := parts[0]
if !isSupportedMedia(mediaType) {
return nil
}
id := parts[len(parts)-1]
if id == "" {
return nil
}
return &ParsedURL{OriginalURL: raw, Source: "qobuz", MediaType: mediaType, ID: id, Kind: KindGeneric}
}
func parseTidal(raw string, parts []string) *ParsedURL {
if len(parts) < 2 {
return nil
}
if parts[0] == "browse" {
parts = parts[1:]
}
if len(parts) < 2 {
return nil
}
mediaType := parts[0]
if !isSupportedMedia(mediaType) {
return nil
}
id := parts[1]
if id == "" {
return nil
}
return &ParsedURL{OriginalURL: raw, Source: "tidal", MediaType: mediaType, ID: id, Kind: KindGeneric}
}
func parseDeezer(raw string, parts []string) *ParsedURL {
if len(parts) < 2 {
return nil
}
if isLangToken(parts[0]) {
parts = parts[1:]
}
if len(parts) < 2 {
return nil
}
mediaType := parts[0]
if !isSupportedMedia(mediaType) {
return nil
}
id := parts[1]
if id == "" {
return nil
}
return &ParsedURL{OriginalURL: raw, Source: "deezer", MediaType: mediaType, ID: id, Kind: KindGeneric}
}
func parseSoundcloud(raw string, parts []string) *ParsedURL {
if len(parts) < 2 {
return nil
}
mediaType := "track"
if len(parts) >= 3 && parts[1] == "sets" {
mediaType = "playlist"
}
return &ParsedURL{OriginalURL: raw, Source: "soundcloud", MediaType: mediaType, ID: raw, Kind: KindSoundcloud}
}
func splitParts(path string) []string {
if path == "" {
return nil
}
raw := strings.Split(path, "/")
parts := make([]string, 0, len(raw))
for _, p := range raw {
if p != "" {
parts = append(parts, p)
}
}
return parts
}
func normalizeHost(host string) string {
h := strings.ToLower(host)
return strings.TrimPrefix(h, "www.")
}
func isQobuzHost(host string) bool {
return host == "qobuz.com" || host == "open.qobuz.com" || host == "play.qobuz.com"
}
func isTidalHost(host string) bool {
return host == "tidal.com" || host == "open.tidal.com" || host == "listen.tidal.com"
}
func isDeezerHost(host string) bool {
return host == "deezer.com"
}
func isSupportedMedia(mediaType string) bool {
switch mediaType {
case "album", "track", "playlist", "artist", "label":
return true
default:
return false
}
}
func isLocaleToken(s string) bool {
if len(s) != 5 {
return false
}
return s[2] == '-' && isAlpha(s[:2]) && isAlpha(s[3:])
}
func isLangToken(s string) bool {
return len(s) == 2 && isAlpha(s)
}
func isAlpha(s string) bool {
for _, r := range s {
if (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') {
return false
}
}
return true
}