This commit is contained in:
aglowinthefield 2026-01-19 00:44:52 +00:00 committed by GitHub
commit c4d50ed37b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 906 additions and 9 deletions

View file

@ -0,0 +1,250 @@
using System.Linq;
using System.Net;
using FluentAssertions;
using Moq;
using NLog;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.ImportLists;
using NzbDrone.Core.ImportLists.Discogs;
using NzbDrone.Core.ImportLists.Exceptions;
namespace NzbDrone.Core.Test.ImportListTests.Discogs;
[TestFixture]
public class DiscogsListsFixture
{
private readonly HttpHeader _defaultHeaders = new () { ContentType = "application/json" };
private DiscogsListsParser _parser;
private Mock<IHttpClient> _httpClient;
private DiscogsListsSettings _settings;
private Logger _logger;
[SetUp]
public void SetUp()
{
_httpClient = new Mock<IHttpClient>();
_settings = new DiscogsListsSettings { Token = "token", ListId = "123", BaseUrl = "https://api.discogs.com" };
_logger = LogManager.GetCurrentClassLogger();
_parser = new DiscogsListsParser(_settings, _httpClient.Object, _logger);
}
[Test]
public void should_parse_release_items()
{
const string resourceUrl = "https://api.discogs.com/releases/3";
GivenReleaseDetails(resourceUrl, BuildReleaseResponse("Josh Wink", "Profound Sounds Vol. 1"));
var response = BuildListResponse(@"{
""items"": [
{
""type"": ""release"",
""id"": 3,
""display_title"": ""Josh Wink - Profound Sounds Vol. 1"",
""resource_url"": ""https://api.discogs.com/releases/3""
}
]
}");
var items = _parser.ParseResponse(response);
items.Should().HaveCount(1);
items.First().Artist.Should().Be("Josh Wink");
items.First().Album.Should().Be("Profound Sounds Vol. 1");
}
[Test]
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"": [
{
""type"": ""label"",
""id"": 7,
""display_title"": ""Ignore me"",
""resource_url"": ""https://api.discogs.com/labels/7""
}
]
}");
var items = _parser.ParseResponse(response);
items.Should().BeEmpty();
_httpClient.Verify(c => c.Execute(It.IsAny<HttpRequest>()), Times.Never);
}
[Test]
public void should_skip_release_when_details_fail()
{
var response = BuildListResponse(@"{
""items"": [
{
""type"": ""release"",
""id"": 3,
""display_title"": ""Josh Wink - Profound Sounds Vol. 1"",
""resource_url"": ""https://api.discogs.com/releases/3""
}
]
}");
_httpClient.Setup(c => c.Execute(It.IsAny<HttpRequest>()))
.Returns(new HttpResponse(new HttpRequest("https://api.discogs.com/releases/3"), _defaultHeaders, string.Empty, HttpStatusCode.NotFound));
var items = _parser.ParseResponse(response);
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<HttpRequest>()))
.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()
{
var response = BuildListResponse("<html></html>", contentType: "text/html");
_parser.Invoking(p => p.ParseResponse(response))
.Should().Throw<ImportListException>()
.WithMessage("*HTML content*");
}
private ImportListResponse BuildListResponse(string content, HttpStatusCode statusCode = HttpStatusCode.OK, string contentType = "application/json")
{
var httpRequest = new HttpRequest("https://api.discogs.com/lists/123");
var importListRequest = new ImportListRequest(httpRequest);
var headers = new HttpHeader { ContentType = contentType };
var httpResponse = new HttpResponse(httpRequest, headers, content, statusCode);
return new ImportListResponse(importListRequest, httpResponse);
}
private void GivenReleaseDetails(string resourceUrl, string payload, HttpStatusCode statusCode = HttpStatusCode.OK)
{
_httpClient.Setup(c => c.Execute(It.Is<HttpRequest>(r => r.Url.FullUri == resourceUrl)))
.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<HttpRequest>(r => r.Url.FullUri == resourceUrl)))
.Returns(new HttpResponse(new HttpRequest(resourceUrl), _defaultHeaders, payload, statusCode));
}
private static string BuildReleaseResponse(string artist, string title)
{
return $@"{{
""title"": ""{title}"",
""artists"": [
{{ ""name"": ""{artist}"", ""id"": 3 }}
]
}}";
}
private static string BuildArtistResponse(string name)
{
return $@"{{
""name"": ""{name}"",
""id"": 3227
}}";
}
}

View file

@ -0,0 +1,134 @@
using System.Linq;
using System.Net;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.ImportLists;
using NzbDrone.Core.ImportLists.Discogs;
using NzbDrone.Core.ImportLists.Exceptions;
namespace NzbDrone.Core.Test.ImportListTests.Discogs;
[TestFixture]
public class DiscogsWantlistFixture
{
private readonly HttpHeader _defaultHeaders = new () { ContentType = "application/json" };
private DiscogsWantlistParser _parser;
private Mock<IHttpClient> _httpClient;
private DiscogsWantlistSettings _settings;
[SetUp]
public void SetUp()
{
_httpClient = new Mock<IHttpClient>();
_settings = new DiscogsWantlistSettings { Token = "token", Username = "user", BaseUrl = "https://api.discogs.com" };
_parser = new DiscogsWantlistParser(_settings, _httpClient.Object);
}
[Test]
public void should_parse_wantlist_items()
{
var response = BuildWantlistResponse(@"{
""wants"": [
{
""basic_information"": {
""id"": 3,
""title"": ""Profound Sounds Vol. 1"",
""resource_url"": ""https://api.discogs.com/releases/3"",
""artists"": [
{ ""name"": ""Josh Wink"", ""id"": 123 }
]
}
}
]
}");
var items = _parser.ParseResponse(response);
items.Should().HaveCount(1);
items.First().Artist.Should().Be("Josh Wink");
items.First().Album.Should().Be("Profound Sounds Vol. 1");
_httpClient.Verify(c => c.Execute(It.IsAny<HttpRequest>()), Times.Never);
}
[Test]
public void should_skip_entries_without_artists()
{
var response = BuildWantlistResponse(@"{
""wants"": [
{
""basic_information"": {
""id"": 3,
""title"": ""Missing artists""
}
}
]
}");
var items = _parser.ParseResponse(response);
items.Should().BeEmpty();
_httpClient.Verify(c => c.Execute(It.IsAny<HttpRequest>()), Times.Never);
}
[Test]
public void should_skip_entries_without_title()
{
var response = BuildWantlistResponse(@"{
""wants"": [
{
""basic_information"": {
""id"": 3,
""artists"": [
{ ""name"": ""Test Artist"", ""id"": 123 }
]
}
}
]
}");
var items = _parser.ParseResponse(response);
items.Should().BeEmpty();
_httpClient.Verify(c => c.Execute(It.IsAny<HttpRequest>()), Times.Never);
}
[Test]
public void should_skip_entries_with_null_basic_information()
{
var response = BuildWantlistResponse(@"{
""wants"": [
{
""basic_information"": null
}
]
}");
var items = _parser.ParseResponse(response);
items.Should().BeEmpty();
_httpClient.Verify(c => c.Execute(It.IsAny<HttpRequest>()), Times.Never);
}
[Test]
public void should_throw_when_discogs_returns_html()
{
var response = BuildWantlistResponse("<html></html>", contentType: "text/html");
_parser.Invoking(p => p.ParseResponse(response))
.Should().Throw<ImportListException>()
.WithMessage("*HTML content*");
}
private ImportListResponse BuildWantlistResponse(string content, HttpStatusCode statusCode = HttpStatusCode.OK, string contentType = "application/json")
{
var httpRequest = new HttpRequest("https://api.discogs.com/users/user/wants");
var importListRequest = new ImportListRequest(httpRequest);
var headers = new HttpHeader { ContentType = contentType };
var httpResponse = new HttpResponse(httpRequest, headers, content, statusCode);
return new ImportListResponse(importListRequest, httpResponse);
}
}

View file

@ -3,6 +3,7 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.ImportLists;
using NzbDrone.Core.ImportLists.Discogs;
using NzbDrone.Core.ImportLists.LidarrLists;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Test.Framework;
@ -19,6 +20,7 @@ public void Setup()
_importLists = new List<IImportList>();
_importLists.Add(Mocker.Resolve<LidarrLists>());
_importLists.Add(Mocker.Resolve<DiscogsLists>());
Mocker.SetConstant<IEnumerable<IImportList>>(_importLists);
}

View file

@ -0,0 +1,58 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace NzbDrone.Core.ImportLists.Discogs;
public class DiscogsListResponse
{
public List<DiscogsListItem> Items { get; set; }
}
public class DiscogsListItem
{
public string Type { get; set; }
public int Id { get; set; }
[JsonProperty("display_title")]
public string DisplayTitle { get; set; }
[JsonProperty("resource_url")]
public string ResourceUrl { get; set; }
public string Uri { get; set; }
}
public class DiscogsReleaseResponse
{
public string Title { get; set; }
public List<DiscogsReleaseArtist> Artists { get; set; }
}
public class DiscogsReleaseArtist
{
public string Name { get; set; }
public int Id { get; set; }
}
public class DiscogsArtistResponse
{
public string Name { get; set; }
public int Id { get; set; }
}
public class DiscogsWantlistResponse
{
public List<DiscogsWantlistItem> Wants { get; set; }
}
public class DiscogsWantlistItem
{
[JsonProperty("basic_information")]
public DiscogsBasicInformation BasicInformation { get; set; }
}
public class DiscogsBasicInformation
{
public int Id { get; set; }
public string Title { get; set; }
[JsonProperty("resource_url")]
public string ResourceUrl { get; set; }
public List<DiscogsReleaseArtist> Artists { get; set; }
}

View file

@ -0,0 +1,35 @@
using System;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.ImportLists.Discogs
{
public class DiscogsLists : HttpImportListBase<DiscogsListsSettings>
{
public override string Name => "Discogs Lists";
public override ImportListType ListType => ImportListType.Discogs;
public override TimeSpan MinRefreshInterval => TimeSpan.FromHours(12);
public override TimeSpan RateLimit => TimeSpan.FromSeconds(3);
public DiscogsLists(IHttpClient httpClient,
IImportListStatusService importListStatusService,
IConfigService configService,
IParsingService parsingService,
Logger logger)
: base(httpClient, importListStatusService, configService, parsingService, logger)
{
}
public override IImportListRequestGenerator GetRequestGenerator()
{
return new DiscogsListsRequestGenerator(Settings);
}
public override IParseImportListResponse GetParser()
{
return new DiscogsListsParser(Settings, _httpClient, _logger);
}
}
}

View file

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.ImportLists.Discogs;
public class DiscogsListsParser(DiscogsListsSettings settings, IHttpClient httpClient, Logger logger)
: IParseImportListResponse
{
private readonly DiscogsListsSettings _settings = settings;
private readonly IHttpClient _httpClient = httpClient;
private readonly Logger _logger = logger;
public IList<ImportListItemInfo> ParseResponse(ImportListResponse importListResponse)
{
var items = new List<ImportListItemInfo>();
if (!PreProcess(importListResponse))
{
return items;
}
var jsonResponse = Json.Deserialize<DiscogsListResponse>(importListResponse.Content);
if (jsonResponse?.Items == null)
{
return items;
}
foreach (var item in jsonResponse.Items)
{
if (item.ResourceUrl.IsNullOrWhiteSpace())
{
continue;
}
try
{
ImportListItemInfo itemInfo = null;
if (item.Type == "release")
{
itemInfo = FetchReleaseDetails(item.ResourceUrl);
}
else if (item.Type == "artist")
{
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");
}
}
return items;
}
private bool PreProcess(ImportListResponse importListResponse)
{
DiscogsParserHelper.EnsureValidResponse(importListResponse,
"Discogs API responded with HTML content. List may be too large or API may be unavailable.");
return true;
}
private ImportListItemInfo FetchReleaseDetails(string resourceUrl)
{
return DiscogsParserHelper.FetchReleaseDetails(_httpClient, _settings.Token, resourceUrl);
}
private ImportListItemInfo FetchArtistDetails(string resourceUrl)
{
return DiscogsParserHelper.FetchArtistDetails(_httpClient, _settings.Token, resourceUrl);
}
}

View file

@ -0,0 +1,33 @@
using System.Collections.Generic;
using NzbDrone.Common.Http;
namespace NzbDrone.Core.ImportLists.Discogs
{
public class DiscogsListsRequestGenerator : IImportListRequestGenerator
{
private readonly DiscogsListsSettings _settings;
public DiscogsListsRequestGenerator(DiscogsListsSettings settings)
{
_settings = settings;
}
public virtual ImportListPageableRequestChain GetListItems()
{
var pageableRequests = new ImportListPageableRequestChain();
pageableRequests.Add(GetPagedRequests());
return pageableRequests;
}
private IEnumerable<ImportListRequest> GetPagedRequests()
{
var request = new HttpRequestBuilder(_settings.BaseUrl.TrimEnd('/'))
.Resource($"/lists/{_settings.ListId}")
.SetHeader("Authorization", $"Discogs token={_settings.Token}")
.Build();
yield return new ImportListRequest(request);
}
}
}

View file

@ -0,0 +1,38 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.ImportLists.Discogs
{
public class DiscogsListsSettingsValidator : AbstractValidator<DiscogsListsSettings>
{
public DiscogsListsSettingsValidator()
{
RuleFor(c => c.Token).NotEmpty();
RuleFor(c => c.ListId).NotEmpty();
}
}
public class DiscogsListsSettings : IImportListSettings
{
private static readonly DiscogsListsSettingsValidator Validator = new ();
public DiscogsListsSettings()
{
BaseUrl = "https://api.discogs.com";
}
public string BaseUrl { get; set; }
[FieldDefinition(0, Label = "Token", Privacy = PrivacyLevel.ApiKey, HelpText = "Discogs Personal Access Token")]
public string Token { get; set; }
[FieldDefinition(1, Label = "List ID", HelpText = "ID of the Discogs list to import")]
public string ListId { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View file

@ -0,0 +1,82 @@
using System.Linq;
using System.Net;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.ImportLists.Exceptions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.ImportLists.Discogs;
internal static class DiscogsParserHelper
{
public static ImportListItemInfo FetchReleaseDetails(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 releaseResponse = Json.Deserialize<DiscogsReleaseResponse>(response.Content);
if (releaseResponse?.Artists?.Any() != true || releaseResponse.Title.IsNullOrWhiteSpace())
{
return null;
}
return new ImportListItemInfo
{
Artist = releaseResponse.Artists.First().Name,
Album = releaseResponse.Title
};
}
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<DiscogsArtistResponse>(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)
{
throw new ImportListException(importListResponse,
"Discogs API call resulted in an unexpected StatusCode [{0}]",
importListResponse.HttpResponse.StatusCode);
}
if (importListResponse.HttpResponse.Headers.ContentType != null &&
importListResponse.HttpResponse.Headers.ContentType.Contains("text/html"))
{
throw new ImportListException(importListResponse, htmlContentMessage);
}
}
}

View file

@ -0,0 +1,36 @@
using System;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.ImportLists.Discogs
{
public class DiscogsWantlist : HttpImportListBase<DiscogsWantlistSettings>
{
public override string Name => "Discogs Wantlist";
public override ImportListType ListType => ImportListType.Discogs;
public override TimeSpan MinRefreshInterval => TimeSpan.FromHours(12);
public override TimeSpan RateLimit => TimeSpan.FromSeconds(3); // Conservative rate limiting to avoid 429 errors when fetching many releases
public override int PageSize => 50; // Discogs API supports pagination with page and per_page parameters
public DiscogsWantlist(IHttpClient httpClient,
IImportListStatusService importListStatusService,
IConfigService configService,
IParsingService parsingService,
Logger logger)
: base(httpClient, importListStatusService, configService, parsingService, logger)
{
}
public override IImportListRequestGenerator GetRequestGenerator()
{
return new DiscogsWantlistRequestGenerator { Settings = Settings };
}
public override IParseImportListResponse GetParser()
{
return new DiscogsWantlistParser(Settings, _httpClient);
}
}
}

View file

@ -0,0 +1,69 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.ImportLists.Discogs;
public class DiscogsWantlistParser : IParseImportListResponse
{
private readonly DiscogsWantlistSettings _settings;
private readonly IHttpClient _httpClient;
public DiscogsWantlistParser(DiscogsWantlistSettings settings, IHttpClient httpClient)
{
_settings = settings;
_httpClient = httpClient;
}
public IList<ImportListItemInfo> ParseResponse(ImportListResponse importListResponse)
{
var items = new List<ImportListItemInfo>();
if (!PreProcess(importListResponse))
{
return items;
}
var jsonResponse = Json.Deserialize<DiscogsWantlistResponse>(importListResponse.Content);
if (jsonResponse?.Wants == null)
{
return items;
}
foreach (var want in jsonResponse.Wants)
{
var basicInfo = want?.BasicInformation;
if (basicInfo == null)
{
continue;
}
// The wantlist API includes artists and title in basic_information, so no need to fetch release details
// If you want is artists.First().Name and title, then fetching the release details is redundant according to their API.
if (basicInfo.Artists?.Any() != true || basicInfo.Title.IsNullOrWhiteSpace())
{
continue;
}
items.AddIfNotNull(new ImportListItemInfo
{
Artist = basicInfo.Artists.First().Name,
Album = basicInfo.Title
});
}
return items;
}
private bool PreProcess(ImportListResponse importListResponse)
{
DiscogsParserHelper.EnsureValidResponse(importListResponse,
"Discogs API responded with HTML content. Wantlist may be too large or API may be unavailable.");
return true;
}
}

View file

@ -0,0 +1,41 @@
using System.Collections.Generic;
using NzbDrone.Common.Http;
namespace NzbDrone.Core.ImportLists.Discogs
{
public class DiscogsWantlistRequestGenerator : IImportListRequestGenerator
{
public DiscogsWantlistSettings Settings { get; set; }
public int MaxPages { get; set; }
public int PageSize { get; set; }
public DiscogsWantlistRequestGenerator()
{
MaxPages = 10; // Allow fetching up to 10 pages
PageSize = 50; // Discogs API supports pagination with page and per_page parameters (max 100 per page)
}
public virtual ImportListPageableRequestChain GetListItems()
{
var pageableRequests = new ImportListPageableRequestChain();
pageableRequests.Add(GetPagedRequests());
return pageableRequests;
}
private IEnumerable<ImportListRequest> GetPagedRequests()
{
for (var page = 1; page <= MaxPages; page++)
{
var request = new HttpRequestBuilder(Settings.BaseUrl.TrimEnd('/'))
.Resource($"/users/{Settings.Username}/wants")
.AddQueryParam("page", page)
.AddQueryParam("per_page", PageSize)
.SetHeader("Authorization", $"Discogs token={Settings.Token}")
.Build();
yield return new ImportListRequest(request);
}
}
}
}

View file

@ -0,0 +1,38 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.ImportLists.Discogs
{
public class DiscogsWantlistSettingsValidator : AbstractValidator<DiscogsWantlistSettings>
{
public DiscogsWantlistSettingsValidator()
{
RuleFor(c => c.Token).NotEmpty();
RuleFor(c => c.Username).NotEmpty();
}
}
public class DiscogsWantlistSettings : IImportListSettings
{
private static readonly DiscogsWantlistSettingsValidator Validator = new DiscogsWantlistSettingsValidator();
public DiscogsWantlistSettings()
{
BaseUrl = "https://api.discogs.com";
}
public string BaseUrl { get; set; }
[FieldDefinition(0, Label = "Token", Privacy = PrivacyLevel.ApiKey, HelpText = "Discogs Personal Access Token")]
public string Token { get; set; }
[FieldDefinition(1, Label = "Username", HelpText = "Discogs username whose wantlist to import")]
public string Username { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View file

@ -1,11 +1,11 @@
namespace NzbDrone.Core.ImportLists
namespace NzbDrone.Core.ImportLists;
public enum ImportListType
{
public enum ImportListType
{
Program,
Spotify,
LastFm,
Other,
Advanced
}
Program,
Spotify,
LastFm,
Discogs,
Other,
Advanced
}