diff --git a/Ombi.Api/SonarrApi.cs b/Ombi.Api/SonarrApi.cs index 2485150b8..3cf8565e8 100644 --- a/Ombi.Api/SonarrApi.cs +++ b/Ombi.Api/SonarrApi.cs @@ -52,9 +52,9 @@ namespace Ombi.Api request.AddHeader("X-Api-Key", apiKey); var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetProfiles for Sonarr, Retrying {0}", timespan), new TimeSpan[] { - TimeSpan.FromSeconds (2), - TimeSpan.FromSeconds(5), - TimeSpan.FromSeconds(10) + TimeSpan.FromSeconds (1), + TimeSpan.FromSeconds(2), + TimeSpan.FromSeconds(5) }); var obj = policy.Execute(() => Api.ExecuteJson>(request, baseUrl)); @@ -68,9 +68,9 @@ namespace Ombi.Api request.AddHeader("X-Api-Key", apiKey); var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetRootFolders for Sonarr, Retrying {0}", timespan), new TimeSpan[] { - TimeSpan.FromSeconds (2), - TimeSpan.FromSeconds(5), - TimeSpan.FromSeconds(10) + TimeSpan.FromSeconds (1), + TimeSpan.FromSeconds(2), + TimeSpan.FromSeconds(5) }); var obj = policy.Execute(() => Api.ExecuteJson>(request, baseUrl)); diff --git a/Ombi.Core/CacheKeys.cs b/Ombi.Core/CacheKeys.cs index 39ed7a71b..32466e897 100644 --- a/Ombi.Core/CacheKeys.cs +++ b/Ombi.Core/CacheKeys.cs @@ -45,6 +45,8 @@ namespace Ombi.Core public const string CouchPotatoQualityProfiles = nameof(CouchPotatoQualityProfiles); public const string CouchPotatoQueued = nameof(CouchPotatoQueued); public const string WatcherQueued = nameof(WatcherQueued); + public const string GetCustomizationSettings = nameof(GetCustomizationSettings); + public const string GetEmbySettings = nameof(GetEmbySettings); public const string GetPlexRequestSettings = nameof(GetPlexRequestSettings); public const string LastestProductVersion = nameof(LastestProductVersion); public const string SonarrRootFolders = nameof(SonarrRootFolders); diff --git a/Ombi.Core/SettingModels/SickRageSettings.cs b/Ombi.Core/SettingModels/SickRageSettings.cs index 45982c624..2da269ee3 100644 --- a/Ombi.Core/SettingModels/SickRageSettings.cs +++ b/Ombi.Core/SettingModels/SickRageSettings.cs @@ -37,7 +37,7 @@ namespace Ombi.Core.SettingModels public Dictionary Qualities => new Dictionary { - { "default", "Use Deafult" }, + { "default", "Use Default" }, { "sdtv", "SD TV" }, { "sddvd", "SD DVD" }, { "hdtv", "HD TV" }, diff --git a/Ombi.Core/Setup.cs b/Ombi.Core/Setup.cs index f70749a40..1eb05837b 100644 --- a/Ombi.Core/Setup.cs +++ b/Ombi.Core/Setup.cs @@ -46,7 +46,7 @@ namespace Ombi.Core { Db = new DbConfiguration(new SqliteFactory()); var created = Db.CheckDb(); - TableCreation.CreateTables(Db.DbConnection()); + Db.DbConnection().CreateTables(); if (created) { @@ -55,7 +55,7 @@ namespace Ombi.Core else { // Shrink DB - TableCreation.Vacuum(Db.DbConnection()); + Db.DbConnection().Vacuum(); } // Add the new 'running' item into the scheduled jobs so we can check if the cachers are running @@ -113,6 +113,7 @@ namespace Ombi.Core try { Task.Run(() => { CacheSonarrQualityProfiles(mc); }); + Task.Run(() => { CacheRadarrQualityProfiles(mc); }); Task.Run(() => { CacheCouchPotatoQualityProfiles(mc); }); // we don't need to cache sickrage profiles, those are static } @@ -126,7 +127,6 @@ namespace Ombi.Core { try { - Log.Info("Executing GetSettings call to Sonarr for quality profiles"); var sonarrSettingsService = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), cacheProvider)); var sonarrSettings = sonarrSettingsService.GetSettings(); if (sonarrSettings.Enabled) @@ -144,11 +144,31 @@ namespace Ombi.Core } } + private void CacheRadarrQualityProfiles(ICacheProvider cacheProvider) + { + try + { + var radarrService = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), cacheProvider)); + var radarrSettings = radarrService.GetSettings(); + if (radarrSettings.Enabled) + { + Log.Info("Begin executing GetProfiles call to Radarr for quality profiles"); + RadarrApi radarrApi = new RadarrApi(); + var profiles = radarrApi.GetProfiles(radarrSettings.ApiKey, radarrSettings.FullUri); + cacheProvider.Set(CacheKeys.RadarrQualityProfiles, profiles); + Log.Info("Finished executing GetProfiles call to Radarr for quality profiles"); + } + } + catch (Exception ex) + { + Log.Error(ex, "Failed to cache Sonarr quality profiles!"); + } + } + private void CacheCouchPotatoQualityProfiles(ICacheProvider cacheProvider) { try { - Log.Info("Executing GetSettings call to CouchPotato for quality profiles"); var cpSettingsService = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), cacheProvider)); var cpSettings = cpSettingsService.GetSettings(); if (cpSettings.Enabled) diff --git a/Ombi.Services/Jobs/RecentlyAddedNewsletter/RecentlyAddedNewsletter.cs b/Ombi.Services/Jobs/RecentlyAddedNewsletter/RecentlyAddedNewsletter.cs index 0183058d0..e2bbf5ede 100644 --- a/Ombi.Services/Jobs/RecentlyAddedNewsletter/RecentlyAddedNewsletter.cs +++ b/Ombi.Services/Jobs/RecentlyAddedNewsletter/RecentlyAddedNewsletter.cs @@ -140,7 +140,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter var escapedHtml = new string(html.Where(c => !char.IsControl(c)).ToArray()); Log.Debug(escapedHtml); - SendNewsletter(newletterSettings, escapedHtml, testEmail); + SendNewsletter(newletterSettings, escapedHtml, testEmail, "New Content On Emby!"); } else { diff --git a/Ombi.Services/Jobs/Templates/RecentlyAddedTemplate.html b/Ombi.Services/Jobs/Templates/RecentlyAddedTemplate.html index 932aae99f..17766147b 100644 --- a/Ombi.Services/Jobs/Templates/RecentlyAddedTemplate.html +++ b/Ombi.Services/Jobs/Templates/RecentlyAddedTemplate.html @@ -151,7 +151,7 @@

-

Here is a list of Movies and TV Shows that have recently been added to Plex!

+

Here is a list of Movies and TV Shows that have recently been added!

diff --git a/Ombi.UI/Content/favicon/android-icon-144x144.png b/Ombi.UI/Content/favicon/android-icon-144x144.png new file mode 100644 index 000000000..5c32f5c66 Binary files /dev/null and b/Ombi.UI/Content/favicon/android-icon-144x144.png differ diff --git a/Ombi.UI/Content/favicon/android-icon-192x192.png b/Ombi.UI/Content/favicon/android-icon-192x192.png new file mode 100644 index 000000000..89a5b5a41 Binary files /dev/null and b/Ombi.UI/Content/favicon/android-icon-192x192.png differ diff --git a/Ombi.UI/Content/favicon/android-icon-36x36.png b/Ombi.UI/Content/favicon/android-icon-36x36.png new file mode 100644 index 000000000..3d4ee0379 Binary files /dev/null and b/Ombi.UI/Content/favicon/android-icon-36x36.png differ diff --git a/Ombi.UI/Content/favicon/android-icon-48x48.png b/Ombi.UI/Content/favicon/android-icon-48x48.png new file mode 100644 index 000000000..3f22d26b4 Binary files /dev/null and b/Ombi.UI/Content/favicon/android-icon-48x48.png differ diff --git a/Ombi.UI/Content/favicon/android-icon-72x72.png b/Ombi.UI/Content/favicon/android-icon-72x72.png new file mode 100644 index 000000000..fd76364cb Binary files /dev/null and b/Ombi.UI/Content/favicon/android-icon-72x72.png differ diff --git a/Ombi.UI/Content/favicon/android-icon-96x96.png b/Ombi.UI/Content/favicon/android-icon-96x96.png new file mode 100644 index 000000000..a362acfd8 Binary files /dev/null and b/Ombi.UI/Content/favicon/android-icon-96x96.png differ diff --git a/Ombi.UI/Content/favicon/apple-icon-114x114.png b/Ombi.UI/Content/favicon/apple-icon-114x114.png new file mode 100644 index 000000000..eb6b1fab2 Binary files /dev/null and b/Ombi.UI/Content/favicon/apple-icon-114x114.png differ diff --git a/Ombi.UI/Content/favicon/apple-icon-120x120.png b/Ombi.UI/Content/favicon/apple-icon-120x120.png new file mode 100644 index 000000000..af175eedb Binary files /dev/null and b/Ombi.UI/Content/favicon/apple-icon-120x120.png differ diff --git a/Ombi.UI/Content/favicon/apple-icon-144x144.png b/Ombi.UI/Content/favicon/apple-icon-144x144.png new file mode 100644 index 000000000..5c32f5c66 Binary files /dev/null and b/Ombi.UI/Content/favicon/apple-icon-144x144.png differ diff --git a/Ombi.UI/Content/favicon/apple-icon-152x152.png b/Ombi.UI/Content/favicon/apple-icon-152x152.png new file mode 100644 index 000000000..ff8f6284d Binary files /dev/null and b/Ombi.UI/Content/favicon/apple-icon-152x152.png differ diff --git a/Ombi.UI/Content/favicon/apple-icon-180x180.png b/Ombi.UI/Content/favicon/apple-icon-180x180.png new file mode 100644 index 000000000..473575679 Binary files /dev/null and b/Ombi.UI/Content/favicon/apple-icon-180x180.png differ diff --git a/Ombi.UI/Content/favicon/apple-icon-57x57.png b/Ombi.UI/Content/favicon/apple-icon-57x57.png new file mode 100644 index 000000000..d62ebe2f7 Binary files /dev/null and b/Ombi.UI/Content/favicon/apple-icon-57x57.png differ diff --git a/Ombi.UI/Content/favicon/apple-icon-60x60.png b/Ombi.UI/Content/favicon/apple-icon-60x60.png new file mode 100644 index 000000000..2eaed9413 Binary files /dev/null and b/Ombi.UI/Content/favicon/apple-icon-60x60.png differ diff --git a/Ombi.UI/Content/favicon/apple-icon-72x72.png b/Ombi.UI/Content/favicon/apple-icon-72x72.png new file mode 100644 index 000000000..fd76364cb Binary files /dev/null and b/Ombi.UI/Content/favicon/apple-icon-72x72.png differ diff --git a/Ombi.UI/Content/favicon/apple-icon-76x76.png b/Ombi.UI/Content/favicon/apple-icon-76x76.png new file mode 100644 index 000000000..b16ee1aeb Binary files /dev/null and b/Ombi.UI/Content/favicon/apple-icon-76x76.png differ diff --git a/Ombi.UI/Content/favicon/apple-icon-precomposed.png b/Ombi.UI/Content/favicon/apple-icon-precomposed.png new file mode 100644 index 000000000..40142b749 Binary files /dev/null and b/Ombi.UI/Content/favicon/apple-icon-precomposed.png differ diff --git a/Ombi.UI/Content/favicon/apple-icon.png b/Ombi.UI/Content/favicon/apple-icon.png new file mode 100644 index 000000000..40142b749 Binary files /dev/null and b/Ombi.UI/Content/favicon/apple-icon.png differ diff --git a/Ombi.UI/Content/favicon/browserconfig.xml b/Ombi.UI/Content/favicon/browserconfig.xml new file mode 100644 index 000000000..c55414822 --- /dev/null +++ b/Ombi.UI/Content/favicon/browserconfig.xml @@ -0,0 +1,2 @@ + +#ffffff \ No newline at end of file diff --git a/Ombi.UI/Content/favicon/favicon-16x16.png b/Ombi.UI/Content/favicon/favicon-16x16.png new file mode 100644 index 000000000..bd6ac2d9d Binary files /dev/null and b/Ombi.UI/Content/favicon/favicon-16x16.png differ diff --git a/Ombi.UI/Content/favicon/favicon-32x32.png b/Ombi.UI/Content/favicon/favicon-32x32.png new file mode 100644 index 000000000..4758d38b7 Binary files /dev/null and b/Ombi.UI/Content/favicon/favicon-32x32.png differ diff --git a/Ombi.UI/Content/favicon/favicon-96x96.png b/Ombi.UI/Content/favicon/favicon-96x96.png new file mode 100644 index 000000000..a362acfd8 Binary files /dev/null and b/Ombi.UI/Content/favicon/favicon-96x96.png differ diff --git a/Ombi.UI/Content/favicon/favicon.ico b/Ombi.UI/Content/favicon/favicon.ico new file mode 100644 index 000000000..487964011 Binary files /dev/null and b/Ombi.UI/Content/favicon/favicon.ico differ diff --git a/Ombi.UI/Content/favicon/manifest.json b/Ombi.UI/Content/favicon/manifest.json new file mode 100644 index 000000000..013d4a6a5 --- /dev/null +++ b/Ombi.UI/Content/favicon/manifest.json @@ -0,0 +1,41 @@ +{ + "name": "App", + "icons": [ + { + "src": "\/android-icon-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "\/android-icon-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "\/android-icon-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "\/android-icon-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "\/android-icon-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "\/android-icon-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + } + ] +} \ No newline at end of file diff --git a/Ombi.UI/Content/favicon/ms-icon-144x144.png b/Ombi.UI/Content/favicon/ms-icon-144x144.png new file mode 100644 index 000000000..5c32f5c66 Binary files /dev/null and b/Ombi.UI/Content/favicon/ms-icon-144x144.png differ diff --git a/Ombi.UI/Content/favicon/ms-icon-150x150.png b/Ombi.UI/Content/favicon/ms-icon-150x150.png new file mode 100644 index 000000000..33ba50221 Binary files /dev/null and b/Ombi.UI/Content/favicon/ms-icon-150x150.png differ diff --git a/Ombi.UI/Content/favicon/ms-icon-310x310.png b/Ombi.UI/Content/favicon/ms-icon-310x310.png new file mode 100644 index 000000000..a2044bbb1 Binary files /dev/null and b/Ombi.UI/Content/favicon/ms-icon-310x310.png differ diff --git a/Ombi.UI/Content/favicon/ms-icon-70x70.png b/Ombi.UI/Content/favicon/ms-icon-70x70.png new file mode 100644 index 000000000..f3566d7c7 Binary files /dev/null and b/Ombi.UI/Content/favicon/ms-icon-70x70.png differ diff --git a/Ombi.UI/Content/wizard.js b/Ombi.UI/Content/wizard.js index 8bb81b171..c14bd6521 100644 --- a/Ombi.UI/Content/wizard.js +++ b/Ombi.UI/Content/wizard.js @@ -25,6 +25,12 @@ $('#contentBody').on('click', '#embyApiKeySave', function (e) { e.preventDefault(); + + var port = $('#portNumber').val(); + if (!port) { + generateNotify("Please provide a port number", "warning"); + } + $('#spinner').attr("class", "fa fa-spinner fa-spin"); var $form = $("#embyAuthForm"); diff --git a/Ombi.UI/Helpers/BaseUrlHelper.cs b/Ombi.UI/Helpers/BaseUrlHelper.cs index cb0600ce7..5badfad5b 100644 --- a/Ombi.UI/Helpers/BaseUrlHelper.cs +++ b/Ombi.UI/Helpers/BaseUrlHelper.cs @@ -283,11 +283,30 @@ namespace Ombi.UI.Helpers var assetLocation = GetBaseUrl(); var content = GetContentUrl(assetLocation); + var sb = new StringBuilder(); - var asset = $""; - asset += $""; + sb.Append($""); + sb.Append($""); + + sb.Append($""); + sb.Append($""); + sb.Append($""); + sb.Append($""); + sb.Append($""); + sb.Append($""); + sb.Append($""); + sb.Append($""); + sb.Append($""); + sb.Append($""); + sb.Append($""); + sb.Append($""); + sb.Append($""); + sb.Append($""); + sb.Append($""); + sb.Append($""); + sb.Append($""); - return helper.Raw(asset); + return helper.Raw(sb.ToString()); } public static IHtmlString GetSidebarUrl(this HtmlHelpers helper, NancyContext context, string url, string title, string icon = null) @@ -354,6 +373,12 @@ namespace Ombi.UI.Helpers return helper.Raw(GetCustomizationSettings().ApplicationName); } + public static IHtmlString GetMediaServerName(this HtmlHelpers helper) + { + var s = GetEmbySettings(); + return helper.Raw(s.Enable ? "Emby" : "Plex"); + } + private static string GetBaseUrl() { return GetSettings().BaseUrl; @@ -371,7 +396,7 @@ namespace Ombi.UI.Helpers private static CustomizationSettings GetCustomizationSettings() { - var returnValue = Cache.GetOrSet(CacheKeys.GetPlexRequestSettings, () => + var returnValue = Cache.GetOrSet(CacheKeys.GetCustomizationSettings, () => { var settings = Locator.Resolve>().GetSettings(); return settings; @@ -379,6 +404,16 @@ namespace Ombi.UI.Helpers return returnValue; } + private static EmbySettings GetEmbySettings() + { + var returnValue = Cache.GetOrSet(CacheKeys.GetEmbySettings, () => + { + var settings = Locator.Resolve>().GetSettings(); + return settings; + }); + return returnValue; + } + private static string GetLinkUrl(string assetLocation) { return string.IsNullOrEmpty(assetLocation) ? string.Empty : $"{assetLocation}"; diff --git a/Ombi.UI/Models/SearchLoadViewModel.cs b/Ombi.UI/Models/SearchLoadViewModel.cs index 2bdf5078c..667906431 100644 --- a/Ombi.UI/Models/SearchLoadViewModel.cs +++ b/Ombi.UI/Models/SearchLoadViewModel.cs @@ -32,6 +32,8 @@ namespace Ombi.UI.Models public class SearchLoadViewModel { public PlexRequestSettings Settings { get; set; } + public bool Plex { get; set; } + public bool Emby { get; set; } public CustomizationSettings CustomizationSettings { get; set; } } } \ No newline at end of file diff --git a/Ombi.UI/Modules/ApprovalModule.cs b/Ombi.UI/Modules/ApprovalModule.cs index 667b3e843..dd094b468 100644 --- a/Ombi.UI/Modules/ApprovalModule.cs +++ b/Ombi.UI/Modules/ApprovalModule.cs @@ -193,7 +193,7 @@ namespace Ombi.UI.Modules { // Approve it request.Approved = true; - Log.Warn("We approved movie: {0} but could not add it to CouchPotato/Watcher because it has not been setup", request.Title); + Log.Warn("We approved movie: {0} but could not add it to CouchPotato/Watcher/Radarr because it has not been setup", request.Title); // Update the record var inserted = await Service.UpdateRequestAsync(request); diff --git a/Ombi.UI/Modules/SearchModule.cs b/Ombi.UI/Modules/SearchModule.cs index 4a822ce81..f11e41b55 100644 --- a/Ombi.UI/Modules/SearchModule.cs +++ b/Ombi.UI/Modules/SearchModule.cs @@ -67,1730 +67,1743 @@ using ISecurityExtensions = Ombi.Core.ISecurityExtensions; 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 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 }); - } - } + 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 emby = await EmbySettings.GetSettingsAsync(); + var plex = await PlexService.GetSettingsAsync(); + var searchViewModel = new SearchLoadViewModel + { + Settings = settings, + CustomizationSettings = custom, + Emby = emby.Enable, + Plex = plex.Enable + }; + + + 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 embySettings = await EmbySettings.GetSettingsAsync(); + + // 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.ImdbId); + + var content = PlexContentRepository.GetAll(); + var plexTvShows = PlexChecker.GetPlexTvShows(content); + var embyContent = EmbyContentRepository.GetAll(); + var embyCached = EmbyChecker.GetEmbyTvShows(embyContent).ToList(); + + foreach (var show in shows) + { + + var providerId = show.Id.ToString(); + + if (embySettings.Enable) + { + var embyShow = EmbyChecker.GetTvShow(embyCached.ToArray(), show.SeriesName, show.FirstAired?.Substring(0, 4), providerId); + if (embyShow != null) + { + show.Available = true; + } + } + if (plexSettings.Enable) + { + var plexShow = PlexChecker.GetTvShow(plexTvShows.ToArray(), show.SeriesName, show.FirstAired?.Substring(0, 4), + providerId); + if (plexShow != null) + { + show.Available = true; + show.PlexUrl = plexShow.Url; + } + } + + if (show.ImdbId != null && !show.Available) + { + var imdbId = show.ImdbId; + if (dbTv.ContainsKey(imdbId)) + { + var dbt = dbTv[imdbId]; + + show.Requested = true; + show.Episodes = dbt.Episodes.ToList(); + show.Approved = dbt.Approved; + } + } + } + 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." + }); + } + var embySettings = await EmbySettings.GetSettingsAsync(); + 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,GetMediaServerName()) + }); + } + //#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 embySettings = await EmbySettings.GetSettingsAsync(); + 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} {string.Format(Resources.UI.Search_AlreadyInPlex,embySettings.Enable ? "Emby" : "Plex")}" + }); + } + } + 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} {string.Format(Resources.UI.Search_AlreadyInPlex,GetMediaServerName())}" + }); + } + } + + 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} {string.Format(Resources.UI.Search_AlreadyInPlex,GetMediaServerName())}" + }); + } + } + } + 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} {string.Format(Resources.UI.Search_AlreadyInPlex,GetMediaServerName())}" + }); + } + } + } + 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} {string.Format(Resources.UI.Search_AlreadyInPlex,GetMediaServerName())}" + }); + } + } + + 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,GetMediaServerName()) + }); + } + + 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 }); + } + + private string GetMediaServerName() + { + var e = EmbySettings.GetSettings(); + return e.Enable ? "Emby" : "Plex"; + } + } } diff --git a/Ombi.UI/Ombi.UI.csproj b/Ombi.UI/Ombi.UI.csproj index eebd32612..09f259740 100644 --- a/Ombi.UI/Ombi.UI.csproj +++ b/Ombi.UI/Ombi.UI.csproj @@ -390,6 +390,84 @@ datepicker.css Always + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -639,6 +717,9 @@ + + PreserveNewest + diff --git a/Ombi.UI/Resources/UI.da.resx b/Ombi.UI/Resources/UI.da.resx index bb2efd6bf..e4164aeed 100644 --- a/Ombi.UI/Resources/UI.da.resx +++ b/Ombi.UI/Resources/UI.da.resx @@ -121,13 +121,13 @@ Log ind - Ønsker du at se en film eller tv-show, men det er i øjeblikket ikke på Plex? Log nedenfor med dit Plex.tv brugernavn og password !! + Ønsker du at se en film eller tv-show, men det er i øjeblikket ikke på {0}? Log nedenfor med dit brugernavn og password !! Dine login-oplysninger bruges kun til at godkende din Plex konto. - Plex.tv Brugernavn + Brugernavn Brugernavn @@ -211,7 +211,7 @@ Album - Ønsker at se noget, der ikke i øjeblikket på Plex ?! Intet problem! Bare søge efter det nedenfor og anmode den ! + Ønsker at se noget, der ikke i øjeblikket på {0}?! Intet problem! Bare søge efter det nedenfor og anmode den ! Søg @@ -409,7 +409,7 @@ allerede er blevet anmodet !! - Vi kunne ikke kontrollere, om {0} er i Plex, er du sikker på det er korrekt setup ?! + Vi kunne ikke kontrollere, om {0} er i {1}, er du sikker på det er korrekt setup ?! Noget gik galt tilføjer filmen til CouchPotato! Tjek venligst din opsætning.! @@ -418,7 +418,7 @@ Du har nået din ugentlige anmodning grænse for film! Kontakt din administrator.! - er allerede i Plex !! + er allerede i {0}!! Noget gik galt tilføjer filmen til SickRage! Tjek venligst din opsætning.! @@ -435,9 +435,6 @@ Du har nået din ugentlige anmodning grænse for tv-shows! Kontakt din administrator.! - - Beklager, men denne funktionalitet er i øjeblikket kun for brugere med Plex konti! - Beklager, men din administrator har endnu ikke gjort det muligt denne funktionalitet.! diff --git a/Ombi.UI/Resources/UI.de.resx b/Ombi.UI/Resources/UI.de.resx index 1cd1d0192..5b908f1b8 100644 --- a/Ombi.UI/Resources/UI.de.resx +++ b/Ombi.UI/Resources/UI.de.resx @@ -121,13 +121,13 @@ Anmelden - Möchten Sie einen Film oder eine Serie schauen, die momentan noch nicht auf Plex ist? Dann loggen Sie sich unten ein und fordern Sie das Material an! + Möchten Sie einen Film oder eine Serie schauen, die momentan noch nicht auf {0}ist? Dann loggen Sie sich unten ein und fordern Sie das Material an! Ihre Login-Daten werden nur zur Authorisierung Ihres Plex-Konto verwendet. - Plex.tv Benutzername + Benutzername Benutzername @@ -211,7 +211,7 @@ Alben - Möchten Sie etwas schauen, das derzeit nicht auf Plex ist?! Kein Problem! Suchen Sie unten einfach danach und fragen Sie es an! + Möchten Sie etwas schauen, das derzeit nicht auf {0} ist?! Kein Problem! Suchen Sie unten einfach danach und fragen Sie es an! Suche @@ -409,7 +409,7 @@ wurde bereits angefragt! - Wir konnten nicht prüfen ob {0} bereits auf Plex ist. Bist du sicher dass alles richtig installiert ist? + Wir konnten nicht prüfen ob {0} bereits auf {1}ist. Bist du sicher dass alles richtig installiert ist? Etwas ging etwas schief beim hinzufügen des Filmes zu CouchPotato! Bitte überprüfe deine Einstellungen. @@ -418,7 +418,7 @@ Du hast deine wöchentliche Maximalanfragen für neue Filme erreicht. Bitte kontaktiere den Administrator. - ist bereits auf Plex! + ist bereits auf {0}! Etwas ging etwas schief beim hinzufügen des Filmes zu SickRage! Bitte überprüfe deine Einstellungen. @@ -435,9 +435,6 @@ Du hast deine wöchentliche Maximalanfragen für neue Serien erreicht. Bitte kontaktiere den Administrator. - - Entschuldige, aber diese Funktion ist momentan nur für Benutzer mit Plex-Accounts freigeschaltet. - Entschuldige, aber dein Administrator hat diese Funktion noch nicht freigeschaltet. diff --git a/Ombi.UI/Resources/UI.es.resx b/Ombi.UI/Resources/UI.es.resx index dd325f2bb..c00323af5 100644 --- a/Ombi.UI/Resources/UI.es.resx +++ b/Ombi.UI/Resources/UI.es.resx @@ -121,13 +121,13 @@ INICIAR SESIÓN - ¿Quieres ver una película o programa de televisión, pero no es actualmente en Plex? Ingresa abajo con su nombre de usuario y contraseña Plex.tv ! + ¿Quieres ver una película o programa de televisión, pero no es actualmente en {0}? Ingresa abajo con su nombre de usuario y contraseña ! Sus datos de acceso sólo se utilizan para autenticar su cuenta Plex. - Plex.tv nombre de usuario + nombre de usuario Username @@ -211,7 +211,7 @@ Álbumes - ¿Quieres ver algo que no se encuentra actualmente en Plex ?! ¡No hay problema! Sólo la búsqueda de abajo y que solicitarlo ! + ¿Quieres ver algo que no se encuentra actualmente en {0}?! ¡No hay problema! Sólo la búsqueda de abajo y que solicitarlo ! Buscar @@ -409,7 +409,7 @@ ya ha sido solicitada !! - No hemos podido comprobar si {0} está en Plex, ¿estás seguro de que es correcta la configuración ?! + No hemos podido comprobar si {0} está en {1}, ¿estás seguro de que es correcta la configuración ?! Algo salió mal la adición de la película para CouchPotato! Por favor verifica la configuracion.! @@ -418,7 +418,7 @@ Ha llegado a su límite de solicitudes semanales de películas! Por favor, póngase en contacto con su administrador.! - ya está en Plex !! + ya está en {0}!! Algo salió mal la adición de la película para SickRage! Por favor verifica la configuracion.! @@ -435,9 +435,6 @@ Ha llegado a su límite de solicitudes semanales de programas de televisión! Por favor, póngase en contacto con su administrador.! - - Lo sentimos, pero esta funcionalidad es actualmente sólo para los usuarios con cuentas Plex! - Lo sentimos, pero el administrador aún no ha habilitado esta funcionalidad.! diff --git a/Ombi.UI/Resources/UI.fr.resx b/Ombi.UI/Resources/UI.fr.resx index 11f787a36..e9a461b23 100644 --- a/Ombi.UI/Resources/UI.fr.resx +++ b/Ombi.UI/Resources/UI.fr.resx @@ -121,13 +121,13 @@ Connexion - Vous souhaitez avoir accès à un contenu qui n'est pas encore disponible dans Plex ? Demandez-le ici ! + Vous souhaitez avoir accès à un contenu qui n'est pas encore disponible dans {0}? Demandez-le ici ! Vos informations de connexion sont uniquement utilisées pour authentifier votre compte Plex. - Nom d'utilisateur Plex.tv + Nom d'utilisateur Nom d’utilisateur @@ -211,7 +211,7 @@ Albums - Vous souhaitez avoir accès à un contenu qui n'est pas encore disponible dans Plex ?! Aucun problème ! Il suffit d'effectuer une recherche ci-dessous et d'en faire la demande! + Vous souhaitez avoir accès à un contenu qui n'est pas encore disponible dans {0} ?! Aucun problème ! Il suffit d'effectuer une recherche ci-dessous et d'en faire la demande! Rechercher @@ -409,7 +409,7 @@ a déjà été demandé! - Nous ne pouvons pas vérifier que {0} est présent dans Plex, êtes-vous sûr que la configuration est correcte? + Nous ne pouvons pas vérifier que {0} est présent dans {1}, êtes-vous sûr que la configuration est correcte? Une erreur s'est produite lors de l'ajout du film dans CouchPotato! Merci de bien vouloir vérifier vos paramètres. @@ -418,7 +418,7 @@ Vous avez atteint votre quota hebdomadaire de demandes pour les films! Merci de bien vouloir contacter l'administrateur. - est déjà présent dans Plex! + est déjà présent dans {0}! Une erreur s'est produite lors de l'ajout de la série TV dans SickRage! Merci de bien vouloir vérifier vos paramètres. @@ -435,9 +435,6 @@ Vous avez atteint votre quota hebdomadaire de demandes pour les séries TV! Merci de bien vouloir contacter l'administrateur. - - Désolé mais cette fonctionnalité est réservée aux utilisateurs possédant un compte Plex. - Désolé mais l'administrateur n'a pas encore activé cette fonctionnalité. diff --git a/Ombi.UI/Resources/UI.it.resx b/Ombi.UI/Resources/UI.it.resx index 3dd1640c4..3613c3356 100644 --- a/Ombi.UI/Resources/UI.it.resx +++ b/Ombi.UI/Resources/UI.it.resx @@ -121,13 +121,13 @@ Accesso - Vuoi guardare un film o una serie tv ma non è attualmente in Plex? Effettua il login con il tuo username e la password Plex.tv ! + Vuoi guardare un film o una serie tv ma non è attualmente in {0}? Effettua il login con il tuo username e la password ! I dati di accesso vengono utilizzati solo per autenticare l'account Plex. - Plex.tv Nome utente + Nome utente Nome utente @@ -214,7 +214,7 @@ Msuica - Vuoi guardare qualcosa che non è attualmente in Plex?! Non c'è problema! Basta cercarla qui sotto e richiederla! + Vuoi guardare qualcosa che non è attualmente in {0}?! Non c'è problema! Basta cercarla qui sotto e richiederla! Suggerimenti @@ -409,7 +409,7 @@ è già stato richiesto! - Non siamo riusciti a controllare se {0} è in Plex, sei sicuro che sia configurato correttamente? + Non siamo riusciti a controllare se {0} è in {1}, sei sicuro che sia configurato correttamente? Qualcosa è andato storto aggiungendo il film a CouchPotato! Controlla le impostazioni @@ -418,7 +418,7 @@ Hai raggiunto il numero massimo di richieste settimanali per i Film! Contatta l'amministratore - è già disponibile in Plex! + è già disponibile in {0}! Qualcosa è andato storto aggiungendo il film a SickRage! Controlla le impostazioni @@ -435,9 +435,6 @@ Hai raggiunto il numero massimo di richieste settimanali per le Serie TV! Contatta l'amministratore - - Spiacente, ma questa funzione è disponibile solo per gli utenti con un account Plex. - Spiacente, ma l'amministratore non ha ancora abilitato questa funzionalità. diff --git a/Ombi.UI/Resources/UI.nl.resx b/Ombi.UI/Resources/UI.nl.resx index 0b85e2aa9..3761a96cc 100644 --- a/Ombi.UI/Resources/UI.nl.resx +++ b/Ombi.UI/Resources/UI.nl.resx @@ -121,13 +121,13 @@ Inloggen - Wilt u een film of een tv serie kijken, maar staat deze niet op Plex? Log hieronder in met uw gebruikersnaam en wachtwoord van Plex.tv + Wilt u een film of een tv serie kijken, maar staat deze niet op {0}? Log hieronder in met uw gebruikersnaam en wachtwoord van Uw login gegevens worden alleen gebruikt om uw account te verifiëren bij Plex. - Plex.tv Gebruikersnaam + Gebruikersnaam Gebruikersnaam @@ -217,7 +217,7 @@ Albums - Wilt u kijken naar iets dat dat momenteel niet op Plex is?! Geen probleem! zoek hieronder en vraag het aan! + Wilt u kijken naar iets dat dat momenteel niet op {0} is?! Geen probleem! zoek hieronder en vraag het aan! Suggesties @@ -409,10 +409,7 @@ Is al aangevraagd! - Staat al op Plex! - - - Sorry, deze functie is momenteel alleen voor gebruikers met een Plex account. + Staat al op {0}! Sorry, uw administrator heeft deze functie nog niet geactiveerd. @@ -424,7 +421,7 @@ Kon niet opslaan, probeer het later nog eens. - We konden niet controleren of {0} al in plex bestaat, weet je zeker dat het correct is ingesteld? + We konden niet controleren of {0} al in {1} bestaat, weet je zeker dat het correct is ingesteld? Er is iets foutgegaan tijdens het toevoegen van de film aan CouchPotato! Controleer je instellingen diff --git a/Ombi.UI/Resources/UI.pt.resx b/Ombi.UI/Resources/UI.pt.resx index b265ea445..681a11549 100644 --- a/Ombi.UI/Resources/UI.pt.resx +++ b/Ombi.UI/Resources/UI.pt.resx @@ -121,13 +121,13 @@ Entrar - Quer assistir a um filme ou programa de TV, mas não está atualmente em Plex? Entre abaixo com seu nome de usuário e senha Plex.tv !! + Quer assistir a um filme ou programa de TV, mas não está atualmente em {0}? Entre abaixo com seu nome de usuário e senha ! Seus dados de login são apenas usados ​​para autenticar sua conta Plex.! - Plex.tv usuário + usuário Nome de usuário @@ -211,7 +211,7 @@ Álbuns - Quer assistir algo que não está atualmente em Plex ?! Sem problemas! Basta procurá-lo abaixo e solicitá-lo !! + Quer assistir algo que não está atualmente em {0}?! Sem problemas! Basta procurá-lo abaixo e solicitá-lo !! Buscar @@ -409,7 +409,7 @@ já foi solicitado !! - Nós não poderia verificar se {0} está em Plex, você tem certeza que é configurada corretamente ?! + Nós não poderia verificar se {0} está em {1}, você tem certeza que é configurada corretamente ?! Algo deu errado adicionando o filme para CouchPotato! Verifique as suas opções.! @@ -418,7 +418,7 @@ Atingiu seu limite semanal de solicitação para filmes! Entre em contato com seu administrador. - Já está no Plex! + Já está no {0}! Algo deu errado adicionar o filme para SickRage! Por favor, verifique suas configurações. @@ -435,9 +435,6 @@ Atingiu seu limite semanal de solicitação para programas de TV! Entre em contato com seu administrador. - - Desculpe, mas essa funcionalidade é atualmente somente para os usuários com contas de Plex - Desculpe, mas o administrador não permitiu ainda esta funcionalidade. diff --git a/Ombi.UI/Resources/UI.resx b/Ombi.UI/Resources/UI.resx index d24c5ea1c..a9f63e0e8 100644 --- a/Ombi.UI/Resources/UI.resx +++ b/Ombi.UI/Resources/UI.resx @@ -101,14 +101,14 @@ Login - Want to watch a movie or tv show but it's not currently on Plex? - Login below with your Plex.tv username and password! + Want to watch a movie or tv show but it's not currently on {0}? + Login below with your username and password! Your login details are only used to authenticate your Plex account. - Plex.tv Username + Username Username @@ -192,7 +192,7 @@ Albums - Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it! + Want to watch something that is not currently on {0}?! No problem! Just search for it below and request it! Search @@ -390,7 +390,7 @@ has already been requested! - We could not check if {0} is in Plex, are you sure it's correctly setup? + We could not check if {0} is in {1}, are you sure it's correctly setup? Something went wrong adding the movie to CouchPotato! Please check your settings. @@ -399,7 +399,7 @@ You have reached your weekly request limit for Movies! Please contact your admin. - is already in Plex! + is already in {0}! Something went wrong adding the movie to SickRage! Please check your settings. @@ -416,9 +416,6 @@ You have reached your weekly request limit for TV Shows! Please contact your admin. - - Sorry, but this functionality is currently only for users with Plex accounts - Sorry, but your administrator has not yet enabled this functionality. @@ -468,7 +465,7 @@ TV show status - Currently we are indexing all of the available tv shows and movies on the Plex server, so there might be some unexpected behavior. This shouldn't take too long. + Currently we are indexing all of the available tv shows and movies on the media server, so there might be some unexpected behavior. This shouldn't take too long. User Management diff --git a/Ombi.UI/Resources/UI.sv.resx b/Ombi.UI/Resources/UI.sv.resx index 4c70109c7..3d21b5df0 100644 --- a/Ombi.UI/Resources/UI.sv.resx +++ b/Ombi.UI/Resources/UI.sv.resx @@ -121,13 +121,13 @@ Logga in - Vill du titta på en film eller TV-show, men det är inte närvarande på Plex? Logga in nedan med Plex.tv användarnamn och lösenord !! + Vill du titta på en film eller TV-show, men det är inte närvarande på {0}? Logga in nedan med användarnamn och lösenord !! Dina inloggningsuppgifter används endast för att autentisera ditt Plex-konto. - Plex.tv användarnamn + Användarnamn Användarnamn @@ -214,7 +214,7 @@ Album - Vill titta på något som inte är närvarande på Plex ?! Inga problem! Bara söka efter den nedan och begär det ! + Vill titta på något som inte är närvarande på {0}?! Inga problem! Bara söka efter den nedan och begär det ! Sök @@ -409,7 +409,7 @@ har redan begärts - Vi kunde inte kontrollera om {0} är i Plex, är du säker det är korrekt installation? + Vi kunde inte kontrollera om {0} är i {1}, är du säker det är korrekt installation? Något gick fel att lägga till filmen i CouchPotato! Kontrollera inställningarna. @@ -418,7 +418,7 @@ Du har nått din weekly begäran gräns för filmer! Kontakta din admin. - är redan i Plex + är redan i {0} Något gick fel att lägga till filmen i SickRage! Kontrollera inställningarna. @@ -435,9 +435,6 @@ Du har nått din weekly begäran gräns för TV-program! Kontakta din admin. - - Ledsen, men denna funktion är för närvarande endast för användare med Plex konton - Ledsen, men administratören har ännu inte aktiverat denna funktion. diff --git a/Ombi.UI/Resources/UI1.Designer.cs b/Ombi.UI/Resources/UI1.Designer.cs index 56013a95f..4920f2e7f 100644 --- a/Ombi.UI/Resources/UI1.Designer.cs +++ b/Ombi.UI/Resources/UI1.Designer.cs @@ -223,7 +223,7 @@ namespace Ombi.UI.Resources { } /// - /// Looks up a localized string similar to Currently we are indexing all of the available tv shows and movies on the Plex server, so there might be some unexpected behavior. This shouldn't take too long.. + /// Looks up a localized string similar to Currently we are indexing all of the available tv shows and movies on the media server, so there might be some unexpected behavior. This shouldn't take too long.. /// public static string Layout_CacherRunning { get { @@ -736,7 +736,7 @@ namespace Ombi.UI.Resources { } /// - /// Looks up a localized string similar to is already in Plex!. + /// Looks up a localized string similar to is already in {0}!. /// public static string Search_AlreadyInPlex { get { @@ -790,7 +790,7 @@ namespace Ombi.UI.Resources { } /// - /// Looks up a localized string similar to We could not check if {0} is in Plex, are you sure it's correctly setup?. + /// Looks up a localized string similar to We could not check if {0} is in {1}, are you sure it's correctly setup?. /// public static string Search_CouldNotCheckPlex { get { @@ -816,15 +816,6 @@ namespace Ombi.UI.Resources { } } - /// - /// Looks up a localized string similar to Sorry, but this functionality is currently only for users with Plex accounts. - /// - public static string Search_ErrorPlexAccountOnly { - get { - return ResourceManager.GetString("Search_ErrorPlexAccountOnly", resourceCulture); - } - } - /// /// Looks up a localized string similar to First Season. /// @@ -907,7 +898,7 @@ namespace Ombi.UI.Resources { } /// - /// Looks up a localized string similar to Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it!. + /// Looks up a localized string similar to Want to watch something that is not currently on {0}?! No problem! Just search for it below and request it!. /// public static string Search_Paragraph { get { @@ -1132,8 +1123,8 @@ namespace Ombi.UI.Resources { } /// - /// Looks up a localized string similar to Want to watch a movie or tv show but it's not currently on Plex? - /// Login below with your Plex.tv username and password!. + /// Looks up a localized string similar to Want to watch a movie or tv show but it's not currently on {0}? + /// Login below with your username and password!. /// public static string UserLogin_Paragraph { get { @@ -1178,7 +1169,7 @@ namespace Ombi.UI.Resources { } /// - /// Looks up a localized string similar to Plex.tv Username . + /// Looks up a localized string similar to Username . /// public static string UserLogin_Username { get { diff --git a/Ombi.UI/Views/Admin/LandingPage.cshtml b/Ombi.UI/Views/Admin/LandingPage.cshtml index 20ad85c53..e48f5ca92 100644 --- a/Ombi.UI/Views/Admin/LandingPage.cshtml +++ b/Ombi.UI/Views/Admin/LandingPage.cshtml @@ -54,7 +54,7 @@

Notice Message

- +
diff --git a/Ombi.UI/Views/Admin/NewsletterSettings.cshtml b/Ombi.UI/Views/Admin/NewsletterSettings.cshtml index 2b9f52838..bf890eb93 100644 --- a/Ombi.UI/Views/Admin/NewsletterSettings.cshtml +++ b/Ombi.UI/Views/Admin/NewsletterSettings.cshtml @@ -66,7 +66,7 @@
- +
diff --git a/Ombi.UI/Views/Admin/Sonarr.cshtml b/Ombi.UI/Views/Admin/Sonarr.cshtml index 28e2a9140..72498b036 100644 --- a/Ombi.UI/Views/Admin/Sonarr.cshtml +++ b/Ombi.UI/Views/Admin/Sonarr.cshtml @@ -10,6 +10,13 @@ { port = Model.Port; } + + var rootFolder = string.Empty; + if (!string.IsNullOrEmpty(Model.RootPath)) + + { + rootFolder = Model.RootPath.Replace("/", "//"); + } }
@@ -184,7 +191,7 @@ console.log('Hit root folders..'); - var rootFolderSelected = '@Model.RootPath'; + var rootFolderSelected = '@rootFolder'; if (!rootFolderSelected) { return; } diff --git a/Ombi.UI/Views/Customization/Customization.cshtml b/Ombi.UI/Views/Customization/Customization.cshtml index a220f950d..083cc63c7 100644 --- a/Ombi.UI/Views/Customization/Customization.cshtml +++ b/Ombi.UI/Views/Customization/Customization.cshtml @@ -34,7 +34,7 @@
diff --git a/Ombi.UI/Views/Landing/Index.cshtml b/Ombi.UI/Views/Landing/Index.cshtml index 21feaf7eb..b5710224f 100644 --- a/Ombi.UI/Views/Landing/Index.cshtml +++ b/Ombi.UI/Views/Landing/Index.cshtml @@ -33,7 +33,7 @@

Checking...

- The Plex server is Loading... (check this page for continuous status updates) + The Media server is Loading... (check this page for continuous status updates)
diff --git a/Ombi.UI/Views/Requests/Index.cshtml b/Ombi.UI/Views/Requests/Index.cshtml index 7199253c7..e94decd85 100644 --- a/Ombi.UI/Views/Requests/Index.cshtml +++ b/Ombi.UI/Views/Requests/Index.cshtml @@ -191,18 +191,36 @@
- {{#if_eq type "tv"}} - @UI.Search_TV_Show_Status: - {{else}} - @UI.Search_Movie_Status: - {{/if_eq}} - {{status}} - {{#if denied}}
- Denied: + {{#if_eq type "tv"}} + @UI.Search_TV_Show_Status: + {{else}} + @UI.Search_Movie_Status: + {{/if_eq}} + {{status}} +
+ +
+ Request status: + {{#if available}} + @UI.Requests_Available + {{else}} + {{#if approved}} + @UI.Search_Processing_Request + {{else if denied}} + @UI.Search_Request_denied {{#if deniedReason}} {{/if}} + {{else}} + @UI.Search_Pending_approval + {{/if}} + {{/if}} +
+ {{#if denied}} +
+ Denied: +
{{/if}} @@ -211,26 +229,7 @@ {{else}}
@UI.Requests_ReleaseDate: {{releaseDate}}
{{/if_eq}} - {{#unless denied}} -
- @UI.Common_Approved: - {{#if_eq approved false}} - - {{/if_eq}} - {{#if_eq approved true}} - - {{/if_eq}} -
- {{/unless}} -
- @UI.Requests_Available - {{#if_eq available false}} - - {{/if_eq}} - {{#if_eq available true}} - - {{/if_eq}} -
+
{{#if_eq type "tv"}} {{#if episodes}} diff --git a/Ombi.UI/Views/Search/Index.cshtml b/Ombi.UI/Views/Search/Index.cshtml index cef2e73e8..07f9ac358 100644 --- a/Ombi.UI/Views/Search/Index.cshtml +++ b/Ombi.UI/Views/Search/Index.cshtml @@ -8,12 +8,14 @@ { url = "/" + baseUrl.ToHtmlString(); } + + }

@UI.Search_Title

-

@UI.Search_Paragraph

+

@string.Format(UI.Search_Paragraph, Model.Emby ? "Emby" : "Plex")


@@ -315,9 +317,7 @@ {{#if status}}{{status}}{{/if}} {{/if_eq}} - {{#if status}} - {{status}} - {{/if}} + {{#if firstAired}} Air Date: {{firstAired}} @@ -583,3 +583,4 @@ @Html.LoadSearchAssets() + diff --git a/Ombi.UI/Views/UserLogin/Username.cshtml b/Ombi.UI/Views/UserLogin/Username.cshtml index fcf46d8e7..a62f456ee 100644 --- a/Ombi.UI/Views/UserLogin/Username.cshtml +++ b/Ombi.UI/Views/UserLogin/Username.cshtml @@ -6,7 +6,7 @@

@UI.UserLogin_Title

- @UI.UserLogin_Paragraph + @string.Format(UI.UserLogin_Paragraph, Html.GetMediaServerName().ToHtmlString()).ToString()

diff --git a/Ombi.UI/Views/UserManagementSettings/UserManagementSettings.cshtml b/Ombi.UI/Views/UserManagementSettings/UserManagementSettings.cshtml index db0fae5f2..03164a4ef 100644 --- a/Ombi.UI/Views/UserManagementSettings/UserManagementSettings.cshtml +++ b/Ombi.UI/Views/UserManagementSettings/UserManagementSettings.cshtml @@ -9,7 +9,7 @@ Here you can manage the default permissions and features that your users get - Note: This will not update your users that are currently there, this is to set the default settings to any users added outside of Ombi e.g. You share your Plex Server with a new user, they will be added into Ombi + Note: This will not update your users that are currently there, this is to set the default settings to any users added outside of Ombi e.g. You share your Server with a new user, they will be added into Ombi automatically and will take the permissions and features you have selected below. diff --git a/Ombi.UI/Views/UserWizard/Index.cshtml b/Ombi.UI/Views/UserWizard/Index.cshtml index b8bae77d2..8ecf52d29 100644 --- a/Ombi.UI/Views/UserWizard/Index.cshtml +++ b/Ombi.UI/Views/UserWizard/Index.cshtml @@ -192,7 +192,7 @@