diff --git a/Ombi.Api.Models/Emby/EmbyEpisodeInformation.cs b/Ombi.Api.Models/Emby/EmbyEpisodeInformation.cs index be173faf9..1cdb2985c 100644 --- a/Ombi.Api.Models/Emby/EmbyEpisodeInformation.cs +++ b/Ombi.Api.Models/Emby/EmbyEpisodeInformation.cs @@ -49,7 +49,7 @@ public class EmbyEpisodeInformation public object[] Taglines { get; set; } public object[] Genres { get; set; } public string[] SeriesGenres { get; set; } - public int CommunityRating { get; set; } + public float CommunityRating { get; set; } public int VoteCount { get; set; } public long RunTimeTicks { get; set; } public string PlayAccess { get; set; } diff --git a/Ombi.Services/Jobs/EmbyAvailabilityChecker.cs b/Ombi.Services/Jobs/EmbyAvailabilityChecker.cs index a86a7469d..166ed987a 100644 --- a/Ombi.Services/Jobs/EmbyAvailabilityChecker.cs +++ b/Ombi.Services/Jobs/EmbyAvailabilityChecker.cs @@ -286,7 +286,9 @@ public async Task> GetEpisodes(int theTvDbId) var ep = await EpisodeRepo.CustomAsync(async connection => { connection.Open(); - var result = await connection.QueryAsync("select * from EmbyEpisodes where ProviderId = @ProviderId", new { ProviderId = theTvDbId }); + var result = await connection.QueryAsync(@"select ee.* from EmbyEpisodes ee inner join EmbyContent ec + on ee.ParentId = ec.EmbyId + where ec.ProviderId = @ProviderId", new { ProviderId = theTvDbId }); return result; }); diff --git a/Ombi.Services/Jobs/EmbyEpisodeCacher.cs b/Ombi.Services/Jobs/EmbyEpisodeCacher.cs index 945908149..5679a24b9 100644 --- a/Ombi.Services/Jobs/EmbyEpisodeCacher.cs +++ b/Ombi.Services/Jobs/EmbyEpisodeCacher.cs @@ -74,6 +74,10 @@ public void CacheEpisodes(EmbySettings settings) { var epInfo = EmbyApi.GetInformation(ep.Id, EmbyMediaType.Episode, settings.ApiKey, settings.AdministratorId, settings.FullUri); + if (epInfo.EpisodeInformation?.ProviderIds?.Tvdb == null) + { + continue; + } model.Add(new EmbyEpisodes { EmbyId = ep.Id, @@ -82,7 +86,7 @@ public void CacheEpisodes(EmbySettings settings) EpisodeTitle = ep.Name, ParentId = ep.SeriesId, ShowTitle = ep.SeriesName, - ProviderId = epInfo.EpisodeInformation.ProviderIds.Tmdb + ProviderId = epInfo.EpisodeInformation.ProviderIds.Tvdb }); } @@ -108,15 +112,6 @@ public void Start() return; } - var jobs = Job.GetJobs(); - var job = jobs.FirstOrDefault(x => x.Name.Equals(JobNames.EmbyEpisodeCacher, StringComparison.CurrentCultureIgnoreCase)); - if (job != null) - { - if (job.LastRun > DateTime.Now.AddHours(-11)) // If it's been run in the last 11 hours - { - return; - } - } Job.SetRunning(true, JobNames.EmbyEpisodeCacher); CacheEpisodes(s); } @@ -141,15 +136,6 @@ public void Execute(IJobExecutionContext context) return; } - var jobs = Job.GetJobs(); - var job = jobs.FirstOrDefault(x => x.Name.Equals(JobNames.EmbyEpisodeCacher, StringComparison.CurrentCultureIgnoreCase)); - if (job != null) - { - if (job.LastRun > DateTime.Now.AddHours(-11)) // If it's been run in the last 11 hours - { - return; - } - } Job.SetRunning(true, JobNames.EmbyEpisodeCacher); CacheEpisodes(s); } diff --git a/Ombi.UI/Jobs/Scheduler.cs b/Ombi.UI/Jobs/Scheduler.cs index 25151b59f..8f43a02b3 100644 --- a/Ombi.UI/Jobs/Scheduler.cs +++ b/Ombi.UI/Jobs/Scheduler.cs @@ -304,8 +304,8 @@ private IEnumerable CreateTriggers() var embyEpisode = TriggerBuilder.Create() .WithIdentity("EmbyEpisodeCacher", "Emby") - //.StartAt(DateBuilder.FutureDate(10, IntervalUnit.Minute)) - .StartAt(DateBuilder.FutureDate(10, IntervalUnit.Minute)) + .StartNow() + //.StartAt(DateBuilder.FutureDate(10, IntervalUnit.Minute)) .WithSimpleSchedule(x => x.WithIntervalInHours(s.EmbyEpisodeCacher).RepeatForever()) .Build(); diff --git a/Ombi.UI/Modules/Admin/AdminModule.cs b/Ombi.UI/Modules/Admin/AdminModule.cs index a83ff8bd1..41864c7b0 100644 --- a/Ombi.UI/Modules/Admin/AdminModule.cs +++ b/Ombi.UI/Modules/Admin/AdminModule.cs @@ -441,11 +441,15 @@ private Negotiator Plex() private async Task SavePlex() { var plexSettings = this.Bind(); - var valid = this.Validate(plexSettings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } + + if (plexSettings.Enable) + { + var valid = this.Validate(plexSettings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + } if (plexSettings.Enable) @@ -462,7 +466,7 @@ private async Task SavePlex() } } - if (string.IsNullOrEmpty(plexSettings.MachineIdentifier)) + if (string.IsNullOrEmpty(plexSettings.MachineIdentifier) && plexSettings.Enable) { //Lookup identifier var server = PlexApi.GetServer(plexSettings.PlexAuthToken); @@ -1166,7 +1170,13 @@ private async Task GetScheduledJobs() FaultQueueHandler = s.FaultQueueHandler, PlexEpisodeCacher = s.PlexEpisodeCacher, PlexUserChecker = s.PlexUserChecker, - UserRequestLimitResetter = s.UserRequestLimitResetter + UserRequestLimitResetter = s.UserRequestLimitResetter, + EmbyAvailabilityChecker = s.EmbyAvailabilityChecker, + EmbyContentCacher = s.EmbyContentCacher, + EmbyEpisodeCacher = s.EmbyEpisodeCacher, + EmbyUserChecker = s.EmbyUserChecker, + RadarrCacher = s.RadarrCacher, + WatcherCacher = s.WatcherCacher }; return View["SchedulerSettings", model]; } diff --git a/Ombi.UI/Modules/SearchModule.cs b/Ombi.UI/Modules/SearchModule.cs index 20b43097d..4a822ce81 100644 --- a/Ombi.UI/Modules/SearchModule.cs +++ b/Ombi.UI/Modules/SearchModule.cs @@ -67,1719 +67,1730 @@ namespace Ombi.UI.Modules { - public class SearchModule : BaseAuthModule - { - public SearchModule(ICacheProvider cache, - ISettingsService prSettings, IAvailabilityChecker plexChecker, - IRequestService request, ISonarrApi sonarrApi, ISettingsService sonarrSettings, - ISettingsService sickRageService, ISickRageApi srApi, - INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, - ISettingsService hpService, - ICouchPotatoCacher cpCacher, IWatcherCacher watcherCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi, - ISettingsService plexService, ISettingsService auth, - IRepository u, ISettingsService email, - IIssueService issue, IAnalytics a, IRepository rl, ITransientFaultQueue tfQueue, IRepository content, - ISecurityExtensions security, IMovieSender movieSender, IRadarrCacher radarrCacher, ITraktApi traktApi, ISettingsService cus, - IEmbyAvailabilityChecker embyChecker, IRepository embyContent, ISettingsService embySettings) - : base("search", prSettings, security) - { - Auth = auth; - PlexService = plexService; - PlexApi = plexApi; - PrService = prSettings; - MovieApi = new TheMovieDbApi(); - Cache = cache; - PlexChecker = plexChecker; - CpCacher = cpCacher; - SonarrCacher = sonarrCacher; - SickRageCacher = sickRageCacher; - RequestService = request; - SonarrApi = sonarrApi; - SonarrService = sonarrSettings; - SickRageService = sickRageService; - SickrageApi = srApi; - NotificationService = notify; - MusicBrainzApi = mbApi; - HeadphonesApi = hpApi; - HeadphonesService = hpService; - UsersToNotifyRepo = u; - EmailNotificationSettings = email; - IssueService = issue; - Analytics = a; - RequestLimitRepo = rl; - FaultQueue = tfQueue; - TvApi = new TvMazeApi(); - PlexContentRepository = content; - MovieSender = movieSender; - WatcherCacher = watcherCacher; - RadarrCacher = radarrCacher; - TraktApi = traktApi; - CustomizationSettings = cus; - EmbyChecker = embyChecker; - EmbyContentRepository = embyContent; - EmbySettings = embySettings; - - Get["SearchIndex", "/", true] = async (x, ct) => await RequestLoad(); - - Get["movie/{searchTerm}", true] = async (x, ct) => await SearchMovie((string)x.searchTerm); - Get["tv/{searchTerm}", true] = async (x, ct) => await SearchTvShow((string)x.searchTerm); - Get["music/{searchTerm}", true] = async (x, ct) => await SearchAlbum((string)x.searchTerm); - Get["music/coverArt/{id}"] = p => GetMusicBrainzCoverArt((string)p.id); - - 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); - Post["request/tvEpisodes", true] = async (x, ct) => await RequestTvShow(0, "episode"); - Post["request/album", true] = async (x, ct) => await RequestAlbum((string)Request.Form.albumId); - - 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 PlexContentRepository { get; } - private IRepository EmbyContentRepository { get; } - private TvMazeApi TvApi { get; } - private IPlexApi PlexApi { get; } - private TheMovieDbApi MovieApi { get; } - private INotificationService NotificationService { get; } - private ISonarrApi SonarrApi { get; } - private ISickRageApi SickrageApi { get; } - private IRequestService RequestService { get; } - private ICacheProvider Cache { get; } - private ISettingsService Auth { get; } - private ISettingsService EmbySettings { get; } - private ISettingsService PlexService { get; } - private ISettingsService PrService { get; } - private ISettingsService SonarrService { get; } - private ISettingsService SickRageService { get; } - private ISettingsService HeadphonesService { get; } - private ISettingsService EmailNotificationSettings { get; } - private IAvailabilityChecker PlexChecker { get; } - private IEmbyAvailabilityChecker EmbyChecker { get; } - private ICouchPotatoCacher CpCacher { get; } - private ISonarrCacher SonarrCacher { get; } - private ISickRageCacher SickRageCacher { get; } - private IMusicBrainzApi MusicBrainzApi { get; } - private IHeadphonesApi HeadphonesApi { get; } - private IRepository UsersToNotifyRepo { get; } - private IIssueService IssueService { get; } - private IAnalytics Analytics { get; } - private ITransientFaultQueue FaultQueue { get; } - private IRepository RequestLimitRepo { get; } - private IRadarrCacher RadarrCacher { get; } - private ISettingsService CustomizationSettings { get; } - private static Logger Log = LogManager.GetCurrentClassLogger(); - - private async Task RequestLoad() - { - - var settings = await PrService.GetSettingsAsync(); - var custom = await CustomizationSettings.GetSettingsAsync(); - var searchViewModel = new SearchLoadViewModel - { - Settings = settings, - CustomizationSettings = custom - }; - - - return View["Search/Index", searchViewModel]; - } - - private async Task UpcomingMovies() - { - Analytics.TrackEventAsync(Category.Search, Action.Movie, "Upcoming", Username, - CookieHelper.GetAnalyticClientId(Cookies)); - return await ProcessMovies(MovieSearchType.Upcoming, string.Empty); - } - - private async Task CurrentlyPlayingMovies() - { - Analytics.TrackEventAsync(Category.Search, Action.Movie, "CurrentlyPlaying", Username, - CookieHelper.GetAnalyticClientId(Cookies)); - return await ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty); - } - - private async Task SearchMovie(string searchTerm) - { - Analytics.TrackEventAsync(Category.Search, Action.Movie, searchTerm, Username, - CookieHelper.GetAnalyticClientId(Cookies)); - 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 ProcessMovies(MovieSearchType searchType, string searchTerm) - { - List apiMovies; - - switch (searchType) - { - case MovieSearchType.Search: - var movies = await MovieApi.SearchMovie(searchTerm).ConfigureAwait(false); - apiMovies = movies.Select(x => - new MovieResult - { - Adult = x.Adult, - BackdropPath = x.BackdropPath, - GenreIds = x.GenreIds, - Id = x.Id, - OriginalLanguage = x.OriginalLanguage, - OriginalTitle = x.OriginalTitle, - Overview = x.Overview, - Popularity = x.Popularity, - PosterPath = x.PosterPath, - ReleaseDate = x.ReleaseDate, - Title = x.Title, - Video = x.Video, - VoteAverage = x.VoteAverage, - VoteCount = x.VoteCount - }) - .ToList(); - break; - case MovieSearchType.CurrentlyPlaying: - apiMovies = await MovieApi.GetCurrentPlayingMovies(); - break; - case MovieSearchType.Upcoming: - apiMovies = await MovieApi.GetUpcomingMovies(); - break; - default: - apiMovies = new List(); - break; - } - - var allResults = await RequestService.GetAllAsync(); - allResults = allResults.Where(x => x.Type == RequestType.Movie); - - var distinctResults = allResults.DistinctBy(x => x.ProviderId); - var dbMovies = distinctResults.ToDictionary(x => x.ProviderId); - - - var cpCached = CpCacher.QueuedIds(); - var watcherCached = WatcherCacher.QueuedIds(); - var radarrCached = RadarrCacher.QueuedIds(); - - var viewMovies = new List(); - var counter = 0; - foreach (var movie in apiMovies) - { - var viewMovie = new SearchMovieViewModel - { - Adult = movie.Adult, - BackdropPath = movie.BackdropPath, - GenreIds = movie.GenreIds, - Id = movie.Id, - OriginalLanguage = movie.OriginalLanguage, - OriginalTitle = movie.OriginalTitle, - Overview = movie.Overview, - Popularity = movie.Popularity, - PosterPath = movie.PosterPath, - ReleaseDate = movie.ReleaseDate, - Title = movie.Title, - Video = movie.Video, - VoteAverage = movie.VoteAverage, - VoteCount = movie.VoteCount - }; - - 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 plexSettings = await PlexService.GetSettingsAsync(); - var embySettings = await EmbySettings.GetSettingsAsync(); - if (plexSettings.Enable) - { - var content = PlexContentRepository.GetAll(); - var plexMovies = PlexChecker.GetPlexMovies(content); - - var plexMovie = PlexChecker.GetMovie(plexMovies.ToArray(), movie.Title, - movie.ReleaseDate?.Year.ToString(), - viewMovie.ImdbId); - if (plexMovie != null) - { - viewMovie.Available = true; - viewMovie.PlexUrl = plexMovie.Url; - } - } - if (embySettings.Enable) - { - var embyContent = EmbyContentRepository.GetAll(); - var embyMovies = EmbyChecker.GetEmbyMovies(embyContent); - - var embyMovie = EmbyChecker.GetMovie(embyMovies.ToArray(), movie.Title, - movie.ReleaseDate?.Year.ToString(), viewMovie.ImdbId); - if (embyMovie != null) - { - viewMovie.Available = true; - } - } - else if (dbMovies.ContainsKey(movie.Id) && canSee) // compare to the requests db - { - var dbm = dbMovies[movie.Id]; - - viewMovie.Requested = true; - viewMovie.Approved = dbm.Approved; - viewMovie.Available = dbm.Available; - } - else if (cpCached.Contains(movie.Id) && canSee) // compare to the couchpotato db - { - viewMovie.Approved = true; - viewMovie.Requested = true; - } - else if (watcherCached.Contains(viewMovie.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); - } - - return Response.AsJson(viewMovies); - } - - private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests, - Dictionary moviesInDb) - { - if (usersCanViewOnlyOwnRequests) - { - var result = moviesInDb.FirstOrDefault(x => x.Value.ProviderId == movieId); - return result.Value == null || result.Value.UserHasRequested(Username); - } - - return true; - } - - private async Task ProcessShows(ShowSearchType type) - { - var shows = new List(); - 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> MapToTvModel(List 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 = PlexChecker.GetPlexTvShows(content).ToList(); - - foreach (var show in shows) - { - if (plexSettings.AdvancedSearch) - { - providerId = show.Id.ToString(); - } - - var plexShow = PlexChecker.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 SearchTvShow(string searchTerm) - { - - Analytics.TrackEventAsync(Category.Search, Action.TvShow, searchTerm, Username, - CookieHelper.GetAnalyticClientId(Cookies)); - var plexSettings = await PlexService.GetSettingsAsync(); - var prSettings = await PrService.GetSettingsAsync(); - var providerId = string.Empty; - - var apiTv = new List(); - await Task.Factory.StartNew(() => new TvMazeApi().Search(searchTerm)).ContinueWith((t) => - { - apiTv = t.Result; - }); - - 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); - - if (!apiTv.Any()) - { - return Response.AsJson(""); - } - - var sonarrCached = SonarrCacher.QueuedIds(); - var sickRageCache = SickRageCacher.QueuedIds(); // consider just merging sonarr/sickrage arrays - var content = PlexContentRepository.GetAll(); - var plexTvShows = PlexChecker.GetPlexTvShows(content); - - var viewTv = new List(); - foreach (var t in apiTv) - { - if (!(t.show.externals?.thetvdb.HasValue) ?? false) - { - continue; - } - var banner = t.show.image?.medium; - if (!string.IsNullOrEmpty(banner)) - { - banner = banner.Replace("http", "https"); // Always use the Https banners - } - - var viewT = new SearchTvShowViewModel - { - Banner = banner, - FirstAired = t.show.premiered, - Id = t.show.externals?.thetvdb ?? 0, - ImdbId = t.show.externals?.imdb, - Network = t.show.network?.name, - NetworkId = t.show.network?.id.ToString(), - Overview = t.show.summary.RemoveHtml(), - Rating = t.score.ToString(CultureInfo.CurrentUICulture), - Runtime = t.show.runtime.ToString(), - SeriesId = t.show.id, - SeriesName = t.show.name, - Status = t.show.status, - DisableTvRequestsByEpisode = prSettings.DisableTvRequestsByEpisode, - DisableTvRequestsBySeason = prSettings.DisableTvRequestsBySeason, - EnableTvRequestsForOnlySeries = (prSettings.DisableTvRequestsByEpisode && prSettings.DisableTvRequestsBySeason) - }; - - - if (plexSettings.AdvancedSearch) - { - providerId = viewT.Id.ToString(); - } - - var plexShow = PlexChecker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), - providerId); - if (plexShow != null) - { - viewT.Available = true; - viewT.PlexUrl = plexShow.Url; - } - else if (t.show?.externals?.thetvdb != null) - { - var tvdbid = (int)t.show.externals.thetvdb; - if (dbTv.ContainsKey(tvdbid)) - { - var dbt = dbTv[tvdbid]; - - viewT.Requested = true; - viewT.Episodes = dbt.Episodes.ToList(); - viewT.Approved = dbt.Approved; - } - if (sonarrCached.Select(x => x.TvdbId).Contains(tvdbid) || sickRageCache.Contains(tvdbid)) - // compare to the sonarr/sickrage db - { - viewT.Requested = true; - } - } - - viewTv.Add(viewT); - } - - return Response.AsJson(viewTv); - } - - private async Task SearchAlbum(string searchTerm) - { - Analytics.TrackEventAsync(Category.Search, Action.Album, searchTerm, Username, - CookieHelper.GetAnalyticClientId(Cookies)); - var apiAlbums = new List(); - await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) => - { - apiAlbums = t.Result.releases ?? new List(); - }); - - var allResults = await RequestService.GetAllAsync(); - allResults = allResults.Where(x => x.Type == RequestType.Album); - - var dbAlbum = allResults.ToDictionary(x => x.MusicBrainzId); - - var content = PlexContentRepository.GetAll(); - var plexAlbums = PlexChecker.GetPlexAlbums(content); - - var viewAlbum = new List(); - foreach (var a in apiAlbums) - { - var viewA = new SearchMusicViewModel - { - Title = a.title, - Id = a.id, - Artist = a.ArtistCredit?.Select(x => x.artist?.name).FirstOrDefault(), - Overview = a.disambiguation, - ReleaseDate = a.date, - TrackCount = a.TrackCount, - ReleaseType = a.status, - Country = a.country - }; - - DateTime release; - DateTimeHelper.CustomParse(a.ReleaseEvents?.FirstOrDefault()?.date, out release); - var artist = a.ArtistCredit?.FirstOrDefault()?.artist; - var plexAlbum = PlexChecker.GetAlbum(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name); - if (plexAlbum != null) - { - viewA.Available = true; - viewA.PlexUrl = plexAlbum.Url; - } - if (!string.IsNullOrEmpty(a.id) && dbAlbum.ContainsKey(a.id)) - { - var dba = dbAlbum[a.id]; - - viewA.Requested = true; - viewA.Approved = dba.Approved; - viewA.Available = dba.Available; - } - - viewAlbum.Add(viewA); - } - return Response.AsJson(viewAlbum); - } - - private async Task RequestMovie(int movieId) - { - if (Security.HasPermissions(User, Permissions.ReadOnlyUser) || !Security.HasPermissions(User, Permissions.RequestMovie)) - { - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = "Sorry, you do not have the correct permissions to request a movie!" - }); - } - var settings = await PrService.GetSettingsAsync(); - if (!await CheckRequestLimit(settings, RequestType.Movie)) - { - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = "You have reached your weekly request limit for Movies! Please contact your admin." - }); - } - - Analytics.TrackEventAsync(Category.Search, Action.Request, "Movie", Username, - CookieHelper.GetAnalyticClientId(Cookies)); - var movieInfo = await MovieApi.GetMovieInformation(movieId); - if (movieInfo == null) - { - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = "There was an issue adding this movie!" - }); - } - var fullMovieName = - $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}"; - - var existingRequest = await RequestService.CheckRequestAsync(movieId); - if (existingRequest != null) - { - // check if the current user is already marked as a requester for this movie, if not, add them - if (!existingRequest.UserHasRequested(Username)) - { - existingRequest.RequestedUsers.Add(Username); - await RequestService.UpdateRequestAsync(existingRequest); - } - - return - Response.AsJson(new JsonResponseModel - { - Result = true, - Message = - Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests) - ? $"{fullMovieName} {Ombi.UI.Resources.UI.Search_SuccessfullyAdded}" - : $"{fullMovieName} {Resources.UI.Search_AlreadyRequested}" - }); - } - - try - { - - var content = PlexContentRepository.GetAll(); - var movies = PlexChecker.GetPlexMovies(content); - if (PlexChecker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) - { - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = $"{fullMovieName} is already in Plex!" - }); - } - } - catch (Exception e) - { - Log.Error(e); - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullMovieName) - }); - } - //#endif - - var model = new RequestedModel - { - ProviderId = movieInfo.Id, - Type = RequestType.Movie, - Overview = movieInfo.Overview, - ImdbId = movieInfo.ImdbId, - PosterPath = movieInfo.PosterPath, - Title = movieInfo.Title, - ReleaseDate = movieInfo.ReleaseDate ?? DateTime.MinValue, - Status = movieInfo.Status, - RequestedDate = DateTime.UtcNow, - Approved = false, - RequestedUsers = new List { Username }, - Issues = IssueState.None, - - }; - try - { - if (ShouldAutoApprove(RequestType.Movie)) - { - model.Approved = true; - - var result = await MovieSender.Send(model); - if (result.Result) - { - return await AddRequest(model, settings, - $"{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) - { - - return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); - } - - return Response.AsJson(new JsonResponseModel - { - Result = false, - Message = Resources.UI.Search_CouchPotatoError - }); - } - - - return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); - } - catch (Exception e) - { - Log.Fatal(e); - await FaultQueue.QueueItemAsync(model, movieInfo.Id.ToString(), RequestType.Movie, FaultType.RequestFault, e.Message); - - await NotificationService.Publish(new NotificationModel - { - DateTime = DateTime.Now, - User = Username, - RequestType = RequestType.Movie, - Title = model.Title, - NotificationType = NotificationType.ItemAddedToFaultQueue - }); - - return Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}" - }); - } - } - - /// - /// Requests the tv show. - /// - /// The show identifier. - /// The seasons. - /// - private async Task RequestTvShow(int showId, string seasons) - { - if (Security.HasPermissions(User, Permissions.ReadOnlyUser) || !Security.HasPermissions(User, Permissions.RequestTvShow)) - { - return - Response.AsJson(new JsonResponseModel() - { - Result = false, - Message = "Sorry, you do not have the correct permissions to request a TV Show!" - }); - } - // Get the JSON from the request - var req = (Dictionary.ValueCollection)Request.Form.Values; - EpisodeRequestModel episodeModel = null; - if (req.Count == 1) - { - var json = req.FirstOrDefault()?.ToString(); - episodeModel = JsonConvert.DeserializeObject(json); // Convert it into the object - } - var episodeRequest = false; - - var settings = await PrService.GetSettingsAsync(); - if (!await CheckRequestLimit(settings, RequestType.TvShow)) - { - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = Resources.UI.Search_WeeklyRequestLimitTVShow - }); - } - Analytics.TrackEventAsync(Category.Search, Action.Request, "TvShow", Username, - CookieHelper.GetAnalyticClientId(Cookies)); - - var sonarrSettings = SonarrService.GetSettingsAsync(); - - // This means we are requesting an episode rather than a whole series or season - if (episodeModel != null) - { - episodeRequest = true; - showId = episodeModel.ShowId; - var s = await sonarrSettings; - if (!s.Enabled) - { - return - Response.AsJson(new JsonResponseModel - { - Message = - "This is currently only supported with Sonarr, Please enable Sonarr for this feature", - Result = false - }); - } - } - - var showInfo = TvApi.ShowLookupByTheTvDbId(showId); - DateTime firstAir; - 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 = posterPath, - Title = showInfo.name, - ReleaseDate = firstAir, - Status = showInfo.status, - RequestedDate = DateTime.UtcNow, - Approved = false, - RequestedUsers = new List { Username }, - Issues = IssueState.None, - ImdbId = showInfo.externals?.imdb ?? string.Empty, - SeasonCount = showInfo.Season.Count, - TvDbId = showId.ToString() - }; - - var seasonsList = new List(); - switch (seasons) - { - case "first": - seasonsList.Add(1); - model.SeasonsRequested = "First"; - break; - case "latest": - seasonsList.Add(model.SeasonCount); - model.SeasonsRequested = "Latest"; - break; - case "all": - model.SeasonsRequested = "All"; - break; - case "episode": - model.Episodes = new List(); - - foreach (var ep in episodeModel?.Episodes ?? new Models.EpisodesModel[0]) - { - model.Episodes.Add(new EpisodesModel - { - EpisodeNumber = ep.EpisodeNumber, - SeasonNumber = ep.SeasonNumber - }); - } - Analytics.TrackEventAsync(Category.Requests, Action.TvShow, $"Episode request for {model.Title}", - Username, CookieHelper.GetAnalyticClientId(Cookies)); - break; - default: - model.SeasonsRequested = seasons; - var split = seasons.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - var seasonsCount = new int[split.Length]; - for (var i = 0; i < split.Length; i++) - { - int tryInt; - int.TryParse(split[i], out tryInt); - seasonsCount[i] = tryInt; - } - seasonsList.AddRange(seasonsCount); - break; - } - - model.SeasonList = seasonsList.ToArray(); - - // check if the show/episodes have already been requested - var existingRequest = await RequestService.CheckRequestAsync(showId); - var difference = new List(); - if (existingRequest != null) - { - if (episodeRequest) - { - // Make sure we are not somehow adding dupes - difference = GetListDifferences(existingRequest.Episodes, episodeModel.Episodes).ToList(); - if (difference.Any()) - { - // Convert the request into the correct shape - var newEpisodes = episodeModel.Episodes?.Select(x => new EpisodesModel - { - SeasonNumber = x.SeasonNumber, - EpisodeNumber = x.EpisodeNumber - }); - - // Add it to the existing requests - existingRequest.Episodes.AddRange(newEpisodes ?? Enumerable.Empty()); - - // 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); - } - else - { - // We no episodes to approve - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" - }); - } - } - else if (model.SeasonList.Except(existingRequest.SeasonList).Any()) - { - // This is a season being requested that we do not yet have - // Let's just continue - } - else - { - return await AddUserToRequest(existingRequest, settings, fullShowName); - } - } - - try - { - - var plexSettings = await PlexService.GetSettingsAsync(); - if (plexSettings.Enable) - { - var content = PlexContentRepository.GetAll(); - var shows = PlexChecker.GetPlexTvShows(content); - - var providerId = string.Empty; - if (plexSettings.AdvancedSearch) - { - providerId = showId.ToString(); - } - if (episodeRequest) - { - var cachedEpisodesTask = await PlexChecker.GetEpisodes(); - var cachedEpisodes = cachedEpisodesTask.ToList(); - foreach (var d in difference) // difference is from an existing request - { - if ( - cachedEpisodes.Any( - x => - x.SeasonNumber == d.SeasonNumber && x.EpisodeNumber == d.EpisodeNumber && - x.ProviderId == providerId)) - { - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = - $"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {Resources.UI.Search_AlreadyInPlex}" - }); - } - } - - var diff = await GetEpisodeRequestDifference(showId, model); - model.Episodes = diff.ToList(); - } - else - { - if (plexSettings.EnableTvEpisodeSearching) - { - foreach (var s in showInfo.Season) - { - var result = PlexChecker.IsEpisodeAvailable(showId.ToString(), s.SeasonNumber, - s.EpisodeNumber); - if (result) - { - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" - }); - } - } - } - else if (PlexChecker.IsTvShowAvailable(shows.ToArray(), showInfo.name, - showInfo.premiered?.Substring(0, 4), - providerId, model.SeasonList)) - { - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" - }); - } - } - } - var embySettings = await EmbySettings.GetSettingsAsync(); - if (embySettings.Enable) - { - var embyContent = EmbyContentRepository.GetAll(); - var embyMovies = EmbyChecker.GetEmbyTvShows(embyContent); - var providerId = showId.ToString(); - if (episodeRequest) - { - var cachedEpisodesTask = await EmbyChecker.GetEpisodes(); - var cachedEpisodes = cachedEpisodesTask.ToList(); - foreach (var d in difference) // difference is from an existing request - { - if ( - cachedEpisodes.Any( - x => - x.SeasonNumber == d.SeasonNumber && x.EpisodeNumber == d.EpisodeNumber && - x.ProviderId == providerId)) - { - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = - $"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {Resources.UI.Search_AlreadyInPlex}" - }); - } - } - - var diff = await GetEpisodeRequestDifference(showId, model); - model.Episodes = diff.ToList(); - } - else - { - if (embySettings.EnableEpisodeSearching) - { - foreach (var s in showInfo.Season) - { - var result = EmbyChecker.IsEpisodeAvailable(showId.ToString(), s.SeasonNumber, - s.EpisodeNumber); - if (result) - { - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = $"{fullShowName} is already in Emby!" - }); - } - } - } - else if (EmbyChecker.IsTvShowAvailable(embyMovies.ToArray(), showInfo.name, - showInfo.premiered?.Substring(0, 4), - providerId, model.SeasonList)) - { - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = $"{fullShowName} is already in Emby!" - }); - } - } - } - } - catch (Exception) - { - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullShowName) - }); - } - - if (showInfo.externals?.thetvdb == null) - { - await FaultQueue.QueueItemAsync(model, showInfo.id.ToString(), RequestType.TvShow, FaultType.MissingInformation, "We do not have a TheTVDBId from TVMaze"); - await NotificationService.Publish(new NotificationModel - { - DateTime = DateTime.Now, - User = Username, - RequestType = RequestType.TvShow, - Title = model.Title, - NotificationType = NotificationType.ItemAddedToFaultQueue - }); - return Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" - }); - } - - model.ProviderId = showInfo.externals?.thetvdb ?? 0; - - try - { - if (ShouldAutoApprove(RequestType.TvShow)) - { - return await SendTv(model, sonarrSettings, existingRequest, fullShowName, settings); - } - return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); - } - catch (Exception e) - { - await FaultQueue.QueueItemAsync(model, showInfo.id.ToString(), RequestType.TvShow, FaultType.RequestFault, e.Message); - await NotificationService.Publish(new NotificationModel - { - DateTime = DateTime.Now, - User = Username, - RequestType = RequestType.TvShow, - Title = model.Title, - NotificationType = NotificationType.ItemAddedToFaultQueue - }); - Log.Error(e); - return - Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" - }); - } - } - - private async Task AddUserToRequest(RequestedModel existingRequest, PlexRequestSettings settings, - string fullShowName, bool episodeReq = false) - { - // check if the current user is already marked as a requester for this show, if not, add them - if (!existingRequest.UserHasRequested(Username)) - { - existingRequest.RequestedUsers.Add(Username); - } - if (Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests) || episodeReq) - { - return - await - UpdateRequest(existingRequest, settings, - $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); - } - - return - await UpdateRequest(existingRequest, settings, $"{fullShowName} {Resources.UI.Search_AlreadyRequested}"); - } - - private bool ShouldSendNotification(RequestType type, PlexRequestSettings prSettings) - { - var sendNotification = ShouldAutoApprove(type) - ? !prSettings.IgnoreNotifyForAutoApprovedRequests - : true; - - if (IsAdmin) - { - sendNotification = false; // Don't bother sending a notification if the user is an admin - - } - return sendNotification; - } - - - private async Task RequestAlbum(string releaseId) - { - if (Security.HasPermissions(User, Permissions.ReadOnlyUser) || !Security.HasPermissions(User, Permissions.RequestMusic)) - { - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = "Sorry, you do not have the correct permissions to request music!" - }); - } - - var settings = await PrService.GetSettingsAsync(); - if (!await CheckRequestLimit(settings, RequestType.Album)) - { - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = Resources.UI.Search_WeeklyRequestLimitAlbums - }); - } - Analytics.TrackEventAsync(Category.Search, Action.Request, "Album", Username, - CookieHelper.GetAnalyticClientId(Cookies)); - var existingRequest = await RequestService.CheckRequestAsync(releaseId); - - if (existingRequest != null) - { - if (!existingRequest.UserHasRequested(Username)) - { - existingRequest.RequestedUsers.Add(Username); - await RequestService.UpdateRequestAsync(existingRequest); - } - return - Response.AsJson(new JsonResponseModel - { - Result = true, - Message = - Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests) - ? $"{existingRequest.Title} {Resources.UI.Search_SuccessfullyAdded}" - : $"{existingRequest.Title} {Resources.UI.Search_AlreadyRequested}" - }); - } - - var albumInfo = MusicBrainzApi.GetAlbum(releaseId); - DateTime release; - DateTimeHelper.CustomParse(albumInfo.ReleaseEvents?.FirstOrDefault()?.date, out release); - - var artist = albumInfo.ArtistCredits?.FirstOrDefault()?.artist; - if (artist == null) - { - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = Resources.UI.Search_MusicBrainzError - }); - } - - - var content = PlexContentRepository.GetAll(); - var albums = PlexChecker.GetPlexAlbums(content); - var alreadyInPlex = PlexChecker.IsAlbumAvailable(albums.ToArray(), albumInfo.title, release.ToString("yyyy"), - artist.name); - - if (alreadyInPlex) - { - return Response.AsJson(new JsonResponseModel - { - Result = false, - Message = $"{albumInfo.title} {Resources.UI.Search_AlreadyInPlex}" - }); - } - - var img = GetMusicBrainzCoverArt(albumInfo.id); - - var model = new RequestedModel - { - Title = albumInfo.title, - MusicBrainzId = albumInfo.id, - Overview = albumInfo.disambiguation, - PosterPath = img, - Type = RequestType.Album, - ProviderId = 0, - RequestedUsers = new List { Username }, - Status = albumInfo.status, - Issues = IssueState.None, - RequestedDate = DateTime.UtcNow, - ReleaseDate = release, - ArtistName = artist.name, - ArtistId = artist.id - }; - - try - { - if (ShouldAutoApprove(RequestType.Album)) - { - model.Approved = true; - var hpSettings = HeadphonesService.GetSettings(); - - if (!hpSettings.Enabled) - { - await RequestService.AddRequestAsync(model); - return - Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}" - }); - } - - var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService); - await sender.AddAlbum(model); - return await AddRequest(model, settings, $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}"); - } - - return await AddRequest(model, settings, $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}"); - } - catch (Exception e) - { - Log.Error(e); - await FaultQueue.QueueItemAsync(model, albumInfo.id, RequestType.Album, FaultType.RequestFault, e.Message); - - await NotificationService.Publish(new NotificationModel - { - DateTime = DateTime.Now, - User = Username, - RequestType = RequestType.Album, - Title = model.Title, - NotificationType = NotificationType.ItemAddedToFaultQueue - }); - throw; - } - } - - private string GetMusicBrainzCoverArt(string id) - { - var coverArt = MusicBrainzApi.GetCoverArt(id); - var firstImage = coverArt?.images?.FirstOrDefault(); - var img = string.Empty; - - if (firstImage != null) - { - img = firstImage.thumbnails?.small ?? firstImage.image; - } - - return img; - } - - private Response GetSeasons() - { - var seriesId = (int)Request.Query.tvId; - var show = TvApi.ShowLookupByTheTvDbId(seriesId); - var seasons = TvApi.GetSeasons(show.id); - var model = seasons.Select(x => x.number); - return Response.AsJson(model); - } - - private async Task GetEpisodes() - { - var seriesId = (int)Request.Query.tvId; - var model = await GetEpisodes(seriesId); - - return Response.AsJson(model); - } - - private async Task> GetEpisodes(int providerId) - { - var s = await SonarrService.GetSettingsAsync(); - var sonarrEnabled = s.Enabled; - var allResults = await RequestService.GetAllAsync(); - - var seriesTask = Task.Run( - () => - { - if (sonarrEnabled) - { - var allSeries = SonarrApi.GetSeries(s.ApiKey, s.FullUri); - var selectedSeries = allSeries.FirstOrDefault(x => x.tvdbId == providerId) ?? new Series(); - return selectedSeries; - } - return new Series(); - }); - - var model = new List(); - - var requests = allResults as RequestedModel[] ?? allResults.ToArray(); - - var existingRequest = requests.FirstOrDefault(x => x.Type == RequestType.TvShow && x.TvDbId == providerId.ToString()); - var show = await Task.Run(() => TvApi.ShowLookupByTheTvDbId(providerId)); - var tvMazeEpisodesTask = await Task.Run(() => TvApi.EpisodeLookup(show.id)); - var tvMazeEpisodes = tvMazeEpisodesTask.ToList(); - - var sonarrEpisodes = new List(); - if (sonarrEnabled) - { - var sonarrSeries = await seriesTask; - var sonarrEp = SonarrApi.GetEpisodes(sonarrSeries.id.ToString(), s.ApiKey, s.FullUri); - sonarrEpisodes = sonarrEp?.ToList() ?? new List(); - } - - var plexSettings = await PlexService.GetSettingsAsync(); - if (plexSettings.Enable) - { - var plexCacheTask = await PlexChecker.GetEpisodes(providerId); - var plexCache = plexCacheTask.ToList(); - foreach (var ep in tvMazeEpisodes) - { - var requested = existingRequest?.Episodes - .Any(episodesModel => - ep.number == episodesModel.EpisodeNumber && - ep.season == episodesModel.SeasonNumber) ?? false; - - var alreadyInPlex = plexCache.Any(x => x.EpisodeNumber == ep.number && x.SeasonNumber == ep.season); - var inSonarr = - sonarrEpisodes.Any(x => x.seasonNumber == ep.season && x.episodeNumber == ep.number && x.hasFile); - - model.Add(new EpisodeListViewModel - { - Id = show.id, - SeasonNumber = ep.season, - EpisodeNumber = ep.number, - Requested = requested || alreadyInPlex || inSonarr, - Name = ep.name, - EpisodeId = ep.id - }); - } - } - var embySettings = await EmbySettings.GetSettingsAsync(); - if (embySettings.Enable) - { - var embyCacheTask = await EmbyChecker.GetEpisodes(providerId); - var cache = embyCacheTask.ToList(); - foreach (var ep in tvMazeEpisodes) - { - var requested = existingRequest?.Episodes - .Any(episodesModel => - ep.number == episodesModel.EpisodeNumber && - ep.season == episodesModel.SeasonNumber) ?? false; - - var alreadyInEmby = cache.Any(x => x.EpisodeNumber == ep.number && x.SeasonNumber == ep.season); - var inSonarr = - sonarrEpisodes.Any(x => x.seasonNumber == ep.season && x.episodeNumber == ep.number && x.hasFile); - - model.Add(new EpisodeListViewModel - { - Id = show.id, - SeasonNumber = ep.season, - EpisodeNumber = ep.number, - Requested = requested || alreadyInEmby || inSonarr, - Name = ep.name, - EpisodeId = ep.id - }); - } - } - return model; - - } - - public async Task CheckRequestLimit(PlexRequestSettings s, RequestType type) - { - if (IsAdmin) - return true; - - if (Security.HasPermissions(User, Permissions.BypassRequestLimit)) - return true; - - var requestLimit = GetRequestLimitForType(type, s); - if (requestLimit == 0) - { - return true; - } - - var limit = await RequestLimitRepo.GetAllAsync(); - var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == type); - if (usersLimit == null) - { - // Have not set a requestLimit yet - return true; - } - - return requestLimit > usersLimit.RequestCount; - } - - private int GetRequestLimitForType(RequestType type, PlexRequestSettings s) - { - int requestLimit; - switch (type) - { - case RequestType.Movie: - requestLimit = s.MovieWeeklyRequestLimit; - break; - case RequestType.TvShow: - requestLimit = s.TvWeeklyRequestLimit; - break; - case RequestType.Album: - requestLimit = s.AlbumWeeklyRequestLimit; - break; - default: - throw new ArgumentOutOfRangeException(nameof(type), type, null); - } - return requestLimit; - } - - private async Task AddRequest(RequestedModel model, PlexRequestSettings settings, string message) - { - await RequestService.AddRequestAsync(model); - - if (ShouldSendNotification(model.Type, settings)) - { - var notificationModel = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest, - RequestType = model.Type, - ImgSrc = model.Type == RequestType.Movie ? $"https://image.tmdb.org/t/p/w300/{model.PosterPath}" : model.PosterPath - }; - await NotificationService.Publish(notificationModel); - } - - var limit = await RequestLimitRepo.GetAllAsync(); - var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == model.Type); - if (usersLimit == null) - { - await RequestLimitRepo.InsertAsync(new RequestLimit - { - Username = Username, - RequestType = model.Type, - FirstRequestDate = DateTime.UtcNow, - RequestCount = 1 - }); - } - else - { - usersLimit.RequestCount++; - await RequestLimitRepo.UpdateAsync(usersLimit); - } - - return Response.AsJson(new JsonResponseModel { Result = true, Message = message }); - } - - private async Task UpdateRequest(RequestedModel model, PlexRequestSettings settings, string message) - { - await RequestService.UpdateRequestAsync(model); - - if (ShouldSendNotification(model.Type, settings)) - { - var notificationModel = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest, - RequestType = model.Type, - ImgSrc = model.Type == RequestType.Movie ? $"https://image.tmdb.org/t/p/w300/{model.PosterPath}" : model.PosterPath - }; - await NotificationService.Publish(notificationModel); - } - - var limit = await RequestLimitRepo.GetAllAsync(); - var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == model.Type); - if (usersLimit == null) - { - await RequestLimitRepo.InsertAsync(new RequestLimit - { - Username = Username, - RequestType = model.Type, - FirstRequestDate = DateTime.UtcNow, - RequestCount = 1 - }); - } - else - { - usersLimit.RequestCount++; - await RequestLimitRepo.UpdateAsync(usersLimit); - } - - return Response.AsJson(new JsonResponseModel { Result = true, Message = message }); - } - - private IEnumerable GetListDifferences(IEnumerable existing, IEnumerable request) - { - var newRequest = request - .Select(r => - new EpisodesModel - { - SeasonNumber = r.SeasonNumber, - EpisodeNumber = r.EpisodeNumber - }).ToList(); - - return newRequest.Except(existing); - } - - private async Task> GetEpisodeRequestDifference(int showId, RequestedModel model) - { - var episodes = await GetEpisodes(showId); - var availableEpisodes = episodes.Where(x => x.Requested).ToList(); - var available = availableEpisodes.Select(a => new EpisodesModel { EpisodeNumber = a.EpisodeNumber, SeasonNumber = a.SeasonNumber }).ToList(); - - var diff = model.Episodes.Except(available); - return diff; - } - - public bool ShouldAutoApprove(RequestType requestType) - { - var admin = Security.HasPermissions(Context.CurrentUser, Permissions.Administrator); - // if the user is an admin, they go ahead and allow auto-approval - if (admin) return true; - - // check by request type if the category requires approval or not - switch (requestType) - { - case RequestType.Movie: - return Security.HasPermissions(User, Permissions.AutoApproveMovie); - case RequestType.TvShow: - return Security.HasPermissions(User, Permissions.AutoApproveTv); - case RequestType.Album: - return Security.HasPermissions(User, Permissions.AutoApproveAlbum); - default: - return false; - } - } - - private enum ShowSearchType - { - Popular, - Anticipated, - MostWatched, - Trending - } - - private async Task SendTv(RequestedModel model, Task 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())); - } - - 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 }); - } - } + public class SearchModule : BaseAuthModule + { + public SearchModule(ICacheProvider cache, + ISettingsService prSettings, IAvailabilityChecker plexChecker, + IRequestService request, ISonarrApi sonarrApi, ISettingsService sonarrSettings, + ISettingsService sickRageService, ISickRageApi srApi, + INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, + ISettingsService hpService, + ICouchPotatoCacher cpCacher, IWatcherCacher watcherCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi, + ISettingsService plexService, ISettingsService auth, + IRepository u, ISettingsService email, + IIssueService issue, IAnalytics a, IRepository rl, ITransientFaultQueue tfQueue, IRepository content, + ISecurityExtensions security, IMovieSender movieSender, IRadarrCacher radarrCacher, ITraktApi traktApi, ISettingsService cus, + IEmbyAvailabilityChecker embyChecker, IRepository embyContent, ISettingsService embySettings) + : base("search", prSettings, security) + { + Auth = auth; + PlexService = plexService; + PlexApi = plexApi; + PrService = prSettings; + MovieApi = new TheMovieDbApi(); + Cache = cache; + PlexChecker = plexChecker; + CpCacher = cpCacher; + SonarrCacher = sonarrCacher; + SickRageCacher = sickRageCacher; + RequestService = request; + SonarrApi = sonarrApi; + SonarrService = sonarrSettings; + SickRageService = sickRageService; + SickrageApi = srApi; + NotificationService = notify; + MusicBrainzApi = mbApi; + HeadphonesApi = hpApi; + HeadphonesService = hpService; + UsersToNotifyRepo = u; + EmailNotificationSettings = email; + IssueService = issue; + Analytics = a; + RequestLimitRepo = rl; + FaultQueue = tfQueue; + TvApi = new TvMazeApi(); + PlexContentRepository = content; + MovieSender = movieSender; + WatcherCacher = watcherCacher; + RadarrCacher = radarrCacher; + TraktApi = traktApi; + CustomizationSettings = cus; + EmbyChecker = embyChecker; + EmbyContentRepository = embyContent; + EmbySettings = embySettings; + + Get["SearchIndex", "/", true] = async (x, ct) => await RequestLoad(); + + Get["movie/{searchTerm}", true] = async (x, ct) => await SearchMovie((string)x.searchTerm); + Get["tv/{searchTerm}", true] = async (x, ct) => await SearchTvShow((string)x.searchTerm); + Get["music/{searchTerm}", true] = async (x, ct) => await SearchAlbum((string)x.searchTerm); + Get["music/coverArt/{id}"] = p => GetMusicBrainzCoverArt((string)p.id); + + 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); + Post["request/tvEpisodes", true] = async (x, ct) => await RequestTvShow(0, "episode"); + Post["request/album", true] = async (x, ct) => await RequestAlbum((string)Request.Form.albumId); + + 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 PlexContentRepository { get; } + private IRepository EmbyContentRepository { get; } + private TvMazeApi TvApi { get; } + private IPlexApi PlexApi { get; } + private TheMovieDbApi MovieApi { get; } + private INotificationService NotificationService { get; } + private ISonarrApi SonarrApi { get; } + private ISickRageApi SickrageApi { get; } + private IRequestService RequestService { get; } + private ICacheProvider Cache { get; } + private ISettingsService Auth { get; } + private ISettingsService EmbySettings { get; } + private ISettingsService PlexService { get; } + private ISettingsService PrService { get; } + private ISettingsService SonarrService { get; } + private ISettingsService SickRageService { get; } + private ISettingsService HeadphonesService { get; } + private ISettingsService EmailNotificationSettings { get; } + private IAvailabilityChecker PlexChecker { get; } + private IEmbyAvailabilityChecker EmbyChecker { get; } + private ICouchPotatoCacher CpCacher { get; } + private ISonarrCacher SonarrCacher { get; } + private ISickRageCacher SickRageCacher { get; } + private IMusicBrainzApi MusicBrainzApi { get; } + private IHeadphonesApi HeadphonesApi { get; } + private IRepository UsersToNotifyRepo { get; } + private IIssueService IssueService { get; } + private IAnalytics Analytics { get; } + private ITransientFaultQueue FaultQueue { get; } + private IRepository RequestLimitRepo { get; } + private IRadarrCacher RadarrCacher { get; } + private ISettingsService CustomizationSettings { get; } + private static Logger Log = LogManager.GetCurrentClassLogger(); + + private async Task RequestLoad() + { + + var settings = await PrService.GetSettingsAsync(); + var custom = await CustomizationSettings.GetSettingsAsync(); + var searchViewModel = new SearchLoadViewModel + { + Settings = settings, + CustomizationSettings = custom + }; + + + return View["Search/Index", searchViewModel]; + } + + private async Task UpcomingMovies() + { + Analytics.TrackEventAsync(Category.Search, Action.Movie, "Upcoming", Username, + CookieHelper.GetAnalyticClientId(Cookies)); + return await ProcessMovies(MovieSearchType.Upcoming, string.Empty); + } + + private async Task CurrentlyPlayingMovies() + { + Analytics.TrackEventAsync(Category.Search, Action.Movie, "CurrentlyPlaying", Username, + CookieHelper.GetAnalyticClientId(Cookies)); + return await ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty); + } + + private async Task SearchMovie(string searchTerm) + { + Analytics.TrackEventAsync(Category.Search, Action.Movie, searchTerm, Username, + CookieHelper.GetAnalyticClientId(Cookies)); + 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 ProcessMovies(MovieSearchType searchType, string searchTerm) + { + List apiMovies; + + switch (searchType) + { + case MovieSearchType.Search: + var movies = await MovieApi.SearchMovie(searchTerm).ConfigureAwait(false); + apiMovies = movies.Select(x => + new MovieResult + { + Adult = x.Adult, + BackdropPath = x.BackdropPath, + GenreIds = x.GenreIds, + Id = x.Id, + OriginalLanguage = x.OriginalLanguage, + OriginalTitle = x.OriginalTitle, + Overview = x.Overview, + Popularity = x.Popularity, + PosterPath = x.PosterPath, + ReleaseDate = x.ReleaseDate, + Title = x.Title, + Video = x.Video, + VoteAverage = x.VoteAverage, + VoteCount = x.VoteCount + }) + .ToList(); + break; + case MovieSearchType.CurrentlyPlaying: + apiMovies = await MovieApi.GetCurrentPlayingMovies(); + break; + case MovieSearchType.Upcoming: + apiMovies = await MovieApi.GetUpcomingMovies(); + break; + default: + apiMovies = new List(); + break; + } + + var allResults = await RequestService.GetAllAsync(); + allResults = allResults.Where(x => x.Type == RequestType.Movie); + + var distinctResults = allResults.DistinctBy(x => x.ProviderId); + var dbMovies = distinctResults.ToDictionary(x => x.ProviderId); + + + var cpCached = CpCacher.QueuedIds(); + var watcherCached = WatcherCacher.QueuedIds(); + var radarrCached = RadarrCacher.QueuedIds(); + + var viewMovies = new List(); + var counter = 0; + foreach (var movie in apiMovies) + { + var viewMovie = new SearchMovieViewModel + { + Adult = movie.Adult, + BackdropPath = movie.BackdropPath, + GenreIds = movie.GenreIds, + Id = movie.Id, + OriginalLanguage = movie.OriginalLanguage, + OriginalTitle = movie.OriginalTitle, + Overview = movie.Overview, + Popularity = movie.Popularity, + PosterPath = movie.PosterPath, + ReleaseDate = movie.ReleaseDate, + Title = movie.Title, + Video = movie.Video, + VoteAverage = movie.VoteAverage, + VoteCount = movie.VoteCount + }; + + 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 plexSettings = await PlexService.GetSettingsAsync(); + var embySettings = await EmbySettings.GetSettingsAsync(); + if (plexSettings.Enable) + { + var content = PlexContentRepository.GetAll(); + var plexMovies = PlexChecker.GetPlexMovies(content); + + var plexMovie = PlexChecker.GetMovie(plexMovies.ToArray(), movie.Title, + movie.ReleaseDate?.Year.ToString(), + viewMovie.ImdbId); + if (plexMovie != null) + { + viewMovie.Available = true; + viewMovie.PlexUrl = plexMovie.Url; + } + } + if (embySettings.Enable) + { + var embyContent = EmbyContentRepository.GetAll(); + var embyMovies = EmbyChecker.GetEmbyMovies(embyContent); + + var embyMovie = EmbyChecker.GetMovie(embyMovies.ToArray(), movie.Title, + movie.ReleaseDate?.Year.ToString(), viewMovie.ImdbId); + if (embyMovie != null) + { + viewMovie.Available = true; + } + } + else if (dbMovies.ContainsKey(movie.Id) && canSee) // compare to the requests db + { + var dbm = dbMovies[movie.Id]; + + viewMovie.Requested = true; + viewMovie.Approved = dbm.Approved; + viewMovie.Available = dbm.Available; + } + else if (cpCached.Contains(movie.Id) && canSee) // compare to the couchpotato db + { + viewMovie.Approved = true; + viewMovie.Requested = true; + } + else if (watcherCached.Contains(viewMovie.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); + } + + return Response.AsJson(viewMovies); + } + + private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests, + Dictionary moviesInDb) + { + if (usersCanViewOnlyOwnRequests) + { + var result = moviesInDb.FirstOrDefault(x => x.Value.ProviderId == movieId); + return result.Value == null || result.Value.UserHasRequested(Username); + } + + return true; + } + + private async Task ProcessShows(ShowSearchType type) + { + var shows = new List(); + 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> MapToTvModel(List 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 = PlexChecker.GetPlexTvShows(content).ToList(); + + foreach (var show in shows) + { + if (plexSettings.AdvancedSearch) + { + providerId = show.Id.ToString(); + } + + var plexShow = PlexChecker.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 SearchTvShow(string searchTerm) + { + + Analytics.TrackEventAsync(Category.Search, Action.TvShow, searchTerm, Username, + CookieHelper.GetAnalyticClientId(Cookies)); + var plexSettings = await PlexService.GetSettingsAsync(); + var embySettings = await EmbySettings.GetSettingsAsync(); + var prSettings = await PrService.GetSettingsAsync(); + var providerId = string.Empty; + + var apiTv = new List(); + await Task.Factory.StartNew(() => new TvMazeApi().Search(searchTerm)).ContinueWith((t) => + { + apiTv = t.Result; + }); + + 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); + + if (!apiTv.Any()) + { + return Response.AsJson(""); + } + + var sonarrCached = SonarrCacher.QueuedIds(); + var sickRageCache = SickRageCacher.QueuedIds(); // consider just merging sonarr/sickrage arrays + var content = PlexContentRepository.GetAll(); + var plexTvShows = PlexChecker.GetPlexTvShows(content); + var embyContent = EmbyContentRepository.GetAll(); + var embyCached = EmbyChecker.GetEmbyTvShows(embyContent); + + var viewTv = new List(); + foreach (var t in apiTv) + { + if (!(t.show.externals?.thetvdb.HasValue) ?? false) + { + continue; + } + var banner = t.show.image?.medium; + if (!string.IsNullOrEmpty(banner)) + { + banner = banner.Replace("http", "https"); // Always use the Https banners + } + + var viewT = new SearchTvShowViewModel + { + Banner = banner, + FirstAired = t.show.premiered, + Id = t.show.externals?.thetvdb ?? 0, + ImdbId = t.show.externals?.imdb, + Network = t.show.network?.name, + NetworkId = t.show.network?.id.ToString(), + Overview = t.show.summary.RemoveHtml(), + Rating = t.score.ToString(CultureInfo.CurrentUICulture), + Runtime = t.show.runtime.ToString(), + SeriesId = t.show.id, + SeriesName = t.show.name, + Status = t.show.status, + DisableTvRequestsByEpisode = prSettings.DisableTvRequestsByEpisode, + DisableTvRequestsBySeason = prSettings.DisableTvRequestsBySeason, + EnableTvRequestsForOnlySeries = (prSettings.DisableTvRequestsByEpisode && prSettings.DisableTvRequestsBySeason) + }; + + providerId = viewT.Id.ToString(); + + if (embySettings.Enable) + { + var embyShow = EmbyChecker.GetTvShow(embyCached.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId); + if (embyShow != null) + { + viewT.Available = true; + } + } + if (plexSettings.Enable) + { + var plexShow = PlexChecker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), + providerId); + if (plexShow != null) + { + viewT.Available = true; + viewT.PlexUrl = plexShow.Url; + } + } + + if (t.show?.externals?.thetvdb != null && !viewT.Available) + { + var tvdbid = (int)t.show.externals.thetvdb; + if (dbTv.ContainsKey(tvdbid)) + { + var dbt = dbTv[tvdbid]; + + viewT.Requested = true; + viewT.Episodes = dbt.Episodes.ToList(); + viewT.Approved = dbt.Approved; + } + if (sonarrCached.Select(x => x.TvdbId).Contains(tvdbid) || sickRageCache.Contains(tvdbid)) + // compare to the sonarr/sickrage db + { + viewT.Requested = true; + } + } + + viewTv.Add(viewT); + } + + return Response.AsJson(viewTv); + } + + private async Task SearchAlbum(string searchTerm) + { + Analytics.TrackEventAsync(Category.Search, Action.Album, searchTerm, Username, + CookieHelper.GetAnalyticClientId(Cookies)); + var apiAlbums = new List(); + await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) => + { + apiAlbums = t.Result.releases ?? new List(); + }); + + var allResults = await RequestService.GetAllAsync(); + allResults = allResults.Where(x => x.Type == RequestType.Album); + + var dbAlbum = allResults.ToDictionary(x => x.MusicBrainzId); + + var content = PlexContentRepository.GetAll(); + var plexAlbums = PlexChecker.GetPlexAlbums(content); + + var viewAlbum = new List(); + foreach (var a in apiAlbums) + { + var viewA = new SearchMusicViewModel + { + Title = a.title, + Id = a.id, + Artist = a.ArtistCredit?.Select(x => x.artist?.name).FirstOrDefault(), + Overview = a.disambiguation, + ReleaseDate = a.date, + TrackCount = a.TrackCount, + ReleaseType = a.status, + Country = a.country + }; + + DateTime release; + DateTimeHelper.CustomParse(a.ReleaseEvents?.FirstOrDefault()?.date, out release); + var artist = a.ArtistCredit?.FirstOrDefault()?.artist; + var plexAlbum = PlexChecker.GetAlbum(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name); + if (plexAlbum != null) + { + viewA.Available = true; + viewA.PlexUrl = plexAlbum.Url; + } + if (!string.IsNullOrEmpty(a.id) && dbAlbum.ContainsKey(a.id)) + { + var dba = dbAlbum[a.id]; + + viewA.Requested = true; + viewA.Approved = dba.Approved; + viewA.Available = dba.Available; + } + + viewAlbum.Add(viewA); + } + return Response.AsJson(viewAlbum); + } + + private async Task RequestMovie(int movieId) + { + if (Security.HasPermissions(User, Permissions.ReadOnlyUser) || !Security.HasPermissions(User, Permissions.RequestMovie)) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = "Sorry, you do not have the correct permissions to request a movie!" + }); + } + var settings = await PrService.GetSettingsAsync(); + if (!await CheckRequestLimit(settings, RequestType.Movie)) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = "You have reached your weekly request limit for Movies! Please contact your admin." + }); + } + + Analytics.TrackEventAsync(Category.Search, Action.Request, "Movie", Username, + CookieHelper.GetAnalyticClientId(Cookies)); + var movieInfo = await MovieApi.GetMovieInformation(movieId); + if (movieInfo == null) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = "There was an issue adding this movie!" + }); + } + var fullMovieName = + $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}"; + + var existingRequest = await RequestService.CheckRequestAsync(movieId); + if (existingRequest != null) + { + // check if the current user is already marked as a requester for this movie, if not, add them + if (!existingRequest.UserHasRequested(Username)) + { + existingRequest.RequestedUsers.Add(Username); + await RequestService.UpdateRequestAsync(existingRequest); + } + + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = + Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests) + ? $"{fullMovieName} {Ombi.UI.Resources.UI.Search_SuccessfullyAdded}" + : $"{fullMovieName} {Resources.UI.Search_AlreadyRequested}" + }); + } + + try + { + + var content = PlexContentRepository.GetAll(); + var movies = PlexChecker.GetPlexMovies(content); + if (PlexChecker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullMovieName} is already in Plex!" + }); + } + } + catch (Exception e) + { + Log.Error(e); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullMovieName) + }); + } + //#endif + + var model = new RequestedModel + { + ProviderId = movieInfo.Id, + Type = RequestType.Movie, + Overview = movieInfo.Overview, + ImdbId = movieInfo.ImdbId, + PosterPath = movieInfo.PosterPath, + Title = movieInfo.Title, + ReleaseDate = movieInfo.ReleaseDate ?? DateTime.MinValue, + Status = movieInfo.Status, + RequestedDate = DateTime.UtcNow, + Approved = false, + RequestedUsers = new List { Username }, + Issues = IssueState.None, + + }; + try + { + if (ShouldAutoApprove(RequestType.Movie)) + { + model.Approved = true; + + var result = await MovieSender.Send(model); + if (result.Result) + { + return await AddRequest(model, settings, + $"{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) + { + + return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); + } + + return Response.AsJson(new JsonResponseModel + { + Result = false, + Message = Resources.UI.Search_CouchPotatoError + }); + } + + + return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); + } + catch (Exception e) + { + Log.Fatal(e); + await FaultQueue.QueueItemAsync(model, movieInfo.Id.ToString(), RequestType.Movie, FaultType.RequestFault, e.Message); + + await NotificationService.Publish(new NotificationModel + { + DateTime = DateTime.Now, + User = Username, + RequestType = RequestType.Movie, + Title = model.Title, + NotificationType = NotificationType.ItemAddedToFaultQueue + }); + + return Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}" + }); + } + } + + /// + /// Requests the tv show. + /// + /// The show identifier. + /// The seasons. + /// + private async Task RequestTvShow(int showId, string seasons) + { + if (Security.HasPermissions(User, Permissions.ReadOnlyUser) || !Security.HasPermissions(User, Permissions.RequestTvShow)) + { + return + Response.AsJson(new JsonResponseModel() + { + Result = false, + Message = "Sorry, you do not have the correct permissions to request a TV Show!" + }); + } + // Get the JSON from the request + var req = (Dictionary.ValueCollection)Request.Form.Values; + EpisodeRequestModel episodeModel = null; + if (req.Count == 1) + { + var json = req.FirstOrDefault()?.ToString(); + episodeModel = JsonConvert.DeserializeObject(json); // Convert it into the object + } + var episodeRequest = false; + + var settings = await PrService.GetSettingsAsync(); + if (!await CheckRequestLimit(settings, RequestType.TvShow)) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = Resources.UI.Search_WeeklyRequestLimitTVShow + }); + } + Analytics.TrackEventAsync(Category.Search, Action.Request, "TvShow", Username, + CookieHelper.GetAnalyticClientId(Cookies)); + + var sonarrSettings = SonarrService.GetSettingsAsync(); + + // This means we are requesting an episode rather than a whole series or season + if (episodeModel != null) + { + episodeRequest = true; + showId = episodeModel.ShowId; + var s = await sonarrSettings; + if (!s.Enabled) + { + return + Response.AsJson(new JsonResponseModel + { + Message = + "This is currently only supported with Sonarr, Please enable Sonarr for this feature", + Result = false + }); + } + } + + var showInfo = TvApi.ShowLookupByTheTvDbId(showId); + DateTime firstAir; + 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 = posterPath, + Title = showInfo.name, + ReleaseDate = firstAir, + Status = showInfo.status, + RequestedDate = DateTime.UtcNow, + Approved = false, + RequestedUsers = new List { Username }, + Issues = IssueState.None, + ImdbId = showInfo.externals?.imdb ?? string.Empty, + SeasonCount = showInfo.Season.Count, + TvDbId = showId.ToString() + }; + + var seasonsList = new List(); + switch (seasons) + { + case "first": + seasonsList.Add(1); + model.SeasonsRequested = "First"; + break; + case "latest": + seasonsList.Add(model.SeasonCount); + model.SeasonsRequested = "Latest"; + break; + case "all": + model.SeasonsRequested = "All"; + break; + case "episode": + model.Episodes = new List(); + + foreach (var ep in episodeModel?.Episodes ?? new Models.EpisodesModel[0]) + { + model.Episodes.Add(new EpisodesModel + { + EpisodeNumber = ep.EpisodeNumber, + SeasonNumber = ep.SeasonNumber + }); + } + Analytics.TrackEventAsync(Category.Requests, Action.TvShow, $"Episode request for {model.Title}", + Username, CookieHelper.GetAnalyticClientId(Cookies)); + break; + default: + model.SeasonsRequested = seasons; + var split = seasons.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var seasonsCount = new int[split.Length]; + for (var i = 0; i < split.Length; i++) + { + int tryInt; + int.TryParse(split[i], out tryInt); + seasonsCount[i] = tryInt; + } + seasonsList.AddRange(seasonsCount); + break; + } + + model.SeasonList = seasonsList.ToArray(); + + // check if the show/episodes have already been requested + var existingRequest = await RequestService.CheckRequestAsync(showId); + var difference = new List(); + if (existingRequest != null) + { + if (episodeRequest) + { + // Make sure we are not somehow adding dupes + difference = GetListDifferences(existingRequest.Episodes, episodeModel.Episodes).ToList(); + if (difference.Any()) + { + // Convert the request into the correct shape + var newEpisodes = episodeModel.Episodes?.Select(x => new EpisodesModel + { + SeasonNumber = x.SeasonNumber, + EpisodeNumber = x.EpisodeNumber + }); + + // Add it to the existing requests + existingRequest.Episodes.AddRange(newEpisodes ?? Enumerable.Empty()); + + // 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); + } + else + { + // We no episodes to approve + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" + }); + } + } + else if (model.SeasonList.Except(existingRequest.SeasonList).Any()) + { + // This is a season being requested that we do not yet have + // Let's just continue + } + else + { + return await AddUserToRequest(existingRequest, settings, fullShowName); + } + } + + try + { + + var plexSettings = await PlexService.GetSettingsAsync(); + if (plexSettings.Enable) + { + var content = PlexContentRepository.GetAll(); + var shows = PlexChecker.GetPlexTvShows(content); + + var providerId = string.Empty; + if (plexSettings.AdvancedSearch) + { + providerId = showId.ToString(); + } + if (episodeRequest) + { + var cachedEpisodesTask = await PlexChecker.GetEpisodes(); + var cachedEpisodes = cachedEpisodesTask.ToList(); + foreach (var d in difference) // difference is from an existing request + { + if ( + cachedEpisodes.Any( + x => + x.SeasonNumber == d.SeasonNumber && x.EpisodeNumber == d.EpisodeNumber && + x.ProviderId == providerId)) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = + $"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {Resources.UI.Search_AlreadyInPlex}" + }); + } + } + + var diff = await GetEpisodeRequestDifference(showId, model); + model.Episodes = diff.ToList(); + } + else + { + if (plexSettings.EnableTvEpisodeSearching) + { + foreach (var s in showInfo.Season) + { + var result = PlexChecker.IsEpisodeAvailable(showId.ToString(), s.SeasonNumber, + s.EpisodeNumber); + if (result) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" + }); + } + } + } + else if (PlexChecker.IsTvShowAvailable(shows.ToArray(), showInfo.name, + showInfo.premiered?.Substring(0, 4), + providerId, model.SeasonList)) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" + }); + } + } + } + var embySettings = await EmbySettings.GetSettingsAsync(); + if (embySettings.Enable) + { + var embyContent = EmbyContentRepository.GetAll(); + var embyMovies = EmbyChecker.GetEmbyTvShows(embyContent); + var providerId = showId.ToString(); + if (episodeRequest) + { + var cachedEpisodesTask = await EmbyChecker.GetEpisodes(); + var cachedEpisodes = cachedEpisodesTask.ToList(); + foreach (var d in difference) // difference is from an existing request + { + if ( + cachedEpisodes.Any( + x => + x.SeasonNumber == d.SeasonNumber && x.EpisodeNumber == d.EpisodeNumber && + x.ProviderId == providerId)) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = + $"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {Resources.UI.Search_AlreadyInPlex}" + }); + } + } + + var diff = await GetEpisodeRequestDifference(showId, model); + model.Episodes = diff.ToList(); + } + else + { + if (embySettings.EnableEpisodeSearching) + { + foreach (var s in showInfo.Season) + { + var result = EmbyChecker.IsEpisodeAvailable(showId.ToString(), s.SeasonNumber, + s.EpisodeNumber); + if (result) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullShowName} is already in Emby!" + }); + } + } + } + else if (EmbyChecker.IsTvShowAvailable(embyMovies.ToArray(), showInfo.name, + showInfo.premiered?.Substring(0, 4), + providerId, model.SeasonList)) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullShowName} is already in Emby!" + }); + } + } + } + } + catch (Exception) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullShowName) + }); + } + + if (showInfo.externals?.thetvdb == null) + { + await FaultQueue.QueueItemAsync(model, showInfo.id.ToString(), RequestType.TvShow, FaultType.MissingInformation, "We do not have a TheTVDBId from TVMaze"); + await NotificationService.Publish(new NotificationModel + { + DateTime = DateTime.Now, + User = Username, + RequestType = RequestType.TvShow, + Title = model.Title, + NotificationType = NotificationType.ItemAddedToFaultQueue + }); + return Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" + }); + } + + model.ProviderId = showInfo.externals?.thetvdb ?? 0; + + try + { + if (ShouldAutoApprove(RequestType.TvShow)) + { + return await SendTv(model, sonarrSettings, existingRequest, fullShowName, settings); + } + return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); + } + catch (Exception e) + { + await FaultQueue.QueueItemAsync(model, showInfo.id.ToString(), RequestType.TvShow, FaultType.RequestFault, e.Message); + await NotificationService.Publish(new NotificationModel + { + DateTime = DateTime.Now, + User = Username, + RequestType = RequestType.TvShow, + Title = model.Title, + NotificationType = NotificationType.ItemAddedToFaultQueue + }); + Log.Error(e); + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" + }); + } + } + + private async Task AddUserToRequest(RequestedModel existingRequest, PlexRequestSettings settings, + string fullShowName, bool episodeReq = false) + { + // check if the current user is already marked as a requester for this show, if not, add them + if (!existingRequest.UserHasRequested(Username)) + { + existingRequest.RequestedUsers.Add(Username); + } + if (Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests) || episodeReq) + { + return + await + UpdateRequest(existingRequest, settings, + $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); + } + + return + await UpdateRequest(existingRequest, settings, $"{fullShowName} {Resources.UI.Search_AlreadyRequested}"); + } + + private bool ShouldSendNotification(RequestType type, PlexRequestSettings prSettings) + { + var sendNotification = ShouldAutoApprove(type) + ? !prSettings.IgnoreNotifyForAutoApprovedRequests + : true; + + if (IsAdmin) + { + sendNotification = false; // Don't bother sending a notification if the user is an admin + + } + return sendNotification; + } + + + private async Task RequestAlbum(string releaseId) + { + if (Security.HasPermissions(User, Permissions.ReadOnlyUser) || !Security.HasPermissions(User, Permissions.RequestMusic)) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = "Sorry, you do not have the correct permissions to request music!" + }); + } + + var settings = await PrService.GetSettingsAsync(); + if (!await CheckRequestLimit(settings, RequestType.Album)) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = Resources.UI.Search_WeeklyRequestLimitAlbums + }); + } + Analytics.TrackEventAsync(Category.Search, Action.Request, "Album", Username, + CookieHelper.GetAnalyticClientId(Cookies)); + var existingRequest = await RequestService.CheckRequestAsync(releaseId); + + if (existingRequest != null) + { + if (!existingRequest.UserHasRequested(Username)) + { + existingRequest.RequestedUsers.Add(Username); + await RequestService.UpdateRequestAsync(existingRequest); + } + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = + Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests) + ? $"{existingRequest.Title} {Resources.UI.Search_SuccessfullyAdded}" + : $"{existingRequest.Title} {Resources.UI.Search_AlreadyRequested}" + }); + } + + var albumInfo = MusicBrainzApi.GetAlbum(releaseId); + DateTime release; + DateTimeHelper.CustomParse(albumInfo.ReleaseEvents?.FirstOrDefault()?.date, out release); + + var artist = albumInfo.ArtistCredits?.FirstOrDefault()?.artist; + if (artist == null) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = Resources.UI.Search_MusicBrainzError + }); + } + + + var content = PlexContentRepository.GetAll(); + var albums = PlexChecker.GetPlexAlbums(content); + var alreadyInPlex = PlexChecker.IsAlbumAvailable(albums.ToArray(), albumInfo.title, release.ToString("yyyy"), + artist.name); + + if (alreadyInPlex) + { + return Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{albumInfo.title} {Resources.UI.Search_AlreadyInPlex}" + }); + } + + var img = GetMusicBrainzCoverArt(albumInfo.id); + + var model = new RequestedModel + { + Title = albumInfo.title, + MusicBrainzId = albumInfo.id, + Overview = albumInfo.disambiguation, + PosterPath = img, + Type = RequestType.Album, + ProviderId = 0, + RequestedUsers = new List { Username }, + Status = albumInfo.status, + Issues = IssueState.None, + RequestedDate = DateTime.UtcNow, + ReleaseDate = release, + ArtistName = artist.name, + ArtistId = artist.id + }; + + try + { + if (ShouldAutoApprove(RequestType.Album)) + { + model.Approved = true; + var hpSettings = HeadphonesService.GetSettings(); + + if (!hpSettings.Enabled) + { + await RequestService.AddRequestAsync(model); + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}" + }); + } + + var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService); + await sender.AddAlbum(model); + return await AddRequest(model, settings, $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}"); + } + + return await AddRequest(model, settings, $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}"); + } + catch (Exception e) + { + Log.Error(e); + await FaultQueue.QueueItemAsync(model, albumInfo.id, RequestType.Album, FaultType.RequestFault, e.Message); + + await NotificationService.Publish(new NotificationModel + { + DateTime = DateTime.Now, + User = Username, + RequestType = RequestType.Album, + Title = model.Title, + NotificationType = NotificationType.ItemAddedToFaultQueue + }); + throw; + } + } + + private string GetMusicBrainzCoverArt(string id) + { + var coverArt = MusicBrainzApi.GetCoverArt(id); + var firstImage = coverArt?.images?.FirstOrDefault(); + var img = string.Empty; + + if (firstImage != null) + { + img = firstImage.thumbnails?.small ?? firstImage.image; + } + + return img; + } + + private Response GetSeasons() + { + var seriesId = (int)Request.Query.tvId; + var show = TvApi.ShowLookupByTheTvDbId(seriesId); + var seasons = TvApi.GetSeasons(show.id); + var model = seasons.Select(x => x.number); + return Response.AsJson(model); + } + + private async Task GetEpisodes() + { + var seriesId = (int)Request.Query.tvId; + var model = await GetEpisodes(seriesId); + + return Response.AsJson(model); + } + + private async Task> GetEpisodes(int providerId) + { + var s = await SonarrService.GetSettingsAsync(); + var sonarrEnabled = s.Enabled; + var allResults = await RequestService.GetAllAsync(); + + var seriesTask = Task.Run( + () => + { + if (sonarrEnabled) + { + var allSeries = SonarrApi.GetSeries(s.ApiKey, s.FullUri); + var selectedSeries = allSeries.FirstOrDefault(x => x.tvdbId == providerId) ?? new Series(); + return selectedSeries; + } + return new Series(); + }); + + var model = new List(); + + var requests = allResults as RequestedModel[] ?? allResults.ToArray(); + + var existingRequest = requests.FirstOrDefault(x => x.Type == RequestType.TvShow && x.TvDbId == providerId.ToString()); + var show = await Task.Run(() => TvApi.ShowLookupByTheTvDbId(providerId)); + var tvMazeEpisodesTask = await Task.Run(() => TvApi.EpisodeLookup(show.id)); + var tvMazeEpisodes = tvMazeEpisodesTask.ToList(); + + var sonarrEpisodes = new List(); + if (sonarrEnabled) + { + var sonarrSeries = await seriesTask; + var sonarrEp = SonarrApi.GetEpisodes(sonarrSeries.id.ToString(), s.ApiKey, s.FullUri); + sonarrEpisodes = sonarrEp?.ToList() ?? new List(); + } + + var plexSettings = await PlexService.GetSettingsAsync(); + if (plexSettings.Enable) + { + var plexCacheTask = await PlexChecker.GetEpisodes(providerId); + var plexCache = plexCacheTask.ToList(); + foreach (var ep in tvMazeEpisodes) + { + var requested = existingRequest?.Episodes + .Any(episodesModel => + ep.number == episodesModel.EpisodeNumber && + ep.season == episodesModel.SeasonNumber) ?? false; + + var alreadyInPlex = plexCache.Any(x => x.EpisodeNumber == ep.number && x.SeasonNumber == ep.season); + var inSonarr = + sonarrEpisodes.Any(x => x.seasonNumber == ep.season && x.episodeNumber == ep.number && x.hasFile); + + model.Add(new EpisodeListViewModel + { + Id = show.id, + SeasonNumber = ep.season, + EpisodeNumber = ep.number, + Requested = requested || alreadyInPlex || inSonarr, + Name = ep.name, + EpisodeId = ep.id + }); + } + } + var embySettings = await EmbySettings.GetSettingsAsync(); + if (embySettings.Enable) + { + var embyCacheTask = await EmbyChecker.GetEpisodes(providerId); + var cache = embyCacheTask.ToList(); + foreach (var ep in tvMazeEpisodes) + { + var requested = existingRequest?.Episodes + .Any(episodesModel => + ep.number == episodesModel.EpisodeNumber && + ep.season == episodesModel.SeasonNumber) ?? false; + + var alreadyInEmby = cache.Any(x => x.EpisodeNumber == ep.number && x.SeasonNumber == ep.season); + var inSonarr = + sonarrEpisodes.Any(x => x.seasonNumber == ep.season && x.episodeNumber == ep.number && x.hasFile); + + model.Add(new EpisodeListViewModel + { + Id = show.id, + SeasonNumber = ep.season, + EpisodeNumber = ep.number, + Requested = requested || alreadyInEmby || inSonarr, + Name = ep.name, + EpisodeId = ep.id + }); + } + } + return model; + + } + + public async Task CheckRequestLimit(PlexRequestSettings s, RequestType type) + { + if (IsAdmin) + return true; + + if (Security.HasPermissions(User, Permissions.BypassRequestLimit)) + return true; + + var requestLimit = GetRequestLimitForType(type, s); + if (requestLimit == 0) + { + return true; + } + + var limit = await RequestLimitRepo.GetAllAsync(); + var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == type); + if (usersLimit == null) + { + // Have not set a requestLimit yet + return true; + } + + return requestLimit > usersLimit.RequestCount; + } + + private int GetRequestLimitForType(RequestType type, PlexRequestSettings s) + { + int requestLimit; + switch (type) + { + case RequestType.Movie: + requestLimit = s.MovieWeeklyRequestLimit; + break; + case RequestType.TvShow: + requestLimit = s.TvWeeklyRequestLimit; + break; + case RequestType.Album: + requestLimit = s.AlbumWeeklyRequestLimit; + break; + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + return requestLimit; + } + + private async Task AddRequest(RequestedModel model, PlexRequestSettings settings, string message) + { + await RequestService.AddRequestAsync(model); + + if (ShouldSendNotification(model.Type, settings)) + { + var notificationModel = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest, + RequestType = model.Type, + ImgSrc = model.Type == RequestType.Movie ? $"https://image.tmdb.org/t/p/w300/{model.PosterPath}" : model.PosterPath + }; + await NotificationService.Publish(notificationModel); + } + + var limit = await RequestLimitRepo.GetAllAsync(); + var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == model.Type); + if (usersLimit == null) + { + await RequestLimitRepo.InsertAsync(new RequestLimit + { + Username = Username, + RequestType = model.Type, + FirstRequestDate = DateTime.UtcNow, + RequestCount = 1 + }); + } + else + { + usersLimit.RequestCount++; + await RequestLimitRepo.UpdateAsync(usersLimit); + } + + return Response.AsJson(new JsonResponseModel { Result = true, Message = message }); + } + + private async Task UpdateRequest(RequestedModel model, PlexRequestSettings settings, string message) + { + await RequestService.UpdateRequestAsync(model); + + if (ShouldSendNotification(model.Type, settings)) + { + var notificationModel = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest, + RequestType = model.Type, + ImgSrc = model.Type == RequestType.Movie ? $"https://image.tmdb.org/t/p/w300/{model.PosterPath}" : model.PosterPath + }; + await NotificationService.Publish(notificationModel); + } + + var limit = await RequestLimitRepo.GetAllAsync(); + var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == model.Type); + if (usersLimit == null) + { + await RequestLimitRepo.InsertAsync(new RequestLimit + { + Username = Username, + RequestType = model.Type, + FirstRequestDate = DateTime.UtcNow, + RequestCount = 1 + }); + } + else + { + usersLimit.RequestCount++; + await RequestLimitRepo.UpdateAsync(usersLimit); + } + + return Response.AsJson(new JsonResponseModel { Result = true, Message = message }); + } + + private IEnumerable GetListDifferences(IEnumerable existing, IEnumerable request) + { + var newRequest = request + .Select(r => + new EpisodesModel + { + SeasonNumber = r.SeasonNumber, + EpisodeNumber = r.EpisodeNumber + }).ToList(); + + return newRequest.Except(existing); + } + + private async Task> GetEpisodeRequestDifference(int showId, RequestedModel model) + { + var episodes = await GetEpisodes(showId); + var availableEpisodes = episodes.Where(x => x.Requested).ToList(); + var available = availableEpisodes.Select(a => new EpisodesModel { EpisodeNumber = a.EpisodeNumber, SeasonNumber = a.SeasonNumber }).ToList(); + + var diff = model.Episodes.Except(available); + return diff; + } + + public bool ShouldAutoApprove(RequestType requestType) + { + var admin = Security.HasPermissions(Context.CurrentUser, Permissions.Administrator); + // if the user is an admin, they go ahead and allow auto-approval + if (admin) return true; + + // check by request type if the category requires approval or not + switch (requestType) + { + case RequestType.Movie: + return Security.HasPermissions(User, Permissions.AutoApproveMovie); + case RequestType.TvShow: + return Security.HasPermissions(User, Permissions.AutoApproveTv); + case RequestType.Album: + return Security.HasPermissions(User, Permissions.AutoApproveAlbum); + default: + return false; + } + } + + private enum ShowSearchType + { + Popular, + Anticipated, + MostWatched, + Trending + } + + private async Task SendTv(RequestedModel model, Task 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())); + } + + 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 }); + } + } } diff --git a/Ombi.UI/Views/Admin/Emby.cshtml b/Ombi.UI/Views/Admin/Emby.cshtml index f0b05c979..7ebceaf14 100644 --- a/Ombi.UI/Views/Admin/Emby.cshtml +++ b/Ombi.UI/Views/Admin/Emby.cshtml @@ -47,6 +47,8 @@ + @Html.Checkbox(Model.EnableEpisodeSearching, "EnableEpisodeSearching", "Enable Episode Searching") +
diff --git a/Ombi.UI/Views/SystemStatus/Status.cshtml b/Ombi.UI/Views/SystemStatus/Status.cshtml index 8be76882a..71c960a6a 100644 --- a/Ombi.UI/Views/SystemStatus/Status.cshtml +++ b/Ombi.UI/Views/SystemStatus/Status.cshtml @@ -50,9 +50,9 @@ {
- + @**@
- + @**@ } else {