parent
2e7788b072
commit
9ed2b4e10b
@ -0,0 +1,31 @@
|
|||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Trakt.List
|
||||||
|
{
|
||||||
|
public class TraktListImport : TraktImportBase<TraktListSettings>
|
||||||
|
{
|
||||||
|
public TraktListImport(IImportListRepository netImportRepository,
|
||||||
|
IHttpClient httpClient,
|
||||||
|
IImportListStatusService netImportStatusService,
|
||||||
|
IConfigService configService,
|
||||||
|
IParsingService parsingService,
|
||||||
|
Logger logger)
|
||||||
|
: base(netImportRepository, httpClient, netImportStatusService, configService, parsingService, logger)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name => "Trakt List";
|
||||||
|
|
||||||
|
public override IImportListRequestGenerator GetRequestGenerator()
|
||||||
|
{
|
||||||
|
return new TraktListRequestGenerator()
|
||||||
|
{
|
||||||
|
Settings = Settings,
|
||||||
|
ClientId = ClientId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Trakt.List
|
||||||
|
{
|
||||||
|
public class TraktListRequestGenerator : IImportListRequestGenerator
|
||||||
|
{
|
||||||
|
public TraktListSettings Settings { get; set; }
|
||||||
|
public string ClientId { get; set; }
|
||||||
|
|
||||||
|
public TraktListRequestGenerator()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual ImportListPageableRequestChain GetListItems()
|
||||||
|
{
|
||||||
|
var pageableRequests = new ImportListPageableRequestChain();
|
||||||
|
|
||||||
|
pageableRequests.Add(GetSeriesRequest());
|
||||||
|
|
||||||
|
return pageableRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<ImportListRequest> GetSeriesRequest()
|
||||||
|
{
|
||||||
|
var link = Settings.BaseUrl.Trim();
|
||||||
|
|
||||||
|
var listName = Settings.Listname.Trim();
|
||||||
|
|
||||||
|
link += $"/users/{Settings.Username.Trim()}/lists/{listName}/items/shows?limit={Settings.Limit}";
|
||||||
|
|
||||||
|
var request = new ImportListRequest($"{link}", HttpAccept.Json);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Trakt.List
|
||||||
|
{
|
||||||
|
public class TraktListSettingsValidator : TraktSettingsBaseValidator<TraktListSettings>
|
||||||
|
{
|
||||||
|
public TraktListSettingsValidator()
|
||||||
|
: base()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TraktListSettings : TraktSettingsBase<TraktListSettings>
|
||||||
|
{
|
||||||
|
protected override AbstractValidator<TraktListSettings> Validator => new TraktListSettingsValidator();
|
||||||
|
|
||||||
|
public TraktListSettings()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[FieldDefinition(1, Label = "Username", HelpText = "Username for the List to import from")]
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(2, Label = "List Name", HelpText = "List name for import, list must be public or you must have access to the list")]
|
||||||
|
public string Listname { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||||
|
{
|
||||||
|
public class TraktPopularImport : TraktImportBase<TraktPopularSettings>
|
||||||
|
{
|
||||||
|
public TraktPopularImport(IImportListRepository netImportRepository,
|
||||||
|
IHttpClient httpClient,
|
||||||
|
IImportListStatusService netImportStatusService,
|
||||||
|
IConfigService configService,
|
||||||
|
IParsingService parsingService,
|
||||||
|
Logger logger)
|
||||||
|
: base(netImportRepository, httpClient, netImportStatusService, configService, parsingService, logger)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name => "Trakt Popular List";
|
||||||
|
|
||||||
|
public override IParseImportListResponse GetParser()
|
||||||
|
{
|
||||||
|
return new TraktPopularParser(Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IImportListRequestGenerator GetRequestGenerator()
|
||||||
|
{
|
||||||
|
return new TraktPopularRequestGenerator()
|
||||||
|
{
|
||||||
|
Settings = Settings,
|
||||||
|
ClientId = ClientId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||||
|
{
|
||||||
|
public enum TraktPopularListType
|
||||||
|
{
|
||||||
|
[EnumMember(Value = "Trending Shows")]
|
||||||
|
Trending = 0,
|
||||||
|
[EnumMember(Value = "Popular Shows")]
|
||||||
|
Popular = 1,
|
||||||
|
[EnumMember(Value = "Anticipated Shows")]
|
||||||
|
Anticipated = 2,
|
||||||
|
|
||||||
|
[EnumMember(Value = "Top Watched Shows By Week")]
|
||||||
|
TopWatchedByWeek = 3,
|
||||||
|
[EnumMember(Value = "Top Watched Shows By Month")]
|
||||||
|
TopWatchedByMonth = 4,
|
||||||
|
[EnumMember(Value = "Top Watched Shows By Year")]
|
||||||
|
TopWatchedByYear = 5,
|
||||||
|
[EnumMember(Value = "Top Watched Shows Of All Time")]
|
||||||
|
TopWatchedByAllTime = 6
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||||
|
{
|
||||||
|
public class TraktPopularParser : TraktParser
|
||||||
|
{
|
||||||
|
private readonly TraktPopularSettings _settings;
|
||||||
|
private ImportListResponse _importResponse;
|
||||||
|
|
||||||
|
public TraktPopularParser(TraktPopularSettings settings)
|
||||||
|
{
|
||||||
|
_settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IList<ImportListItemInfo> ParseResponse(ImportListResponse importResponse)
|
||||||
|
{
|
||||||
|
_importResponse = importResponse;
|
||||||
|
|
||||||
|
var listItems = new List<ImportListItemInfo>();
|
||||||
|
|
||||||
|
if (!PreProcess(_importResponse))
|
||||||
|
{
|
||||||
|
return listItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonResponse = new List<TraktSeriesResource>();
|
||||||
|
|
||||||
|
if (_settings.TraktListType == (int)TraktPopularListType.Popular)
|
||||||
|
{
|
||||||
|
jsonResponse = JsonConvert.DeserializeObject<List<TraktSeriesResource>>(_importResponse.Content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jsonResponse = JsonConvert.DeserializeObject<List<TraktResponse>>(_importResponse.Content).SelectList(c => c.Show);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no movies were return
|
||||||
|
if (jsonResponse == null)
|
||||||
|
{
|
||||||
|
return listItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var series in jsonResponse)
|
||||||
|
{
|
||||||
|
listItems.AddIfNotNull(new ImportListItemInfo()
|
||||||
|
{
|
||||||
|
Title = series.Title,
|
||||||
|
TvdbId = series.Ids.Tvdb.GetValueOrDefault(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return listItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||||
|
{
|
||||||
|
public class TraktPopularRequestGenerator : IImportListRequestGenerator
|
||||||
|
{
|
||||||
|
public TraktPopularSettings Settings { get; set; }
|
||||||
|
|
||||||
|
public string ClientId { get; set; }
|
||||||
|
|
||||||
|
public TraktPopularRequestGenerator()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual ImportListPageableRequestChain GetListItems()
|
||||||
|
{
|
||||||
|
var pageableRequests = new ImportListPageableRequestChain();
|
||||||
|
|
||||||
|
pageableRequests.Add(GetSeriesRequest());
|
||||||
|
|
||||||
|
return pageableRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<ImportListRequest> GetSeriesRequest()
|
||||||
|
{
|
||||||
|
var link = Settings.BaseUrl.Trim();
|
||||||
|
|
||||||
|
var filtersAndLimit = $"?years={Settings.Years}&genres={Settings.Genres.ToLower()}&ratings={Settings.Rating}&limit={Settings.Limit}{Settings.TraktAdditionalParameters}";
|
||||||
|
|
||||||
|
switch (Settings.TraktListType)
|
||||||
|
{
|
||||||
|
case (int)TraktPopularListType.Trending:
|
||||||
|
link += "/shows/trending" + filtersAndLimit;
|
||||||
|
break;
|
||||||
|
case (int)TraktPopularListType.Popular:
|
||||||
|
link += "/shows/popular" + filtersAndLimit;
|
||||||
|
break;
|
||||||
|
case (int)TraktPopularListType.Anticipated:
|
||||||
|
link += "/shows/anticipated" + filtersAndLimit;
|
||||||
|
break;
|
||||||
|
case (int)TraktPopularListType.TopWatchedByWeek:
|
||||||
|
link += "/shows/watched/weekly" + filtersAndLimit;
|
||||||
|
break;
|
||||||
|
case (int)TraktPopularListType.TopWatchedByMonth:
|
||||||
|
link += "/shows/watched/monthly" + filtersAndLimit;
|
||||||
|
break;
|
||||||
|
case (int)TraktPopularListType.TopWatchedByYear:
|
||||||
|
link += "/shows/watched/yearly" + filtersAndLimit;
|
||||||
|
break;
|
||||||
|
case (int)TraktPopularListType.TopWatchedByAllTime:
|
||||||
|
link += "/shows/watched/all" + filtersAndLimit;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = new ImportListRequest($"{link}", HttpAccept.Json);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||||
|
{
|
||||||
|
public class TraktPopularSettingsValidator : TraktSettingsBaseValidator<TraktPopularSettings>
|
||||||
|
{
|
||||||
|
public TraktPopularSettingsValidator()
|
||||||
|
: base()
|
||||||
|
{
|
||||||
|
RuleFor(c => c.TraktListType).NotNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TraktPopularSettings : TraktSettingsBase<TraktPopularSettings>
|
||||||
|
{
|
||||||
|
protected override AbstractValidator<TraktPopularSettings> Validator => new TraktPopularSettingsValidator();
|
||||||
|
|
||||||
|
public TraktPopularSettings()
|
||||||
|
{
|
||||||
|
TraktListType = (int)TraktPopularListType.Popular;
|
||||||
|
}
|
||||||
|
|
||||||
|
[FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(TraktPopularListType), HelpText = "Type of list your seeking to import from")]
|
||||||
|
public int TraktListType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Trakt
|
||||||
|
{
|
||||||
|
public class TraktSeriesIdsResource
|
||||||
|
{
|
||||||
|
public int Trakt { get; set; }
|
||||||
|
public string Slug { get; set; }
|
||||||
|
public string Imdb { get; set; }
|
||||||
|
public int? Tmdb { get; set; }
|
||||||
|
public int? Tvdb { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TraktSeriesResource
|
||||||
|
{
|
||||||
|
public string Title { get; set; }
|
||||||
|
public int? Year { get; set; }
|
||||||
|
public TraktSeriesIdsResource Ids { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TraktResponse
|
||||||
|
{
|
||||||
|
public TraktSeriesResource Show { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RefreshRequestResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("access_token")]
|
||||||
|
public string AccessToken { get; set; }
|
||||||
|
[JsonProperty("expires_in")]
|
||||||
|
public int ExpiresIn { get; set; }
|
||||||
|
[JsonProperty("refresh_token")]
|
||||||
|
public string RefreshToken { 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; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Trakt
|
||||||
|
{
|
||||||
|
public abstract class TraktImportBase<TSettings> : HttpImportListBase<TSettings>
|
||||||
|
where TSettings : TraktSettingsBase<TSettings>, new()
|
||||||
|
{
|
||||||
|
public override ImportListType ListType => ImportListType.Trakt;
|
||||||
|
|
||||||
|
public const string OAuthUrl = "https://api.trakt.tv/oauth/authorize";
|
||||||
|
public const string RedirectUri = "https://auth.servarr.com/v1/trakt_sonarr/auth";
|
||||||
|
public const string RenewUri = "https://auth.servarr.com/v1/trakt_sonarr/renew";
|
||||||
|
public const string ClientId = "d44ba57cab40c31eb3f797dcfccd203500796539125b333883ec1d94aa62ed4c";
|
||||||
|
|
||||||
|
|
||||||
|
private IImportListRepository _importListRepository;
|
||||||
|
|
||||||
|
protected TraktImportBase(IImportListRepository netImportRepository,
|
||||||
|
IHttpClient httpClient,
|
||||||
|
IImportListStatusService importListStatusService,
|
||||||
|
IConfigService configService,
|
||||||
|
IParsingService parsingService,
|
||||||
|
Logger logger)
|
||||||
|
: base(httpClient, importListStatusService, configService, parsingService, logger)
|
||||||
|
{
|
||||||
|
_importListRepository = netImportRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IList<ImportListItemInfo> Fetch()
|
||||||
|
{
|
||||||
|
Settings.Validate().Filter("AccessToken", "RefreshToken").ThrowOnError();
|
||||||
|
_logger.Trace($"Access token expires at {Settings.Expires}");
|
||||||
|
|
||||||
|
if (Settings.Expires < DateTime.UtcNow.AddMinutes(5))
|
||||||
|
{
|
||||||
|
RefreshToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
var generator = GetRequestGenerator();
|
||||||
|
return FetchItems(g => g.GetListItems(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IParseImportListResponse GetParser()
|
||||||
|
{
|
||||||
|
return new TraktParser();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object RequestAction(string action, IDictionary<string, string> query)
|
||||||
|
{
|
||||||
|
if (action == "startOAuth")
|
||||||
|
{
|
||||||
|
var request = new HttpRequestBuilder(OAuthUrl)
|
||||||
|
.AddQueryParam("client_id", ClientId)
|
||||||
|
.AddQueryParam("response_type", "code")
|
||||||
|
.AddQueryParam("redirect_uri", RedirectUri)
|
||||||
|
.AddQueryParam("state", query["callbackUrl"])
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
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 = GetUserName(query["access_token"])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new { };
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetUserName(string accessToken)
|
||||||
|
{
|
||||||
|
var request = new HttpRequestBuilder(string.Format("{0}/users/settings", Settings.BaseUrl))
|
||||||
|
.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()
|
||||||
|
{
|
||||||
|
_logger.Trace("Refreshing Token");
|
||||||
|
|
||||||
|
Settings.Validate().Filter("RefreshToken").ThrowOnError();
|
||||||
|
|
||||||
|
var request = new HttpRequestBuilder(RenewUri)
|
||||||
|
.AddQueryParam("refresh_token", Settings.RefreshToken)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = _httpClient.Get<RefreshRequestResponse>(request);
|
||||||
|
|
||||||
|
if (response != null && 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;
|
||||||
|
|
||||||
|
if (Definition.Id > 0)
|
||||||
|
{
|
||||||
|
_importListRepository.UpdateSettings((ImportListDefinition)Definition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (HttpException)
|
||||||
|
{
|
||||||
|
_logger.Warn($"Error refreshing trakt access token");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.ImportLists.Exceptions;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Trakt
|
||||||
|
{
|
||||||
|
public class TraktParser : IParseImportListResponse
|
||||||
|
{
|
||||||
|
private ImportListResponse _importResponse;
|
||||||
|
|
||||||
|
public TraktParser()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual IList<ImportListItemInfo> ParseResponse(ImportListResponse importResponse)
|
||||||
|
{
|
||||||
|
_importResponse = importResponse;
|
||||||
|
|
||||||
|
var series = new List<ImportListItemInfo>();
|
||||||
|
|
||||||
|
if (!PreProcess(_importResponse))
|
||||||
|
{
|
||||||
|
return series;
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonResponse = JsonConvert.DeserializeObject<List<TraktResponse>>(_importResponse.Content);
|
||||||
|
|
||||||
|
// no movies were return
|
||||||
|
if (jsonResponse == null)
|
||||||
|
{
|
||||||
|
return series;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var movie in jsonResponse)
|
||||||
|
{
|
||||||
|
series.AddIfNotNull(new ImportListItemInfo()
|
||||||
|
{
|
||||||
|
Title = movie.Show.Title,
|
||||||
|
TvdbId = movie.Show.Ids.Tvdb.GetValueOrDefault()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return series;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool PreProcess(ImportListResponse netImportResponse)
|
||||||
|
{
|
||||||
|
if (netImportResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
throw new ImportListException(netImportResponse, "Trakt API call resulted in an unexpected StatusCode [{0}]", netImportResponse.HttpResponse.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (netImportResponse.HttpResponse.Headers.ContentType != null && netImportResponse.HttpResponse.Headers.ContentType.Contains("text/json") &&
|
||||||
|
netImportResponse.HttpRequest.Headers.Accept != null && !netImportResponse.HttpRequest.Headers.Accept.Contains("text/json"))
|
||||||
|
{
|
||||||
|
throw new ImportListException(netImportResponse, "Trakt API responded with html content. Site is likely blocked or unavailable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Trakt
|
||||||
|
{
|
||||||
|
public class TraktSettingsBaseValidator<TSettings> : AbstractValidator<TSettings>
|
||||||
|
where TSettings : TraktSettingsBase<TSettings>
|
||||||
|
{
|
||||||
|
public TraktSettingsBaseValidator()
|
||||||
|
{
|
||||||
|
RuleFor(c => c.BaseUrl).ValidRootUrl();
|
||||||
|
RuleFor(c => c.AccessToken).NotEmpty();
|
||||||
|
RuleFor(c => c.RefreshToken).NotEmpty();
|
||||||
|
RuleFor(c => c.Expires).NotEmpty();
|
||||||
|
|
||||||
|
// Loose validation @TODO
|
||||||
|
RuleFor(c => c.Rating)
|
||||||
|
.Matches(@"^\d+\-\d+$", RegexOptions.IgnoreCase)
|
||||||
|
.When(c => c.Rating.IsNotNullOrWhiteSpace())
|
||||||
|
.WithMessage("Not a valid rating");
|
||||||
|
|
||||||
|
// Loose validation @TODO
|
||||||
|
RuleFor(c => c.Years)
|
||||||
|
.Matches(@"^\d+(\-\d+)?$", RegexOptions.IgnoreCase)
|
||||||
|
.When(c => c.Years.IsNotNullOrWhiteSpace())
|
||||||
|
.WithMessage("Not a valid year or range of years");
|
||||||
|
|
||||||
|
// Limit not smaller than 1 and not larger than 100
|
||||||
|
RuleFor(c => c.Limit)
|
||||||
|
.GreaterThan(0)
|
||||||
|
.WithMessage("Must be integer greater than 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TraktSettingsBase<TSettings> : IImportListSettings
|
||||||
|
where TSettings : TraktSettingsBase<TSettings>
|
||||||
|
{
|
||||||
|
protected virtual AbstractValidator<TSettings> Validator => new TraktSettingsBaseValidator<TSettings>();
|
||||||
|
|
||||||
|
public TraktSettingsBase()
|
||||||
|
{
|
||||||
|
BaseUrl = "https://api.trakt.tv";
|
||||||
|
SignIn = "startOAuth";
|
||||||
|
Rating = "0-100";
|
||||||
|
Genres = "";
|
||||||
|
Years = "";
|
||||||
|
Limit = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BaseUrl { get; set; }
|
||||||
|
|
||||||
|
[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(1, Label = "Rating", HelpText = "Filter series by rating range (0-100)")]
|
||||||
|
public string Rating { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(3, Label = "Genres", HelpText = "Filter series by Trakt Genre Slug (Comma Separated)")]
|
||||||
|
public string Genres { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(4, Label = "Years", HelpText = "Filter series by year or year range")]
|
||||||
|
public string Years { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(5, Label = "Limit", HelpText = "Limit the number of series to get")]
|
||||||
|
public int Limit { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(6, Label = "Additional Parameters", HelpText = "Additional Trakt API parameters", Advanced = true)]
|
||||||
|
public string TraktAdditionalParameters { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(99, Label = "Authenticate with Trakt", Type = FieldType.OAuth)]
|
||||||
|
public string SignIn { get; set; }
|
||||||
|
|
||||||
|
public NzbDroneValidationResult Validate()
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationResult(Validator.Validate((TSettings)this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Trakt.User
|
||||||
|
{
|
||||||
|
public class TraktUserImport : TraktImportBase<TraktUserSettings>
|
||||||
|
{
|
||||||
|
public TraktUserImport(IImportListRepository netImportRepository,
|
||||||
|
IHttpClient httpClient,
|
||||||
|
IImportListStatusService netImportStatusService,
|
||||||
|
IConfigService configService,
|
||||||
|
IParsingService parsingService,
|
||||||
|
Logger logger)
|
||||||
|
: base(netImportRepository, httpClient, netImportStatusService, configService, parsingService, logger)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name => "Trakt User";
|
||||||
|
|
||||||
|
public override IImportListRequestGenerator GetRequestGenerator()
|
||||||
|
{
|
||||||
|
return new TraktUserRequestGenerator()
|
||||||
|
{
|
||||||
|
Settings = Settings,
|
||||||
|
ClientId = ClientId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Trakt.User
|
||||||
|
{
|
||||||
|
public enum TraktUserListType
|
||||||
|
{
|
||||||
|
[EnumMember(Value = "User Watch List")]
|
||||||
|
UserWatchList = 0,
|
||||||
|
[EnumMember(Value = "User Watched List")]
|
||||||
|
UserWatchedList = 1,
|
||||||
|
[EnumMember(Value = "User Collection List")]
|
||||||
|
UserCollectionList = 2
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Trakt.User
|
||||||
|
{
|
||||||
|
public class TraktUserRequestGenerator : IImportListRequestGenerator
|
||||||
|
{
|
||||||
|
public TraktUserSettings Settings { get; set; }
|
||||||
|
|
||||||
|
public string ClientId { get; set; }
|
||||||
|
|
||||||
|
public TraktUserRequestGenerator()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual ImportListPageableRequestChain GetListItems()
|
||||||
|
{
|
||||||
|
var pageableRequests = new ImportListPageableRequestChain();
|
||||||
|
|
||||||
|
pageableRequests.Add(GetSeriesRequest());
|
||||||
|
|
||||||
|
return pageableRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<ImportListRequest> GetSeriesRequest()
|
||||||
|
{
|
||||||
|
var link = Settings.BaseUrl.Trim();
|
||||||
|
|
||||||
|
switch (Settings.TraktListType)
|
||||||
|
{
|
||||||
|
case (int)TraktUserListType.UserWatchList:
|
||||||
|
link += $"/users/{Settings.AuthUser.Trim()}/watchlist/shows?limit={Settings.Limit}";
|
||||||
|
break;
|
||||||
|
case (int)TraktUserListType.UserWatchedList:
|
||||||
|
link += $"/users/{Settings.AuthUser.Trim()}/watched/shows?limit={Settings.Limit}";
|
||||||
|
break;
|
||||||
|
case (int)TraktUserListType.UserCollectionList:
|
||||||
|
link += $"/users/{Settings.AuthUser.Trim()}/collection/shows?limit={Settings.Limit}";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = new ImportListRequest($"{link}", HttpAccept.Json);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ImportLists.Trakt.User
|
||||||
|
{
|
||||||
|
public class TraktUserSettingsValidator : TraktSettingsBaseValidator<TraktUserSettings>
|
||||||
|
{
|
||||||
|
public TraktUserSettingsValidator()
|
||||||
|
: base()
|
||||||
|
{
|
||||||
|
RuleFor(c => c.TraktListType).NotNull();
|
||||||
|
RuleFor(c => c.AuthUser).NotEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TraktUserSettings : TraktSettingsBase<TraktUserSettings>
|
||||||
|
{
|
||||||
|
protected override AbstractValidator<TraktUserSettings> Validator => new TraktUserSettingsValidator();
|
||||||
|
|
||||||
|
public TraktUserSettings()
|
||||||
|
{
|
||||||
|
TraktListType = (int)TraktUserListType.UserWatchList;
|
||||||
|
}
|
||||||
|
|
||||||
|
[FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(TraktUserListType), HelpText = "Type of list your seeking to import from")]
|
||||||
|
public int TraktListType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue