mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-20 13:33:34 -07:00
Update SpotifyAPI.Web
This commit is contained in:
parent
d19ea704c5
commit
23730ddded
13 changed files with 324 additions and 230 deletions
|
@ -5,7 +5,6 @@ using NUnit.Framework;
|
|||
using NzbDrone.Core.ImportLists.Spotify;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using SpotifyAPI.Web;
|
||||
using SpotifyAPI.Web.Models;
|
||||
|
||||
namespace NzbDrone.Core.Test.ImportListTests
|
||||
{
|
||||
|
@ -13,15 +12,15 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
public class SpotifyFollowedArtistsFixture : CoreTest<SpotifyFollowedArtists>
|
||||
{
|
||||
// placeholder, we don't use real API
|
||||
private readonly SpotifyWebAPI _api = null;
|
||||
private readonly SpotifyClient _api = null;
|
||||
|
||||
[Test]
|
||||
public void should_not_throw_if_followed_is_null()
|
||||
{
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetFollowedArtists(It.IsAny<SpotifyFollowedArtists>(),
|
||||
It.IsAny<SpotifyWebAPI>()))
|
||||
.Returns(default(FollowedArtists));
|
||||
It.IsAny<SpotifyClient>()))
|
||||
.Returns(default(FollowedArtistsResponse));
|
||||
|
||||
var result = Subject.Fetch(_api);
|
||||
|
||||
|
@ -31,14 +30,14 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
[Test]
|
||||
public void should_not_throw_if_followed_artists_is_null()
|
||||
{
|
||||
var followed = new FollowedArtists
|
||||
var followed = new FollowedArtistsResponse
|
||||
{
|
||||
Artists = null
|
||||
};
|
||||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetFollowedArtists(It.IsAny<SpotifyFollowedArtists>(),
|
||||
It.IsAny<SpotifyWebAPI>()))
|
||||
It.IsAny<SpotifyClient>()))
|
||||
.Returns(followed);
|
||||
|
||||
var result = Subject.Fetch(_api);
|
||||
|
@ -49,9 +48,9 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
[Test]
|
||||
public void should_not_throw_if_followed_artist_items_is_null()
|
||||
{
|
||||
var followed = new FollowedArtists
|
||||
var followed = new FollowedArtistsResponse
|
||||
{
|
||||
Artists = new CursorPaging<FullArtist>
|
||||
Artists = new CursorPaging<FullArtist, FollowedArtistsResponse>
|
||||
{
|
||||
Items = null
|
||||
}
|
||||
|
@ -59,7 +58,7 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetFollowedArtists(It.IsAny<SpotifyFollowedArtists>(),
|
||||
It.IsAny<SpotifyWebAPI>()))
|
||||
It.IsAny<SpotifyClient>()))
|
||||
.Returns(followed);
|
||||
|
||||
var result = Subject.Fetch(_api);
|
||||
|
@ -71,9 +70,9 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
[Test]
|
||||
public void should_not_throw_if_artist_is_null()
|
||||
{
|
||||
var followed = new FollowedArtists
|
||||
var followed = new FollowedArtistsResponse
|
||||
{
|
||||
Artists = new CursorPaging<FullArtist>
|
||||
Artists = new CursorPaging<FullArtist, FollowedArtistsResponse>
|
||||
{
|
||||
Items = new List<FullArtist>
|
||||
{
|
||||
|
@ -84,7 +83,7 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetFollowedArtists(It.IsAny<SpotifyFollowedArtists>(),
|
||||
It.IsAny<SpotifyWebAPI>()))
|
||||
It.IsAny<SpotifyClient>()))
|
||||
.Returns(followed);
|
||||
|
||||
var result = Subject.Fetch(_api);
|
||||
|
@ -96,9 +95,9 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
[Test]
|
||||
public void should_parse_followed_artist()
|
||||
{
|
||||
var followed = new FollowedArtists
|
||||
var followed = new FollowedArtistsResponse
|
||||
{
|
||||
Artists = new CursorPaging<FullArtist>
|
||||
Artists = new CursorPaging<FullArtist, FollowedArtistsResponse>
|
||||
{
|
||||
Items = new List<FullArtist>
|
||||
{
|
||||
|
@ -112,7 +111,7 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetFollowedArtists(It.IsAny<SpotifyFollowedArtists>(),
|
||||
It.IsAny<SpotifyWebAPI>()))
|
||||
It.IsAny<SpotifyClient>()))
|
||||
.Returns(followed);
|
||||
|
||||
var result = Subject.Fetch(_api);
|
||||
|
@ -123,9 +122,9 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
[Test]
|
||||
public void should_not_throw_if_get_next_page_returns_null()
|
||||
{
|
||||
var followed = new FollowedArtists
|
||||
var followed = new FollowedArtistsResponse
|
||||
{
|
||||
Artists = new CursorPaging<FullArtist>
|
||||
Artists = new CursorPaging<FullArtist, FollowedArtistsResponse>
|
||||
{
|
||||
Items = new List<FullArtist>
|
||||
{
|
||||
|
@ -140,14 +139,14 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetFollowedArtists(It.IsAny<SpotifyFollowedArtists>(),
|
||||
It.IsAny<SpotifyWebAPI>()))
|
||||
It.IsAny<SpotifyClient>()))
|
||||
.Returns(followed);
|
||||
|
||||
Mocker.GetMock<ISpotifyProxy>()
|
||||
.Setup(x => x.GetNextPage(It.IsAny<SpotifyFollowedArtists>(),
|
||||
It.IsAny<SpotifyWebAPI>(),
|
||||
It.IsAny<FollowedArtists>()))
|
||||
.Returns(default(FollowedArtists));
|
||||
It.IsAny<SpotifyClient>(),
|
||||
It.IsAny<FollowedArtistsResponse>()))
|
||||
.Returns(default(FollowedArtistsResponse));
|
||||
|
||||
var result = Subject.Fetch(_api);
|
||||
|
||||
|
@ -155,8 +154,8 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>()
|
||||
.Verify(v => v.GetNextPage(It.IsAny<SpotifyFollowedArtists>(),
|
||||
It.IsAny<SpotifyWebAPI>(),
|
||||
It.IsAny<FollowedArtists>()),
|
||||
It.IsAny<SpotifyClient>(),
|
||||
It.IsAny<FollowedArtistsResponse>()),
|
||||
Times.Once());
|
||||
}
|
||||
|
||||
|
@ -164,9 +163,9 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
[TestCase("")]
|
||||
public void should_skip_bad_artist_names(string name)
|
||||
{
|
||||
var followed = new FollowedArtists
|
||||
var followed = new FollowedArtistsResponse
|
||||
{
|
||||
Artists = new CursorPaging<FullArtist>
|
||||
Artists = new CursorPaging<FullArtist, FollowedArtistsResponse>
|
||||
{
|
||||
Items = new List<FullArtist>
|
||||
{
|
||||
|
@ -180,7 +179,7 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetFollowedArtists(It.IsAny<SpotifyFollowedArtists>(),
|
||||
It.IsAny<SpotifyWebAPI>()))
|
||||
It.IsAny<SpotifyClient>()))
|
||||
.Returns(followed);
|
||||
|
||||
var result = Subject.Fetch(_api);
|
||||
|
|
|
@ -5,7 +5,6 @@ using NUnit.Framework;
|
|||
using NzbDrone.Core.ImportLists.Spotify;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using SpotifyAPI.Web;
|
||||
using SpotifyAPI.Web.Models;
|
||||
|
||||
namespace NzbDrone.Core.Test.ImportListTests
|
||||
{
|
||||
|
@ -13,16 +12,16 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
public class SpotifyPlaylistFixture : CoreTest<SpotifyPlaylist>
|
||||
{
|
||||
// placeholder, we don't use real API
|
||||
private readonly SpotifyWebAPI _api = null;
|
||||
private readonly SpotifyClient _api = null;
|
||||
|
||||
[Test]
|
||||
public void should_not_throw_if_playlist_tracks_is_null()
|
||||
{
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetPlaylist(It.IsAny<SpotifyPlaylist>(),
|
||||
It.IsAny<SpotifyWebAPI>(),
|
||||
It.IsAny<SpotifyClient>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>()))
|
||||
It.IsAny<List<string>>()))
|
||||
.Returns(default(FullPlaylist));
|
||||
|
||||
var result = Subject.Fetch(_api, "playlistid");
|
||||
|
@ -33,16 +32,16 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
[Test]
|
||||
public void should_not_throw_if_playlist_tracks_items_is_null()
|
||||
{
|
||||
var playlistTracks = new Paging<PlaylistTrack>
|
||||
var playlistTracks = new Paging<PlaylistTrack<IPlayableItem>>
|
||||
{
|
||||
Items = null
|
||||
};
|
||||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetPlaylist(It.IsAny<SpotifyPlaylist>(),
|
||||
It.IsAny<SpotifyWebAPI>(),
|
||||
It.IsAny<SpotifyClient>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>()))
|
||||
It.IsAny<List<string>>()))
|
||||
.Returns(new FullPlaylist { Tracks = playlistTracks });
|
||||
|
||||
var result = Subject.Fetch(_api, "playlistid");
|
||||
|
@ -53,9 +52,9 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
[Test]
|
||||
public void should_not_throw_if_playlist_track_is_null()
|
||||
{
|
||||
var playlistTracks = new Paging<PlaylistTrack>
|
||||
var playlistTracks = new Paging<PlaylistTrack<IPlayableItem>>
|
||||
{
|
||||
Items = new List<PlaylistTrack>
|
||||
Items = new List<PlaylistTrack<IPlayableItem>>
|
||||
{
|
||||
null
|
||||
}
|
||||
|
@ -63,9 +62,9 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetPlaylist(It.IsAny<SpotifyPlaylist>(),
|
||||
It.IsAny<SpotifyWebAPI>(),
|
||||
It.IsAny<SpotifyClient>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>()))
|
||||
It.IsAny<List<string>>()))
|
||||
.Returns(new FullPlaylist { Tracks = playlistTracks });
|
||||
|
||||
var result = Subject.Fetch(_api, "playlistid");
|
||||
|
@ -76,11 +75,11 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
[Test]
|
||||
public void should_use_album_artist_when_it_exists()
|
||||
{
|
||||
var playlistTracks = new Paging<PlaylistTrack>
|
||||
var playlistTracks = new Paging<PlaylistTrack<IPlayableItem>>
|
||||
{
|
||||
Items = new List<PlaylistTrack>
|
||||
Items = new List<PlaylistTrack<IPlayableItem>>
|
||||
{
|
||||
new PlaylistTrack
|
||||
new PlaylistTrack<IPlayableItem>
|
||||
{
|
||||
Track = new FullTrack
|
||||
{
|
||||
|
@ -109,9 +108,9 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetPlaylist(It.IsAny<SpotifyPlaylist>(),
|
||||
It.IsAny<SpotifyWebAPI>(),
|
||||
It.IsAny<SpotifyClient>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>()))
|
||||
It.IsAny<List<string>>()))
|
||||
.Returns(new FullPlaylist { Tracks = playlistTracks });
|
||||
|
||||
var result = Subject.Fetch(_api, "playlistid");
|
||||
|
@ -123,11 +122,11 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
[Test]
|
||||
public void should_fall_back_to_track_artist_if_album_artist_missing()
|
||||
{
|
||||
var playlistTracks = new Paging<PlaylistTrack>
|
||||
var playlistTracks = new Paging<PlaylistTrack<IPlayableItem>>
|
||||
{
|
||||
Items = new List<PlaylistTrack>
|
||||
Items = new List<PlaylistTrack<IPlayableItem>>
|
||||
{
|
||||
new PlaylistTrack
|
||||
new PlaylistTrack<IPlayableItem>
|
||||
{
|
||||
Track = new FullTrack
|
||||
{
|
||||
|
@ -156,9 +155,9 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetPlaylist(It.IsAny<SpotifyPlaylist>(),
|
||||
It.IsAny<SpotifyWebAPI>(),
|
||||
It.IsAny<SpotifyClient>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>()))
|
||||
It.IsAny<List<string>>()))
|
||||
.Returns(new FullPlaylist { Tracks = playlistTracks });
|
||||
|
||||
var result = Subject.Fetch(_api, "playlistid");
|
||||
|
@ -172,11 +171,11 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
[TestCase(null, "TrackArtist", null)]
|
||||
public void should_skip_bad_artist_or_album_names(string albumArtistName, string trackArtistName, string albumName)
|
||||
{
|
||||
var playlistTracks = new Paging<PlaylistTrack>
|
||||
var playlistTracks = new Paging<PlaylistTrack<IPlayableItem>>
|
||||
{
|
||||
Items = new List<PlaylistTrack>
|
||||
Items = new List<PlaylistTrack<IPlayableItem>>
|
||||
{
|
||||
new PlaylistTrack
|
||||
new PlaylistTrack<IPlayableItem>
|
||||
{
|
||||
Track = new FullTrack
|
||||
{
|
||||
|
@ -205,9 +204,9 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetPlaylist(It.IsAny<SpotifyPlaylist>(),
|
||||
It.IsAny<SpotifyWebAPI>(),
|
||||
It.IsAny<SpotifyClient>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>()))
|
||||
It.IsAny<List<string>>()))
|
||||
.Returns(new FullPlaylist { Tracks = playlistTracks });
|
||||
|
||||
var result = Subject.Fetch(_api, "playlistid");
|
||||
|
@ -218,11 +217,11 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
[Test]
|
||||
public void should_not_throw_if_get_next_page_returns_null()
|
||||
{
|
||||
var playlistTracks = new Paging<PlaylistTrack>
|
||||
var playlistTracks = new Paging<PlaylistTrack<IPlayableItem>>
|
||||
{
|
||||
Items = new List<PlaylistTrack>
|
||||
Items = new List<PlaylistTrack<IPlayableItem>>
|
||||
{
|
||||
new PlaylistTrack
|
||||
new PlaylistTrack<IPlayableItem>
|
||||
{
|
||||
Track = new FullTrack
|
||||
{
|
||||
|
@ -252,16 +251,16 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetPlaylist(It.IsAny<SpotifyPlaylist>(),
|
||||
It.IsAny<SpotifyWebAPI>(),
|
||||
It.IsAny<SpotifyClient>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>()))
|
||||
It.IsAny<List<string>>()))
|
||||
.Returns(new FullPlaylist { Tracks = playlistTracks });
|
||||
|
||||
Mocker.GetMock<ISpotifyProxy>()
|
||||
.Setup(x => x.GetNextPage(It.IsAny<SpotifyFollowedArtists>(),
|
||||
It.IsAny<SpotifyWebAPI>(),
|
||||
It.IsAny<Paging<PlaylistTrack>>()))
|
||||
.Returns(default(Paging<PlaylistTrack>));
|
||||
It.IsAny<SpotifyClient>(),
|
||||
It.IsAny<Paging<PlaylistTrack<IPlayableItem>>>()))
|
||||
.Returns(default(Paging<PlaylistTrack<IPlayableItem>>));
|
||||
|
||||
var result = Subject.Fetch(_api, "playlistid");
|
||||
|
||||
|
@ -269,8 +268,8 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>()
|
||||
.Verify(x => x.GetNextPage(It.IsAny<SpotifyPlaylist>(),
|
||||
It.IsAny<SpotifyWebAPI>(),
|
||||
It.IsAny<Paging<PlaylistTrack>>()),
|
||||
It.IsAny<SpotifyClient>(),
|
||||
It.IsAny<Paging<PlaylistTrack<IPlayableItem>>>()),
|
||||
Times.Once());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ using NUnit.Framework;
|
|||
using NzbDrone.Core.ImportLists.Spotify;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using SpotifyAPI.Web;
|
||||
using SpotifyAPI.Web.Models;
|
||||
|
||||
namespace NzbDrone.Core.Test.ImportListTests
|
||||
{
|
||||
|
@ -13,14 +12,14 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
public class SpotifySavedAlbumsFixture : CoreTest<SpotifySavedAlbums>
|
||||
{
|
||||
// placeholder, we don't use real API
|
||||
private readonly SpotifyWebAPI _api = null;
|
||||
private readonly SpotifyClient _api = null;
|
||||
|
||||
[Test]
|
||||
public void should_not_throw_if_saved_albums_is_null()
|
||||
{
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetSavedAlbums(It.IsAny<SpotifySavedAlbums>(),
|
||||
It.IsAny<SpotifyWebAPI>()))
|
||||
It.IsAny<SpotifyClient>()))
|
||||
.Returns(default(Paging<SavedAlbum>));
|
||||
|
||||
var result = Subject.Fetch(_api);
|
||||
|
@ -38,7 +37,7 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetSavedAlbums(It.IsAny<SpotifySavedAlbums>(),
|
||||
It.IsAny<SpotifyWebAPI>()))
|
||||
It.IsAny<SpotifyClient>()))
|
||||
.Returns(savedAlbums);
|
||||
|
||||
var result = Subject.Fetch(_api);
|
||||
|
@ -59,7 +58,7 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetSavedAlbums(It.IsAny<SpotifySavedAlbums>(),
|
||||
It.IsAny<SpotifyWebAPI>()))
|
||||
It.IsAny<SpotifyClient>()))
|
||||
.Returns(savedAlbums);
|
||||
|
||||
var result = Subject.Fetch(_api);
|
||||
|
@ -93,7 +92,7 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetSavedAlbums(It.IsAny<SpotifySavedAlbums>(),
|
||||
It.IsAny<SpotifyWebAPI>()))
|
||||
It.IsAny<SpotifyClient>()))
|
||||
.Returns(savedAlbums);
|
||||
|
||||
var result = Subject.Fetch(_api);
|
||||
|
@ -128,12 +127,12 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetSavedAlbums(It.IsAny<SpotifySavedAlbums>(),
|
||||
It.IsAny<SpotifyWebAPI>()))
|
||||
It.IsAny<SpotifyClient>()))
|
||||
.Returns(savedAlbums);
|
||||
|
||||
Mocker.GetMock<ISpotifyProxy>()
|
||||
.Setup(x => x.GetNextPage(It.IsAny<SpotifyFollowedArtists>(),
|
||||
It.IsAny<SpotifyWebAPI>(),
|
||||
It.IsAny<SpotifyClient>(),
|
||||
It.IsAny<Paging<SavedAlbum>>()))
|
||||
.Returns(default(Paging<SavedAlbum>));
|
||||
|
||||
|
@ -143,7 +142,7 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>()
|
||||
.Verify(x => x.GetNextPage(It.IsAny<SpotifySavedAlbums>(),
|
||||
It.IsAny<SpotifyWebAPI>(),
|
||||
It.IsAny<SpotifyClient>(),
|
||||
It.IsAny<Paging<SavedAlbum>>()),
|
||||
Times.Once());
|
||||
}
|
||||
|
@ -176,7 +175,7 @@ namespace NzbDrone.Core.Test.ImportListTests
|
|||
|
||||
Mocker.GetMock<ISpotifyProxy>().
|
||||
Setup(x => x.GetSavedAlbums(It.IsAny<SpotifySavedAlbums>(),
|
||||
It.IsAny<SpotifyWebAPI>()))
|
||||
It.IsAny<SpotifyClient>()))
|
||||
.Returns(savedAlbums);
|
||||
|
||||
var result = Subject.Fetch(_api);
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using SpotifyAPI.Web;
|
||||
using SpotifyAPI.Web.Http;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Spotify
|
||||
{
|
||||
public class SpotifyAuthenticator : IAuthenticator
|
||||
{
|
||||
private readonly AuthorizationCodeTokenResponse _token;
|
||||
private readonly Func<AuthorizationCodeRefreshResponse> _refreshToken;
|
||||
|
||||
public SpotifyAuthenticator(AuthorizationCodeTokenResponse token, Func<AuthorizationCodeRefreshResponse> refreshToken)
|
||||
{
|
||||
_token = token;
|
||||
_refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
public Task Apply(IRequest request, IAPIConnector apiConnector)
|
||||
{
|
||||
if (_token.AccessToken.IsNullOrWhiteSpace() || _token.IsExpired)
|
||||
{
|
||||
var refreshedToken = _refreshToken();
|
||||
|
||||
_token.AccessToken = refreshedToken.AccessToken;
|
||||
_token.CreatedAt = refreshedToken.CreatedAt;
|
||||
_token.ExpiresIn = refreshedToken.ExpiresIn;
|
||||
_token.Scope = refreshedToken.Scope;
|
||||
_token.TokenType = refreshedToken.TokenType;
|
||||
}
|
||||
|
||||
request.Headers["Authorization"] = $"{_token.TokenType} {_token.AccessToken}";
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Spotify
|
||||
{
|
||||
public class SpotifyAuthorizationCodeRefreshResponse
|
||||
{
|
||||
[JsonProperty("access_token")]
|
||||
public string AccessToken { get; set; }
|
||||
[JsonProperty("token_type")]
|
||||
public string TokenType { get; set; }
|
||||
[JsonProperty("expires_in")]
|
||||
public int ExpiresIn { get; set; }
|
||||
public string Scope { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public bool IsExpired { get => CreatedAt.AddSeconds(ExpiresIn) <= DateTime.UtcNow; }
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ using NzbDrone.Core.Configuration;
|
|||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.Parser;
|
||||
using SpotifyAPI.Web;
|
||||
using SpotifyAPI.Web.Models;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Spotify
|
||||
{
|
||||
|
@ -31,7 +30,7 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
|
||||
public override string Name => "Spotify Followed Artists";
|
||||
|
||||
public override IList<SpotifyImportListItemInfo> Fetch(SpotifyWebAPI api)
|
||||
public override IList<SpotifyImportListItemInfo> Fetch(SpotifyClient api)
|
||||
{
|
||||
var result = new List<SpotifyImportListItemInfo>();
|
||||
|
||||
|
@ -50,7 +49,7 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
result.AddIfNotNull(ParseFullArtist(artist));
|
||||
}
|
||||
|
||||
if (!artists.HasNext())
|
||||
if (artists.Next == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ using NzbDrone.Core.Parser;
|
|||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
using SpotifyAPI.Web;
|
||||
using SpotifyAPI.Web.Models;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Spotify
|
||||
{
|
||||
|
@ -48,7 +47,7 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
|
||||
public string AccessToken => Settings.AccessToken;
|
||||
|
||||
public void RefreshToken()
|
||||
public AuthorizationCodeRefreshResponse RefreshToken()
|
||||
{
|
||||
_logger.Trace("Refreshing Token");
|
||||
|
||||
|
@ -60,52 +59,64 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
|
||||
try
|
||||
{
|
||||
var response = _httpClient.Get<Token>(request);
|
||||
var response = _httpClient.Get<SpotifyAuthorizationCodeRefreshResponse>(request);
|
||||
|
||||
if (response != null && response.Resource != null)
|
||||
if (response?.Resource != null)
|
||||
{
|
||||
var token = response.Resource;
|
||||
|
||||
Settings.AccessToken = token.AccessToken;
|
||||
Settings.Expires = DateTime.UtcNow.AddSeconds(token.ExpiresIn);
|
||||
Settings.RefreshToken = token.RefreshToken != null ? token.RefreshToken : Settings.RefreshToken;
|
||||
Settings.Expires = token.CreatedAt.AddSeconds(token.ExpiresIn);
|
||||
|
||||
if (Definition.Id > 0)
|
||||
{
|
||||
_importListRepository.UpdateSettings((ImportListDefinition)Definition);
|
||||
}
|
||||
|
||||
return new AuthorizationCodeRefreshResponse
|
||||
{
|
||||
AccessToken = token.AccessToken,
|
||||
TokenType = token.TokenType,
|
||||
ExpiresIn = token.ExpiresIn,
|
||||
Scope = token.Scope
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (HttpException)
|
||||
{
|
||||
_logger.Warn($"Error refreshing spotify access token");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public SpotifyWebAPI GetApi()
|
||||
public SpotifyClient GetApi()
|
||||
{
|
||||
Settings.Validate().Filter("AccessToken", "RefreshToken").ThrowOnError();
|
||||
Settings.Validate().Filter("RefreshToken").ThrowOnError();
|
||||
_logger.Trace($"Access token expires at {Settings.Expires}");
|
||||
|
||||
if (Settings.Expires < DateTime.UtcNow.AddMinutes(5))
|
||||
{
|
||||
RefreshToken();
|
||||
}
|
||||
|
||||
return new SpotifyWebAPI
|
||||
var token = new AuthorizationCodeTokenResponse
|
||||
{
|
||||
AccessToken = Settings.AccessToken,
|
||||
ExpiresIn = (int)(Settings.Expires - DateTime.UtcNow).TotalSeconds,
|
||||
RefreshToken = Settings.RefreshToken,
|
||||
TokenType = "Bearer"
|
||||
};
|
||||
|
||||
var config = SpotifyClientConfig.CreateDefault()
|
||||
.WithAuthenticator(new SpotifyAuthenticator(token, RefreshToken))
|
||||
.WithRetryHandler(new SpotifyRetryHandler(token));
|
||||
|
||||
return new SpotifyClient(config);
|
||||
}
|
||||
|
||||
public override IList<ImportListItemInfo> Fetch()
|
||||
{
|
||||
IList<SpotifyImportListItemInfo> releases;
|
||||
using (var api = GetApi())
|
||||
{
|
||||
_logger.Debug("Starting spotify import list sync");
|
||||
releases = Fetch(api);
|
||||
}
|
||||
var api = GetApi();
|
||||
|
||||
_logger.Debug("Starting spotify import list sync");
|
||||
releases = Fetch(api);
|
||||
|
||||
// map to musicbrainz ids
|
||||
releases = MapSpotifyReleases(releases);
|
||||
|
@ -115,7 +126,7 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
return CleanupListItems(releases);
|
||||
}
|
||||
|
||||
public abstract IList<SpotifyImportListItemInfo> Fetch(SpotifyWebAPI api);
|
||||
public abstract IList<SpotifyImportListItemInfo> Fetch(SpotifyClient api);
|
||||
|
||||
protected DateTime ParseSpotifyDate(string date, string precision)
|
||||
{
|
||||
|
@ -134,7 +145,6 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
case "month":
|
||||
format = "yyyy-MM";
|
||||
break;
|
||||
case "day":
|
||||
default:
|
||||
format = "yyyy-MM-dd";
|
||||
break;
|
||||
|
@ -307,12 +317,10 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
{
|
||||
try
|
||||
{
|
||||
using (var api = GetApi())
|
||||
{
|
||||
var profile = _spotifyProxy.GetPrivateProfile(this, api);
|
||||
_logger.Debug($"Connected to spotify profile {profile.DisplayName} [{profile.Id}]");
|
||||
return null;
|
||||
}
|
||||
var api = GetApi();
|
||||
var profile = _spotifyProxy.GetPrivateProfile(this, api);
|
||||
_logger.Debug($"Connected to spotify profile {profile.DisplayName} [{profile.Id}]");
|
||||
return null;
|
||||
}
|
||||
catch (SpotifyAuthorizationException ex)
|
||||
{
|
||||
|
|
|
@ -11,13 +11,12 @@ using NzbDrone.Core.Music;
|
|||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Validation;
|
||||
using SpotifyAPI.Web;
|
||||
using SpotifyAPI.Web.Enums;
|
||||
using SpotifyAPI.Web.Models;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Spotify
|
||||
{
|
||||
public class SpotifyPlaylist : SpotifyImportListBase<SpotifyPlaylistSettings>
|
||||
{
|
||||
private readonly List<string> _fields = new () { "id", "name", "tracks.next", "tracks.items(track(type, name, artists(id, name), album(id, album_type, name, release_date, release_date_precision, artists(id, name))))" };
|
||||
private readonly IPlaylistService _playlistService;
|
||||
|
||||
public SpotifyPlaylist(ISpotifyProxy spotifyProxy,
|
||||
|
@ -36,18 +35,18 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
|
||||
public override string Name => "Spotify Playlists";
|
||||
|
||||
public override IList<SpotifyImportListItemInfo> Fetch(SpotifyWebAPI api)
|
||||
public override IList<SpotifyImportListItemInfo> Fetch(SpotifyClient api)
|
||||
{
|
||||
return Settings.PlaylistIds.SelectMany(x => Fetch(api, x)).ToList();
|
||||
}
|
||||
|
||||
public IList<SpotifyImportListItemInfo> Fetch(SpotifyWebAPI api, string playlistId)
|
||||
public IList<SpotifyImportListItemInfo> Fetch(SpotifyClient api, string playlistId)
|
||||
{
|
||||
var result = new List<SpotifyImportListItemInfo>();
|
||||
|
||||
_logger.Trace($"Processing playlist {playlistId}");
|
||||
|
||||
var playlist = _spotifyProxy.GetPlaylist(this, api, playlistId, "id, name, tracks.next, tracks.items(track(name, artists(id, name), album(id, album_type, name, release_date, release_date_precision, artists(id, name))))");
|
||||
var playlist = _spotifyProxy.GetPlaylist(this, api, playlistId, _fields);
|
||||
var playlistTracks = playlist?.Tracks;
|
||||
int order = 0;
|
||||
|
||||
|
@ -63,7 +62,7 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
result.AddIfNotNull(ParsePlaylistTrack(api, playlistTrack, playlistId, playlist.Name, ref order));
|
||||
}
|
||||
|
||||
if (!playlistTracks.HasNextPage())
|
||||
if (playlistTracks.Next == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
@ -103,17 +102,19 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
}
|
||||
}
|
||||
|
||||
private SpotifyPlaylistItemInfo ParsePlaylistTrack(SpotifyWebAPI api, PlaylistTrack playlistTrack, string playlistId, string playlistName, ref int order)
|
||||
private SpotifyPlaylistItemInfo ParsePlaylistTrack(SpotifyClient api, PlaylistTrack<IPlayableItem> playableItem, string playlistId, string playlistName, ref int order)
|
||||
{
|
||||
var track = playableItem.Track as FullTrack;
|
||||
|
||||
// From spotify docs: "Note, a track object may be null. This can happen if a track is no longer available."
|
||||
if (playlistTrack?.Track?.Album == null)
|
||||
if (track?.Album == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var album = playlistTrack.Track.Album;
|
||||
var trackName = playlistTrack.Track.Name;
|
||||
var artistName = album.Artists?.FirstOrDefault()?.Name ?? playlistTrack.Track?.Artists?.FirstOrDefault()?.Name;
|
||||
var album = track.Album;
|
||||
var trackName = track.Name;
|
||||
var artistName = album.Artists?.FirstOrDefault()?.Name ?? track?.Artists?.FirstOrDefault()?.Name;
|
||||
|
||||
if (artistName.IsNullOrWhiteSpace())
|
||||
{
|
||||
|
@ -124,11 +125,10 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
|
||||
if (album.AlbumType == "single")
|
||||
{
|
||||
album = GetBestAlbum(api, artistName, trackName) ?? album;
|
||||
album = GetBestAlbum(api, artistName, trackName, album.TotalTracks) ?? album;
|
||||
_logger.Trace($"revised type: {album.AlbumType}");
|
||||
}
|
||||
|
||||
_logger.Trace($"revised type: {album.AlbumType}");
|
||||
|
||||
var albumName = album.Name;
|
||||
|
||||
if (albumName.IsNullOrWhiteSpace())
|
||||
|
@ -143,25 +143,41 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
Album = album.Name,
|
||||
AlbumSpotifyId = album.Id,
|
||||
PlaylistTitle = playlistName,
|
||||
TrackTitle = playlistTrack.Track.Name,
|
||||
TrackTitle = track.Name,
|
||||
Order = ++order,
|
||||
ReleaseDate = ParseSpotifyDate(album.ReleaseDate, album.ReleaseDatePrecision)
|
||||
};
|
||||
}
|
||||
|
||||
private SimpleAlbum GetBestAlbum(SpotifyWebAPI api, string artistName, string trackName)
|
||||
private SimpleAlbum GetBestAlbum(SpotifyClient api, string artistName, string trackName, int currentTrackCount)
|
||||
{
|
||||
_logger.Trace($"Finding full album for {artistName}: {trackName}");
|
||||
var search = _spotifyProxy.SearchItems(this, api, $"artist:\"{artistName}\" track:\"{trackName}\"", SearchType.Track);
|
||||
var search = _spotifyProxy.SearchItems(this, api, $"artist:\"{artistName}\" track:\"{trackName}\"", SearchRequest.Types.Track);
|
||||
|
||||
return search?.Tracks?.Items?.FirstOrDefault(x => x?.Album?.AlbumType == "album" && !(x?.Album?.Artists?.Any(a => a.Name == "Various Artists") ?? false))?.Album;
|
||||
var result = search?.Tracks?.Items?.FirstOrDefault(x => x?.Album?.AlbumType == "album" && IsAcceptableAlbumOrSingle(x, artistName, trackName))?.Album ??
|
||||
search?.Tracks?.Items?.FirstOrDefault(x => x?.Album?.AlbumType == "single" && x.Album.TotalTracks > 3 && x.Album.TotalTracks > currentTrackCount && IsAcceptableAlbumOrSingle(x, artistName, trackName))?.Album;
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
_logger.Trace($"Found {result.AlbumType} {result.Name} by {result.Artists.FirstOrDefault()?.Name}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool IsAcceptableAlbumOrSingle(FullTrack x, string artistName, string trackName)
|
||||
{
|
||||
return x.Name == trackName &&
|
||||
(x.Artists?.Any(a => a.Name == artistName) ?? false) &&
|
||||
!(x.Album.Artists?.Any(a => a.Name == "Various Artists") ?? false) &&
|
||||
ParseSpotifyDate(x?.Album.ReleaseDate, x.Album.ReleaseDatePrecision) <= DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public override object RequestAction(string action, IDictionary<string, string> query)
|
||||
{
|
||||
if (action == "getPlaylists")
|
||||
{
|
||||
if (Settings.AccessToken.IsNullOrWhiteSpace())
|
||||
if (Settings.RefreshToken.IsNullOrWhiteSpace())
|
||||
{
|
||||
return new
|
||||
{
|
||||
|
@ -169,53 +185,51 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
};
|
||||
}
|
||||
|
||||
Settings.Validate().Filter("AccessToken").ThrowOnError();
|
||||
Settings.Validate().Filter("RefreshToken").ThrowOnError();
|
||||
|
||||
using (var api = GetApi())
|
||||
var api = GetApi();
|
||||
try
|
||||
{
|
||||
try
|
||||
var profile = _spotifyProxy.GetPrivateProfile(this, api);
|
||||
var playlistPage = _spotifyProxy.GetUserPlaylists(this, api, profile.Id);
|
||||
_logger.Trace($"Got {playlistPage.Total} playlists");
|
||||
|
||||
var playlists = new List<SimplePlaylist>();
|
||||
while (true)
|
||||
{
|
||||
var profile = _spotifyProxy.GetPrivateProfile(this, api);
|
||||
var playlistPage = _spotifyProxy.GetUserPlaylists(this, api, profile.Id);
|
||||
_logger.Trace($"Got {playlistPage.Total} playlists");
|
||||
|
||||
var playlists = new List<SimplePlaylist>(playlistPage.Total);
|
||||
while (true)
|
||||
if (playlistPage == null)
|
||||
{
|
||||
if (playlistPage == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
playlists.AddRange(playlistPage.Items);
|
||||
|
||||
if (!playlistPage.HasNextPage())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
playlistPage = _spotifyProxy.GetNextPage(this, api, playlistPage);
|
||||
break;
|
||||
}
|
||||
|
||||
return new
|
||||
playlists.AddRange(playlistPage.Items);
|
||||
|
||||
if (playlistPage.Next == null)
|
||||
{
|
||||
options = new
|
||||
{
|
||||
user = profile.DisplayName,
|
||||
playlists = playlists.OrderBy(p => p.Name)
|
||||
.Select(p => new
|
||||
{
|
||||
id = p.Id,
|
||||
name = p.Name
|
||||
})
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
playlistPage = _spotifyProxy.GetNextPage(this, api, playlistPage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
return new
|
||||
{
|
||||
_logger.Warn(ex, "Error fetching playlists from Spotify");
|
||||
return new { };
|
||||
}
|
||||
options = new
|
||||
{
|
||||
user = profile.DisplayName,
|
||||
playlists = playlists.OrderBy(p => p.Name)
|
||||
.Select(p => new
|
||||
{
|
||||
id = p.Id,
|
||||
name = p.Name
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "Error fetching playlists from Spotify");
|
||||
return new { };
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using SpotifyAPI.Web;
|
||||
using SpotifyAPI.Web.Enums;
|
||||
using SpotifyAPI.Web.Models;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Spotify
|
||||
{
|
||||
public interface ISpotifyProxy
|
||||
{
|
||||
PrivateProfile GetPrivateProfile<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api)
|
||||
PrivateUser GetPrivateProfile<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new();
|
||||
Paging<SimplePlaylist> GetUserPlaylists<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, string id)
|
||||
Paging<SimplePlaylist> GetUserPlaylists<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api, string id)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new();
|
||||
FollowedArtists GetFollowedArtists<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api)
|
||||
FollowedArtistsResponse GetFollowedArtists<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new();
|
||||
Paging<SavedAlbum> GetSavedAlbums<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api)
|
||||
Paging<SavedAlbum> GetSavedAlbums<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new();
|
||||
FullPlaylist GetPlaylist<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, string id, string fields)
|
||||
FullPlaylist GetPlaylist<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api, string id, List<string> fields)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new();
|
||||
Paging<T> GetNextPage<T, TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, Paging<T> item)
|
||||
Paging<T> GetNextPage<T, TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api, Paging<T> item)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new();
|
||||
FollowedArtists GetNextPage<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, FollowedArtists item)
|
||||
FollowedArtistsResponse GetNextPage<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api, FollowedArtistsResponse item)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new();
|
||||
SearchItem SearchItems<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, string query, SearchType type)
|
||||
SearchResponse SearchItems<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api, string query, SearchRequest.Types type)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new();
|
||||
}
|
||||
|
||||
|
@ -35,83 +35,64 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public PrivateProfile GetPrivateProfile<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api)
|
||||
public PrivateUser GetPrivateProfile<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||
{
|
||||
return Execute(list, api, x => x.GetPrivateProfile());
|
||||
return Execute(list, api, x => x.UserProfile.Current());
|
||||
}
|
||||
|
||||
public Paging<SimplePlaylist> GetUserPlaylists<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, string id)
|
||||
public Paging<SimplePlaylist> GetUserPlaylists<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api, string id)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||
{
|
||||
return Execute(list, api, x => x.GetUserPlaylists(id));
|
||||
return Execute(list, api, x => x.Playlists.GetUsers(id));
|
||||
}
|
||||
|
||||
public FollowedArtists GetFollowedArtists<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api)
|
||||
public FollowedArtistsResponse GetFollowedArtists<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||
{
|
||||
return Execute(list, api, x => x.GetFollowedArtists(FollowType.Artist, 50));
|
||||
return Execute(list, api, x => x.Follow.OfCurrentUser(new FollowOfCurrentUserRequest { Limit = 50 }));
|
||||
}
|
||||
|
||||
public Paging<SavedAlbum> GetSavedAlbums<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api)
|
||||
public Paging<SavedAlbum> GetSavedAlbums<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||
{
|
||||
return Execute(list, api, x => x.GetSavedAlbums(50));
|
||||
return Execute(list, api, x => x.Library.GetAlbums(new LibraryAlbumsRequest { Limit = 50 }));
|
||||
}
|
||||
|
||||
public FullPlaylist GetPlaylist<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, string id, string fields)
|
||||
public FullPlaylist GetPlaylist<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api, string id, List<string> fields)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||
{
|
||||
return Execute(list, api, x => x.GetPlaylist(id, fields: fields));
|
||||
}
|
||||
|
||||
public Paging<T> GetNextPage<T, TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, Paging<T> item)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||
{
|
||||
return Execute(list, api, (x) => x.GetNextPage(item));
|
||||
}
|
||||
|
||||
public FollowedArtists GetNextPage<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, FollowedArtists item)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||
{
|
||||
return Execute(list, api, (x) => x.GetNextPage<FollowedArtists, FullArtist>(item.Artists));
|
||||
}
|
||||
|
||||
public SearchItem SearchItems<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, string query, SearchType type)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||
{
|
||||
return Execute(list, api, (x) => x.SearchItems(query, type));
|
||||
}
|
||||
|
||||
public T Execute<T, TSettings>(SpotifyImportListBase<TSettings> list, SpotifyWebAPI api, Func<SpotifyWebAPI, T> method, bool allowReauth = true)
|
||||
where T : BasicModel
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||
{
|
||||
T result = method(api);
|
||||
if (result.HasError())
|
||||
var request = new PlaylistGetRequest(PlaylistGetRequest.AdditionalTypes.Track);
|
||||
foreach (var field in fields)
|
||||
{
|
||||
// If unauthorized, refresh token and try again
|
||||
if (result.Error.Status == 401)
|
||||
{
|
||||
if (allowReauth)
|
||||
{
|
||||
_logger.Debug("Spotify authorization error, refreshing token and retrying");
|
||||
list.RefreshToken();
|
||||
api.AccessToken = list.AccessToken;
|
||||
return Execute(list, api, method, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new SpotifyAuthorizationException(result.Error.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new SpotifyException("[{0}] {1}", result.Error.Status, result.Error.Message);
|
||||
}
|
||||
request.Fields.Add(field);
|
||||
}
|
||||
|
||||
return result;
|
||||
return Execute(list, api, x => x.Playlists.Get(id, request));
|
||||
}
|
||||
|
||||
public Paging<T> GetNextPage<T, TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api, Paging<T> item)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||
{
|
||||
return Execute(list, api, (x) => x.NextPage(item));
|
||||
}
|
||||
|
||||
public FollowedArtistsResponse GetNextPage<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api, FollowedArtistsResponse item)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||
{
|
||||
return Execute(list, api, (x) => x.NextPage(item.Artists));
|
||||
}
|
||||
|
||||
public SearchResponse SearchItems<TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api, string query, SearchRequest.Types type)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||
{
|
||||
return Execute(list, api, (x) => x.Search.Item(new SearchRequest(type, query)));
|
||||
}
|
||||
|
||||
public T Execute<T, TSettings>(SpotifyImportListBase<TSettings> list, SpotifyClient api, Func<SpotifyClient, Task<T>> method)
|
||||
where TSettings : SpotifySettingsBase<TSettings>, new()
|
||||
{
|
||||
return method(api).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
40
src/NzbDrone.Core/ImportLists/Spotify/SpotifyRetryHandler.cs
Normal file
40
src/NzbDrone.Core/ImportLists/Spotify/SpotifyRetryHandler.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using SpotifyAPI.Web;
|
||||
using SpotifyAPI.Web.Http;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Spotify
|
||||
{
|
||||
public class SpotifyRetryHandler : IRetryHandler
|
||||
{
|
||||
private readonly SimpleRetryHandler _simpleHandler;
|
||||
private readonly AuthorizationCodeTokenResponse _token;
|
||||
|
||||
public SpotifyRetryHandler(AuthorizationCodeTokenResponse token)
|
||||
{
|
||||
_token = token;
|
||||
|
||||
_simpleHandler = new SimpleRetryHandler
|
||||
{
|
||||
RetryTimes = 3,
|
||||
RetryErrorCodes = new[]
|
||||
{
|
||||
HttpStatusCode.InternalServerError,
|
||||
HttpStatusCode.BadGateway,
|
||||
HttpStatusCode.ServiceUnavailable,
|
||||
HttpStatusCode.Unauthorized
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Task<IResponse> HandleRetry(IRequest request, IResponse response, IRetryHandler.RetryFunc retry)
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
_token.ExpiresIn = -1;
|
||||
}
|
||||
|
||||
return _simpleHandler.HandleRetry(request, response, retry);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@ using NzbDrone.Core.Configuration;
|
|||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.Parser;
|
||||
using SpotifyAPI.Web;
|
||||
using SpotifyAPI.Web.Models;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Spotify
|
||||
{
|
||||
|
@ -32,7 +31,7 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
|
||||
public override string Name => "Spotify Saved Albums";
|
||||
|
||||
public override IList<SpotifyImportListItemInfo> Fetch(SpotifyWebAPI api)
|
||||
public override IList<SpotifyImportListItemInfo> Fetch(SpotifyClient api)
|
||||
{
|
||||
var result = new List<SpotifyImportListItemInfo>();
|
||||
|
||||
|
@ -52,7 +51,7 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
result.AddIfNotNull(ParseSavedAlbum(savedAlbum));
|
||||
}
|
||||
|
||||
if (!savedAlbums.HasNextPage())
|
||||
if (savedAlbums.Next == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ namespace NzbDrone.Core.ImportLists.Spotify
|
|||
{
|
||||
public SpotifySettingsBaseValidator()
|
||||
{
|
||||
RuleFor(c => c.AccessToken).NotEmpty();
|
||||
RuleFor(c => c.RefreshToken).NotEmpty();
|
||||
RuleFor(c => c.Expires).NotEmpty();
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<PackageReference Include="System.IO.Abstractions" Version="17.0.24" />
|
||||
<PackageReference Include="TagLibSharp-Lidarr" Version="2.2.0.27" />
|
||||
<PackageReference Include="Kveer.XmlRPC" Version="1.2.0" />
|
||||
<PackageReference Include="SpotifyAPI.Web" Version="4.2.2" />
|
||||
<PackageReference Include="SpotifyAPI.Web" Version="6.2.2" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="Equ" Version="2.3.0" />
|
||||
<PackageReference Include="MonoTorrent" Version="2.0.6" />
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue