mirror of
https://github.com/Radarr/Radarr.git
synced 2024-11-04 10:02:40 +01:00
New: Trakt connection for adding movies to collection
This commit is contained in:
parent
431a3f6f8f
commit
00f631c623
@ -0,0 +1,97 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
|
using NzbDrone.Core.Movies;
|
||||||
|
using NzbDrone.Core.Notifications;
|
||||||
|
using NzbDrone.Core.Notifications.Trakt;
|
||||||
|
using NzbDrone.Core.Notifications.Trakt.Resource;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.NotificationTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TraktServiceFixture : CoreTest<TraktService>
|
||||||
|
{
|
||||||
|
private DownloadMessage _downloadMessage;
|
||||||
|
private TraktSettings _traktSettings;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_downloadMessage = new DownloadMessage
|
||||||
|
{
|
||||||
|
Movie = new Movie(),
|
||||||
|
MovieFile = new MovieFile
|
||||||
|
{
|
||||||
|
MediaInfo = null,
|
||||||
|
Quality = new QualityModel
|
||||||
|
{
|
||||||
|
Quality = Quality.Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_traktSettings = new TraktSettings
|
||||||
|
{
|
||||||
|
AccessToken = "",
|
||||||
|
RefreshToken = ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GiventValidMediaInfo(Quality quality, string audioChannels, string audioFormat, string scanType)
|
||||||
|
{
|
||||||
|
_downloadMessage.MovieFile.MediaInfo = new MediaInfoModel
|
||||||
|
{
|
||||||
|
AudioChannelPositions = audioChannels,
|
||||||
|
AudioFormat = audioFormat,
|
||||||
|
ScanType = scanType
|
||||||
|
};
|
||||||
|
|
||||||
|
_downloadMessage.MovieFile.Quality.Quality = quality;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_add_collection_movie_if_null_mediainfo()
|
||||||
|
{
|
||||||
|
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
|
||||||
|
|
||||||
|
Mocker.GetMock<ITraktProxy>()
|
||||||
|
.Verify(v => v.AddToCollection(It.IsAny<TraktCollectMoviesResource>(), It.IsAny<string>()), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_add_collection_movie_if_valid_mediainfo()
|
||||||
|
{
|
||||||
|
GiventValidMediaInfo(Quality.Bluray1080p, "3/2/0.1", "DTS", "Interlaced");
|
||||||
|
|
||||||
|
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
|
||||||
|
|
||||||
|
Mocker.GetMock<ITraktProxy>()
|
||||||
|
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>
|
||||||
|
t.Movies.First().Audio == "dts" &&
|
||||||
|
t.Movies.First().AudioChannels == "5.1" &&
|
||||||
|
t.Movies.First().Resolution == "hd_1080i" &&
|
||||||
|
t.Movies.First().MediaType == "bluray"),
|
||||||
|
It.IsAny<string>()), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_format_audio_channels_to_one_decimal_when_adding_collection_movie()
|
||||||
|
{
|
||||||
|
GiventValidMediaInfo(Quality.Bluray1080p, "2/0/0", "DTS", "Interlaced");
|
||||||
|
|
||||||
|
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
|
||||||
|
|
||||||
|
Mocker.GetMock<ITraktProxy>()
|
||||||
|
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>
|
||||||
|
t.Movies.First().Audio == "dts" &&
|
||||||
|
t.Movies.First().AudioChannels == "2.0" &&
|
||||||
|
t.Movies.First().Resolution == "hd_1080i" &&
|
||||||
|
t.Movies.First().MediaType == "bluray"),
|
||||||
|
It.IsAny<string>()), Times.Once());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Notifications.Trakt;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.NotificationTests.TraktTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TraktSettingsValidatorFixture : CoreTest<TraktSettingsValidator>
|
||||||
|
{
|
||||||
|
private TraktSettings _traktSettings;
|
||||||
|
private TestValidator<TraktSettings> _validator;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_validator = new TestValidator<TraktSettings>
|
||||||
|
{
|
||||||
|
v => v.RuleFor(s => s).SetValidator(Subject)
|
||||||
|
};
|
||||||
|
|
||||||
|
_traktSettings = Builder<TraktSettings>.CreateNew()
|
||||||
|
.With(s => s.AccessToken = "sometoken")
|
||||||
|
.With(s => s.RefreshToken = "sometoken")
|
||||||
|
.With(s => s.Expires = DateTime.Now.AddDays(2))
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_be_valid_if_all_settings_valid()
|
||||||
|
{
|
||||||
|
_validator.Validate(_traktSettings).IsValid.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_be_valid_if_port_is_out_of_range()
|
||||||
|
{
|
||||||
|
_traktSettings.AccessToken = "";
|
||||||
|
|
||||||
|
_validator.Validate(_traktSettings).IsValid.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_be_valid_if_server_is_empty()
|
||||||
|
{
|
||||||
|
_traktSettings.RefreshToken = "";
|
||||||
|
|
||||||
|
_validator.Validate(_traktSettings).IsValid.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_be_valid_if_from_is_empty()
|
||||||
|
{
|
||||||
|
_traktSettings.Expires = default(DateTime);
|
||||||
|
|
||||||
|
_validator.Validate(_traktSettings).IsValid.Should().BeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
|
|
||||||
namespace NzbDrone.Core.NetImport.RadarrList
|
namespace NzbDrone.Core.NetImport.RadarrList
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Notifications.Trakt;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
|
|
||||||
namespace NzbDrone.Core.NetImport.Trakt.List
|
namespace NzbDrone.Core.NetImport.Trakt.List
|
||||||
@ -8,12 +9,13 @@ namespace NzbDrone.Core.NetImport.Trakt.List
|
|||||||
public class TraktListImport : TraktImportBase<TraktListSettings>
|
public class TraktListImport : TraktImportBase<TraktListSettings>
|
||||||
{
|
{
|
||||||
public TraktListImport(INetImportRepository netImportRepository,
|
public TraktListImport(INetImportRepository netImportRepository,
|
||||||
|
ITraktProxy traktProxy,
|
||||||
IHttpClient httpClient,
|
IHttpClient httpClient,
|
||||||
INetImportStatusService netImportStatusService,
|
INetImportStatusService netImportStatusService,
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(netImportRepository, httpClient, netImportStatusService, configService, parsingService, logger)
|
: base(netImportRepository, traktProxy, httpClient, netImportStatusService, configService, parsingService, logger)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,10 +25,9 @@ public TraktListImport(INetImportRepository netImportRepository,
|
|||||||
|
|
||||||
public override INetImportRequestGenerator GetRequestGenerator()
|
public override INetImportRequestGenerator GetRequestGenerator()
|
||||||
{
|
{
|
||||||
return new TraktListRequestGenerator()
|
return new TraktListRequestGenerator(_traktProxy)
|
||||||
{
|
{
|
||||||
Settings = Settings,
|
Settings = Settings
|
||||||
ClientId = ClientId
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.Notifications.Trakt;
|
||||||
|
|
||||||
namespace NzbDrone.Core.NetImport.Trakt.List
|
namespace NzbDrone.Core.NetImport.Trakt.List
|
||||||
{
|
{
|
||||||
public class TraktListRequestGenerator : INetImportRequestGenerator
|
public class TraktListRequestGenerator : INetImportRequestGenerator
|
||||||
{
|
{
|
||||||
|
private readonly ITraktProxy _traktProxy;
|
||||||
public TraktListSettings Settings { get; set; }
|
public TraktListSettings Settings { get; set; }
|
||||||
public string ClientId { get; set; }
|
|
||||||
|
|
||||||
public TraktListRequestGenerator()
|
public TraktListRequestGenerator(ITraktProxy traktProxy)
|
||||||
{
|
{
|
||||||
|
_traktProxy = traktProxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual NetImportPageableRequestChain GetMovies()
|
public virtual NetImportPageableRequestChain GetMovies()
|
||||||
@ -24,20 +25,12 @@ public virtual NetImportPageableRequestChain GetMovies()
|
|||||||
|
|
||||||
private IEnumerable<NetImportRequest> GetMoviesRequest()
|
private IEnumerable<NetImportRequest> GetMoviesRequest()
|
||||||
{
|
{
|
||||||
var link = Settings.Link.Trim();
|
var link = string.Empty;
|
||||||
|
|
||||||
var listName = Parser.Parser.ToUrlSlug(Settings.Listname.Trim());
|
var listName = Parser.Parser.ToUrlSlug(Settings.Listname.Trim());
|
||||||
link += $"/users/{Settings.Username.Trim()}/lists/{listName}/items/movies?limit={Settings.Limit}";
|
link += $"users/{Settings.Username.Trim()}/lists/{listName}/items/movies?limit={Settings.Limit}";
|
||||||
|
|
||||||
var request = new NetImportRequest($"{link}", HttpAccept.Json);
|
var request = new NetImportRequest(_traktProxy.BuildTraktRequest(link, HttpMethod.GET, Settings.AccessToken));
|
||||||
|
|
||||||
request.HttpRequest.Headers.Add("trakt-api-version", "2");
|
|
||||||
request.HttpRequest.Headers.Add("trakt-api-key", ClientId); //aeon
|
|
||||||
|
|
||||||
if (Settings.AccessToken.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
request.HttpRequest.Headers.Add("Authorization", "Bearer " + Settings.AccessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return request;
|
yield return request;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Notifications.Trakt;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
|
|
||||||
namespace NzbDrone.Core.NetImport.Trakt.Popular
|
namespace NzbDrone.Core.NetImport.Trakt.Popular
|
||||||
@ -8,12 +9,13 @@ namespace NzbDrone.Core.NetImport.Trakt.Popular
|
|||||||
public class TraktPopularImport : TraktImportBase<TraktPopularSettings>
|
public class TraktPopularImport : TraktImportBase<TraktPopularSettings>
|
||||||
{
|
{
|
||||||
public TraktPopularImport(INetImportRepository netImportRepository,
|
public TraktPopularImport(INetImportRepository netImportRepository,
|
||||||
|
ITraktProxy traktProxy,
|
||||||
IHttpClient httpClient,
|
IHttpClient httpClient,
|
||||||
INetImportStatusService netImportStatusService,
|
INetImportStatusService netImportStatusService,
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(netImportRepository, httpClient, netImportStatusService, configService, parsingService, logger)
|
: base(netImportRepository, traktProxy, httpClient, netImportStatusService, configService, parsingService, logger)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,10 +30,9 @@ public override IParseNetImportResponse GetParser()
|
|||||||
|
|
||||||
public override INetImportRequestGenerator GetRequestGenerator()
|
public override INetImportRequestGenerator GetRequestGenerator()
|
||||||
{
|
{
|
||||||
return new TraktPopularRequestGenerator()
|
return new TraktPopularRequestGenerator(_traktProxy)
|
||||||
{
|
{
|
||||||
Settings = Settings,
|
Settings = Settings
|
||||||
ClientId = ClientId
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Movies;
|
using NzbDrone.Core.Movies;
|
||||||
|
using NzbDrone.Core.Notifications.Trakt.Resource;
|
||||||
|
|
||||||
namespace NzbDrone.Core.NetImport.Trakt.Popular
|
namespace NzbDrone.Core.NetImport.Trakt.Popular
|
||||||
{
|
{
|
||||||
@ -34,7 +35,7 @@ public override IList<Movie> ParseResponse(NetImportResponse importResponse)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
jsonResponse = JsonConvert.DeserializeObject<List<TraktResponse>>(_importResponse.Content).SelectList(c => c.Movie);
|
jsonResponse = JsonConvert.DeserializeObject<List<TraktListResource>>(_importResponse.Content).SelectList(c => c.Movie);
|
||||||
}
|
}
|
||||||
|
|
||||||
// no movies were return
|
// no movies were return
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.Notifications.Trakt;
|
||||||
|
|
||||||
namespace NzbDrone.Core.NetImport.Trakt.Popular
|
namespace NzbDrone.Core.NetImport.Trakt.Popular
|
||||||
{
|
{
|
||||||
public class TraktPopularRequestGenerator : INetImportRequestGenerator
|
public class TraktPopularRequestGenerator : INetImportRequestGenerator
|
||||||
{
|
{
|
||||||
|
private readonly ITraktProxy _traktProxy;
|
||||||
public TraktPopularSettings Settings { get; set; }
|
public TraktPopularSettings Settings { get; set; }
|
||||||
|
|
||||||
public string ClientId { get; set; }
|
public TraktPopularRequestGenerator(ITraktProxy traktProxy)
|
||||||
|
|
||||||
public TraktPopularRequestGenerator()
|
|
||||||
{
|
{
|
||||||
|
_traktProxy = traktProxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual NetImportPageableRequestChain GetMovies()
|
public virtual NetImportPageableRequestChain GetMovies()
|
||||||
@ -25,47 +25,39 @@ public virtual NetImportPageableRequestChain GetMovies()
|
|||||||
|
|
||||||
private IEnumerable<NetImportRequest> GetMoviesRequest()
|
private IEnumerable<NetImportRequest> GetMoviesRequest()
|
||||||
{
|
{
|
||||||
var link = Settings.Link.Trim();
|
var link = string.Empty;
|
||||||
|
|
||||||
var filtersAndLimit = $"?years={Settings.Years}&genres={Settings.Genres.ToLower()}&ratings={Settings.Rating}&certifications={Settings.Certification.ToLower()}&limit={Settings.Limit}{Settings.TraktAdditionalParameters}";
|
var filtersAndLimit = $"?years={Settings.Years}&genres={Settings.Genres.ToLower()}&ratings={Settings.Rating}&certifications={Settings.Certification.ToLower()}&limit={Settings.Limit}{Settings.TraktAdditionalParameters}";
|
||||||
|
|
||||||
switch (Settings.TraktListType)
|
switch (Settings.TraktListType)
|
||||||
{
|
{
|
||||||
case (int)TraktPopularListType.Trending:
|
case (int)TraktPopularListType.Trending:
|
||||||
link += "/movies/trending" + filtersAndLimit;
|
link += "movies/trending" + filtersAndLimit;
|
||||||
break;
|
break;
|
||||||
case (int)TraktPopularListType.Popular:
|
case (int)TraktPopularListType.Popular:
|
||||||
link += "/movies/popular" + filtersAndLimit;
|
link += "movies/popular" + filtersAndLimit;
|
||||||
break;
|
break;
|
||||||
case (int)TraktPopularListType.Anticipated:
|
case (int)TraktPopularListType.Anticipated:
|
||||||
link += "/movies/anticipated" + filtersAndLimit;
|
link += "movies/anticipated" + filtersAndLimit;
|
||||||
break;
|
break;
|
||||||
case (int)TraktPopularListType.BoxOffice:
|
case (int)TraktPopularListType.BoxOffice:
|
||||||
link += "/movies/boxoffice" + filtersAndLimit;
|
link += "movies/boxoffice" + filtersAndLimit;
|
||||||
break;
|
break;
|
||||||
case (int)TraktPopularListType.TopWatchedByWeek:
|
case (int)TraktPopularListType.TopWatchedByWeek:
|
||||||
link += "/movies/watched/weekly" + filtersAndLimit;
|
link += "movies/watched/weekly" + filtersAndLimit;
|
||||||
break;
|
break;
|
||||||
case (int)TraktPopularListType.TopWatchedByMonth:
|
case (int)TraktPopularListType.TopWatchedByMonth:
|
||||||
link += "/movies/watched/monthly" + filtersAndLimit;
|
link += "movies/watched/monthly" + filtersAndLimit;
|
||||||
break;
|
break;
|
||||||
case (int)TraktPopularListType.TopWatchedByYear:
|
case (int)TraktPopularListType.TopWatchedByYear:
|
||||||
link += "/movies/watched/yearly" + filtersAndLimit;
|
link += "movies/watched/yearly" + filtersAndLimit;
|
||||||
break;
|
break;
|
||||||
case (int)TraktPopularListType.TopWatchedByAllTime:
|
case (int)TraktPopularListType.TopWatchedByAllTime:
|
||||||
link += "/movies/watched/all" + filtersAndLimit;
|
link += "movies/watched/all" + filtersAndLimit;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = new NetImportRequest($"{link}", HttpAccept.Json);
|
var request = new NetImportRequest(_traktProxy.BuildTraktRequest(link, HttpMethod.GET, Settings.AccessToken));
|
||||||
|
|
||||||
request.HttpRequest.Headers.Add("trakt-api-version", "2");
|
|
||||||
request.HttpRequest.Headers.Add("trakt-api-key", ClientId); //aeon
|
|
||||||
|
|
||||||
if (Settings.AccessToken.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
request.HttpRequest.Headers.Add("Authorization", "Bearer " + Settings.AccessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return request;
|
yield return request;
|
||||||
}
|
}
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
namespace NzbDrone.Core.NetImport.Trakt
|
|
||||||
{
|
|
||||||
public class TraktMovieIdsResource
|
|
||||||
{
|
|
||||||
public int Trakt { get; set; }
|
|
||||||
public string Slug { get; set; }
|
|
||||||
public string Imdb { get; set; }
|
|
||||||
public int Tmdb { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TraktMovieResource
|
|
||||||
{
|
|
||||||
public string Title { get; set; }
|
|
||||||
public int? Year { get; set; }
|
|
||||||
public TraktMovieIdsResource Ids { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TraktResponse
|
|
||||||
{
|
|
||||||
public int? Rank { get; set; }
|
|
||||||
public string Listed_at { get; set; }
|
|
||||||
public string Type { get; set; }
|
|
||||||
|
|
||||||
public int? Watchers { get; set; }
|
|
||||||
|
|
||||||
public long? Revenue { get; set; }
|
|
||||||
|
|
||||||
public long? Watcher_count { get; set; }
|
|
||||||
public long? Play_count { get; set; }
|
|
||||||
public long? Collected_count { get; set; }
|
|
||||||
|
|
||||||
public TraktMovieResource Movie { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RefreshRequestResponse
|
|
||||||
{
|
|
||||||
public string Access_token { get; set; }
|
|
||||||
public string Token_type { get; set; }
|
|
||||||
public int Expires_in { get; set; }
|
|
||||||
public string Refresh_token { get; set; }
|
|
||||||
public string Scope { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UserSettingsResponse
|
|
||||||
{
|
|
||||||
public TraktUserResource User { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TraktUserResource
|
|
||||||
{
|
|
||||||
public string Username { get; set; }
|
|
||||||
public TraktUserIdsResource Ids { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TraktUserIdsResource
|
|
||||||
{
|
|
||||||
public string Slug { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Notifications.Trakt;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
@ -12,16 +12,12 @@ namespace NzbDrone.Core.NetImport.Trakt
|
|||||||
public abstract class TraktImportBase<TSettings> : HttpNetImportBase<TSettings>
|
public abstract class TraktImportBase<TSettings> : HttpNetImportBase<TSettings>
|
||||||
where TSettings : TraktSettingsBase<TSettings>, new()
|
where TSettings : TraktSettingsBase<TSettings>, new()
|
||||||
{
|
{
|
||||||
|
public ITraktProxy _traktProxy;
|
||||||
|
private readonly INetImportRepository _netImportRepository;
|
||||||
public override NetImportType ListType => NetImportType.Trakt;
|
public override NetImportType ListType => NetImportType.Trakt;
|
||||||
|
|
||||||
public const string OAuthUrl = "https://api.trakt.tv/oauth/authorize";
|
|
||||||
public const string RedirectUri = "https://auth.servarr.com/v1/trakt/auth";
|
|
||||||
public const string RenewUri = "https://auth.servarr.com/v1/trakt/renew";
|
|
||||||
public const string ClientId = "64508a8bf370cee550dde4806469922fd7cd70afb2d5690e3ee7f75ae784b70e";
|
|
||||||
|
|
||||||
private INetImportRepository _netImportRepository;
|
|
||||||
|
|
||||||
protected TraktImportBase(INetImportRepository netImportRepository,
|
protected TraktImportBase(INetImportRepository netImportRepository,
|
||||||
|
ITraktProxy traktProxy,
|
||||||
IHttpClient httpClient,
|
IHttpClient httpClient,
|
||||||
INetImportStatusService netImportStatusService,
|
INetImportStatusService netImportStatusService,
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
@ -30,6 +26,7 @@ protected TraktImportBase(INetImportRepository netImportRepository,
|
|||||||
: base(httpClient, netImportStatusService, configService, parsingService, logger)
|
: base(httpClient, netImportStatusService, configService, parsingService, logger)
|
||||||
{
|
{
|
||||||
_netImportRepository = netImportRepository;
|
_netImportRepository = netImportRepository;
|
||||||
|
_traktProxy = traktProxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override NetImportFetchResult Fetch()
|
public override NetImportFetchResult Fetch()
|
||||||
@ -55,12 +52,7 @@ public override object RequestAction(string action, IDictionary<string, string>
|
|||||||
{
|
{
|
||||||
if (action == "startOAuth")
|
if (action == "startOAuth")
|
||||||
{
|
{
|
||||||
var request = new HttpRequestBuilder(OAuthUrl)
|
var request = _traktProxy.GetOAuthRequest(query["callbackUrl"]);
|
||||||
.AddQueryParam("client_id", ClientId)
|
|
||||||
.AddQueryParam("response_type", "code")
|
|
||||||
.AddQueryParam("redirect_uri", RedirectUri)
|
|
||||||
.AddQueryParam("state", query["callbackUrl"])
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
return new
|
return new
|
||||||
{
|
{
|
||||||
@ -74,63 +66,29 @@ public override object RequestAction(string action, IDictionary<string, string>
|
|||||||
accessToken = query["access_token"],
|
accessToken = query["access_token"],
|
||||||
expires = DateTime.UtcNow.AddSeconds(int.Parse(query["expires_in"])),
|
expires = DateTime.UtcNow.AddSeconds(int.Parse(query["expires_in"])),
|
||||||
refreshToken = query["refresh_token"],
|
refreshToken = query["refresh_token"],
|
||||||
authUser = GetUserName(query["access_token"])
|
authUser = _traktProxy.GetUserName(query["access_token"])
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return new { };
|
return new { };
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetUserName(string accessToken)
|
|
||||||
{
|
|
||||||
var request = new HttpRequestBuilder(string.Format("{0}/users/settings", Settings.Link))
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
request.Headers.Add("trakt-api-version", "2");
|
|
||||||
request.Headers.Add("trakt-api-key", ClientId);
|
|
||||||
|
|
||||||
if (accessToken.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
request.Headers.Add("Authorization", "Bearer " + accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = _httpClient.Get<UserSettingsResponse>(request);
|
|
||||||
|
|
||||||
if (response != null && response.Resource != null)
|
|
||||||
{
|
|
||||||
return response.Resource.User.Ids.Slug;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (HttpException)
|
|
||||||
{
|
|
||||||
_logger.Warn($"Error refreshing trakt access token");
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RefreshToken()
|
private void RefreshToken()
|
||||||
{
|
{
|
||||||
_logger.Trace("Refreshing Token");
|
_logger.Trace("Refreshing Token");
|
||||||
|
|
||||||
Settings.Validate().Filter("RefreshToken").ThrowOnError();
|
Settings.Validate().Filter("RefreshToken").ThrowOnError();
|
||||||
|
|
||||||
var request = new HttpRequestBuilder(RenewUri)
|
|
||||||
.AddQueryParam("refresh_token", Settings.RefreshToken)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = _httpClient.Get<RefreshRequestResponse>(request);
|
var response = _traktProxy.RefreshAuthToken(Settings.RefreshToken);
|
||||||
|
|
||||||
if (response != null && response.Resource != null)
|
if (response != null)
|
||||||
{
|
{
|
||||||
var token = response.Resource;
|
var token = response;
|
||||||
Settings.AccessToken = token.Access_token;
|
Settings.AccessToken = token.AccessToken;
|
||||||
Settings.Expires = DateTime.UtcNow.AddSeconds(token.Expires_in);
|
Settings.Expires = DateTime.UtcNow.AddSeconds(token.ExpiresIn);
|
||||||
Settings.RefreshToken = token.Refresh_token != null ? token.Refresh_token : Settings.RefreshToken;
|
Settings.RefreshToken = token.RefreshToken ?? Settings.RefreshToken;
|
||||||
|
|
||||||
if (Definition.Id > 0)
|
if (Definition.Id > 0)
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.NetImport.Exceptions;
|
using NzbDrone.Core.NetImport.Exceptions;
|
||||||
|
using NzbDrone.Core.Notifications.Trakt.Resource;
|
||||||
|
|
||||||
namespace NzbDrone.Core.NetImport.Trakt
|
namespace NzbDrone.Core.NetImport.Trakt
|
||||||
{
|
{
|
||||||
@ -25,7 +26,7 @@ public TraktParser()
|
|||||||
return movies;
|
return movies;
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsonResponse = JsonConvert.DeserializeObject<List<TraktResponse>>(_importResponse.Content);
|
var jsonResponse = JsonConvert.DeserializeObject<List<TraktListResource>>(_importResponse.Content);
|
||||||
|
|
||||||
// no movies were return
|
// no movies were return
|
||||||
if (jsonResponse == null)
|
if (jsonResponse == null)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Notifications.Trakt;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
|
|
||||||
namespace NzbDrone.Core.NetImport.Trakt.User
|
namespace NzbDrone.Core.NetImport.Trakt.User
|
||||||
@ -8,12 +9,13 @@ namespace NzbDrone.Core.NetImport.Trakt.User
|
|||||||
public class TraktUserImport : TraktImportBase<TraktUserSettings>
|
public class TraktUserImport : TraktImportBase<TraktUserSettings>
|
||||||
{
|
{
|
||||||
public TraktUserImport(INetImportRepository netImportRepository,
|
public TraktUserImport(INetImportRepository netImportRepository,
|
||||||
|
ITraktProxy traktProxy,
|
||||||
IHttpClient httpClient,
|
IHttpClient httpClient,
|
||||||
INetImportStatusService netImportStatusService,
|
INetImportStatusService netImportStatusService,
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(netImportRepository, httpClient, netImportStatusService, configService, parsingService, logger)
|
: base(netImportRepository, traktProxy, httpClient, netImportStatusService, configService, parsingService, logger)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,10 +25,9 @@ public TraktUserImport(INetImportRepository netImportRepository,
|
|||||||
|
|
||||||
public override INetImportRequestGenerator GetRequestGenerator()
|
public override INetImportRequestGenerator GetRequestGenerator()
|
||||||
{
|
{
|
||||||
return new TraktUserRequestGenerator()
|
return new TraktUserRequestGenerator(_traktProxy)
|
||||||
{
|
{
|
||||||
Settings = Settings,
|
Settings = Settings
|
||||||
ClientId = ClientId
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.Notifications.Trakt;
|
||||||
|
|
||||||
namespace NzbDrone.Core.NetImport.Trakt.User
|
namespace NzbDrone.Core.NetImport.Trakt.User
|
||||||
{
|
{
|
||||||
public class TraktUserRequestGenerator : INetImportRequestGenerator
|
public class TraktUserRequestGenerator : INetImportRequestGenerator
|
||||||
{
|
{
|
||||||
|
private readonly ITraktProxy _traktProxy;
|
||||||
public TraktUserSettings Settings { get; set; }
|
public TraktUserSettings Settings { get; set; }
|
||||||
|
|
||||||
public string ClientId { get; set; }
|
public TraktUserRequestGenerator(ITraktProxy traktProxy)
|
||||||
|
|
||||||
public TraktUserRequestGenerator()
|
|
||||||
{
|
{
|
||||||
|
_traktProxy = traktProxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual NetImportPageableRequestChain GetMovies()
|
public virtual NetImportPageableRequestChain GetMovies()
|
||||||
@ -25,30 +25,22 @@ public virtual NetImportPageableRequestChain GetMovies()
|
|||||||
|
|
||||||
private IEnumerable<NetImportRequest> GetMoviesRequest()
|
private IEnumerable<NetImportRequest> GetMoviesRequest()
|
||||||
{
|
{
|
||||||
var link = Settings.Link.Trim();
|
var link = string.Empty;
|
||||||
|
|
||||||
switch (Settings.TraktListType)
|
switch (Settings.TraktListType)
|
||||||
{
|
{
|
||||||
case (int)TraktUserListType.UserWatchList:
|
case (int)TraktUserListType.UserWatchList:
|
||||||
link += $"/users/{Settings.AuthUser.Trim()}/watchlist/movies?limit={Settings.Limit}";
|
link += $"users/{Settings.AuthUser.Trim()}/watchlist/movies?limit={Settings.Limit}";
|
||||||
break;
|
break;
|
||||||
case (int)TraktUserListType.UserWatchedList:
|
case (int)TraktUserListType.UserWatchedList:
|
||||||
link += $"/users/{Settings.AuthUser.Trim()}/watched/movies?limit={Settings.Limit}";
|
link += $"users/{Settings.AuthUser.Trim()}/watched/movies?limit={Settings.Limit}";
|
||||||
break;
|
break;
|
||||||
case (int)TraktUserListType.UserCollectionList:
|
case (int)TraktUserListType.UserCollectionList:
|
||||||
link += $"/users/{Settings.AuthUser.Trim()}/collection/movies?limit={Settings.Limit}";
|
link += $"users/{Settings.AuthUser.Trim()}/collection/movies?limit={Settings.Limit}";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = new NetImportRequest($"{link}", HttpAccept.Json);
|
var request = new NetImportRequest(_traktProxy.BuildTraktRequest(link, HttpMethod.GET, Settings.AccessToken));
|
||||||
|
|
||||||
request.HttpRequest.Headers.Add("trakt-api-version", "2");
|
|
||||||
request.HttpRequest.Headers.Add("trakt-api-key", ClientId);
|
|
||||||
|
|
||||||
if (Settings.AccessToken.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
request.HttpRequest.Headers.Add("Authorization", "Bearer " + Settings.AccessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return request;
|
yield return request;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
@ -6,6 +6,7 @@ namespace NzbDrone.Core.Notifications
|
|||||||
{
|
{
|
||||||
public interface INotificationRepository : IProviderRepository<NotificationDefinition>
|
public interface INotificationRepository : IProviderRepository<NotificationDefinition>
|
||||||
{
|
{
|
||||||
|
void UpdateSettings(NotificationDefinition model);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class NotificationRepository : ProviderRepository<NotificationDefinition>, INotificationRepository
|
public class NotificationRepository : ProviderRepository<NotificationDefinition>, INotificationRepository
|
||||||
@ -14,5 +15,10 @@ public NotificationRepository(IMainDatabase database, IEventAggregator eventAggr
|
|||||||
: base(database, eventAggregator)
|
: base(database, eventAggregator)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateSettings(NotificationDefinition model)
|
||||||
|
{
|
||||||
|
SetFields(model, m => m.Settings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Trakt.Resource
|
||||||
|
{
|
||||||
|
public class TraktAuthRefreshResource
|
||||||
|
{
|
||||||
|
[JsonProperty(PropertyName = "access_token")]
|
||||||
|
public string AccessToken { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "token_type")]
|
||||||
|
public string TokenType { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "expires_in")]
|
||||||
|
public int ExpiresIn { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "refresh_token")]
|
||||||
|
public string RefreshToken { get; set; }
|
||||||
|
public string Scope { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Trakt.Resource
|
||||||
|
{
|
||||||
|
public class TraktCollectMovie : TraktMovieResource
|
||||||
|
{
|
||||||
|
[JsonProperty(PropertyName = "collected_at")]
|
||||||
|
public DateTime CollectedAt { get; set; }
|
||||||
|
public string Resolution { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "audio_channels")]
|
||||||
|
public string AudioChannels { get; set; }
|
||||||
|
public string Audio { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "media_type")]
|
||||||
|
public string MediaType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Trakt.Resource
|
||||||
|
{
|
||||||
|
public class TraktCollectMoviesResource
|
||||||
|
{
|
||||||
|
public List<TraktCollectMovie> Movies { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Trakt.Resource
|
||||||
|
{
|
||||||
|
public class TraktListResource
|
||||||
|
{
|
||||||
|
public int? Rank { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "listed_at")]
|
||||||
|
public string ListedAt { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
public int? Watchers { get; set; }
|
||||||
|
|
||||||
|
public long? Revenue { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "watcher_count")]
|
||||||
|
public long? WatcherCount { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "play_count")]
|
||||||
|
public long? PlayCount { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "collected_count")]
|
||||||
|
public long? CollectedCount { get; set; }
|
||||||
|
|
||||||
|
public TraktMovieResource Movie { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
namespace NzbDrone.Core.Notifications.Trakt.Resource
|
||||||
|
{
|
||||||
|
public class TraktMovieIdsResource
|
||||||
|
{
|
||||||
|
public int Trakt { get; set; }
|
||||||
|
public string Slug { get; set; }
|
||||||
|
public string Imdb { get; set; }
|
||||||
|
public int Tmdb { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
namespace NzbDrone.Core.Notifications.Trakt.Resource
|
||||||
|
{
|
||||||
|
public class TraktMovieResource
|
||||||
|
{
|
||||||
|
public string Title { get; set; }
|
||||||
|
public int? Year { get; set; }
|
||||||
|
public TraktMovieIdsResource Ids { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
namespace NzbDrone.Core.Notifications.Trakt.Resource
|
||||||
|
{
|
||||||
|
public class TraktUserIdsResource
|
||||||
|
{
|
||||||
|
public string Slug { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
using NzbDrone.Core.Notifications.Trakt.Resource;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Trakt
|
||||||
|
{
|
||||||
|
public class TraktUserResource
|
||||||
|
{
|
||||||
|
public string Username { get; set; }
|
||||||
|
public TraktUserIdsResource Ids { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
namespace NzbDrone.Core.Notifications.Trakt.Resource
|
||||||
|
{
|
||||||
|
public class TraktUserSettingsResource
|
||||||
|
{
|
||||||
|
public TraktUserResource User { get; set; }
|
||||||
|
}
|
||||||
|
}
|
95
src/NzbDrone.Core/Notifications/Trakt/Trakt.cs
Normal file
95
src/NzbDrone.Core/Notifications/Trakt/Trakt.cs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Trakt
|
||||||
|
{
|
||||||
|
public class Trakt : NotificationBase<TraktSettings>
|
||||||
|
{
|
||||||
|
private readonly ITraktService _traktService;
|
||||||
|
private readonly INotificationRepository _notificationRepository;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public Trakt(ITraktService traktService, INotificationRepository notificationRepository, Logger logger)
|
||||||
|
{
|
||||||
|
_traktService = traktService;
|
||||||
|
_notificationRepository = notificationRepository;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Link => "https://trakt.tv/";
|
||||||
|
public override string Name => "Trakt";
|
||||||
|
|
||||||
|
public override void OnDownload(DownloadMessage message)
|
||||||
|
{
|
||||||
|
_traktService.AddMovieToCollection(Settings, message.Movie, message.MovieFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ValidationResult Test()
|
||||||
|
{
|
||||||
|
var failures = new List<ValidationFailure>();
|
||||||
|
|
||||||
|
failures.AddIfNotNull(_traktService.Test(Settings));
|
||||||
|
|
||||||
|
return new ValidationResult(failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object RequestAction(string action, IDictionary<string, string> query)
|
||||||
|
{
|
||||||
|
if (action == "startOAuth")
|
||||||
|
{
|
||||||
|
var request = _traktService.GetOAuthRequest(query["callbackUrl"]);
|
||||||
|
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
OauthUrl = request.Url.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (action == "getOAuthToken")
|
||||||
|
{
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
accessToken = query["access_token"],
|
||||||
|
expires = DateTime.UtcNow.AddSeconds(int.Parse(query["expires_in"])),
|
||||||
|
refreshToken = query["refresh_token"],
|
||||||
|
authUser = _traktService.GetUserName(query["access_token"])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new { };
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshToken()
|
||||||
|
{
|
||||||
|
_logger.Trace("Refreshing Token");
|
||||||
|
|
||||||
|
Settings.Validate().Filter("RefreshToken").ThrowOnError();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = _traktService.RefreshAuthToken(Settings.RefreshToken);
|
||||||
|
|
||||||
|
if (response != null)
|
||||||
|
{
|
||||||
|
var token = response;
|
||||||
|
Settings.AccessToken = token.AccessToken;
|
||||||
|
Settings.Expires = DateTime.UtcNow.AddSeconds(token.ExpiresIn);
|
||||||
|
Settings.RefreshToken = token.RefreshToken ?? Settings.RefreshToken;
|
||||||
|
|
||||||
|
if (Definition.Id > 0)
|
||||||
|
{
|
||||||
|
_notificationRepository.UpdateSettings((NotificationDefinition)Definition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (HttpException)
|
||||||
|
{
|
||||||
|
_logger.Warn($"Error refreshing trakt access token");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/NzbDrone.Core/Notifications/Trakt/TraktException.cs
Normal file
18
src/NzbDrone.Core/Notifications/Trakt/TraktException.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using NzbDrone.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Trakt
|
||||||
|
{
|
||||||
|
public class TraktException : NzbDroneException
|
||||||
|
{
|
||||||
|
public TraktException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TraktException(string message, Exception innerException, params object[] args)
|
||||||
|
: base(message, innerException, args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
114
src/NzbDrone.Core/Notifications/Trakt/TraktProxy.cs
Normal file
114
src/NzbDrone.Core/Notifications/Trakt/TraktProxy.cs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
|
using NzbDrone.Core.Notifications.Trakt.Resource;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Trakt
|
||||||
|
{
|
||||||
|
public interface ITraktProxy
|
||||||
|
{
|
||||||
|
string GetUserName(string accessToken);
|
||||||
|
HttpRequest GetOAuthRequest(string callbackUrl);
|
||||||
|
TraktAuthRefreshResource RefreshAuthToken(string refreshToken);
|
||||||
|
void AddToCollection(TraktCollectMoviesResource payload, string accessToken);
|
||||||
|
HttpRequest BuildTraktRequest(string resource, HttpMethod method, string accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TraktProxy : ITraktProxy
|
||||||
|
{
|
||||||
|
private const string URL = "https://api.trakt.tv";
|
||||||
|
private const string OAuthUrl = "https://api.trakt.tv/oauth/authorize";
|
||||||
|
private const string RedirectUri = "https://auth.servarr.com/v1/trakt/auth";
|
||||||
|
private const string RenewUri = "https://auth.servarr.com/v1/trakt/renew";
|
||||||
|
private const string ClientId = "64508a8bf370cee550dde4806469922fd7cd70afb2d5690e3ee7f75ae784b70e";
|
||||||
|
|
||||||
|
private readonly IHttpClient _httpClient;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public TraktProxy(IHttpClient httpClient, Logger logger)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddToCollection(TraktCollectMoviesResource payload, string accessToken)
|
||||||
|
{
|
||||||
|
var request = BuildTraktRequest("sync/collection", HttpMethod.POST, accessToken);
|
||||||
|
|
||||||
|
request.Headers.ContentType = "application/json";
|
||||||
|
request.SetContent(payload.ToJson());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_httpClient.Execute(request);
|
||||||
|
}
|
||||||
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Unable to post payload {0}", payload);
|
||||||
|
throw new TraktException("Unable to post payload", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetUserName(string accessToken)
|
||||||
|
{
|
||||||
|
var request = BuildTraktRequest("users/settings", HttpMethod.GET, accessToken);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = _httpClient.Get<TraktUserSettingsResource>(request);
|
||||||
|
|
||||||
|
if (response != null && response.Resource != null)
|
||||||
|
{
|
||||||
|
return response.Resource.User.Ids.Slug;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (HttpException)
|
||||||
|
{
|
||||||
|
_logger.Warn($"Error refreshing trakt access token");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpRequest GetOAuthRequest(string callbackUrl)
|
||||||
|
{
|
||||||
|
return new HttpRequestBuilder(OAuthUrl)
|
||||||
|
.AddQueryParam("client_id", ClientId)
|
||||||
|
.AddQueryParam("response_type", "code")
|
||||||
|
.AddQueryParam("redirect_uri", RedirectUri)
|
||||||
|
.AddQueryParam("state", callbackUrl)
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TraktAuthRefreshResource RefreshAuthToken(string refreshToken)
|
||||||
|
{
|
||||||
|
var request = new HttpRequestBuilder(RenewUri)
|
||||||
|
.AddQueryParam("refresh_token", refreshToken)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
return _httpClient.Get<TraktAuthRefreshResource>(request)?.Resource ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpRequest BuildTraktRequest(string resource, HttpMethod method, string accessToken)
|
||||||
|
{
|
||||||
|
var request = new HttpRequestBuilder(URL).Resource(resource).Build();
|
||||||
|
|
||||||
|
request.Headers.Accept = HttpAccept.Json.Value;
|
||||||
|
request.Method = method;
|
||||||
|
|
||||||
|
request.Headers.Add("trakt-api-version", "2");
|
||||||
|
request.Headers.Add("trakt-api-key", ClientId);
|
||||||
|
|
||||||
|
if (accessToken.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
request.Headers.Add("Authorization", "Bearer " + accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
250
src/NzbDrone.Core/Notifications/Trakt/TraktService.cs
Normal file
250
src/NzbDrone.Core/Notifications/Trakt/TraktService.cs
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
|
using NzbDrone.Core.Movies;
|
||||||
|
using NzbDrone.Core.Notifications.Trakt.Resource;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Trakt
|
||||||
|
{
|
||||||
|
public interface ITraktService
|
||||||
|
{
|
||||||
|
HttpRequest GetOAuthRequest(string callbackUrl);
|
||||||
|
TraktAuthRefreshResource RefreshAuthToken(string refreshToken);
|
||||||
|
void AddMovieToCollection(TraktSettings settings, Movie movie, MovieFile movieFile);
|
||||||
|
string GetUserName(string accessToken);
|
||||||
|
ValidationFailure Test(TraktSettings settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TraktService : ITraktService
|
||||||
|
{
|
||||||
|
private readonly ITraktProxy _proxy;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public TraktService(ITraktProxy proxy,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_proxy = proxy;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetUserName(string accessToken)
|
||||||
|
{
|
||||||
|
return _proxy.GetUserName(accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpRequest GetOAuthRequest(string callbackUrl)
|
||||||
|
{
|
||||||
|
return _proxy.GetOAuthRequest(callbackUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TraktAuthRefreshResource RefreshAuthToken(string refreshToken)
|
||||||
|
{
|
||||||
|
return _proxy.RefreshAuthToken(refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationFailure Test(TraktSettings settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GetUserName(settings.AccessToken);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Access Token is invalid: " + ex.Message);
|
||||||
|
return new ValidationFailure("Token", "Access Token is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Error(ex, "Unable to send test message: " + ex.Message);
|
||||||
|
return new ValidationFailure("Token", "Unable to send test message");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Unable to send test message: " + ex.Message);
|
||||||
|
return new ValidationFailure("", "Unable to send test message");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddMovieToCollection(TraktSettings settings, Movie movie, MovieFile movieFile)
|
||||||
|
{
|
||||||
|
var payload = new TraktCollectMoviesResource
|
||||||
|
{
|
||||||
|
Movies = new List<TraktCollectMovie>()
|
||||||
|
};
|
||||||
|
|
||||||
|
var traktResolution = MapResolution(movieFile.Quality.Quality.Resolution, movieFile.MediaInfo?.ScanType);
|
||||||
|
var mediaType = MapMediaType(movieFile.Quality.Quality.Source);
|
||||||
|
var audio = MapAudio(movieFile);
|
||||||
|
var audioChannels = MapAudioChannels(movieFile, audio);
|
||||||
|
|
||||||
|
payload.Movies.Add(new TraktCollectMovie
|
||||||
|
{
|
||||||
|
Title = movie.Title,
|
||||||
|
Year = movie.Year,
|
||||||
|
CollectedAt = DateTime.Now,
|
||||||
|
Resolution = traktResolution,
|
||||||
|
MediaType = mediaType,
|
||||||
|
AudioChannels = audioChannels,
|
||||||
|
Audio = audio,
|
||||||
|
Ids = new TraktMovieIdsResource
|
||||||
|
{
|
||||||
|
Tmdb = movie.TmdbId,
|
||||||
|
Imdb = movie.ImdbId ?? "",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_proxy.AddToCollection(payload, settings.AccessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string MapMediaType(Source source)
|
||||||
|
{
|
||||||
|
var traktSource = string.Empty;
|
||||||
|
|
||||||
|
switch (source)
|
||||||
|
{
|
||||||
|
case Source.BLURAY:
|
||||||
|
traktSource = "bluray";
|
||||||
|
break;
|
||||||
|
case Source.WEBDL:
|
||||||
|
traktSource = "digital";
|
||||||
|
break;
|
||||||
|
case Source.WEBRIP:
|
||||||
|
traktSource = "digital";
|
||||||
|
break;
|
||||||
|
case Source.DVD:
|
||||||
|
traktSource = "dvd";
|
||||||
|
break;
|
||||||
|
case Source.TV:
|
||||||
|
traktSource = "dvd";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return traktSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string MapResolution(int resolution, string scanType)
|
||||||
|
{
|
||||||
|
var traktResolution = string.Empty;
|
||||||
|
var interlacedTypes = new string[] { "Interlaced", "MBAFF", "PAFF" };
|
||||||
|
|
||||||
|
var scanIdentifier = scanType.IsNotNullOrWhiteSpace() && interlacedTypes.Contains(scanType) ? "i" : "p";
|
||||||
|
|
||||||
|
switch (resolution)
|
||||||
|
{
|
||||||
|
case 2160:
|
||||||
|
traktResolution = "uhd_4k";
|
||||||
|
break;
|
||||||
|
case 1080:
|
||||||
|
traktResolution = string.Format("hd_1080{0}", scanIdentifier);
|
||||||
|
break;
|
||||||
|
case 720:
|
||||||
|
traktResolution = "hd_720p";
|
||||||
|
break;
|
||||||
|
case 576:
|
||||||
|
traktResolution = string.Format("sd_576{0}", scanIdentifier);
|
||||||
|
break;
|
||||||
|
case 480:
|
||||||
|
traktResolution = string.Format("sd_480{0}", scanIdentifier);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return traktResolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string MapAudio(MovieFile movieFile)
|
||||||
|
{
|
||||||
|
var traktAudioFormat = string.Empty;
|
||||||
|
|
||||||
|
var audioCodec = movieFile.MediaInfo != null ? MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, movieFile.SceneName) : string.Empty;
|
||||||
|
|
||||||
|
switch (audioCodec)
|
||||||
|
{
|
||||||
|
case "AC3":
|
||||||
|
traktAudioFormat = "dolby_digital";
|
||||||
|
break;
|
||||||
|
case "EAC3":
|
||||||
|
traktAudioFormat = "dolby_digital_plus";
|
||||||
|
break;
|
||||||
|
case "TrueHD":
|
||||||
|
traktAudioFormat = "dolby_truehd";
|
||||||
|
break;
|
||||||
|
case "EAC3 Atmos":
|
||||||
|
case "TrueHD Atmos":
|
||||||
|
traktAudioFormat = "dolby_atmos";
|
||||||
|
break;
|
||||||
|
case "DTS":
|
||||||
|
case "DTS-ES":
|
||||||
|
traktAudioFormat = "dts";
|
||||||
|
break;
|
||||||
|
case "DTS-HD MA":
|
||||||
|
traktAudioFormat = "dts_ma";
|
||||||
|
break;
|
||||||
|
case "DTS-HD HRA":
|
||||||
|
traktAudioFormat = "dts_hr";
|
||||||
|
break;
|
||||||
|
case "DTS-X":
|
||||||
|
traktAudioFormat = "dts_x";
|
||||||
|
break;
|
||||||
|
case "MP3":
|
||||||
|
case "MP2":
|
||||||
|
traktAudioFormat = "mp3";
|
||||||
|
break;
|
||||||
|
case "Vorbis":
|
||||||
|
traktAudioFormat = "ogg";
|
||||||
|
break;
|
||||||
|
case "WMA":
|
||||||
|
traktAudioFormat = "wma";
|
||||||
|
break;
|
||||||
|
case "AAC":
|
||||||
|
traktAudioFormat = "aac";
|
||||||
|
break;
|
||||||
|
case "PCM":
|
||||||
|
traktAudioFormat = "lpcm";
|
||||||
|
break;
|
||||||
|
case "FLAC":
|
||||||
|
traktAudioFormat = "flac";
|
||||||
|
break;
|
||||||
|
case "Opus":
|
||||||
|
traktAudioFormat = "ogg";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return traktAudioFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string MapAudioChannels(MovieFile movieFile, string audioFormat)
|
||||||
|
{
|
||||||
|
var audioChannels = movieFile.MediaInfo != null ? MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo).ToString("0.0") : string.Empty;
|
||||||
|
|
||||||
|
// Map cases where Radarr doesn't handle MI correctly, can purge once mediainfo handling is improved
|
||||||
|
if (audioChannels == "8.0")
|
||||||
|
{
|
||||||
|
audioChannels = "7.1";
|
||||||
|
}
|
||||||
|
else if (audioChannels == "6.0" && audioFormat == "dts_ma")
|
||||||
|
{
|
||||||
|
audioChannels = "7.1";
|
||||||
|
}
|
||||||
|
else if (audioChannels == "6.0" && audioFormat != "dts_ma")
|
||||||
|
{
|
||||||
|
audioChannels = "5.1";
|
||||||
|
}
|
||||||
|
else if (audioChannels == "0.0")
|
||||||
|
{
|
||||||
|
audioChannels = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return audioChannels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
src/NzbDrone.Core/Notifications/Trakt/TraktSettings.cs
Normal file
48
src/NzbDrone.Core/Notifications/Trakt/TraktSettings.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Trakt
|
||||||
|
{
|
||||||
|
public class TraktSettingsValidator : AbstractValidator<TraktSettings>
|
||||||
|
{
|
||||||
|
public TraktSettingsValidator()
|
||||||
|
{
|
||||||
|
RuleFor(c => c.AccessToken).NotEmpty();
|
||||||
|
RuleFor(c => c.RefreshToken).NotEmpty();
|
||||||
|
RuleFor(c => c.Expires).NotEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TraktSettings : IProviderConfig
|
||||||
|
{
|
||||||
|
private static readonly TraktSettingsValidator Validator = new TraktSettingsValidator();
|
||||||
|
|
||||||
|
public TraktSettings()
|
||||||
|
{
|
||||||
|
SignIn = "startOAuth";
|
||||||
|
}
|
||||||
|
|
||||||
|
[FieldDefinition(0, Label = "Access Token", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)]
|
||||||
|
public string AccessToken { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(0, Label = "Refresh Token", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)]
|
||||||
|
public string RefreshToken { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(0, Label = "Expires", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)]
|
||||||
|
public DateTime Expires { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(0, Label = "Auth User", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)]
|
||||||
|
public string AuthUser { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(99, Label = "Authenticate with Trakt", Type = FieldType.OAuth)]
|
||||||
|
public string SignIn { get; set; }
|
||||||
|
|
||||||
|
public NzbDroneValidationResult Validate()
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user