@ -58,6 +58,7 @@ using Ombi.Store.Repository;
using Ombi.UI.Helpers ;
using Ombi.UI.Models ;
using TMDbLib.Objects.General ;
using TraktApiSharp.Objects.Get.Shows ;
using Action = Ombi . Helpers . Analytics . Action ;
using EpisodesModel = Ombi . Store . EpisodesModel ;
using ISecurityExtensions = Ombi . Core . ISecurityExtensions ;
@ -76,7 +77,7 @@ namespace Ombi.UI.Modules
ISettingsService < PlexSettings > plexService , ISettingsService < AuthenticationSettings > auth ,
IRepository < UsersToNotify > u , ISettingsService < EmailNotificationSettings > email ,
IIssueService issue , IAnalytics a , IRepository < RequestLimit > rl , ITransientFaultQueue tfQueue , IRepository < PlexContent > content ,
ISecurityExtensions security , IMovieSender movieSender )
ISecurityExtensions security , IMovieSender movieSender , IRadarrCacher radarrCacher , ITraktApi traktApi , ISettingsService < CustomizationSettings > cus )
: base ( "search" , prSettings , security )
{
Auth = auth ;
@ -108,6 +109,9 @@ namespace Ombi.UI.Modules
PlexContentRepository = content ;
MovieSender = movieSender ;
WatcherCacher = watcherCacher ;
RadarrCacher = radarrCacher ;
TraktApi = traktApi ;
CustomizationSettings = cus ;
Get [ "SearchIndex" , "/" , true ] = async ( x , ct ) = > await RequestLoad ( ) ;
@ -119,6 +123,13 @@ namespace Ombi.UI.Modules
Get [ "movie/upcoming" , true ] = async ( x , ct ) = > await UpcomingMovies ( ) ;
Get [ "movie/playing" , true ] = async ( x , ct ) = > await CurrentlyPlayingMovies ( ) ;
Get [ "tv/popular" , true ] = async ( x , ct ) = > await ProcessShows ( ShowSearchType . Popular ) ;
Get [ "tv/trending" , true ] = async ( x , ct ) = > await ProcessShows ( ShowSearchType . Trending ) ;
Get [ "tv/mostwatched" , true ] = async ( x , ct ) = > await ProcessShows ( ShowSearchType . MostWatched ) ;
Get [ "tv/anticipated" , true ] = async ( x , ct ) = > await ProcessShows ( ShowSearchType . Anticipated ) ;
Get [ "tv/poster/{id}" ] = p = > GetTvPoster ( ( int ) p . id ) ;
Post [ "request/movie" , true ] = async ( x , ct ) = > await RequestMovie ( ( int ) Request . Form . movieId ) ;
Post [ "request/tv" , true ] =
async ( x , ct ) = > await RequestTvShow ( ( int ) Request . Form . tvId , ( string ) Request . Form . seasons ) ;
@ -128,6 +139,7 @@ namespace Ombi.UI.Modules
Get [ "/seasons" ] = x = > GetSeasons ( ) ;
Get [ "/episodes" , true ] = async ( x , ct ) = > await GetEpisodes ( ) ;
}
private ITraktApi TraktApi { get ; }
private IWatcherCacher WatcherCacher { get ; }
private IMovieSender MovieSender { get ; }
private IRepository < PlexContent > PlexContentRepository { get ; }
@ -157,14 +169,23 @@ namespace Ombi.UI.Modules
private IAnalytics Analytics { get ; }
private ITransientFaultQueue FaultQueue { get ; }
private IRepository < RequestLimit > RequestLimitRepo { get ; }
private IRadarrCacher RadarrCacher { get ; }
private ISettingsService < CustomizationSettings > CustomizationSettings { get ; }
private static Logger Log = LogManager . GetCurrentClassLogger ( ) ;
private async Task < Negotiator > RequestLoad ( )
{
var settings = await PrService . GetSettingsAsync ( ) ;
var custom = await CustomizationSettings . GetSettingsAsync ( ) ;
var searchViewModel = new SearchLoadViewModel
{
Settings = settings ,
CustomizationSettings = custom
} ;
return View [ "Search/Index" , settings ] ;
return View [ "Search/Index" , se archViewModel ] ;
}
private async Task < Response > UpcomingMovies ( )
@ -188,6 +209,17 @@ namespace Ombi.UI.Modules
return await ProcessMovies ( MovieSearchType . Search , searchTerm ) ;
}
private Response GetTvPoster ( int theTvDbId )
{
var result = TvApi . ShowLookupByTheTvDbId ( theTvDbId ) ;
var banner = result . image ? . medium ;
if ( ! string . IsNullOrEmpty ( banner ) )
{
banner = banner . Replace ( "http" , "https" ) ; // Always use the Https banners
}
return banner ;
}
private async Task < Response > ProcessMovies ( MovieSearchType searchType , string searchTerm )
{
List < MovieResult > apiMovies ;
@ -236,22 +268,13 @@ namespace Ombi.UI.Modules
var cpCached = CpCacher . QueuedIds ( ) ;
var watcherCached = WatcherCacher . QueuedIds ( ) ;
var radarrCached = RadarrCacher . QueuedIds ( ) ;
var content = PlexContentRepository . GetAll ( ) ;
var plexMovies = Checker . GetPlexMovies ( content ) ;
var viewMovies = new List < SearchMovieViewModel > ( ) ;
var counter = 0 ;
foreach ( var movie in apiMovies )
{
var imdbId = string . Empty ;
if ( counter < = 5 ) // Let's only do it for the first 5 items
{
var movieInfoTask = await MovieApi . GetMovieInformation ( movie . Id ) . ConfigureAwait ( false ) ;
// TODO needs to be careful about this, it's adding extra time to search...
// https://www.themoviedb.org/talk/5807f4cdc3a36812160041f2
imdbId = movieInfoTask ? . ImdbId ;
counter + + ;
}
var viewMovie = new SearchMovieViewModel
{
Adult = movie . Adult ,
@ -269,6 +292,28 @@ namespace Ombi.UI.Modules
VoteAverage = movie . VoteAverage ,
VoteCount = movie . VoteCount
} ;
var imdbId = string . Empty ;
if ( counter < = 5 ) // Let's only do it for the first 5 items
{
var movieInfo = MovieApi . GetMovieInformationWithVideos ( movie . Id ) ;
// TODO needs to be careful about this, it's adding extra time to search...
// https://www.themoviedb.org/talk/5807f4cdc3a36812160041f2
viewMovie . ImdbId = movieInfo ? . imdb_id ;
viewMovie . Homepage = movieInfo ? . homepage ;
var videoId = movieInfo ? . video ? ? false
? movieInfo ? . videos ? . results ? . FirstOrDefault ( ) ? . key
: string . Empty ;
viewMovie . Trailer = string . IsNullOrEmpty ( videoId )
? string . Empty
: $"https://www.youtube.com/watch?v={videoId}" ;
counter + + ;
}
var canSee = CanUserSeeThisRequest ( viewMovie . Id , Security . HasPermissions ( User , Permissions . UsersCanViewOnlyOwnRequests ) , dbMovies ) ;
var plexMovie = Checker . GetMovie ( plexMovies . ToArray ( ) , movie . Title , movie . ReleaseDate ? . Year . ToString ( ) ,
imdbId ) ;
@ -287,13 +332,19 @@ namespace Ombi.UI.Modules
}
else if ( cpCached . Contains ( movie . Id ) & & canSee ) // compare to the couchpotato db
{
viewMovie . Approved = true ;
viewMovie . Requested = true ;
}
else if ( watcherCached . Contains ( imdbId ) & & canSee ) // compare to the watcher db
{
viewMovie . Approved = true ;
viewMovie . Requested = true ;
}
else if ( radarrCached . Contains ( movie . Id ) & & canSee )
{
viewMovie . Approved = true ;
viewMovie . Requested = true ;
}
viewMovies . Add ( viewMovie ) ;
}
@ -312,6 +363,186 @@ namespace Ombi.UI.Modules
return true ;
}
private async Task < Response > ProcessShows ( ShowSearchType type )
{
var shows = new List < SearchTvShowViewModel > ( ) ;
var prSettings = await PrService . GetSettingsAsync ( ) ;
switch ( type )
{
case ShowSearchType . Popular :
Analytics . TrackEventAsync ( Category . Search , Action . TvShow , "Popular" , Username , CookieHelper . GetAnalyticClientId ( Cookies ) ) ;
var popularShows = await TraktApi . GetPopularShows ( ) ;
foreach ( var popularShow in popularShows )
{
var theTvDbId = int . Parse ( popularShow . Ids . Tvdb . ToString ( ) ) ;
var model = new SearchTvShowViewModel
{
FirstAired = popularShow . FirstAired ? . ToString ( "yyyy-MM-ddTHH:mm:ss" ) ,
Id = theTvDbId ,
ImdbId = popularShow . Ids . Imdb ,
Network = popularShow . Network ,
Overview = popularShow . Overview . RemoveHtml ( ) ,
Rating = popularShow . Rating . ToString ( ) ,
Runtime = popularShow . Runtime . ToString ( ) ,
SeriesName = popularShow . Title ,
Status = popularShow . Status . DisplayName ,
DisableTvRequestsByEpisode = prSettings . DisableTvRequestsByEpisode ,
DisableTvRequestsBySeason = prSettings . DisableTvRequestsBySeason ,
EnableTvRequestsForOnlySeries = ( prSettings . DisableTvRequestsByEpisode & & prSettings . DisableTvRequestsBySeason ) ,
Trailer = popularShow . Trailer ,
Homepage = popularShow . Homepage
} ;
shows . Add ( model ) ;
}
shows = await MapToTvModel ( shows , prSettings ) ;
break ;
case ShowSearchType . Anticipated :
Analytics . TrackEventAsync ( Category . Search , Action . TvShow , "Anticipated" , Username , CookieHelper . GetAnalyticClientId ( Cookies ) ) ;
var anticipated = await TraktApi . GetAnticipatedShows ( ) ;
foreach ( var anticipatedShow in anticipated )
{
var show = anticipatedShow . Show ;
var theTvDbId = int . Parse ( show . Ids . Tvdb . ToString ( ) ) ;
var model = new SearchTvShowViewModel
{
FirstAired = show . FirstAired ? . ToString ( "yyyy-MM-ddTHH:mm:ss" ) ,
Id = theTvDbId ,
ImdbId = show . Ids . Imdb ,
Network = show . Network ? ? string . Empty ,
Overview = show . Overview ? . RemoveHtml ( ) ? ? string . Empty ,
Rating = show . Rating . ToString ( ) ,
Runtime = show . Runtime . ToString ( ) ,
SeriesName = show . Title ,
Status = show . Status ? . DisplayName ? ? string . Empty ,
DisableTvRequestsByEpisode = prSettings . DisableTvRequestsByEpisode ,
DisableTvRequestsBySeason = prSettings . DisableTvRequestsBySeason ,
EnableTvRequestsForOnlySeries = ( prSettings . DisableTvRequestsByEpisode & & prSettings . DisableTvRequestsBySeason ) ,
Trailer = show . Trailer ,
Homepage = show . Homepage
} ;
shows . Add ( model ) ;
}
shows = await MapToTvModel ( shows , prSettings ) ;
break ;
case ShowSearchType . MostWatched :
Analytics . TrackEventAsync ( Category . Search , Action . TvShow , "MostWatched" , Username , CookieHelper . GetAnalyticClientId ( Cookies ) ) ;
var mostWatched = await TraktApi . GetMostWatchesShows ( ) ;
foreach ( var watched in mostWatched )
{
var show = watched . Show ;
var theTvDbId = int . Parse ( show . Ids . Tvdb . ToString ( ) ) ;
var model = new SearchTvShowViewModel
{
FirstAired = show . FirstAired ? . ToString ( "yyyy-MM-ddTHH:mm:ss" ) ,
Id = theTvDbId ,
ImdbId = show . Ids . Imdb ,
Network = show . Network ,
Overview = show . Overview . RemoveHtml ( ) ,
Rating = show . Rating . ToString ( ) ,
Runtime = show . Runtime . ToString ( ) ,
SeriesName = show . Title ,
Status = show . Status . DisplayName ,
DisableTvRequestsByEpisode = prSettings . DisableTvRequestsByEpisode ,
DisableTvRequestsBySeason = prSettings . DisableTvRequestsBySeason ,
EnableTvRequestsForOnlySeries = ( prSettings . DisableTvRequestsByEpisode & & prSettings . DisableTvRequestsBySeason ) ,
Trailer = show . Trailer ,
Homepage = show . Homepage
} ;
shows . Add ( model ) ;
}
shows = await MapToTvModel ( shows , prSettings ) ;
break ;
case ShowSearchType . Trending :
Analytics . TrackEventAsync ( Category . Search , Action . TvShow , "Trending" , Username , CookieHelper . GetAnalyticClientId ( Cookies ) ) ;
var trending = await TraktApi . GetTrendingShows ( ) ;
foreach ( var watched in trending )
{
var show = watched . Show ;
var theTvDbId = int . Parse ( show . Ids . Tvdb . ToString ( ) ) ;
var model = new SearchTvShowViewModel
{
FirstAired = show . FirstAired ? . ToString ( "yyyy-MM-ddTHH:mm:ss" ) ,
Id = theTvDbId ,
ImdbId = show . Ids . Imdb ,
Network = show . Network ,
Overview = show . Overview . RemoveHtml ( ) ,
Rating = show . Rating . ToString ( ) ,
Runtime = show . Runtime . ToString ( ) ,
SeriesName = show . Title ,
Status = show . Status . DisplayName ,
DisableTvRequestsByEpisode = prSettings . DisableTvRequestsByEpisode ,
DisableTvRequestsBySeason = prSettings . DisableTvRequestsBySeason ,
EnableTvRequestsForOnlySeries = ( prSettings . DisableTvRequestsByEpisode & & prSettings . DisableTvRequestsBySeason ) ,
Trailer = show . Trailer ,
Homepage = show . Homepage
} ;
shows . Add ( model ) ;
}
shows = await MapToTvModel ( shows , prSettings ) ;
break ;
default :
throw new ArgumentOutOfRangeException ( nameof ( type ) , type , null ) ;
}
return Response . AsJson ( shows ) ;
}
private async Task < List < SearchTvShowViewModel > > MapToTvModel ( List < SearchTvShowViewModel > shows , PlexRequestSettings prSettings )
{
var plexSettings = await PlexService . GetSettingsAsync ( ) ;
var providerId = string . Empty ;
// Get the requests
var allResults = await RequestService . GetAllAsync ( ) ;
allResults = allResults . Where ( x = > x . Type = = RequestType . TvShow ) ;
var distinctResults = allResults . DistinctBy ( x = > x . ProviderId ) ;
var dbTv = distinctResults . ToDictionary ( x = > x . ProviderId ) ;
// Check the external applications
var sonarrCached = SonarrCacher . QueuedIds ( ) . ToList ( ) ;
var sickRageCache = SickRageCacher . QueuedIds ( ) ; // consider just merging sonarr/sickrage arrays
var content = PlexContentRepository . GetAll ( ) ;
var plexTvShows = Checker . GetPlexTvShows ( content ) . ToList ( ) ;
foreach ( var show in shows )
{
if ( plexSettings . AdvancedSearch )
{
providerId = show . Id . ToString ( ) ;
}
var plexShow = Checker . GetTvShow ( plexTvShows . ToArray ( ) , show . SeriesName , show . FirstAired ? . Substring ( 0 , 4 ) ,
providerId ) ;
if ( plexShow ! = null )
{
show . Available = true ;
show . PlexUrl = plexShow . Url ;
}
else
{
if ( dbTv . ContainsKey ( show . Id ) )
{
var dbt = dbTv [ show . Id ] ;
show . Requested = true ;
show . Episodes = dbt . Episodes . ToList ( ) ;
show . Approved = dbt . Approved ;
}
if ( sonarrCached . Select ( x = > x . TvdbId ) . Contains ( show . Id ) | | sickRageCache . Contains ( show . Id ) )
// compare to the sonarr/sickrage db
{
show . Requested = true ;
}
}
}
return shows ;
}
private async Task < Response > SearchTvShow ( string searchTerm )
{
@ -345,6 +576,10 @@ namespace Ombi.UI.Modules
var viewTv = new List < SearchTvShowViewModel > ( ) ;
foreach ( var t in apiTv )
{
if ( ! ( t . show . externals ? . thetvdb . HasValue ) ? ? false )
{
continue ;
}
var banner = t . show . image ? . medium ;
if ( ! string . IsNullOrEmpty ( banner ) )
{
@ -574,7 +809,17 @@ namespace Ombi.UI.Modules
if ( result . Result )
{
return await AddRequest ( model , settings ,
$"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}" ) ;
$"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}" ) ;
}
if ( result . Error )
{
return
Response . AsJson ( new JsonResponseModel
{
Message = "Could not add movie, please contract your administrator" ,
Result = false
} ) ;
}
if ( ! result . MovieSendingEnabled )
{
@ -679,11 +924,13 @@ namespace Ombi.UI.Modules
DateTime . TryParse ( showInfo . premiered , out firstAir ) ;
string fullShowName = $"{showInfo.name} ({firstAir.Year})" ;
// For some reason the poster path is always http
var posterPath = showInfo . image ? . medium . Replace ( "http:" , "https:" ) ;
var model = new RequestedModel
{
Type = RequestType . TvShow ,
Overview = showInfo . summary . RemoveHtml ( ) ,
PosterPath = showInfo. image ? . medium ,
PosterPath = posterPath ,
Title = showInfo . name ,
ReleaseDate = firstAir ,
Status = showInfo . status ,
@ -762,6 +1009,11 @@ namespace Ombi.UI.Modules
existingRequest . Episodes . AddRange ( newEpisodes ? ? Enumerable . Empty < EpisodesModel > ( ) ) ;
// It's technically a new request now, so set the status to not approved.
var autoApprove = ShouldAutoApprove ( RequestType . TvShow ) ;
if ( autoApprove )
{
return await SendTv ( model , sonarrSettings , existingRequest , fullShowName , settings ) ;
}
existingRequest . Approved = false ;
return await AddUserToRequest ( existingRequest , settings , fullShowName , true ) ;
@ -888,54 +1140,7 @@ namespace Ombi.UI.Modules
{
if ( ShouldAutoApprove ( RequestType . TvShow ) )
{
model . Approved = true ;
var s = await sonarrSettings ;
var sender = new TvSenderOld ( SonarrApi , SickrageApi ) ; // TODO put back
if ( s . Enabled )
{
var result = await sender . SendToSonarr ( s , model ) ;
if ( ! string . IsNullOrEmpty ( result ? . title ) )
{
if ( existingRequest ! = null )
{
return await UpdateRequest ( model , settings ,
$"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" ) ;
}
return
await
AddRequest ( model , settings ,
$"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" ) ;
}
Log . Debug ( "Error with sending to sonarr." ) ;
return
Response . AsJson ( ValidationHelper . SendSonarrError ( result ? . ErrorMessages ? ? new List < string > ( ) ) ) ;
}
var srSettings = SickRageService . GetSettings ( ) ;
if ( srSettings . Enabled )
{
var result = sender . SendToSickRage ( srSettings , model ) ;
if ( result ? . result = = "success" )
{
return await AddRequest ( model , settings ,
$"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" ) ;
}
return
Response . AsJson ( new JsonResponseModel
{
Result = false ,
Message = result ? . message ? ? Resources . UI . Search_SickrageError
} ) ;
}
if ( ! srSettings . Enabled & & ! s . Enabled )
{
return await AddRequest ( model , settings , $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" ) ;
}
return
Response . AsJson ( new JsonResponseModel { Result = false , Message = Resources . UI . Search_TvNotSetUp } ) ;
return await SendTv ( model , sonarrSettings , existingRequest , fullShowName , settings ) ;
}
return await AddRequest ( model , settings , $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" ) ;
}
@ -1385,5 +1590,64 @@ namespace Ombi.UI.Modules
return false ;
}
}
private enum ShowSearchType
{
Popular ,
Anticipated ,
MostWatched ,
Trending
}
private async Task < Response > SendTv ( RequestedModel model , Task < SonarrSettings > sonarrSettings , RequestedModel existingRequest , string fullShowName , PlexRequestSettings settings )
{
model . Approved = true ;
var s = await sonarrSettings ;
var sender = new TvSenderOld ( SonarrApi , SickrageApi , Cache ) ; // TODO put back
if ( s . Enabled )
{
var result = await sender . SendToSonarr ( s , model ) ;
if ( ! string . IsNullOrEmpty ( result ? . title ) )
{
if ( existingRequest ! = null )
{
return await UpdateRequest ( model , settings ,
$"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" ) ;
}
return
await
AddRequest ( model , settings ,
$"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" ) ;
}
Log . Debug ( "Error with sending to sonarr." ) ;
return
Response . AsJson ( ValidationHelper . SendSonarrError ( result ? . ErrorMessages ? ? new List < string > ( ) ) ) ;
}
var srSettings = SickRageService . GetSettings ( ) ;
if ( srSettings . Enabled )
{
var result = sender . SendToSickRage ( srSettings , model ) ;
if ( result ? . result = = "success" )
{
return await AddRequest ( model , settings ,
$"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" ) ;
}
return
Response . AsJson ( new JsonResponseModel
{
Result = false ,
Message = result ? . message ? ? Resources . UI . Search_SickrageError
} ) ;
}
if ( ! srSettings . Enabled & & ! s . Enabled )
{
return await AddRequest ( model , settings , $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" ) ;
}
return
Response . AsJson ( new JsonResponseModel { Result = false , Message = Resources . UI . Search_TvNotSetUp } ) ;
}
}
}