2015-01-09 01:43:51 +00:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
2015-05-07 15:43:52 +00:00
using System.Net ;
2015-01-09 01:43:51 +00:00
using NLog ;
2016-02-28 15:41:22 +00:00
using NzbDrone.Common.Cloud ;
2015-01-09 01:43:51 +00:00
using NzbDrone.Common.Extensions ;
using NzbDrone.Common.Http ;
2015-08-12 18:45:44 +00:00
using NzbDrone.Core.Exceptions ;
2015-01-09 01:43:51 +00:00
using NzbDrone.Core.MediaCover ;
using NzbDrone.Core.MetadataSource.SkyHook.Resource ;
using NzbDrone.Core.Tv ;
2017-04-28 22:05:35 +00:00
using Newtonsoft.Json.Linq ;
using NzbDrone.Core.Music ;
using Newtonsoft.Json ;
2015-01-09 01:43:51 +00:00
namespace NzbDrone.Core.MetadataSource.SkyHook
{
2017-04-30 21:54:01 +00:00
public class SkyHookProxy : IProvideSeriesInfo , IProvideArtistInfo , ISearchForNewSeries
2015-01-09 01:43:51 +00:00
{
private readonly IHttpClient _httpClient ;
2015-05-07 15:43:52 +00:00
private readonly Logger _logger ;
2015-01-09 01:43:51 +00:00
2016-02-28 15:41:22 +00:00
private readonly IHttpRequestBuilderFactory _requestBuilder ;
2017-05-05 17:57:58 +00:00
private readonly IHttpRequestBuilderFactory _internalRequestBuilder ;
2016-02-28 15:41:22 +00:00
2017-03-30 03:49:38 +00:00
public SkyHookProxy ( IHttpClient httpClient , ILidarrCloudRequestBuilder requestBuilder , Logger logger )
2015-01-09 01:43:51 +00:00
{
_httpClient = httpClient ;
2017-04-28 22:05:35 +00:00
_requestBuilder = requestBuilder . Search ;
2017-05-05 17:57:58 +00:00
_internalRequestBuilder = requestBuilder . InternalSearch ;
2015-05-07 15:43:52 +00:00
_logger = logger ;
2015-01-09 01:43:51 +00:00
}
2017-05-07 16:58:24 +00:00
2015-01-09 01:43:51 +00:00
public Tuple < Series , List < Episode > > GetSeriesInfo ( int tvdbSeriesId )
{
2017-04-28 22:05:35 +00:00
Console . WriteLine ( "[GetSeriesInfo] id:" + tvdbSeriesId ) ;
2016-02-28 15:41:22 +00:00
var httpRequest = _requestBuilder . Create ( )
. SetSegment ( "route" , "shows" )
. Resource ( tvdbSeriesId . ToString ( ) )
. Build ( ) ;
2015-08-12 18:45:44 +00:00
httpRequest . AllowAutoRedirect = true ;
httpRequest . SuppressHttpError = true ;
2015-05-07 15:43:52 +00:00
2015-01-09 01:43:51 +00:00
var httpResponse = _httpClient . Get < ShowResource > ( httpRequest ) ;
2015-08-12 18:45:44 +00:00
if ( httpResponse . HasHttpError )
{
if ( httpResponse . StatusCode = = HttpStatusCode . NotFound )
{
throw new SeriesNotFoundException ( tvdbSeriesId ) ;
}
else
{
throw new HttpException ( httpRequest , httpResponse ) ;
}
}
2015-01-09 01:43:51 +00:00
var episodes = httpResponse . Resource . Episodes . Select ( MapEpisode ) ;
var series = MapSeries ( httpResponse . Resource ) ;
return new Tuple < Series , List < Episode > > ( series , episodes . ToList ( ) ) ;
}
2015-05-07 15:43:52 +00:00
public List < Series > SearchForNewSeries ( string title )
{
try
{
var lowerTitle = title . ToLowerInvariant ( ) ;
2017-04-28 22:05:35 +00:00
Console . WriteLine ( "Searching for " + lowerTitle ) ;
//if (lowerTitle.StartsWith("tvdb:") || lowerTitle.StartsWith("tvdbid:"))
//{
// var slug = lowerTitle.Split(':')[1].Trim();
// int tvdbId;
// if (slug.IsNullOrWhiteSpace() || slug.Any(char.IsWhiteSpace) || !int.TryParse(slug, out tvdbId) || tvdbId <= 0)
// {
// return new List<Series>();
// }
// try
// {
// return new List<Series> { GetSeriesInfo(tvdbId).Item1 };
// }
// catch (SeriesNotFoundException)
// {
// return new List<Series>();
// }
//}
// Majora: Temporarily, use iTunes to test.
var httpRequest = _requestBuilder . Create ( )
. AddQueryParam ( "entity" , "album" )
. AddQueryParam ( "term" , title . ToLower ( ) . Trim ( ) )
. Build ( ) ;
2015-05-07 15:43:52 +00:00
2017-04-28 22:05:35 +00:00
Console . WriteLine ( "httpRequest: " , httpRequest ) ;
2017-04-30 13:54:37 +00:00
var httpResponse = _httpClient . Get < List < ShowResource > > ( httpRequest ) ;
2017-04-28 22:05:35 +00:00
//Console.WriteLine("Response: ", httpResponse.GetType());
//_logger.Info("Response: ", httpResponse.Resource.ResultCount);
//_logger.Info("HTTP Response: ", httpResponse.Resource.ResultCount);
var tempList = new List < Series > ( ) ;
var tempSeries = new Series ( ) ;
tempSeries . Title = "AFI" ;
tempList . Add ( tempSeries ) ;
return tempList ;
2017-04-30 13:54:37 +00:00
return httpResponse . Resource . SelectList ( MapSeries ) ;
2017-04-28 22:05:35 +00:00
}
catch ( HttpException )
{
throw new SkyHookException ( "Search for '{0}' failed. Unable to communicate with SkyHook." , title ) ;
}
catch ( Exception ex )
{
_logger . Warn ( ex , ex . Message ) ;
throw new SkyHookException ( "Search for '{0}' failed. Invalid response received from SkyHook." , title ) ;
}
}
2017-05-05 17:57:58 +00:00
//public Artist GetArtistInfo(int itunesId)
//{
// Console.WriteLine("[GetArtistInfo] id:" + itunesId);
// //https://itunes.apple.com/lookup?id=909253
// //var httpRequest = _requestBuilder.Create()
// // .SetSegment("route", "lookup")
// // .AddQueryParam("id", itunesId.ToString())
// // .Build();
// // TODO: Add special header, add Overview to Artist model
// var httpRequest = _requestBuilder.Create()
// .SetSegment("route", "viewArtist")
// .AddQueryParam("id", itunesId.ToString())
// .Build();
// httpRequest.Headers.Add("X-Apple-Store-Front", "143459-2,32 t:music3");
// httpRequest.AllowAutoRedirect = true;
// httpRequest.SuppressHttpError = true;
// var httpResponse = _httpClient.Get<ArtistResource>(httpRequest);
// if (httpResponse.HasHttpError)
// {
// if (httpResponse.StatusCode == HttpStatusCode.NotFound)
// {
// throw new ArtistNotFoundException(itunesId);
// }
// else
// {
// throw new HttpException(httpRequest, httpResponse);
// }
// }
// Console.WriteLine("GetArtistInfo, GetArtistInfo");
// return MapArtists(httpResponse.Resource)[0];
//}
2017-04-30 21:54:01 +00:00
public Tuple < Artist , List < Track > > GetArtistInfo ( int itunesId )
{
2017-05-07 16:58:24 +00:00
// TODO: [GetArtistInfo]: This needs to return a set of tracks from iTunes.
// This call is expected to return information about an artist and the tracks that make up said artist.
// To do this, we need 2-3 API calls. 1st is to gather information about the artist and the albums the artist has. This is https://itunes.apple.com/search?entity=album&id=itunesId
// Next call is to populate the overview field and calls the internal API
// Finally, we need to, for each album, get all tracks, which means calling this N times: https://itunes.apple.com/search?entity=musicTrack&term=artistName (id will not work)
2017-05-05 18:35:28 +00:00
_logger . Debug ( "Getting Artist with iTunesID of {0}" , itunesId ) ;
var httpRequest1 = _requestBuilder . Create ( )
. SetSegment ( "route" , "lookup" )
. AddQueryParam ( "id" , itunesId . ToString ( ) )
. Build ( ) ;
var httpRequest2 = _internalRequestBuilder . Create ( )
2017-05-05 17:57:58 +00:00
. SetSegment ( "route" , "viewArtist" )
2017-04-30 21:54:01 +00:00
. AddQueryParam ( "id" , itunesId . ToString ( ) )
. Build ( ) ;
2017-05-05 18:35:28 +00:00
httpRequest2 . Headers . Add ( "X-Apple-Store-Front" , "143459-2,32 t:music3" ) ;
httpRequest2 . Headers . ContentType = "application/json" ;
2017-04-30 21:54:01 +00:00
2017-05-05 18:35:28 +00:00
httpRequest1 . AllowAutoRedirect = true ;
httpRequest1 . SuppressHttpError = true ;
2017-04-30 21:54:01 +00:00
2017-05-05 18:35:28 +00:00
var httpResponse = _httpClient . Get < ArtistResource > ( httpRequest1 ) ;
2017-05-05 19:33:46 +00:00
2017-04-30 21:54:01 +00:00
if ( httpResponse . HasHttpError )
{
if ( httpResponse . StatusCode = = HttpStatusCode . NotFound )
{
throw new ArtistNotFoundException ( itunesId ) ;
}
else
{
2017-05-05 18:35:28 +00:00
throw new HttpException ( httpRequest1 , httpResponse ) ;
}
}
List < Artist > artists = MapArtists ( httpResponse . Resource ) ;
2017-05-05 19:33:46 +00:00
List < Artist > newArtists = new List < Artist > ( artists . Count ) ;
int count = 0 ;
foreach ( var artist in artists )
2017-05-05 18:35:28 +00:00
{
2017-05-05 19:33:46 +00:00
newArtists . Add ( AddOverview ( artist ) ) ;
count + + ;
2017-04-30 21:54:01 +00:00
}
// I don't know how we are getting tracks from iTunes yet.
2017-05-05 19:33:46 +00:00
return new Tuple < Artist , List < Track > > ( newArtists [ 0 ] , new List < Track > ( ) ) ;
2017-04-30 21:54:01 +00:00
}
2017-05-05 19:33:46 +00:00
2017-04-28 22:05:35 +00:00
public List < Artist > SearchForNewArtist ( string title )
{
try
{
var lowerTitle = title . ToLowerInvariant ( ) ;
Console . WriteLine ( "Searching for " + lowerTitle ) ;
2017-04-30 13:54:37 +00:00
if ( lowerTitle . StartsWith ( "itunes:" ) | | lowerTitle . StartsWith ( "itunesid:" ) )
{
var slug = lowerTitle . Split ( ':' ) [ 1 ] . Trim ( ) ;
int itunesId ;
if ( slug . IsNullOrWhiteSpace ( ) | | slug . Any ( char . IsWhiteSpace ) | | ! int . TryParse ( slug , out itunesId ) | | itunesId < = 0 )
{
return new List < Artist > ( ) ;
}
2017-04-30 21:54:01 +00:00
try
{
return new List < Artist > { GetArtistInfo ( itunesId ) . Item1 } ;
}
catch ( ArtistNotFoundException )
{
return new List < Artist > ( ) ;
}
2017-04-30 13:54:37 +00:00
}
2015-05-07 15:43:52 +00:00
2016-02-28 15:41:22 +00:00
var httpRequest = _requestBuilder . Create ( )
2017-04-30 21:54:01 +00:00
. SetSegment ( "route" , "search" )
2017-04-28 22:05:35 +00:00
. AddQueryParam ( "entity" , "album" )
. AddQueryParam ( "term" , title . ToLower ( ) . Trim ( ) )
. Build ( ) ;
2015-05-07 15:43:52 +00:00
2017-04-28 22:05:35 +00:00
var httpResponse = _httpClient . Get < ArtistResource > ( httpRequest ) ;
2017-05-05 19:33:46 +00:00
List < Artist > artists = MapArtists ( httpResponse . Resource ) ;
List < Artist > newArtists = new List < Artist > ( artists . Count ) ;
int count = 0 ;
foreach ( var artist in artists )
{
newArtists . Add ( AddOverview ( artist ) ) ;
count + + ;
}
return newArtists ;
2015-05-07 15:43:52 +00:00
}
2015-08-12 18:45:44 +00:00
catch ( HttpException )
2015-05-07 15:43:52 +00:00
{
throw new SkyHookException ( "Search for '{0}' failed. Unable to communicate with SkyHook." , title ) ;
}
catch ( Exception ex )
{
2016-02-11 21:13:42 +00:00
_logger . Warn ( ex , ex . Message ) ;
2015-05-07 15:43:52 +00:00
throw new SkyHookException ( "Search for '{0}' failed. Invalid response received from SkyHook." , title ) ;
}
}
2017-05-05 19:33:46 +00:00
private Artist AddOverview ( Artist artist )
{
var httpRequest = _internalRequestBuilder . Create ( )
. SetSegment ( "route" , "viewArtist" )
. AddQueryParam ( "id" , artist . ItunesId . ToString ( ) )
. Build ( ) ;
httpRequest . Headers . Add ( "X-Apple-Store-Front" , "143459-2,32 t:music3" ) ;
httpRequest . Headers . ContentType = "application/json" ;
var httpResponse = _httpClient . Get < ArtistResource > ( httpRequest ) ;
if ( ! httpResponse . HasHttpError )
{
artist . Overview = httpResponse . Resource . StorePlatformData . Artist . Results [ artist . ItunesId ] . artistBio ;
}
return artist ;
}
2017-05-05 17:57:58 +00:00
private Artist MapArtistInfo ( ArtistInfoResource resource )
{
// This expects ArtistInfoResource, thus just need to populate one artist
Artist artist = new Artist ( ) ;
artist . Overview = resource . artistBio ;
artist . ArtistName = resource . name ;
foreach ( var genre in resource . genreNames )
{
artist . Genres . Add ( genre ) ;
}
return artist ;
}
2017-04-30 21:54:01 +00:00
private List < Artist > MapArtists ( ArtistResource resource )
{
Album tempAlbum ;
List < Artist > artists = new List < Artist > ( ) ;
foreach ( var album in resource . Results )
{
int index = artists . FindIndex ( a = > a . ItunesId = = album . ArtistId ) ;
tempAlbum = MapAlbum ( album ) ;
if ( index > = 0 )
{
artists [ index ] . Albums . Add ( tempAlbum ) ;
}
else
{
Artist tempArtist = new Artist ( ) ;
tempArtist . ItunesId = album . ArtistId ;
tempArtist . ArtistName = album . ArtistName ;
tempArtist . Genres . Add ( album . PrimaryGenreName ) ;
tempArtist . Albums . Add ( tempAlbum ) ;
artists . Add ( tempArtist ) ;
}
}
return artists ;
}
2017-04-30 16:34:53 +00:00
private Album MapAlbum ( AlbumResource albumQuery )
2017-04-28 22:05:35 +00:00
{
2017-04-30 16:34:53 +00:00
Album album = new Album ( ) ;
album . AlbumId = albumQuery . CollectionId ;
album . Title = albumQuery . CollectionName ;
album . Year = albumQuery . ReleaseDate . Year ;
album . ArtworkUrl = albumQuery . ArtworkUrl100 ;
album . Explicitness = albumQuery . CollectionExplicitness ;
return album ;
2017-04-28 22:05:35 +00:00
}
2015-01-09 01:43:51 +00:00
private static Series MapSeries ( ShowResource show )
{
var series = new Series ( ) ;
series . TvdbId = show . TvdbId ;
if ( show . TvRageId . HasValue )
{
series . TvRageId = show . TvRageId . Value ;
}
2015-10-09 20:22:28 +00:00
if ( show . TvMazeId . HasValue )
{
series . TvMazeId = show . TvMazeId . Value ;
}
2015-01-09 01:43:51 +00:00
series . ImdbId = show . ImdbId ;
series . Title = show . Title ;
series . CleanTitle = Parser . Parser . CleanSeriesTitle ( show . Title ) ;
series . SortTitle = SeriesTitleNormalizer . Normalize ( show . Title , show . TvdbId ) ;
if ( show . FirstAired ! = null )
{
series . FirstAired = DateTime . Parse ( show . FirstAired ) . ToUniversalTime ( ) ;
series . Year = series . FirstAired . Value . Year ;
}
series . Overview = show . Overview ;
if ( show . Runtime ! = null )
{
series . Runtime = show . Runtime . Value ;
}
series . Network = show . Network ;
if ( show . TimeOfDay ! = null )
{
series . AirTime = string . Format ( "{0:00}:{1:00}" , show . TimeOfDay . Hours , show . TimeOfDay . Minutes ) ;
}
series . TitleSlug = show . Slug ;
series . Status = MapSeriesStatus ( show . Status ) ;
series . Ratings = MapRatings ( show . Rating ) ;
series . Genres = show . Genres ;
if ( show . ContentRating . IsNotNullOrWhiteSpace ( ) )
{
series . Certification = show . ContentRating . ToUpper ( ) ;
}
series . Actors = show . Actors . Select ( MapActors ) . ToList ( ) ;
series . Seasons = show . Seasons . Select ( MapSeason ) . ToList ( ) ;
series . Images = show . Images . Select ( MapImage ) . ToList ( ) ;
2017-02-28 05:37:33 +00:00
series . Monitored = true ;
2015-01-09 01:43:51 +00:00
return series ;
}
private static Actor MapActors ( ActorResource arg )
{
var newActor = new Actor
{
Name = arg . Name ,
2015-05-07 15:43:52 +00:00
Character = arg . Character
2015-01-09 01:43:51 +00:00
} ;
if ( arg . Image ! = null )
{
newActor . Images = new List < MediaCover . MediaCover >
{
new MediaCover . MediaCover ( MediaCoverTypes . Headshot , arg . Image )
} ;
}
return newActor ;
}
private static Episode MapEpisode ( EpisodeResource oracleEpisode )
{
var episode = new Episode ( ) ;
episode . Overview = oracleEpisode . Overview ;
episode . SeasonNumber = oracleEpisode . SeasonNumber ;
episode . EpisodeNumber = oracleEpisode . EpisodeNumber ;
episode . AbsoluteEpisodeNumber = oracleEpisode . AbsoluteEpisodeNumber ;
episode . Title = oracleEpisode . Title ;
episode . AirDate = oracleEpisode . AirDate ;
episode . AirDateUtc = oracleEpisode . AirDateUtc ;
2015-01-09 18:03:44 +00:00
episode . Ratings = MapRatings ( oracleEpisode . Rating ) ;
2015-01-09 01:43:51 +00:00
//Don't include series fanart images as episode screenshot
if ( oracleEpisode . Image ! = null )
{
episode . Images . Add ( new MediaCover . MediaCover ( MediaCoverTypes . Screenshot , oracleEpisode . Image ) ) ;
}
return episode ;
}
private static Season MapSeason ( SeasonResource seasonResource )
{
return new Season
{
SeasonNumber = seasonResource . SeasonNumber ,
2017-02-28 05:37:33 +00:00
Images = seasonResource . Images . Select ( MapImage ) . ToList ( ) ,
Monitored = seasonResource . SeasonNumber > 0
2015-01-09 01:43:51 +00:00
} ;
}
private static SeriesStatusType MapSeriesStatus ( string status )
{
if ( status . Equals ( "ended" , StringComparison . InvariantCultureIgnoreCase ) )
{
return SeriesStatusType . Ended ;
}
return SeriesStatusType . Continuing ;
}
private static Ratings MapRatings ( RatingResource rating )
{
if ( rating = = null )
{
return new Ratings ( ) ;
}
return new Ratings
{
Votes = rating . Count ,
Value = rating . Value
} ;
}
private static MediaCover . MediaCover MapImage ( ImageResource arg )
{
return new MediaCover . MediaCover
{
Url = arg . Url ,
CoverType = MapCoverType ( arg . CoverType )
} ;
}
private static MediaCoverTypes MapCoverType ( string coverType )
{
switch ( coverType . ToLower ( ) )
{
case "poster" :
return MediaCoverTypes . Poster ;
case "banner" :
return MediaCoverTypes . Banner ;
case "fanart" :
return MediaCoverTypes . Fanart ;
default :
return MediaCoverTypes . Unknown ;
}
}
}
}