using System; using AutoMapper; using System.Collections.Generic; using System.Linq; using System.Security.Principal; using System.Threading.Tasks; using Ombi.Core.Rule.Interfaces; using Ombi.Store.Repository.Requests; using Ombi.Core.Authentication; using Ombi.Helpers; using Ombi.Settings.Settings.Models; using Ombi.Store.Entities; using Ombi.Api.Trakt; using Ombi.Api.TvMaze; using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search; using Ombi.Core.Models.Search.V2; using Ombi.Core.Settings; using Ombi.Store.Repository; using TraktSharp.Entities; using Microsoft.EntityFrameworkCore; using System.Threading; using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb.Models; using System.Diagnostics; using Ombi.Core.Engine.Interfaces; using Ombi.Core.Models.UI; using Ombi.Core.Helpers; using Ombi.Core.Services; namespace Ombi.Core.Engine.V2 { public class TvSearchEngineV2 : BaseMediaEngine, ITVSearchEngineV2 { private readonly ITvMazeApi _tvMaze; private readonly IMapper _mapper; private readonly ITraktApi _traktApi; private readonly IMovieDbApi _movieApi; private readonly ISettingsService _customization; private readonly ITvRequestEngine _requestEngine; private readonly IFeatureService _feature; public TvSearchEngineV2(ICurrentUser identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, ICacheService memCache, ISettingsService s, IRepository sub, IMovieDbApi movieApi, ISettingsService customization, ITvRequestEngine requestEngine, IFeatureService feature) : base(identity, service, r, um, memCache, s, sub) { _tvMaze = tvMaze; _mapper = mapper; _traktApi = trakt; _movieApi = movieApi; _customization = customization; _requestEngine = requestEngine; _feature = feature; } public async Task GetShowByRequest(int requestId, CancellationToken token) { var request = await RequestService.TvRequestService.Get().FirstOrDefaultAsync(x => x.Id == requestId); return await GetShowInformation(request.ExternalProviderId.ToString(), token); } public async Task GetShowInformation(string tvdbid, CancellationToken token) { var langCode = await DefaultLanguageCode(null); var show = await Cache.GetOrAddAsync(nameof(GetShowInformation) + langCode + tvdbid, async () => await _movieApi.GetTVInfo(tvdbid, langCode), DateTimeOffset.Now.AddHours(12)); if (show == null || show.name == null) { // We don't have enough information return null; } if (!show.Images?.Posters?.Any() ?? false && !string.Equals(langCode, "en", StringComparison.OrdinalIgnoreCase)) { // There's no regional assets for this, so // lookup the en-us version to get them var enShow = await Cache.GetOrAddAsync(nameof(GetShowInformation) + "en" + tvdbid, async () => await _movieApi.GetTVInfo(tvdbid, "en"), DateTimeOffset.Now.AddHours(12)); // For some of the more obsecure cases if (!show.overview.HasValue()) { show.overview = enShow.overview; } show.Images = enShow.Images; } var mapped = _mapper.Map(show); foreach (var tvSeason in show.seasons.Where(x => x.season_number != 0)) // skip the first season { var seasonEpisodes = (await _movieApi.GetSeasonEpisodes(show.id, tvSeason.season_number, token, langCode)); MapSeasons(mapped.SeasonRequests, tvSeason, seasonEpisodes); } return await ProcessResult(mapped); } public async Task> Popular(int currentlyLoaded, int amountToLoad, string langCustomCode = null) { var langCode = await DefaultLanguageCode(langCustomCode); var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit); var results = new List(); foreach (var pagesToLoad in pages) { var apiResult = await Cache.GetOrAddAsync(nameof(Popular) + langCode + pagesToLoad.Page, async () => await _movieApi.PopularTv(langCode, pagesToLoad.Page), DateTimeOffset.Now.AddHours(12)); results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take)); } var processed = ProcessResults(results); return await processed; } public async Task> Anticipated(int currentlyLoaded, int amountToLoad) { var langCode = await DefaultLanguageCode(null); var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit); var results = new List(); foreach (var pagesToLoad in pages) { var apiResult = await Cache.GetOrAddAsync(nameof(Anticipated) + langCode + pagesToLoad.Page, async () => await _movieApi.UpcomingTv(langCode, pagesToLoad.Page), DateTimeOffset.Now.AddHours(12)); results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take)); } var processed = ProcessResults(results); return await processed; } public async Task> Trending(int currentlyLoaded, int amountToLoad) { var langCode = await DefaultLanguageCode(null); var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit); var results = new List(); foreach (var pagesToLoad in pages) { var apiResult = await Cache.GetOrAddAsync(nameof(Trending) + langCode + pagesToLoad.Page, () => _movieApi.TrendingTv(langCode, pagesToLoad.Page), DateTimeOffset.Now.AddHours(12)); results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take)); } var processed = ProcessResults(results); return await processed; } public async Task GetTvByActor(int actorId, string langCode) { langCode = await DefaultLanguageCode(langCode); var result = await Cache.GetOrAddAsync(nameof(GetTvByActor) + actorId + langCode, () => _movieApi.GetActorTvCredits(actorId, langCode), DateTimeOffset.Now.AddHours(12)); return result; } public async Task> GetStreamInformation(int movieDbId, CancellationToken cancellationToken) { var providers = await _movieApi.GetTvWatchProviders(movieDbId, cancellationToken); var results = await GetUserWatchProvider(providers); var data = new List(); foreach (var result in results) { data.Add(new StreamingData { Logo = result.logo_path, Order = result.display_priority, StreamingProvider = result.provider_name }); } return data; } public async Task> RecentlyRequestedShows(int currentlyLoaded, int toLoad, CancellationToken cancellationToken) { var langCode = await DefaultLanguageCode(null); var results = new List(); var requestResult = await Cache.GetOrAddAsync(nameof(RecentlyRequestedShows) + "Requests" + toLoad + langCode, async () => { return await _requestEngine.GetRequests(toLoad, currentlyLoaded, new Models.UI.OrderFilterModel { OrderType = OrderType.RequestedDateDesc }); }, DateTimeOffset.Now.AddMinutes(15)); var movieDBResults = await Cache.GetOrAddAsync(nameof(RecentlyRequestedShows) + toLoad + langCode, async () => { var responses = new List(); foreach (var movie in requestResult.Collection) { responses.Add(await _movieApi.GetTVInfo(movie.ExternalProviderId.ToString())); } return responses; }, DateTimeOffset.Now.AddHours(12)); var mapped = _mapper.Map>(movieDBResults); foreach(var map in mapped) { var processed = await ProcessResult(map); results.Add(processed); } return results; } private async Task> ProcessResults(List items) { var retVal = new List(); var settings = await _customization.GetSettingsAsync(); foreach (var tvMazeSearch in items) { if (DemoCheck(tvMazeSearch.Title)) { continue; } if (settings.HideAvailableFromDiscover) { // To hide, we need to know if it's fully available, the only way to do this is to lookup it's episodes to check if we have every episode var show = await Cache.GetOrAddAsync(nameof(GetShowInformation) + tvMazeSearch.Id.ToString(), async () => await _movieApi.GetTVInfo(tvMazeSearch.Id.ToString()), DateTime.Now.AddHours(12)); foreach (var tvSeason in show.seasons.Where(x => x.season_number != 0)) // skip the first season { var seasonEpisodes = await Cache.GetOrAddAsync("SeasonEpisodes" + show.id + tvSeason.season_number, async () => { return await _movieApi.GetSeasonEpisodes(show.id, tvSeason.season_number, CancellationToken.None); }, DateTimeOffset.Now.AddHours(12)); MapSeasons(tvMazeSearch.SeasonRequests, tvSeason, seasonEpisodes); } } var result = await ProcessResult(tvMazeSearch); if (result == null || settings.HideAvailableFromDiscover && result.FullyAvailable) { continue; } retVal.Add(result); } return retVal; } private static void MapSeasons(List seasonRequests, Season tvSeason, SeasonDetails seasonEpisodes) { foreach (var episode in seasonEpisodes.episodes) { var season = seasonRequests.FirstOrDefault(x => x.SeasonNumber == episode.season_number); if (season == null) { var newSeason = new SeasonRequests { SeasonNumber = episode.season_number, Overview = tvSeason.overview, Episodes = new List() }; var hasAirDate = episode.air_date.HasValue(); var airDate = DateTime.MinValue; if (hasAirDate) { DateTime.TryParse(episode.air_date, out airDate); } newSeason.Episodes.Add(new EpisodeRequests { //Url = episode...ToHttpsUrl(), Title = episode.name, AirDate = airDate, EpisodeNumber = episode.episode_number, }); seasonRequests.Add(newSeason); } else { var hasAirDate = episode.air_date.HasValue(); var airDate = DateTime.MinValue; if (hasAirDate) { DateTime.TryParse(episode.air_date, out airDate); } // We already have the season, so just add the episode season.Episodes.Add(new EpisodeRequests { //Url = e.url.ToHttpsUrl(), Title = episode.name, AirDate = airDate, EpisodeNumber = episode.episode_number, }); } } } private async Task ProcessResult(T tvMazeSearch) { var item = _mapper.Map(tvMazeSearch); await RunSearchRules(item); return item; } private async Task ProcessResult(SearchFullInfoTvShowViewModel item) { item.TheMovieDbId = item.Id.ToString(); var oldModel = _mapper.Map(item); await RunSearchRules(oldModel); item.Available = oldModel.Available; item.FullyAvailable = oldModel.FullyAvailable; item.PartlyAvailable = oldModel.PartlyAvailable; item.Requested = oldModel.Requested; item.Available = oldModel.Available; item.Denied = oldModel.Denied; item.DeniedReason = oldModel.DeniedReason; item.Approved = oldModel.Approved; item.SeasonRequests = oldModel.SeasonRequests; item.RequestId = oldModel.RequestId; item.PlexUrl = oldModel.PlexUrl; item.EmbyUrl = oldModel.EmbyUrl; item.JellyfinUrl = oldModel.JellyfinUrl; if (!string.IsNullOrEmpty(item.Images?.Medium)) { item.Images.Medium = item.Images.Medium.ToHttpsUrl(); } return item; //return await GetExtraInfo(showInfoTask, item); } private async Task GetExtraInfo(Task showInfoTask, SearchFullInfoTvShowViewModel model) { if (showInfoTask != null) { var result = await showInfoTask; if (result == null) { return model; } model.Trailer = result.Trailer?.AbsoluteUri.ToHttpsUrl() ?? string.Empty; model.Certification = result.Certification; model.Homepage = result.Homepage?.AbsoluteUri.ToHttpsUrl() ?? string.Empty; } return model; } } }