added musicbrainz client
This commit is contained in:
228
internal/clients/musicbrainz/musicbrainz.go
Normal file
228
internal/clients/musicbrainz/musicbrainz.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package musicbrainz
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
baseURL = "https://musicbrainz.org/ws/2"
|
||||
userAgent = "customTool/1.0 (djeeberjr@gmail.com)"
|
||||
)
|
||||
|
||||
type MusicBrainzClient struct {
|
||||
httpClient *http.Client
|
||||
userAgent string
|
||||
}
|
||||
|
||||
func NewMusicBrainzClient() *MusicBrainzClient {
|
||||
return &MusicBrainzClient{
|
||||
httpClient: &http.Client{Timeout: 15 * time.Second},
|
||||
userAgent: userAgent,
|
||||
}
|
||||
}
|
||||
|
||||
type Artist struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
SortName string `json:"sort-name"`
|
||||
Type string `json:"type"`
|
||||
Country string `json:"country"`
|
||||
Disambiguation string `json:"disambiguation"`
|
||||
LifeSpan LifeSpan `json:"life-span"`
|
||||
Tags []Tag `json:"tags"`
|
||||
Releases []Release `json:"releases"`
|
||||
ReleaseGroups []ReleaseGroup `json:"release-groups"`
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
Date string `json:"date"`
|
||||
Country string `json:"country"`
|
||||
Disambiguation string `json:"disambiguation"`
|
||||
ArtistCredit []Credit `json:"artist-credit"`
|
||||
LabelInfo []LabelInfo `json:"label-info"`
|
||||
TrackCount int `json:"track-count"`
|
||||
}
|
||||
|
||||
type ReleaseGroup struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
PrimaryType string `json:"primary-type"`
|
||||
FirstRelease string `json:"first-release-date"`
|
||||
ArtistCredit []Credit `json:"artist-credit"`
|
||||
}
|
||||
|
||||
type Recording struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
LengthInMS int `json:"length"`
|
||||
Disambiguation string `json:"disambiguation"`
|
||||
ArtistCredit []Credit `json:"artist-credit"`
|
||||
Releases []Release `json:"releases"`
|
||||
Tags []Tag `json:"tags"`
|
||||
}
|
||||
|
||||
type Credit struct {
|
||||
Name string `json:"name"`
|
||||
JoinPhrase string `json:"joinphrase"`
|
||||
Artist Artist `json:"artist"`
|
||||
}
|
||||
|
||||
type LabelInfo struct {
|
||||
CatalogNumber string `json:"catalog-number"`
|
||||
Label struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
} `json:"label"`
|
||||
}
|
||||
|
||||
type LifeSpan struct {
|
||||
Begin string `json:"begin"`
|
||||
End string `json:"end"`
|
||||
Ended bool `json:"ended"`
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
Name string `json:"name"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
type ArtistSearchResult struct {
|
||||
Created string `json:"created"`
|
||||
Count int `json:"count"`
|
||||
Offset int `json:"offset"`
|
||||
Artists []Artist `json:"artists"`
|
||||
}
|
||||
|
||||
type ReleaseSearchResult struct {
|
||||
Created string `json:"created"`
|
||||
Count int `json:"count"`
|
||||
Offset int `json:"offset"`
|
||||
Releases []Release `json:"releases"`
|
||||
}
|
||||
|
||||
type RecordingSearchResult struct {
|
||||
Created string `json:"created"`
|
||||
Count int `json:"count"`
|
||||
Offset int `json:"offset"`
|
||||
Recordings []Recording `json:"recordings"`
|
||||
}
|
||||
|
||||
type SearchOptions struct {
|
||||
Limit int // max results (default 25, max 100)
|
||||
Offset int // results offset for pagination
|
||||
}
|
||||
|
||||
func (o SearchOptions) params() url.Values {
|
||||
v := url.Values{}
|
||||
if o.Limit > 0 {
|
||||
v.Set("limit", fmt.Sprintf("%d", o.Limit))
|
||||
}
|
||||
if o.Offset > 0 {
|
||||
v.Set("offset", fmt.Sprintf("%d", o.Offset))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (c *MusicBrainzClient) SearchByISRC(isrc string, opts SearchOptions) (*RecordingSearchResult, error) {
|
||||
var result RecordingSearchResult
|
||||
err := c.search("recording", fmt.Sprintf("isrc:%s", isrc), opts, &result)
|
||||
return &result, err
|
||||
}
|
||||
|
||||
func (c *MusicBrainzClient) SearchArtists(query string, opts SearchOptions) (*ArtistSearchResult, error) {
|
||||
var result ArtistSearchResult
|
||||
err := c.search("artist", query, opts, &result)
|
||||
return &result, err
|
||||
}
|
||||
|
||||
func (c *MusicBrainzClient) SearchReleases(query string, opts SearchOptions) (*ReleaseSearchResult, error) {
|
||||
var result ReleaseSearchResult
|
||||
err := c.search("release", query, opts, &result)
|
||||
return &result, err
|
||||
}
|
||||
|
||||
func (c *MusicBrainzClient) SearchRecordings(query string, opts SearchOptions) (*RecordingSearchResult, error) {
|
||||
var result RecordingSearchResult
|
||||
err := c.search("recording", query, opts, &result)
|
||||
return &result, err
|
||||
}
|
||||
|
||||
func (c *MusicBrainzClient) GetArtist(mbid string, inc []string) (*Artist, error) {
|
||||
var artist Artist
|
||||
err := c.lookup("artist", mbid, inc, &artist)
|
||||
return &artist, err
|
||||
}
|
||||
|
||||
func (c *MusicBrainzClient) GetRelease(mbid string, inc []string) (*Release, error) {
|
||||
var release Release
|
||||
err := c.lookup("release", mbid, inc, &release)
|
||||
return &release, err
|
||||
}
|
||||
|
||||
func (c *MusicBrainzClient) GetRecording(mbid string, inc []string) (*Recording, error) {
|
||||
var recording Recording
|
||||
err := c.lookup("recording", mbid, inc, &recording)
|
||||
return &recording, err
|
||||
}
|
||||
|
||||
func (c *MusicBrainzClient) search(entity, query string, opts SearchOptions, out any) error {
|
||||
params := opts.params()
|
||||
params.Set("query", query)
|
||||
params.Set("fmt", "json")
|
||||
|
||||
endpoint := fmt.Sprintf("%s/%s?%s", baseURL, entity, params.Encode())
|
||||
return c.get(endpoint, out)
|
||||
}
|
||||
|
||||
func (c *MusicBrainzClient) lookup(entity, mbid string, inc []string, out any) error {
|
||||
params := url.Values{"fmt": []string{"json"}}
|
||||
if len(inc) > 0 {
|
||||
incStr := ""
|
||||
for i, s := range inc {
|
||||
if i > 0 {
|
||||
incStr += "+"
|
||||
}
|
||||
incStr += s
|
||||
}
|
||||
params.Set("inc", incStr)
|
||||
}
|
||||
endpoint := fmt.Sprintf("%s/%s/%s?%s", baseURL, entity, mbid, params.Encode())
|
||||
return c.get(endpoint, out)
|
||||
}
|
||||
|
||||
func (c *MusicBrainzClient) get(endpoint string, out any) error {
|
||||
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("build request: %w", err)
|
||||
}
|
||||
req.Header.Set("User-Agent", c.userAgent)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("http: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, out); err != nil {
|
||||
return fmt.Errorf("decode JSON: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user