From d900890e6ba033e73151a64cf56ee0b22d18fc5a Mon Sep 17 00:00:00 2001 From: aglowinthefield <146008217+aglowinthefield@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:28:06 -0500 Subject: [PATCH] Support artist type from discogs api --- .../Discogs/DiscogsListsFixture.cs | 118 +++++++++++++++++- .../ImportLists/Discogs/DiscogsApi.cs | 6 + .../ImportLists/Discogs/DiscogsListsParser.cs | 44 ++++--- .../Discogs/DiscogsParserHelper.cs | 27 ++++ 4 files changed, 177 insertions(+), 18 deletions(-) diff --git a/src/NzbDrone.Core.Test/ImportListTests/Discogs/DiscogsListsFixture.cs b/src/NzbDrone.Core.Test/ImportListTests/Discogs/DiscogsListsFixture.cs index f1fc83181..390b4f57c 100644 --- a/src/NzbDrone.Core.Test/ImportListTests/Discogs/DiscogsListsFixture.cs +++ b/src/NzbDrone.Core.Test/ImportListTests/Discogs/DiscogsListsFixture.cs @@ -55,7 +55,65 @@ public void should_parse_release_items() } [Test] - public void should_ignore_non_release_items() + public void should_parse_artist_items() + { + const string resourceUrl = "https://api.discogs.com/artists/3227"; + GivenArtistDetails(resourceUrl, BuildArtistResponse("Silent Phase")); + + var response = BuildListResponse(@"{ + ""items"": [ + { + ""type"": ""artist"", + ""id"": 3227, + ""display_title"": ""Silent Phase"", + ""resource_url"": ""https://api.discogs.com/artists/3227"" + } + ] + }"); + + var items = _parser.ParseResponse(response); + + items.Should().HaveCount(1); + items.First().Artist.Should().Be("Silent Phase"); + items.First().Album.Should().BeNull(); + } + + [Test] + public void should_parse_mixed_release_and_artist_items() + { + const string releaseUrl = "https://api.discogs.com/releases/4674"; + const string artistUrl = "https://api.discogs.com/artists/3227"; + GivenReleaseDetails(releaseUrl, BuildReleaseResponse("Silent Phase", "The Rewired Mixes")); + GivenArtistDetails(artistUrl, BuildArtistResponse("Silent Phase")); + + var response = BuildListResponse(@"{ + ""items"": [ + { + ""type"": ""release"", + ""id"": 4674, + ""display_title"": ""Silent Phase - The Rewired Mixes"", + ""resource_url"": ""https://api.discogs.com/releases/4674"" + }, + { + ""type"": ""artist"", + ""id"": 3227, + ""display_title"": ""Silent Phase"", + ""resource_url"": ""https://api.discogs.com/artists/3227"" + } + ] + }"); + + var items = _parser.ParseResponse(response); + + items.Should().HaveCount(2); + items.First().Artist.Should().Be("Silent Phase"); + items.First().Album.Should().Be("The Rewired Mixes"); + items.Last().Artist.Should().Be("Silent Phase"); + items.Last().Album.Should().BeNull(); + } + + [Test] + public void should_ignore_non_release_and_non_artist_items() { var response = BuildListResponse(@"{ ""items"": [ @@ -96,6 +154,50 @@ public void should_skip_release_when_details_fail() items.Should().BeEmpty(); } + [Test] + public void should_skip_artist_when_details_fail() + { + var response = BuildListResponse(@"{ + ""items"": [ + { + ""type"": ""artist"", + ""id"": 3227, + ""display_title"": ""Silent Phase"", + ""resource_url"": ""https://api.discogs.com/artists/3227"" + } + ] + }"); + + _httpClient.Setup(c => c.Execute(It.IsAny())) + .Returns(new HttpResponse(new HttpRequest("https://api.discogs.com/artists/3227"), _defaultHeaders, string.Empty, HttpStatusCode.NotFound)); + + var items = _parser.ParseResponse(response); + + items.Should().BeEmpty(); + } + + [Test] + public void should_skip_artist_when_name_is_missing() + { + const string resourceUrl = "https://api.discogs.com/artists/3227"; + GivenArtistDetails(resourceUrl, @"{ ""id"": 3227 }"); + + var response = BuildListResponse(@"{ + ""items"": [ + { + ""type"": ""artist"", + ""id"": 3227, + ""display_title"": ""Silent Phase"", + ""resource_url"": ""https://api.discogs.com/artists/3227"" + } + ] + }"); + + var items = _parser.ParseResponse(response); + + items.Should().BeEmpty(); + } + [Test] public void should_throw_when_discogs_returns_html() { @@ -122,6 +224,12 @@ private void GivenReleaseDetails(string resourceUrl, string payload, HttpStatusC .Returns(new HttpResponse(new HttpRequest(resourceUrl), _defaultHeaders, payload, statusCode)); } + private void GivenArtistDetails(string resourceUrl, string payload, HttpStatusCode statusCode = HttpStatusCode.OK) + { + _httpClient.Setup(c => c.Execute(It.Is(r => r.Url.FullUri == resourceUrl))) + .Returns(new HttpResponse(new HttpRequest(resourceUrl), _defaultHeaders, payload, statusCode)); + } + private static string BuildReleaseResponse(string artist, string title) { return $@"{{ @@ -131,4 +239,12 @@ private static string BuildReleaseResponse(string artist, string title) ] }}"; } + + private static string BuildArtistResponse(string name) + { + return $@"{{ + ""name"": ""{name}"", + ""id"": 3227 + }}"; + } } diff --git a/src/NzbDrone.Core/ImportLists/Discogs/DiscogsApi.cs b/src/NzbDrone.Core/ImportLists/Discogs/DiscogsApi.cs index f9a1b6c65..a23e50cd5 100644 --- a/src/NzbDrone.Core/ImportLists/Discogs/DiscogsApi.cs +++ b/src/NzbDrone.Core/ImportLists/Discogs/DiscogsApi.cs @@ -31,6 +31,12 @@ public class DiscogsReleaseArtist public int Id { get; set; } } +public class DiscogsArtistResponse +{ + public string Name { get; set; } + public int Id { get; set; } +} + public class DiscogsWantlistResponse { public List Wants { get; set; } diff --git a/src/NzbDrone.Core/ImportLists/Discogs/DiscogsListsParser.cs b/src/NzbDrone.Core/ImportLists/Discogs/DiscogsListsParser.cs index 79f10526b..4f36069b6 100644 --- a/src/NzbDrone.Core/ImportLists/Discogs/DiscogsListsParser.cs +++ b/src/NzbDrone.Core/ImportLists/Discogs/DiscogsListsParser.cs @@ -8,18 +8,11 @@ namespace NzbDrone.Core.ImportLists.Discogs; -public class DiscogsListsParser : IParseImportListResponse +public class DiscogsListsParser(DiscogsListsSettings settings, IHttpClient httpClient, Logger logger) : IParseImportListResponse { - private readonly DiscogsListsSettings _settings; - private readonly IHttpClient _httpClient; - private readonly Logger _logger; - - public DiscogsListsParser(DiscogsListsSettings settings, IHttpClient httpClient, Logger logger) - { - _settings = settings; - _httpClient = httpClient; - _logger = logger; - } + private readonly DiscogsListsSettings _settings = settings; + private readonly IHttpClient _httpClient = httpClient; + private readonly Logger _logger = logger; public IList ParseResponse(ImportListResponse importListResponse) { @@ -39,17 +32,29 @@ public IList ParseResponse(ImportListResponse importListResp foreach (var item in jsonResponse.Items) { - if (item.Type == "release" && item.ResourceUrl.IsNotNullOrWhiteSpace()) + if (item.ResourceUrl.IsNullOrWhiteSpace()) { - try + continue; + } + + try + { + ImportListItemInfo itemInfo = null; + + if (item.Type == "release") { - var releaseInfo = FetchReleaseDetails(item.ResourceUrl); - items.AddIfNotNull(releaseInfo); + itemInfo = FetchReleaseDetails(item.ResourceUrl); } - catch (Exception ex) + else if (item.Type == "artist") { - _logger.Error(ex, "Discogs release details API call resulted in an unexpected exception"); + itemInfo = FetchArtistDetails(item.ResourceUrl); } + + items.AddIfNotNull(itemInfo); + } + catch (Exception ex) + { + _logger.Error(ex, "Discogs API call resulted in an unexpected exception for {0} type item", item.Type ?? "unknown"); } } @@ -67,4 +72,9 @@ private ImportListItemInfo FetchReleaseDetails(string resourceUrl) { return DiscogsParserHelper.FetchReleaseDetails(_httpClient, _settings.Token, resourceUrl); } + + private ImportListItemInfo FetchArtistDetails(string resourceUrl) + { + return DiscogsParserHelper.FetchArtistDetails(_httpClient, _settings.Token, resourceUrl); + } } diff --git a/src/NzbDrone.Core/ImportLists/Discogs/DiscogsParserHelper.cs b/src/NzbDrone.Core/ImportLists/Discogs/DiscogsParserHelper.cs index e16a2750a..3765aaf28 100644 --- a/src/NzbDrone.Core/ImportLists/Discogs/DiscogsParserHelper.cs +++ b/src/NzbDrone.Core/ImportLists/Discogs/DiscogsParserHelper.cs @@ -37,6 +37,33 @@ public static ImportListItemInfo FetchReleaseDetails(IHttpClient httpClient, str }; } + public static ImportListItemInfo FetchArtistDetails(IHttpClient httpClient, string token, string resourceUrl) + { + var request = new HttpRequestBuilder(resourceUrl) + .SetHeader("Authorization", $"Discogs token={token}") + .Build(); + + var response = httpClient.Execute(request); + + if (response.StatusCode != HttpStatusCode.OK) + { + return null; + } + + var artistResponse = Json.Deserialize(response.Content); + + if (artistResponse?.Name.IsNullOrWhiteSpace() == true) + { + return null; + } + + return new ImportListItemInfo + { + Artist = artistResponse.Name, + Album = null // Artists don't have a specific album, just the artist name + }; + } + public static void EnsureValidResponse(ImportListResponse importListResponse, string htmlContentMessage) { if (importListResponse.HttpResponse.StatusCode != HttpStatusCode.OK)