mirror of
https://github.com/Dvorinka/SpotifyRecAlg.git
synced 2026-06-03 20:13:03 +00:00
175 lines
4.2 KiB
Go
175 lines
4.2 KiB
Go
package unlocker
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
ErrNotConfigured = errors.New("unlocker service not configured")
|
|
ErrTrackNotFound = errors.New("track not found")
|
|
)
|
|
|
|
// Client for the Python unlocker service (auth-free Spotify access)
|
|
type Client struct {
|
|
baseURL string
|
|
client *http.Client
|
|
}
|
|
|
|
func NewClient(baseURL string) *Client {
|
|
if baseURL == "" {
|
|
return nil
|
|
}
|
|
return &Client{
|
|
baseURL: baseURL,
|
|
client: &http.Client{Timeout: 30 * time.Second},
|
|
}
|
|
}
|
|
|
|
func (c *Client) Configured() bool {
|
|
return c != nil && c.baseURL != ""
|
|
}
|
|
|
|
type TrackResponse struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Artist string `json:"artist"`
|
|
Artists []string `json:"artists"`
|
|
Album string `json:"album"`
|
|
DurationMS int `json:"duration_ms"`
|
|
Explicit bool `json:"explicit"`
|
|
ExternalURLs map[string]string `json:"external_urls"`
|
|
}
|
|
|
|
type ImportRequest struct {
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
type ImportResponse struct {
|
|
Track *TrackResponse `json:"track"`
|
|
Links map[string]string `json:"links"`
|
|
Parsed *ParsedInfo `json:"parsed,omitempty"`
|
|
Note string `json:"note,omitempty"`
|
|
}
|
|
|
|
type ParsedInfo struct {
|
|
Service string `json:"service"`
|
|
Type string `json:"type"`
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
type LinksResponse struct {
|
|
SpotifyID string `json:"spotify_id"`
|
|
ISRC string `json:"isrc"`
|
|
Links map[string]LinkDetails `json:"links"`
|
|
}
|
|
|
|
type LinkDetails struct {
|
|
URL string `json:"url"`
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
// ImportFromURL imports a track from any streaming service URL (auth-free)
|
|
func (c *Client) ImportFromURL(ctx context.Context, url string) (*ImportResponse, error) {
|
|
if !c.Configured() {
|
|
return nil, ErrNotConfigured
|
|
}
|
|
|
|
reqBody, _ := json.Marshal(ImportRequest{URL: url})
|
|
req, err := http.NewRequestWithContext(ctx, "POST", c.baseURL+"/import", bytes.NewReader(reqBody))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unlocker request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("unlocker returned %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
var result ImportResponse
|
|
if err := json.Unmarshal(body, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse unlocker response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// GetTrack gets a track by Spotify ID (auth-free)
|
|
func (c *Client) GetTrack(ctx context.Context, trackID string) (*TrackResponse, error) {
|
|
if !c.Configured() {
|
|
return nil, ErrNotConfigured
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/spotify/track/"+trackID, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := c.client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unlocker request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
|
|
if resp.StatusCode == http.StatusNotFound {
|
|
return nil, ErrTrackNotFound
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("unlocker returned %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
var result TrackResponse
|
|
if err := json.Unmarshal(body, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse unlocker response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// GetLinks gets cross-platform links for a Spotify track
|
|
func (c *Client) GetLinks(ctx context.Context, spotifyID string) (*LinksResponse, error) {
|
|
if !c.Configured() {
|
|
return nil, ErrNotConfigured
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/links/"+spotifyID, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := c.client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unlocker request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("unlocker returned %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
var result LinksResponse
|
|
if err := json.Unmarshal(body, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse unlocker response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|