diff --git a/README.md b/README.md index 2d344b6..bfc1198 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,9 @@ The lib is under MIT but be sure you are familiar with [Discogs API Terms of Use * Folder * Collection Items by Folder * Collection Items by Release + * [Marketplace](#marketplace) + * Price Suggestions + * Release Statistics Install -------- @@ -84,7 +87,6 @@ type SearchRequest struct { } ``` -Example ```go request := discogs.SearchRequest{Artist: "reggaenauts", ReleaseTitle: "river rock", Page: 0, PerPage: 1} search, _ := client.Search(request) @@ -99,22 +101,38 @@ Example Query a users [collection](https://www.discogs.com/developers#page:user-collection). ##### Collection Folders -Example ```go collection, err := client.CollectionFolders("my_user") ``` ##### Folder -Example ```go folder, err := client.Folder("my_user", 0) ``` ##### Collection Items by Folder -Example ```go items, err := client.CollectionItemsByFolder("my_user", 0, &Pagination{Sort: "artist", SortOrder: "desc", PerPage: 2}) ``` ##### Collection Items by Release -Example ```go items, err := client.CollectionItemsByRelease("my_user", 12934893) ``` + +#### Marketplace + +Query a user's [marketplace](https://www.discogs.com/developers/#page:marketplace) + +##### Price Suggestions + +Retrieve price suggestions for the provided Release ID + +```go + suggestions, err := client.PriceSuggestions(12345) +``` + +##### Release Statistics + +Retrieve marketplace statistics for the provided Release ID + +```go + stats, err := client.ReleaseStatisctics(12345) +``` \ No newline at end of file diff --git a/discogs.go b/discogs.go index c274f1d..73fac98 100644 --- a/discogs.go +++ b/discogs.go @@ -28,6 +28,7 @@ type Options struct { type Discogs interface { CollectionService DatabaseService + MarketPlaceService SearchService } @@ -35,6 +36,7 @@ type discogs struct { CollectionService DatabaseService SearchService + MarketPlaceService } var header *http.Header @@ -67,6 +69,7 @@ func New(o *Options) (Discogs, error) { newCollectionService(o.URL + "/users"), newDatabaseService(o.URL, cur), newSearchService(o.URL + "/database/search"), + newMarketPlaceService(o.URL+"/marketplace", cur), }, nil } diff --git a/marketplace.go b/marketplace.go new file mode 100644 index 0000000..79a398e --- /dev/null +++ b/marketplace.go @@ -0,0 +1,72 @@ +package discogs + +import ( + "net/url" + "strconv" +) + +const ( + priceSuggestionsURI = "/price_suggestions/" + releaseStatsURI = "/stats/" +) + +type marketPlaceService struct { + url string + currency string +} + +type MarketPlaceService interface { + // The best price suggestions according to grading + // Authentication is required. + PriceSuggestions(releaseID int) (*PriceListing, error) + // Short summary of marketplace listings + // Authentication is optional. + ReleaseStatistics(releaseID int) (*Stats, error) +} + +func newMarketPlaceService(url string, currency string) MarketPlaceService { + return &marketPlaceService{ + url: url, + currency: currency, + } +} + +// Listing is a marketplace listing with the user's currency and a price value +type Listing struct { + Currency string `json:"currency"` + Value float64 `json:"value"` +} + +// PriceListings are Listings per grading quality +type PriceListing struct { + VeryGood *Listing `json:"Very Good (VG),omitempty"` + GoodPlus *Listing `json:"Good Plus (G+),omitempty"` + NearMint *Listing `json:"Near Mint (NM or M-)"` + Good *Listing `json:"Good (G),omitempty"` + VeryGoodPlus *Listing `json:"Very Good Plus (VG+),omitempty"` + Mint *Listing `json:"Mint (M),omitempty"` + Fair *Listing `json:"Fair (F),omitempty"` + Poor *Listing `json:"Poor (P),omitempty"` +} + +// Stats returns the marketplace stats summary for a release containing +type Stats struct { + LowestPrice *Listing `json:"lowest_price"` + ForSale int `json:"num_for_sale"` + Blocked bool `json:"blocked_from_sale"` +} + +func (s *marketPlaceService) ReleaseStatistics(releaseID int) (*Stats, error) { + params := url.Values{} + params.Set("curr_abbr", s.currency) + + var stats *Stats + err := request(s.url+releaseStatsURI+strconv.Itoa(releaseID), params, &stats) + return stats, err +} + +func (s *marketPlaceService) PriceSuggestions(releaseID int) (*PriceListing, error) { + var listings *PriceListing + err := request(s.url+priceSuggestionsURI+strconv.Itoa(releaseID), nil, &listings) + return listings, err +} diff --git a/marketplace_test.go b/marketplace_test.go new file mode 100644 index 0000000..1924604 --- /dev/null +++ b/marketplace_test.go @@ -0,0 +1,76 @@ +package discogs + +import ( + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" +) + +const testReleaseID = 9893847 + +func MarketplaceServer(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + switch r.URL.Path { + case "/marketplace" + priceSuggestionsURI + strconv.Itoa(testReleaseID): + w.WriteHeader(http.StatusOK) + if _, err := io.WriteString(w, priceSuggestionJson); err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + case "/marketplace" + releaseStatsURI + strconv.Itoa(testReleaseID): + w.WriteHeader(http.StatusOK) + if _, err := io.WriteString(w, releaseStatsJson); err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + default: + w.WriteHeader(http.StatusMethodNotAllowed) + } +} + +func TestMarketplacePriceSuggestions(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(MarketplaceServer)) + defer ts.Close() + + d := initDiscogsClient(t, &Options{URL: ts.URL}) + + suggestion, err := d.PriceSuggestions(testReleaseID) + if err != nil { + t.Fatalf("failed to get price suggestion: %s", err) + } + + json, err := json.Marshal(suggestion) + if err != nil { + t.Fatalf("failed to marshal folder: %s", err) + } + + compareJson(t, string(json), priceSuggestionJson) +} + +func TestMarketplaceReleaseStatistics(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(MarketplaceServer)) + defer ts.Close() + + d := initDiscogsClient(t, &Options{URL: ts.URL}) + + stats, err := d.ReleaseStatistics(testReleaseID) + if err != nil { + t.Fatalf("failed to get price suggestion: %s", err) + } + + json, err := json.Marshal(stats) + if err != nil { + t.Fatalf("failed to marshal folder: %s", err) + } + + compareJson(t, string(json), releaseStatsJson) +} diff --git a/testing_data.go b/testing_data.go index cd8694a..7da0273 100644 --- a/testing_data.go +++ b/testing_data.go @@ -15,3 +15,7 @@ const collectionJson = `{"folders": [{"id": 0, "name": "All", "count": 95, "reso const collectionItemsByFolderJson = `{"pagination": {"page": 1, "pages": 48, "per_page": 2, "items": 95, "urls": {"last": "https://api.discogs.com/users/test_user/collection/folders/0/releases?sort=artist&sort_order=desc&per_page=2&page=48", "next": "https://api.discogs.com/users/test_user/collection/folders/0/releases?sort=artist&sort_order=desc&per_page=2&page=2"}}, "releases": [{"id": 12934893, "instance_id": 431009995, "date_added": "2020-01-19T14:19:11-08:00", "rating": 0, "basic_information": {"id": 12934893, "master_id": 0, "master_url": null, "resource_url": "https://api.discogs.com/releases/12934893", "thumb": "", "cover_image": "", "title": "Zonk", "year": 2018, "formats": [{"name": "Vinyl", "qty": "1", "text": "Purple", "descriptions": ["LP", "Album"]}], "labels": [{"name": "Permanent Record", "catno": "PR014", "entity_type": "1", "entity_type_name": "Label", "id": 833694, "resource_url": "https://api.discogs.com/labels/833694"}], "artists": [{"name": "Zoo Lake", "anv": "", "join": "", "role": "", "tracks": "", "id": 6868154, "resource_url": "https://api.discogs.com/artists/6868154"}], "genres": ["Rock"], "styles": ["Post-Punk", "Shoegaze", "Psychedelic Rock", "Noise", "Garage Rock", "Lo-Fi"]}}, {"id": 4825435, "instance_id": 146424864, "date_added": "2015-11-08T14:42:02-08:00", "rating": 0, "basic_information": {"id": 4825435, "master_id": 17200, "master_url": "https://api.discogs.com/masters/17200", "resource_url": "https://api.discogs.com/releases/4825435", "thumb": "", "cover_image": "", "title": "You And Me Both", "year": 1983, "formats": [{"name": "Vinyl", "qty": "1", "descriptions": ["LP", "Album"]}], "labels": [{"name": "Mute", "catno": "STUMM 12", "entity_type": "1", "entity_type_name": "Label", "id": 26391, "resource_url": "https://api.discogs.com/labels/26391"}, {"name": "CBS", "catno": "DNW 2885", "entity_type": "1", "entity_type_name": "Label", "id": 3072, "resource_url": "https://api.discogs.com/labels/3072"}], "artists": [{"name": "Yazoo", "anv": "", "join": "", "role": "", "tracks": "", "id": 2713, "resource_url": "https://api.discogs.com/artists/2713"}], "genres": ["Electronic"], "styles": ["Synth-pop"]}}]}` const collectionItemsByRelease = `{"pagination": {"page": 1, "pages": 1, "per_page": 50, "items": 1, "urls": {}}, "releases": [{"id": 12934893, "instance_id": 431009995, "date_added": "2020-01-19T14:19:11-08:00", "rating": 0, "basic_information": {"id": 12934893, "master_id": 0, "master_url": null, "resource_url": "https://api.discogs.com/releases/12934893", "thumb": "", "cover_image": "", "title": "Zonk", "year": 2018, "formats": [{"name": "Vinyl", "qty": "1", "text": "Purple", "descriptions": ["LP", "Album"]}], "labels": [{"name": "Permanent Record", "catno": "PR014", "entity_type": "1", "entity_type_name": "Label", "id": 833694, "resource_url": "https://api.discogs.com/labels/833694"}], "artists": [{"name": "Zoo Lake", "anv": "", "join": "", "role": "", "tracks": "", "id": 6868154, "resource_url": "https://api.discogs.com/artists/6868154"}], "genres": ["Rock"], "styles": ["Post-Punk", "Shoegaze", "Psychedelic Rock", "Noise", "Garage Rock", "Lo-Fi"]}}]}` + +const priceSuggestionJson = `{"Mint (M)": {"currency": "EUR", "value": 16.625}, "Near Mint (NM or M-)": {"currency": "EUR", "value": 14.875000000000002}, "Very Good Plus (VG+)": {"currency": "EUR", "value": 11.375000000000002}, "Very Good (VG)": {"currency": "EUR", "value": 7.875000000000001}, "Good Plus (G+)": {"currency": "EUR", "value": 4.375}, "Good (G)": {"currency": "EUR", "value": 2.625}, "Fair (F)": {"currency": "EUR", "value": 1.7500000000000002}, "Poor (P)": {"currency": "EUR", "value": 0.8750000000000001}}` + +const releaseStatsJson = `{"num_for_sale": 4, "lowest_price": {"value": 18.07, "currency": "USD"}, "blocked_from_sale": false}`