From 88c0651b1e0985996ab9fc88422d9b857068eb5e Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Mon, 16 Jan 2017 15:23:04 +0000 Subject: [PATCH 01/41] Improved the search UI and made it more consistant. Finished the Netflix API Part #884 --- Ombi.Api.Interfaces/INetflixApi.cs | 2 +- Ombi.Api.Models/Netflix/NetflixMovieResult.cs | 7 +++ Ombi.Api/ApiRequest.cs | 2 +- Ombi.Api/NetflixRouletteApi.cs | 5 +- Ombi.UI/Content/search.js | 18 ++++++ Ombi.UI/Modules/SearchExtensionModule.cs | 62 +++++++++++++++++++ Ombi.UI/NinjectModules/ApiModule.cs | 1 + Ombi.UI/Ombi.UI.csproj | 1 + Ombi.UI/Views/Search/Index.cshtml | 7 ++- 9 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 Ombi.UI/Modules/SearchExtensionModule.cs diff --git a/Ombi.Api.Interfaces/INetflixApi.cs b/Ombi.Api.Interfaces/INetflixApi.cs index 9e1a02b6b..2c427f6f3 100644 --- a/Ombi.Api.Interfaces/INetflixApi.cs +++ b/Ombi.Api.Interfaces/INetflixApi.cs @@ -31,6 +31,6 @@ namespace Ombi.Api.Interfaces { public interface INetflixApi { - NetflixMovieResult GetMovies(string movieName, string year = null); + NetflixMovieResult CheckNetflix(string title, string year = null); } } \ No newline at end of file diff --git a/Ombi.Api.Models/Netflix/NetflixMovieResult.cs b/Ombi.Api.Models/Netflix/NetflixMovieResult.cs index 71007d34f..0ed213cf2 100644 --- a/Ombi.Api.Models/Netflix/NetflixMovieResult.cs +++ b/Ombi.Api.Models/Netflix/NetflixMovieResult.cs @@ -58,5 +58,12 @@ public class NetflixMovieResult public string Mediatype { get; set; } [JsonProperty(PropertyName = "runtime")] public string Runtime { get; set; } + + + // For errors + [JsonProperty(PropertyName = "errorcode")] + public int ErrorCode { get; set; } + [JsonProperty(PropertyName = "message")] + public string Message { get; set; } } } \ No newline at end of file diff --git a/Ombi.Api/ApiRequest.cs b/Ombi.Api/ApiRequest.cs index 67a5bbc9e..a27d4af28 100644 --- a/Ombi.Api/ApiRequest.cs +++ b/Ombi.Api/ApiRequest.cs @@ -70,7 +70,7 @@ public class ApiRequest : IApiRequest return response.Data; } - + public IRestResponse Execute(IRestRequest request, Uri baseUri) { var client = new RestClient { BaseUrl = baseUri }; diff --git a/Ombi.Api/NetflixRouletteApi.cs b/Ombi.Api/NetflixRouletteApi.cs index ee556a410..dc6e6e220 100644 --- a/Ombi.Api/NetflixRouletteApi.cs +++ b/Ombi.Api/NetflixRouletteApi.cs @@ -26,6 +26,7 @@ #endregion using System; +using System.Threading.Tasks; using Newtonsoft.Json; using Ombi.Api.Interfaces; using Ombi.Api.Models.Netflix; @@ -43,10 +44,10 @@ public NetflixRouletteApi(IApiRequest req) private IApiRequest Api { get; } private Uri Endpoint => new Uri("http://netflixroulette.net/api/api.php"); - public NetflixMovieResult GetMovies(string movieName, string year = null) + public NetflixMovieResult CheckNetflix(string title, string year = null) { var request = new RestRequest(); - request.AddQueryParameter("title", movieName); + request.AddQueryParameter("title", title); if (!string.IsNullOrEmpty(year)) { request.AddQueryParameter("year", year); diff --git a/Ombi.UI/Content/search.js b/Ombi.UI/Content/search.js index 674660b11..8b71215fb 100644 --- a/Ombi.UI/Content/search.js +++ b/Ombi.UI/Content/search.js @@ -304,6 +304,8 @@ $(function () { var html = searchTemplate(context); $("#movieList").append(html); + + checkNetflix(context.title, context.id); }); } else { @@ -334,6 +336,9 @@ $(function () { var context = buildTvShowContext(result); var html = searchTemplate(context); $("#tvList").append(html); + + checkNetflix(context.title, context.id); + }); } else { @@ -343,6 +348,19 @@ $(function () { }); }; + function checkNetflix(title, id) { + var url = createBaseUrl(base, '/searchextension/netflix/' + title); + $.ajax(url).success(function (results) { + + if (results.result) { + // It's on Netflix + $('#' + id + 'netflixTab') + .html("Avaialble on Netflix"); + } + + }); + } + function resetTvShows() { $("#tvList").html(""); } diff --git a/Ombi.UI/Modules/SearchExtensionModule.cs b/Ombi.UI/Modules/SearchExtensionModule.cs new file mode 100644 index 000000000..05fdfe003 --- /dev/null +++ b/Ombi.UI/Modules/SearchExtensionModule.cs @@ -0,0 +1,62 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: SearchExtensionModule.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System.Threading.Tasks; +using Nancy; +using Ombi.Api.Interfaces; +using Ombi.Core; +using Ombi.Core.SettingModels; + +namespace Ombi.UI.Modules +{ + public class SearchExtensionModule : BaseAuthModule + { + public SearchExtensionModule(ISettingsService pr, ISecurityExtensions security, INetflixApi netflix) : base("searchextension",pr, security) + { + NetflixApi = netflix; + + Get["/netflix/{searchTerm}", true] = async (x, ctx) => await Netflix(x.searchTerm); + } + + private INetflixApi NetflixApi { get; } + + + public async Task Netflix(string title) + { + var result = NetflixApi.CheckNetflix(title); + + if (!string.IsNullOrEmpty(result.Message)) + { + return Response.AsJson(new { Result = false }); + } + + return Response.AsJson(new { Result = true }); + } + + + } +} \ No newline at end of file diff --git a/Ombi.UI/NinjectModules/ApiModule.cs b/Ombi.UI/NinjectModules/ApiModule.cs index 4dd37a8db..9926f30e3 100644 --- a/Ombi.UI/NinjectModules/ApiModule.cs +++ b/Ombi.UI/NinjectModules/ApiModule.cs @@ -46,6 +46,7 @@ public override void Load() Bind().To(); Bind().To(); Bind().To(); + Bind().To(); } } } \ No newline at end of file diff --git a/Ombi.UI/Ombi.UI.csproj b/Ombi.UI/Ombi.UI.csproj index 38a6bdf40..d984e4562 100644 --- a/Ombi.UI/Ombi.UI.csproj +++ b/Ombi.UI/Ombi.UI.csproj @@ -273,6 +273,7 @@ + diff --git a/Ombi.UI/Views/Search/Index.cshtml b/Ombi.UI/Views/Search/Index.cshtml index 1ec29775f..59fc41464 100644 --- a/Ombi.UI/Views/Search/Index.cshtml +++ b/Ombi.UI/Views/Search/Index.cshtml @@ -107,7 +107,7 @@ } - \ No newline at end of file diff --git a/Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml b/Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml index df5969a95..d38827cf8 100644 --- a/Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml +++ b/Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml @@ -19,6 +19,7 @@ @Html.GetSidebarUrl(Context, "/admin/pushbulletnotification", "Pushbullet Notifications") @Html.GetSidebarUrl(Context, "/admin/pushovernotification", "Pushover Notifications") @Html.GetSidebarUrl(Context, "/admin/slacknotification", "Slack Notifications") + @Html.GetSidebarUrl(Context, "/admin/discordnotification", "Discord Notifications") @Html.GetSidebarUrl(Context, "/admin/logs", "Logs") @Html.GetSidebarUrl(Context, "/admin/status", "Status") @Html.GetSidebarUrl(Context, "/admin/scheduledjobs", "Scheduled Jobs") From 809d0103c3e27dd08f32257250f321f419294bd1 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Tue, 17 Jan 2017 15:14:35 +0000 Subject: [PATCH 03/41] Finished #739 --- Ombi.Api/DiscordApi.cs | 6 ++++-- Ombi.UI/NinjectModules/ApiModule.cs | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Ombi.Api/DiscordApi.cs b/Ombi.Api/DiscordApi.cs index a5214c503..d293d65a2 100644 --- a/Ombi.Api/DiscordApi.cs +++ b/Ombi.Api/DiscordApi.cs @@ -50,7 +50,8 @@ public void SendMessage(string message, string webhookId, string webhookToken, s { var request = new RestRequest { - Resource = "webhooks/{webhookId}/{webhookToken}" + Resource = "webhooks/{webhookId}/{webhookToken}", + Method = Method.POST }; request.AddUrlSegment("webhookId", webhookId); @@ -72,7 +73,8 @@ public async Task SendMessageAsync(string message, string webhookId, string webh { var request = new RestRequest { - Resource = "webhooks/{webhookId}/{webhookToken}" + Resource = "webhooks/{webhookId}/{webhookToken}", + Method = Method.POST }; request.AddUrlSegment("webhookId", webhookId); diff --git a/Ombi.UI/NinjectModules/ApiModule.cs b/Ombi.UI/NinjectModules/ApiModule.cs index 9926f30e3..9de21af99 100644 --- a/Ombi.UI/NinjectModules/ApiModule.cs +++ b/Ombi.UI/NinjectModules/ApiModule.cs @@ -47,6 +47,7 @@ public override void Load() Bind().To(); Bind().To(); Bind().To(); + Bind().To(); } } } \ No newline at end of file From e5d7c4c3ec36959ead54e1ec8b77f7eaf4e2ed42 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Tue, 17 Jan 2017 21:28:11 +0000 Subject: [PATCH 04/41] Fixed #940 don't show any shows without a tvdb id Added a link to the netflix movie when you click the label --- Ombi.UI/Content/search.js | 2 +- Ombi.UI/Modules/SearchExtensionModule.cs | 2 +- Ombi.UI/Modules/SearchModule.cs | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Ombi.UI/Content/search.js b/Ombi.UI/Content/search.js index 8b71215fb..8b9c10109 100644 --- a/Ombi.UI/Content/search.js +++ b/Ombi.UI/Content/search.js @@ -355,7 +355,7 @@ $(function () { if (results.result) { // It's on Netflix $('#' + id + 'netflixTab') - .html("Avaialble on Netflix"); + .html("Avaialble on Netflix"); } }); diff --git a/Ombi.UI/Modules/SearchExtensionModule.cs b/Ombi.UI/Modules/SearchExtensionModule.cs index 05fdfe003..4c0ee4a6f 100644 --- a/Ombi.UI/Modules/SearchExtensionModule.cs +++ b/Ombi.UI/Modules/SearchExtensionModule.cs @@ -54,7 +54,7 @@ public async Task Netflix(string title) return Response.AsJson(new { Result = false }); } - return Response.AsJson(new { Result = true }); + return Response.AsJson(new { Result = true, NetflixId = result.ShowId }); } diff --git a/Ombi.UI/Modules/SearchModule.cs b/Ombi.UI/Modules/SearchModule.cs index 8759d0325..30acdf68a 100644 --- a/Ombi.UI/Modules/SearchModule.cs +++ b/Ombi.UI/Modules/SearchModule.cs @@ -246,6 +246,7 @@ private async Task ProcessMovies(MovieSearchType searchType, string se if (counter <= 5) // Let's only do it for the first 5 items { var movieInfoTask = await MovieApi.GetMovieInformation(movie.Id).ConfigureAwait(false); + // TODO needs to be careful about this, it's adding extra time to search... // https://www.themoviedb.org/talk/5807f4cdc3a36812160041f2 imdbId = movieInfoTask?.ImdbId; @@ -345,6 +346,10 @@ private async Task SearchTvShow(string searchTerm) 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)) { From 5e06d9bd2692e82c8d4beea93fa9e6c080273fcc Mon Sep 17 00:00:00 2001 From: tidusjar Date: Tue, 17 Jan 2017 22:28:13 +0000 Subject: [PATCH 05/41] Radarr integartion in progress #923 --- Ombi.Api.Interfaces/IRadarrApi.cs | 15 + .../Ombi.Api.Interfaces.csproj | 1 + Ombi.Api.Models/Ombi.Api.Models.csproj | 4 + Ombi.Api.Models/Radarr/RadarrAddMovie.cs | 50 ++++ Ombi.Api.Models/Radarr/RadarrAddOptions.cs | 35 +++ Ombi.Api.Models/Radarr/RadarrError.cs | 34 +++ Ombi.Api.Models/Radarr/RadarrMovieResponse.cs | 80 ++++++ Ombi.Api/Ombi.Api.csproj | 1 + Ombi.Api/RadarrApi.cs | 152 ++++++++++ Ombi.Core/Ombi.Core.csproj | 1 + Ombi.Core/SettingModels/RadarrSettings.cs | 37 +++ Ombi.UI/Ombi.UI.csproj | 3 + Ombi.UI/Views/Admin/Radarr.cshtml | 260 ++++++++++++++++++ 13 files changed, 673 insertions(+) create mode 100644 Ombi.Api.Interfaces/IRadarrApi.cs create mode 100644 Ombi.Api.Models/Radarr/RadarrAddMovie.cs create mode 100644 Ombi.Api.Models/Radarr/RadarrAddOptions.cs create mode 100644 Ombi.Api.Models/Radarr/RadarrError.cs create mode 100644 Ombi.Api.Models/Radarr/RadarrMovieResponse.cs create mode 100644 Ombi.Api/RadarrApi.cs create mode 100644 Ombi.Core/SettingModels/RadarrSettings.cs create mode 100644 Ombi.UI/Views/Admin/Radarr.cshtml diff --git a/Ombi.Api.Interfaces/IRadarrApi.cs b/Ombi.Api.Interfaces/IRadarrApi.cs new file mode 100644 index 000000000..8d6af90ce --- /dev/null +++ b/Ombi.Api.Interfaces/IRadarrApi.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using Ombi.Api.Models.Radarr; +using Ombi.Api.Models.Sonarr; + +namespace Ombi.Api.Interfaces +{ + public interface IRadarrApi + { + RadarrAddMovie AddMovie(int tmdbId, string title, int qualityId, string rootPath, string apiKey, Uri baseUrl, bool searchNow = false); + List GetMovies(string apiKey, Uri baseUrl); + List GetProfiles(string apiKey, Uri baseUrl); + SystemStatus SystemStatus(string apiKey, Uri baseUrl); + } +} \ No newline at end of file diff --git a/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj b/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj index 05c1aae09..1cc18964a 100644 --- a/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj +++ b/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj @@ -53,6 +53,7 @@ + diff --git a/Ombi.Api.Models/Ombi.Api.Models.csproj b/Ombi.Api.Models/Ombi.Api.Models.csproj index f053da006..003b6d465 100644 --- a/Ombi.Api.Models/Ombi.Api.Models.csproj +++ b/Ombi.Api.Models/Ombi.Api.Models.csproj @@ -82,6 +82,10 @@ + + + + diff --git a/Ombi.Api.Models/Radarr/RadarrAddMovie.cs b/Ombi.Api.Models/Radarr/RadarrAddMovie.cs new file mode 100644 index 000000000..7c2000985 --- /dev/null +++ b/Ombi.Api.Models/Radarr/RadarrAddMovie.cs @@ -0,0 +1,50 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: RadarrAddMovie.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System.Collections.Generic; +using Newtonsoft.Json; +using Ombi.Api.Models.Sonarr; + +namespace Ombi.Api.Models.Radarr +{ + public class RadarrAddMovie + { + + public RadarrError Error { get; set; } + public RadarrAddOptions addOptions { get; set; } + public string title { get; set; } + public string rootFolderPath { get; set; } + public int qualityProfileId { get; set; } + public bool monitored { get; set; } + public int tmdbId { get; set; } + public string cleanTitle { get; set; } + public string imdbId { get; set; } + public string titleSlug { get; set; } + public int id { get; set; } + + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Radarr/RadarrAddOptions.cs b/Ombi.Api.Models/Radarr/RadarrAddOptions.cs new file mode 100644 index 000000000..dbe7ed729 --- /dev/null +++ b/Ombi.Api.Models/Radarr/RadarrAddOptions.cs @@ -0,0 +1,35 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: RadarrAddOptions.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +namespace Ombi.Api.Models.Radarr +{ + public class RadarrAddOptions + { + public bool ignoreEpisodesWithFiles { get; set; } + public bool ignoreEpisodesWithoutFiles { get; set; } + public bool searchForMovie { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Radarr/RadarrError.cs b/Ombi.Api.Models/Radarr/RadarrError.cs new file mode 100644 index 000000000..73ca43f1a --- /dev/null +++ b/Ombi.Api.Models/Radarr/RadarrError.cs @@ -0,0 +1,34 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: RadarrError.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +namespace Ombi.Api.Models.Radarr +{ + public class RadarrError + { + public string message { get; set; } + public string description { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Radarr/RadarrMovieResponse.cs b/Ombi.Api.Models/Radarr/RadarrMovieResponse.cs new file mode 100644 index 000000000..69e48af23 --- /dev/null +++ b/Ombi.Api.Models/Radarr/RadarrMovieResponse.cs @@ -0,0 +1,80 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: RadarrMovieResponse.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System.Collections.Generic; + +namespace Ombi.Api.Models.Radarr +{ + + + public class Image + { + public string coverType { get; set; } + public string url { get; set; } + } + + public class Ratings + { + public int votes { get; set; } + public double value { get; set; } + } + + public class RadarrMovieResponse + { + public string title { get; set; } + public string sortTitle { get; set; } + public int sizeOnDisk { get; set; } + public string status { get; set; } + public string overview { get; set; } + public string inCinemas { get; set; } + public string physicalRelease { get; set; } + public List images { get; set; } + public string website { get; set; } + public bool downloaded { get; set; } + public int year { get; set; } + public bool hasFile { get; set; } + public string youTubeTrailerId { get; set; } + public string studio { get; set; } + public string path { get; set; } + public int profileId { get; set; } + public bool monitored { get; set; } + public int runtime { get; set; } + public string lastInfoSync { get; set; } + public string cleanTitle { get; set; } + public string imdbId { get; set; } + public int tmdbId { get; set; } + public string titleSlug { get; set; } + public List genres { get; set; } + public List tags { get; set; } + public string added { get; set; } + public Ratings ratings { get; set; } + public List alternativeTitles { get; set; } + public int qualityProfileId { get; set; } + public int id { get; set; } + } + +} \ No newline at end of file diff --git a/Ombi.Api/Ombi.Api.csproj b/Ombi.Api/Ombi.Api.csproj index 0e60de918..27a889451 100644 --- a/Ombi.Api/Ombi.Api.csproj +++ b/Ombi.Api/Ombi.Api.csproj @@ -71,6 +71,7 @@ + diff --git a/Ombi.Api/RadarrApi.cs b/Ombi.Api/RadarrApi.cs new file mode 100644 index 000000000..92bb0435b --- /dev/null +++ b/Ombi.Api/RadarrApi.cs @@ -0,0 +1,152 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: CouchPotatoApi.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using NLog; +using Ombi.Api.Interfaces; +using Ombi.Api.Models.Radarr; +using Ombi.Api.Models.Sonarr; +using Ombi.Helpers; +using RestSharp; + +namespace Ombi.Api +{ + public class RadarrApi : IRadarrApi + { + public RadarrApi() + { + Api = new ApiRequest(); + } + private ApiRequest Api { get; set; } + private static Logger Log = LogManager.GetCurrentClassLogger(); + + public List GetProfiles(string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/profile", Method = Method.GET }; + + 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) + }); + + var obj = policy.Execute(() => Api.ExecuteJson>(request, baseUrl)); + + return obj; + } + + public RadarrAddMovie AddMovie(int tmdbId, string title, int qualityId, string rootPath, string apiKey, Uri baseUrl, bool searchNow = false) + { + var request = new RestRequest + { + Resource = "/api/movie", + Method = Method.POST + }; + + var options = new RadarrAddMovie + { + title = title, + tmdbId = tmdbId, + qualityProfileId = qualityId, + rootFolderPath = rootPath, + titleSlug = title + + }; + + if (searchNow) + { + options.addOptions = new RadarrAddOptions + { + searchForMovie = true + }; + } + + + request.AddHeader("X-Api-Key", apiKey); + request.AddJsonBody(options); + + RadarrAddMovie result; + try + { + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling AddSeries for Sonarr, Retrying {0}", timespan), new TimeSpan[] { + TimeSpan.FromSeconds (2) + }); + + var response = policy.Execute(() => Api.Execute(request, baseUrl)); + if (response.Content.Contains("\"message\":")) + { + var error = JsonConvert.DeserializeObject < RadarrError>(response.Content); + return new RadarrAddMovie {Error = error}; + } + return JsonConvert.DeserializeObject < RadarrAddMovie>(response.Content); + } + catch (JsonSerializationException jse) + { + Log.Error(jse); + } + return null; + } + + + public SystemStatus SystemStatus(string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/system/status", Method = Method.GET }; + request.AddHeader("X-Api-Key", apiKey); + + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling SystemStatus for Sonarr, Retrying {0}", timespan), new TimeSpan[] { + TimeSpan.FromSeconds (2), + TimeSpan.FromSeconds(5), + TimeSpan.FromSeconds(10) + }); + + var obj = policy.Execute(() => Api.ExecuteJson(request, baseUrl)); + + return obj; + } + + + public List GetMovies(string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/movie", Method = Method.GET }; + request.AddHeader("X-Api-Key", apiKey); + + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling SystemStatus for Sonarr, Retrying {0}", timespan), new TimeSpan[] { + TimeSpan.FromSeconds (2), + TimeSpan.FromSeconds(5), + TimeSpan.FromSeconds(10) + }); + + var obj = policy.Execute(() => Api.Execute(request, baseUrl)); + + return JsonConvert.DeserializeObject>(obj.Content); + } + } +} \ No newline at end of file diff --git a/Ombi.Core/Ombi.Core.csproj b/Ombi.Core/Ombi.Core.csproj index 6746f6631..f3f05b9da 100644 --- a/Ombi.Core/Ombi.Core.csproj +++ b/Ombi.Core/Ombi.Core.csproj @@ -123,6 +123,7 @@ + diff --git a/Ombi.Core/SettingModels/RadarrSettings.cs b/Ombi.Core/SettingModels/RadarrSettings.cs new file mode 100644 index 000000000..b8a6287f7 --- /dev/null +++ b/Ombi.Core/SettingModels/RadarrSettings.cs @@ -0,0 +1,37 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SonarrSettings.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +namespace Ombi.Core.SettingModels +{ + public sealed class RadarrSettings : ExternalSettings + { + public bool Enabled { get; set; } + public string ApiKey { get; set; } + public string QualityProfile { get; set; } + public string RootPath { get; set; } + + } +} \ No newline at end of file diff --git a/Ombi.UI/Ombi.UI.csproj b/Ombi.UI/Ombi.UI.csproj index 30083f4d5..524d11c81 100644 --- a/Ombi.UI/Ombi.UI.csproj +++ b/Ombi.UI/Ombi.UI.csproj @@ -792,6 +792,9 @@ Always + + Always + web.config diff --git a/Ombi.UI/Views/Admin/Radarr.cshtml b/Ombi.UI/Views/Admin/Radarr.cshtml new file mode 100644 index 000000000..bec991c71 --- /dev/null +++ b/Ombi.UI/Views/Admin/Radarr.cshtml @@ -0,0 +1,260 @@ +@using Ombi.UI.Helpers +@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase +@Html.Partial("Shared/Partial/_Sidebar") +@{ + int port; + if (Model.Port == 0) + { + port = 7878; + } + else + { + port = Model.Port; + } +} +
+
+
+ Radarr Settings + @Html.Checkbox(Model.Enabled, "Enabled", "Enabled") + + +
+ +
+ +
+
+ +
+ + +
+ +
+
+ + +
+ +
+ +
+
+ @Html.Checkbox(Model.Ssl, "Ssl", "Ssl") + + + +
+ +
+ +
+
+
+
+ +
+
+
+ +
+ +
+
+ +
+ +
+ + +
+
+ +
+
+ + @if (Model.SeasonFolders) + { + + } + else + { + + } + + +
+ +
+
+
+ +
+
+ + +
+
+ +
+
+
+
+
+ + + + \ No newline at end of file From 02a1770b3106a35358d523c5f5ee8fc2bd3df799 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Wed, 18 Jan 2017 08:39:10 +0000 Subject: [PATCH 06/41] More for #923 --- Ombi.Core/CacheKeys.cs | 1 + Ombi.UI/Modules/Admin/AdminModule.cs | 55 +++++++++++++++++++- Ombi.UI/Modules/ApplicationTesterModule.cs | 36 ++++++++++++- Ombi.UI/NinjectModules/ApiModule.cs | 1 + Ombi.UI/Startup.cs | 4 ++ Ombi.UI/Views/Admin/Radarr.cshtml | 26 ++------- Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml | 1 + 7 files changed, 99 insertions(+), 25 deletions(-) diff --git a/Ombi.Core/CacheKeys.cs b/Ombi.Core/CacheKeys.cs index 3638875eb..5e6e6c1f3 100644 --- a/Ombi.Core/CacheKeys.cs +++ b/Ombi.Core/CacheKeys.cs @@ -37,6 +37,7 @@ public struct TimeFrameMinutes public const string PlexEpisodes = nameof(PlexEpisodes); public const string TvDbToken = nameof(TvDbToken); public const string SonarrQualityProfiles = nameof(SonarrQualityProfiles); + public const string RadarrQualityProfiles = nameof(RadarrQualityProfiles); public const string SonarrQueued = nameof(SonarrQueued); public const string SickRageQualityProfiles = nameof(SickRageQualityProfiles); public const string SickRageQueued = nameof(SickRageQueued); diff --git a/Ombi.UI/Modules/Admin/AdminModule.cs b/Ombi.UI/Modules/Admin/AdminModule.cs index c0d79d183..81b0f8422 100644 --- a/Ombi.UI/Modules/Admin/AdminModule.cs +++ b/Ombi.UI/Modules/Admin/AdminModule.cs @@ -95,6 +95,8 @@ public class AdminModule : BaseModule private ISettingsService NotifySettings { get; } private ISettingsService DiscordSettings { get; } private IDiscordApi DiscordApi { get; } + private ISettingsService RadarrSettings { get; } + private IRadarrApi RadarrApi { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); public AdminModule(ISettingsService prService, @@ -122,7 +124,7 @@ public AdminModule(ISettingsService prService, ISettingsService notifyService, IRecentlyAdded recentlyAdded, ISettingsService watcherSettings , ISettingsService discord, - IDiscordApi discordapi + IDiscordApi discordapi, ISettingsService settings, IRadarrApi radarrApi , ISecurityExtensions security) : base("admin", prService, security) { PrService = prService; @@ -156,6 +158,8 @@ IDiscordApi discordapi WatcherSettings = watcherSettings; DiscordSettings = discord; DiscordApi = discordapi; + RadarrSettings = settings; + RadarrApi = radarrApi; Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx); @@ -178,11 +182,15 @@ IDiscordApi discordapi Get["/sonarr"] = _ => Sonarr(); Post["/sonarr"] = _ => SaveSonarr(); + Post["/sonarrprofiles"] = _ => GetSonarrQualityProfiles(); + + Get["/radarr", true] = async (x, ct) => await Radarr(); + Post["/radarr"] = _ => SaveRadarr(); + Post["/radarrprofiles"] = _ => GetRadarrQualityProfiles(); Get["/sickrage"] = _ => Sickrage(); Post["/sickrage"] = _ => SaveSickrage(); - Post["/sonarrprofiles"] = _ => GetSonarrQualityProfiles(); Post["/cpprofiles", true] = async (x, ct) => await GetCpProfiles(); Post["/cpapikey"] = x => GetCpApiKey(); @@ -465,6 +473,49 @@ private Response SaveSonarr() : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); } + private async Task Radarr() + { + var settings = await RadarrSettings.GetSettingsAsync(); + + return View["Radarr", settings]; + } + + private Response SaveRadarr() + { + var sonarrSettings = this.Bind(); + + var valid = this.Validate(sonarrSettings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + var sickRageEnabled = SickRageService.GetSettings().Enabled; + if (sickRageEnabled) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "SickRage is enabled, we cannot enable Sonarr and SickRage" }); + } + sonarrSettings.ApiKey = sonarrSettings.ApiKey.Trim(); + var result = SonarrService.SaveSettings(sonarrSettings); + + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Sonarr!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private Response GetRadarrQualityProfiles() + { + var settings = this.Bind(); + var profiles = RadarrApi.GetProfiles(settings.ApiKey, settings.FullUri); + + // set the cache + if (profiles != null) + { + Cache.Set(CacheKeys.RadarrQualityProfiles, profiles); + } + + return Response.AsJson(profiles); + } + private Negotiator Sickrage() { var settings = SickRageService.GetSettings(); diff --git a/Ombi.UI/Modules/ApplicationTesterModule.cs b/Ombi.UI/Modules/ApplicationTesterModule.cs index b0d4f2d17..a7608f547 100644 --- a/Ombi.UI/Modules/ApplicationTesterModule.cs +++ b/Ombi.UI/Modules/ApplicationTesterModule.cs @@ -46,7 +46,7 @@ public class ApplicationTesterModule : BaseAuthModule public ApplicationTesterModule(ICouchPotatoApi cpApi, ISonarrApi sonarrApi, IPlexApi plexApi, ISickRageApi srApi, IHeadphonesApi hpApi, ISettingsService pr, ISecurityExtensions security, - IWatcherApi watcherApi) : base("test", pr, security) + IWatcherApi watcherApi, IRadarrApi radarrApi) : base("test", pr, security) { this.RequiresAuthentication(); @@ -56,9 +56,11 @@ public ApplicationTesterModule(ICouchPotatoApi cpApi, ISonarrApi sonarrApi, IPle SickRageApi = srApi; HeadphonesApi = hpApi; WatcherApi = watcherApi; + RadarrApi = radarrApi; Post["/cp"] = _ => CouchPotatoTest(); Post["/sonarr"] = _ => SonarrTest(); + Post["/radarr"] = _ => RadarrTest(); Post["/plex"] = _ => PlexTest(); Post["/sickrage"] = _ => SickRageTest(); Post["/headphones"] = _ => HeadphonesTest(); @@ -73,6 +75,7 @@ public ApplicationTesterModule(ICouchPotatoApi cpApi, ISonarrApi sonarrApi, IPle private ISickRageApi SickRageApi { get; } private IHeadphonesApi HeadphonesApi { get; } private IWatcherApi WatcherApi { get; } + private IRadarrApi RadarrApi { get; } private Response CouchPotatoTest() { @@ -148,7 +151,7 @@ private Response SonarrTest() : Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to Sonarr, please check your settings." }); } - catch (Exception e) // Exceptions are expected, if we cannot connect so we will just log and swallow them. + catch (Exception e) // Exceptions are expected, if we cannot connect so we will just log and swallow them. { Log.Warn("Exception thrown when attempting to get Sonarr's status: "); Log.Warn(e); @@ -161,6 +164,35 @@ private Response SonarrTest() } } + private Response RadarrTest() + { + var radarrSettings = this.Bind(); + var valid = this.Validate(radarrSettings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + try + { + var status = RadarrApi.SystemStatus(radarrSettings.ApiKey, radarrSettings.FullUri); + return status?.version != null + ? Response.AsJson(new JsonResponseModel { Result = true, Message = "Connected to Radarr successfully!" }) + : Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to Radarr, please check your settings." }); + + } + catch (Exception e) // Exceptions are expected, if we cannot connect so we will just log and swallow them. + { + Log.Warn("Exception thrown when attempting to get Radarr's status: "); + Log.Warn(e); + var message = $"Could not connect to Radarr, please check your settings. Exception Message: {e.Message}"; + if (e.InnerException != null) + { + message = $"Could not connect to Radarr, please check your settings. Exception Message: {e.InnerException.Message}"; + } + return Response.AsJson(new JsonResponseModel { Result = false, Message = message }); + } + } + private Response PlexTest() { var plexSettings = this.Bind(); diff --git a/Ombi.UI/NinjectModules/ApiModule.cs b/Ombi.UI/NinjectModules/ApiModule.cs index 9de21af99..873a3c527 100644 --- a/Ombi.UI/NinjectModules/ApiModule.cs +++ b/Ombi.UI/NinjectModules/ApiModule.cs @@ -48,6 +48,7 @@ public override void Load() Bind().To(); Bind().To(); Bind().To(); + Bind().To(); } } } \ No newline at end of file diff --git a/Ombi.UI/Startup.cs b/Ombi.UI/Startup.cs index e366901c1..ff8590da4 100644 --- a/Ombi.UI/Startup.cs +++ b/Ombi.UI/Startup.cs @@ -124,6 +124,10 @@ private void SubscribeAllObservers(IResolutionRoot container) var slackService = container.Get>(); var slackSettings = slackService.GetSettings(); SubScribeOvserver(slackSettings, notificationService, new SlackNotification(container.Get(), slackService)); + + var discordSettings = container.Get>(); + var discordService = discordSettings.GetSettings(); + SubScribeOvserver(discordService, notificationService, new DiscordNotification(container.Get(), discordSettings)); } private void SubScribeOvserver(T settings, INotificationService notificationService, INotification notification) diff --git a/Ombi.UI/Views/Admin/Radarr.cshtml b/Ombi.UI/Views/Admin/Radarr.cshtml index bec991c71..e0ce08d07 100644 --- a/Ombi.UI/Views/Admin/Radarr.cshtml +++ b/Ombi.UI/Views/Admin/Radarr.cshtml @@ -67,26 +67,10 @@
- +
-
-
- - @if (Model.SeasonFolders) - { - - } - else - { - - } - - -
- -
@@ -124,7 +108,7 @@ $.ajax({ type: $form.prop("method"), data: $form.serialize(), - url: "sonarrprofiles", + url: "radarrprofiles", dataType: "json", success: function(response) { response.forEach(function(result) { @@ -202,7 +186,7 @@ $.ajax({ type: $form.prop("method"), data: $form.serialize(), - url: "sonarrprofiles", + url: "radarrprofiles", dataType: "json", success: function (response) { response.forEach(function (result) { @@ -219,7 +203,7 @@ }); var base = '@Html.GetBaseUrl()'; - $('#testSonarr').click(function (e) { + $('#testRadarr').click(function (e) { $('#spinner').attr("class", "fa fa-spinner fa-spin"); e.preventDefault(); @@ -230,7 +214,7 @@ var data = $form.serialize(); data = data + "&qualityProfile=" + qualityProfile; - var url = createBaseUrl(base, '/test/sonarr'); + var url = createBaseUrl(base, '/test/radarr'); $.ajax({ type: $form.prop("method"), url: url, diff --git a/Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml b/Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml index d38827cf8..a2b1dc021 100644 --- a/Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml +++ b/Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml @@ -11,6 +11,7 @@ @Html.GetSidebarUrl(Context, "/admin/plex", "Plex") @Html.GetSidebarUrl(Context, "/admin/couchpotato", "CouchPotato") @Html.GetSidebarUrl(Context, "/admin/watcher", "Watcher (beta)") + @Html.GetSidebarUrl(Context, "/admin/radarr", "Radarr (beta)") @Html.GetSidebarUrl(Context, "/admin/sonarr", "Sonarr") @Html.GetSidebarUrl(Context, "/admin/sickrage", "SickRage") @Html.GetSidebarUrl(Context, "/admin/headphones", "Headphones (beta)") From 4926255094ae3ac26b57052e5d2f1aee60f875c1 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Wed, 18 Jan 2017 21:05:08 +0000 Subject: [PATCH 07/41] Finished #923 !!! --- Ombi.Api.Models/Radarr/RadarrAddMovie.cs | 5 + Ombi.Api/RadarrApi.cs | 4 +- Ombi.Core/CacheKeys.cs | 1 + Ombi.Core/MovieSender.cs | 30 +++++- .../SettingModels/ScheduledJobsSettings.cs | 1 + Ombi.Services/Interfaces/IRadarrCacher.cs | 11 ++ Ombi.Services/Jobs/JobNames.cs | 1 + Ombi.Services/Jobs/RadarrCacher.cs | 102 ++++++++++++++++++ Ombi.Services/Ombi.Services.csproj | 2 + Ombi.UI/Jobs/Scheduler.cs | 14 +++ Ombi.UI/Modules/Admin/AdminModule.cs | 54 +++++++--- Ombi.UI/Modules/Admin/IntegrationModule.cs | 12 +++ Ombi.UI/Modules/SearchModule.cs | 13 ++- Ombi.UI/NinjectModules/ServicesModule.cs | 1 + Ombi.UI/Ombi.UI.csproj | 1 + Ombi.UI/Validators/RadarrValidator.cs | 43 ++++++++ Ombi.UI/Views/Admin/Radarr.cshtml | 2 +- Ombi.UI/Views/Admin/SchedulerSettings.cshtml | 8 ++ 18 files changed, 283 insertions(+), 22 deletions(-) create mode 100644 Ombi.Services/Interfaces/IRadarrCacher.cs create mode 100644 Ombi.Services/Jobs/RadarrCacher.cs create mode 100644 Ombi.UI/Validators/RadarrValidator.cs diff --git a/Ombi.Api.Models/Radarr/RadarrAddMovie.cs b/Ombi.Api.Models/Radarr/RadarrAddMovie.cs index 7c2000985..5c589bd12 100644 --- a/Ombi.Api.Models/Radarr/RadarrAddMovie.cs +++ b/Ombi.Api.Models/Radarr/RadarrAddMovie.cs @@ -34,6 +34,10 @@ namespace Ombi.Api.Models.Radarr public class RadarrAddMovie { + public RadarrAddMovie() + { + images = new List(); + } public RadarrError Error { get; set; } public RadarrAddOptions addOptions { get; set; } public string title { get; set; } @@ -41,6 +45,7 @@ public class RadarrAddMovie public int qualityProfileId { get; set; } public bool monitored { get; set; } public int tmdbId { get; set; } + public List images { get; set; } public string cleanTitle { get; set; } public string imdbId { get; set; } public string titleSlug { get; set; } diff --git a/Ombi.Api/RadarrApi.cs b/Ombi.Api/RadarrApi.cs index 92bb0435b..648ff87b3 100644 --- a/Ombi.Api/RadarrApi.cs +++ b/Ombi.Api/RadarrApi.cs @@ -77,8 +77,8 @@ public RadarrAddMovie AddMovie(int tmdbId, string title, int qualityId, string r tmdbId = tmdbId, qualityProfileId = qualityId, rootFolderPath = rootPath, - titleSlug = title - + titleSlug = title, + monitored = true }; if (searchNow) diff --git a/Ombi.Core/CacheKeys.cs b/Ombi.Core/CacheKeys.cs index 5e6e6c1f3..889ac6df7 100644 --- a/Ombi.Core/CacheKeys.cs +++ b/Ombi.Core/CacheKeys.cs @@ -39,6 +39,7 @@ public struct TimeFrameMinutes public const string SonarrQualityProfiles = nameof(SonarrQualityProfiles); public const string RadarrQualityProfiles = nameof(RadarrQualityProfiles); public const string SonarrQueued = nameof(SonarrQueued); + public const string RadarrMovies = nameof(RadarrMovies); public const string SickRageQualityProfiles = nameof(SickRageQualityProfiles); public const string SickRageQueued = nameof(SickRageQueued); public const string CouchPotatoQualityProfiles = nameof(CouchPotatoQualityProfiles); diff --git a/Ombi.Core/MovieSender.cs b/Ombi.Core/MovieSender.cs index 39e2e0554..cf5b4fda3 100644 --- a/Ombi.Core/MovieSender.cs +++ b/Ombi.Core/MovieSender.cs @@ -37,16 +37,20 @@ namespace Ombi.Core public class MovieSender : IMovieSender { public MovieSender(ISettingsService cp, ISettingsService watcher, - ICouchPotatoApi cpApi, IWatcherApi watcherApi) + ICouchPotatoApi cpApi, IWatcherApi watcherApi, IRadarrApi radarrApi, ISettingsService radarrSettings) { CouchPotatoSettings = cp; WatcherSettings = watcher; CpApi = cpApi; WatcherApi = watcherApi; + RadarrSettings = radarrSettings; + RadarrApi = radarrApi; } private ISettingsService CouchPotatoSettings { get; } private ISettingsService WatcherSettings { get; } + private ISettingsService RadarrSettings { get; } + private IRadarrApi RadarrApi { get; } private ICouchPotatoApi CpApi { get; } private IWatcherApi WatcherApi { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); @@ -55,6 +59,7 @@ public async Task Send(RequestedModel model, string qualityId { var cpSettings = await CouchPotatoSettings.GetSettingsAsync(); var watcherSettings = await WatcherSettings.GetSettingsAsync(); + var radarrSettings = await RadarrSettings.GetSettingsAsync(); if (cpSettings.Enabled) { @@ -66,6 +71,11 @@ public async Task Send(RequestedModel model, string qualityId return SendToWatcher(model, watcherSettings); } + if (radarrSettings.Enabled) + { + return SendToRadarr(model, radarrSettings); + } + return new MovieSenderResult { Result = false, MovieSendingEnabled = false }; } @@ -91,5 +101,23 @@ private MovieSenderResult SendToCp(RequestedModel model, CouchPotatoSettings set var result = CpApi.AddMovie(model.ImdbId, settings.ApiKey, model.Title, settings.FullUri, qualityId); return new MovieSenderResult { Result = result, MovieSendingEnabled = true }; } + + private MovieSenderResult SendToRadarr(RequestedModel model, RadarrSettings settings) + { + var qualityProfile = 0; + int.TryParse(settings.QualityProfile, out qualityProfile); + var result = RadarrApi.AddMovie(model.ProviderId, model.Title, qualityProfile, settings.RootPath, settings.ApiKey, settings.FullUri, true); + + if (!string.IsNullOrEmpty(result.Error?.message)) + { + Log.Error(result.Error.message); + return new MovieSenderResult { Result = false }; + } + if (!string.IsNullOrEmpty(result.title)) + { + return new MovieSenderResult { Result = true, MovieSendingEnabled = true }; + } + return new MovieSenderResult { Result = false, MovieSendingEnabled = true }; + } } } \ No newline at end of file diff --git a/Ombi.Core/SettingModels/ScheduledJobsSettings.cs b/Ombi.Core/SettingModels/ScheduledJobsSettings.cs index 76921a679..a2609206b 100644 --- a/Ombi.Core/SettingModels/ScheduledJobsSettings.cs +++ b/Ombi.Core/SettingModels/ScheduledJobsSettings.cs @@ -46,5 +46,6 @@ public class ScheduledJobsSettings : Settings public int FaultQueueHandler { get; set; } public int PlexContentCacher { get; set; } public int PlexUserChecker { get; set; } + public int RadarrCacher { get; set; } } } \ No newline at end of file diff --git a/Ombi.Services/Interfaces/IRadarrCacher.cs b/Ombi.Services/Interfaces/IRadarrCacher.cs new file mode 100644 index 000000000..85b2fae38 --- /dev/null +++ b/Ombi.Services/Interfaces/IRadarrCacher.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Ombi.Services.Models; + +namespace Ombi.Services.Interfaces +{ + public interface IRadarrCacher + { + void Queued(); + int[] QueuedIds(); + } +} diff --git a/Ombi.Services/Jobs/JobNames.cs b/Ombi.Services/Jobs/JobNames.cs index 06b5b32ac..8b663a8ae 100644 --- a/Ombi.Services/Jobs/JobNames.cs +++ b/Ombi.Services/Jobs/JobNames.cs @@ -32,6 +32,7 @@ public static class JobNames public const string CpCacher = "CouchPotato Cacher"; public const string WatcherCacher = "Watcher Cacher"; public const string SonarrCacher = "Sonarr Cacher"; + public const string RadarrCacher = "Radarr Cacher"; public const string SrCacher = "SickRage Cacher"; public const string PlexChecker = "Plex Availability Cacher"; public const string PlexCacher = "Plex Cacher"; diff --git a/Ombi.Services/Jobs/RadarrCacher.cs b/Ombi.Services/Jobs/RadarrCacher.cs new file mode 100644 index 000000000..7273fb626 --- /dev/null +++ b/Ombi.Services/Jobs/RadarrCacher.cs @@ -0,0 +1,102 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexAvailabilityChecker.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System.Collections.Generic; +using System.Linq; +using NLog; +using Ombi.Api.Interfaces; +using Ombi.Api.Models.Radarr; +using Ombi.Core; +using Ombi.Core.SettingModels; +using Ombi.Helpers; +using Ombi.Services.Interfaces; +using Quartz; + +namespace Ombi.Services.Jobs +{ + public class RadarrCacher : IJob, IRadarrCacher + { + public RadarrCacher(ISettingsService radarrService, IRadarrApi radarrApi, ICacheProvider cache, IJobRecord rec) + { + RadarrSettings = radarrService; + RadarrApi = radarrApi; + Job = rec; + Cache = cache; + } + + private ISettingsService RadarrSettings { get; } + private ICacheProvider Cache { get; } + private IRadarrApi RadarrApi { get; } + private IJobRecord Job { get; } + + private static Logger Log = LogManager.GetCurrentClassLogger(); + + public void Queued() + { + var settings = RadarrSettings.GetSettings(); + if (settings.Enabled) + { + Job.SetRunning(true, JobNames.RadarrCacher); + try + { + var movies = RadarrApi.GetMovies(settings.ApiKey, settings.FullUri); + if (movies != null) + { + var movieIds = movies.Select(x => x.tmdbId).ToList(); + Cache.Set(CacheKeys.RadarrMovies, movieIds, CacheKeys.TimeFrameMinutes.SchedulerCaching); + } + } + catch (System.Exception ex) + { + Log.Error(ex, "Failed caching queued items from Radarr"); + } + finally + { + Job.Record(JobNames.RadarrCacher); + Job.SetRunning(false, JobNames.RadarrCacher); + } + } + } + + // we do not want to set here... + public int[] QueuedIds() + { + var retVal = new List(); + var movies = Cache.Get>(CacheKeys.RadarrMovies); + if (movies != null) + { + retVal.AddRange(movies); + } + return retVal.ToArray(); + } + + public void Execute(IJobExecutionContext context) + { + Queued(); + } + } +} \ No newline at end of file diff --git a/Ombi.Services/Ombi.Services.csproj b/Ombi.Services/Ombi.Services.csproj index ec412e87f..444f86980 100644 --- a/Ombi.Services/Ombi.Services.csproj +++ b/Ombi.Services/Ombi.Services.csproj @@ -86,9 +86,11 @@ + + diff --git a/Ombi.UI/Jobs/Scheduler.cs b/Ombi.UI/Jobs/Scheduler.cs index 7450e1c86..b2c32188a 100644 --- a/Ombi.UI/Jobs/Scheduler.cs +++ b/Ombi.UI/Jobs/Scheduler.cs @@ -75,6 +75,7 @@ private IEnumerable CreateJobs() JobBuilder.Create().WithIdentity("UserRequestLimiter", "Request").Build(), JobBuilder.Create().WithIdentity("RecentlyAddedModel", "Email").Build(), JobBuilder.Create().WithIdentity("FaultQueueHandler", "Fault").Build(), + JobBuilder.Create().WithIdentity("RadarrCacher", "Cache").Build(), }; jobs.AddRange(jobList); @@ -170,6 +171,10 @@ private IEnumerable CreateTriggers() { s.PlexUserChecker = 24; } + if (s.RadarrCacher == 0) + { + s.RadarrCacher = 60; + } var triggers = new List(); @@ -222,6 +227,14 @@ private IEnumerable CreateTriggers() .WithSimpleSchedule(x => x.WithIntervalInMinutes(s.WatcherCacher).RepeatForever()) .Build(); + var radarrCacher = + TriggerBuilder.Create() + .WithIdentity("RadarrCacher", "Cache") + .StartNow() + //.StartAt(DateBuilder.FutureDate(2, IntervalUnit.Minute)) + .WithSimpleSchedule(x => x.WithIntervalInMinutes(s.RadarrCacher).RepeatForever()) + .Build(); + var storeBackup = TriggerBuilder.Create() .WithIdentity("StoreBackup", "Database") @@ -280,6 +293,7 @@ private IEnumerable CreateTriggers() triggers.Add(fault); triggers.Add(plexCacher); triggers.Add(plexUserChecker); + triggers.Add(radarrCacher); return triggers; } diff --git a/Ombi.UI/Modules/Admin/AdminModule.cs b/Ombi.UI/Modules/Admin/AdminModule.cs index 81b0f8422..06533b7d3 100644 --- a/Ombi.UI/Modules/Admin/AdminModule.cs +++ b/Ombi.UI/Modules/Admin/AdminModule.cs @@ -175,7 +175,7 @@ public AdminModule(ISettingsService prService, Get["/getusers"] = _ => GetUsers(); Get["/couchpotato"] = _ => CouchPotato(); - Post["/couchpotato"] = _ => SaveCouchPotato(); + Post["/couchpotato", true] = async (x, ct) => await SaveCouchPotato(); Get["/plex"] = _ => Plex(); Post["/plex", true] = async (x, ct) => await SavePlex(); @@ -185,7 +185,7 @@ public AdminModule(ISettingsService prService, Post["/sonarrprofiles"] = _ => GetSonarrQualityProfiles(); Get["/radarr", true] = async (x, ct) => await Radarr(); - Post["/radarr"] = _ => SaveRadarr(); + Post["/radarr", true] = async (x, ct) => await SaveRadarr(); Post["/radarrprofiles"] = _ => GetRadarrQualityProfiles(); Get["/sickrage"] = _ => Sickrage(); @@ -385,7 +385,7 @@ private Negotiator CouchPotato() return View["CouchPotato", settings]; } - private Response SaveCouchPotato() + private async Task SaveCouchPotato() { var couchPotatoSettings = this.Bind(); var valid = this.Validate(couchPotatoSettings); @@ -394,7 +394,7 @@ private Response SaveCouchPotato() return Response.AsJson(valid.SendJsonError()); } - var watcherSettings = WatcherSettings.GetSettings(); + var watcherSettings = await WatcherSettings.GetSettingsAsync(); if (watcherSettings.Enabled) { @@ -406,8 +406,20 @@ private Response SaveCouchPotato() }); } + var radarrSettings = await RadarrSettings.GetSettingsAsync(); + + if (radarrSettings.Enabled) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = "Cannot have Radarr and CouchPotato both enabled." + }); + } + couchPotatoSettings.ApiKey = couchPotatoSettings.ApiKey.Trim(); - var result = CpService.SaveSettings(couchPotatoSettings); + var result = await CpService.SaveSettingsAsync(couchPotatoSettings); return Response.AsJson(result ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for CouchPotato!" } : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); @@ -465,6 +477,7 @@ private Response SaveSonarr() { return Response.AsJson(new JsonResponseModel { Result = false, Message = "SickRage is enabled, we cannot enable Sonarr and SickRage" }); } + sonarrSettings.ApiKey = sonarrSettings.ApiKey.Trim(); var result = SonarrService.SaveSettings(sonarrSettings); @@ -480,25 +493,34 @@ private async Task Radarr() return View["Radarr", settings]; } - private Response SaveRadarr() + private async Task SaveRadarr() { - var sonarrSettings = this.Bind(); + var radarrSettings = this.Bind(); - var valid = this.Validate(sonarrSettings); - if (!valid.IsValid) + //Check Watcher and CP make sure they are not enabled + var watcher = await WatcherSettings.GetSettingsAsync(); + if (watcher.Enabled) { - return Response.AsJson(valid.SendJsonError()); + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Watcher is enabled, we cannot enable Watcher and Radarr" }); } - var sickRageEnabled = SickRageService.GetSettings().Enabled; - if (sickRageEnabled) + + var cp = await CpService.GetSettingsAsync(); + if (cp.Enabled) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "SickRage is enabled, we cannot enable Sonarr and SickRage" }); + return Response.AsJson(new JsonResponseModel { Result = false, Message = "CouchPotato is enabled, we cannot enable Watcher and CouchPotato" }); } - sonarrSettings.ApiKey = sonarrSettings.ApiKey.Trim(); - var result = SonarrService.SaveSettings(sonarrSettings); + + var valid = this.Validate(radarrSettings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + radarrSettings.ApiKey = radarrSettings.ApiKey.Trim(); + var result = await RadarrSettings.SaveSettingsAsync(radarrSettings); return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Sonarr!" } + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Radarr!" } : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); } diff --git a/Ombi.UI/Modules/Admin/IntegrationModule.cs b/Ombi.UI/Modules/Admin/IntegrationModule.cs index 9cc718e40..b8a91e520 100644 --- a/Ombi.UI/Modules/Admin/IntegrationModule.cs +++ b/Ombi.UI/Modules/Admin/IntegrationModule.cs @@ -97,6 +97,18 @@ private async Task SaveWatcher() }); } + var watcherSettings = await WatcherSettings.GetSettingsAsync(); + + if (watcherSettings.Enabled) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = "Cannot have Watcher and CouchPotato both enabled." + }); + } + settings.ApiKey = settings.ApiKey.Trim(); var result = await WatcherSettings.SaveSettingsAsync(settings); return Response.AsJson(result diff --git a/Ombi.UI/Modules/SearchModule.cs b/Ombi.UI/Modules/SearchModule.cs index 30acdf68a..b64b586f8 100644 --- a/Ombi.UI/Modules/SearchModule.cs +++ b/Ombi.UI/Modules/SearchModule.cs @@ -76,7 +76,7 @@ public SearchModule(ICacheProvider cache, ISettingsService plexService, ISettingsService auth, IRepository u, ISettingsService email, IIssueService issue, IAnalytics a, IRepository rl, ITransientFaultQueue tfQueue, IRepository content, - ISecurityExtensions security, IMovieSender movieSender) + ISecurityExtensions security, IMovieSender movieSender, IRadarrCacher radarrCacher) : base("search", prSettings, security) { Auth = auth; @@ -108,6 +108,7 @@ public SearchModule(ICacheProvider cache, PlexContentRepository = content; MovieSender = movieSender; WatcherCacher = watcherCacher; + RadarrCacher = radarrCacher; Get["SearchIndex", "/", true] = async (x, ct) => await RequestLoad(); @@ -157,6 +158,7 @@ public SearchModule(ICacheProvider cache, private IAnalytics Analytics { get; } private ITransientFaultQueue FaultQueue { get; } private IRepository RequestLimitRepo { get; } + private IRadarrCacher RadarrCacher { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); private async Task RequestLoad() @@ -236,6 +238,7 @@ private async Task ProcessMovies(MovieSearchType searchType, string se var cpCached = CpCacher.QueuedIds(); var watcherCached = WatcherCacher.QueuedIds(); + var radarrCached = RadarrCacher.QueuedIds(); var content = PlexContentRepository.GetAll(); var plexMovies = Checker.GetPlexMovies(content); var viewMovies = new List(); @@ -288,13 +291,19 @@ private async Task ProcessMovies(MovieSearchType searchType, string se } else if (cpCached.Contains(movie.Id) && canSee) // compare to the couchpotato db { + viewMovie.Approved = true; viewMovie.Requested = true; } else if(watcherCached.Contains(imdbId) && canSee) // compare to the watcher db { + viewMovie.Approved = true; + viewMovie.Requested = true; + } + else if (radarrCached.Contains(movie.Id) && canSee) + { + viewMovie.Approved = true; viewMovie.Requested = true; } - viewMovies.Add(viewMovie); } diff --git a/Ombi.UI/NinjectModules/ServicesModule.cs b/Ombi.UI/NinjectModules/ServicesModule.cs index 7c5f9095a..3b02a0213 100644 --- a/Ombi.UI/NinjectModules/ServicesModule.cs +++ b/Ombi.UI/NinjectModules/ServicesModule.cs @@ -48,6 +48,7 @@ public override void Load() Bind().To(); Bind().To(); Bind().To(); + Bind().To(); Bind().To(); Bind().To(); Bind().To(); diff --git a/Ombi.UI/Ombi.UI.csproj b/Ombi.UI/Ombi.UI.csproj index 524d11c81..ce3f1453b 100644 --- a/Ombi.UI/Ombi.UI.csproj +++ b/Ombi.UI/Ombi.UI.csproj @@ -288,6 +288,7 @@ + diff --git a/Ombi.UI/Validators/RadarrValidator.cs b/Ombi.UI/Validators/RadarrValidator.cs new file mode 100644 index 000000000..75550c787 --- /dev/null +++ b/Ombi.UI/Validators/RadarrValidator.cs @@ -0,0 +1,43 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SonarrValidator.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using FluentValidation; +using Ombi.Core.SettingModels; + +namespace Ombi.UI.Validators +{ + public class RadarrValidator : AbstractValidator + { + public RadarrValidator() + { + RuleFor(request => request.ApiKey).NotEmpty().WithMessage("You must specify a Api Key."); + RuleFor(request => request.Ip).NotEmpty().WithMessage("You must specify a IP/Host name."); + RuleFor(request => request.Port).NotEmpty().WithMessage("You must specify a Port."); + RuleFor(request => request.QualityProfile).NotEmpty().WithMessage("You must specify a Quality Profile."); + } + } +} \ No newline at end of file diff --git a/Ombi.UI/Views/Admin/Radarr.cshtml b/Ombi.UI/Views/Admin/Radarr.cshtml index e0ce08d07..3d4520e68 100644 --- a/Ombi.UI/Views/Admin/Radarr.cshtml +++ b/Ombi.UI/Views/Admin/Radarr.cshtml @@ -73,7 +73,7 @@
- +
diff --git a/Ombi.UI/Views/Admin/SchedulerSettings.cshtml b/Ombi.UI/Views/Admin/SchedulerSettings.cshtml index 0c0b0200a..9ba6ed904 100644 --- a/Ombi.UI/Views/Admin/SchedulerSettings.cshtml +++ b/Ombi.UI/Views/Admin/SchedulerSettings.cshtml @@ -45,6 +45,14 @@
+
+ + +
+
+ + +
Please note, the minimum time for this to run is 11 hours, if set below 11 then we will ignore that value. This is a very resource intensive job, the less we run it the better.
From 2976e87cc56d0cba73eef46c55de6ae29a46366f Mon Sep 17 00:00:00 2001 From: tidusjar Date: Wed, 18 Jan 2017 21:30:19 +0000 Subject: [PATCH 08/41] #951 --- Ombi.Services/Jobs/RecentlyAdded.cs | 2 +- Ombi.Services/Notification/EmailMessageNotification.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Ombi.Services/Jobs/RecentlyAdded.cs b/Ombi.Services/Jobs/RecentlyAdded.cs index 7e7293578..667ce542e 100644 --- a/Ombi.Services/Jobs/RecentlyAdded.cs +++ b/Ombi.Services/Jobs/RecentlyAdded.cs @@ -455,7 +455,7 @@ private void Send(NewletterSettings newletterSettings, string html, PlexSettings if (!testEmail) { - var users = UserHelper.GetUsersWithFeature(Features.RequestAddedNotification); + var users = UserHelper.GetUsersWithFeature(Features.Newsletter); if (users != null) { foreach (var user in users) diff --git a/Ombi.Services/Notification/EmailMessageNotification.cs b/Ombi.Services/Notification/EmailMessageNotification.cs index f073dcf02..1de8ef7e6 100644 --- a/Ombi.Services/Notification/EmailMessageNotification.cs +++ b/Ombi.Services/Notification/EmailMessageNotification.cs @@ -244,9 +244,9 @@ private async Task EmailAvailableRequest(NotificationModel model, EmailNotificat var email = new EmailBasicTemplate(); var html = email.LoadTemplate( $"Ombi: {model.Title} is now available!", - $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)", + $"Hello! You requested {model.Title} on Ombi! This is now available on Plex! :)", model.ImgSrc); - var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)" }; + var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! You requested {model.Title} on Ombi! This is now available on Plex! :)" }; var message = new MimeMessage { From 80a9e54c4bb1a2802a02236baffbdaed44ad4b90 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Thu, 19 Jan 2017 08:19:23 +0000 Subject: [PATCH 09/41] Fixed #947 --- Ombi.UI/Modules/UserManagementModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ombi.UI/Modules/UserManagementModule.cs b/Ombi.UI/Modules/UserManagementModule.cs index 92d848606..af62f880e 100644 --- a/Ombi.UI/Modules/UserManagementModule.cs +++ b/Ombi.UI/Modules/UserManagementModule.cs @@ -420,7 +420,7 @@ private UserManagementUsersViewModel MapPlexUser(UserFriends plexInfo, PlexUsers FeaturesFormattedString = newUser ? "Processing..." : features.ToString(), Username = plexInfo.Title, Type = UserType.PlexUser, - EmailAddress = plexInfo.Email, + EmailAddress = string.IsNullOrEmpty(plexInfo.Email) ? dbUser.EmailAddress : plexInfo.Email, Alias = dbUser?.UserAlias ?? string.Empty, LastLoggedIn = lastLoggedIn, PlexInfo = new UserManagementPlexInformation From eec7d42a8894eec3e52662767bec96c934ed192a Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Thu, 19 Jan 2017 08:45:13 +0000 Subject: [PATCH 10/41] #956 --- Ombi.UI/Modules/UserLoginModule.cs | 32 ++++++++++++++++++- .../UserManagementSettings.cshtml | 4 +++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Ombi.UI/Modules/UserLoginModule.cs b/Ombi.UI/Modules/UserLoginModule.cs index 434892496..59c30a1aa 100644 --- a/Ombi.UI/Modules/UserLoginModule.cs +++ b/Ombi.UI/Modules/UserLoginModule.cs @@ -233,13 +233,28 @@ private async Task UsernameLogin() var result = await AuthenticationSetup(userId, username, dateTimeOffset, loginGuid, isOwner); + var landingSettings = await LandingPageSettings.GetSettingsAsync(); + + if (landingSettings.Enabled) + { + if (!landingSettings.BeforeLogin) // After Login + { + var uri = Linker.BuildRelativeUri(Context, "LandingPageIndex"); + if (loginGuid != Guid.Empty) + { + return CustomModuleExtensions.LoginAndRedirect(this, result.LoginGuid, null, uri.ToString()); + } + return Response.AsRedirect(uri.ToString()); + } + } + + var retVal = Linker.BuildRelativeUri(Context, "SearchIndex"); if (result.LoginGuid != Guid.Empty) { return CustomModuleExtensions.LoginAndRedirect(this, result.LoginGuid, null, retVal.ToString()); } return Response.AsJson(new { result = true, url = retVal.ToString() }); - } private async Task IsPlexUser(string username) @@ -318,6 +333,21 @@ private async Task PasswordLogin() var m = await AuthenticationSetup(userId, username, dateTimeOffset, loginGuid, isOwner); + var landingSettings = await LandingPageSettings.GetSettingsAsync(); + + if (landingSettings.Enabled) + { + if (!landingSettings.BeforeLogin) // After Login + { + var uri = Linker.BuildRelativeUri(Context, "LandingPageIndex"); + if (m.LoginGuid != Guid.Empty) + { + return CustomModuleExtensions.LoginAndRedirect(this, m.LoginGuid, null, uri.ToString()); + } + return Response.AsRedirect(uri.ToString()); + } + } + var retVal = Linker.BuildRelativeUri(Context, "SearchIndex"); if (m.LoginGuid != Guid.Empty) { diff --git a/Ombi.UI/Views/UserManagementSettings/UserManagementSettings.cshtml b/Ombi.UI/Views/UserManagementSettings/UserManagementSettings.cshtml index a48eae095..db0fae5f2 100644 --- a/Ombi.UI/Views/UserManagementSettings/UserManagementSettings.cshtml +++ b/Ombi.UI/Views/UserManagementSettings/UserManagementSettings.cshtml @@ -8,6 +8,10 @@ User Management Settings 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 + automatically and will take the permissions and features you have selected below. +

Permissions

From 05fbdf2a384f6a07287d95ecef928d00f5df0499 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Fri, 20 Jan 2017 08:11:22 +0000 Subject: [PATCH 11/41] Fixed #955 --- Ombi.UI/Modules/RequestsModule.cs | 3 +++ Ombi.UI/Modules/SearchModule.cs | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Ombi.UI/Modules/RequestsModule.cs b/Ombi.UI/Modules/RequestsModule.cs index f0c0934ac..9c5460323 100644 --- a/Ombi.UI/Modules/RequestsModule.cs +++ b/Ombi.UI/Modules/RequestsModule.cs @@ -235,6 +235,8 @@ private async Task GetTvShows() } + + var canManageRequest = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests); var viewModel = dbTv.Select(tv => new RequestViewModel { @@ -243,6 +245,7 @@ private async Task GetTvShows() Status = tv.Status, ImdbId = tv.ImdbId, Id = tv.Id, + //PosterPath = tv.PosterPath.Contains("http:") ? tv.PosterPath.Replace("http:", "https:") : tv.PosterPath, PosterPath = tv.PosterPath, ReleaseDate = tv.ReleaseDate, ReleaseDateTicks = tv.ReleaseDate.Ticks, diff --git a/Ombi.UI/Modules/SearchModule.cs b/Ombi.UI/Modules/SearchModule.cs index b64b586f8..10be9bfdb 100644 --- a/Ombi.UI/Modules/SearchModule.cs +++ b/Ombi.UI/Modules/SearchModule.cs @@ -693,11 +693,13 @@ private async Task RequestTvShow(int showId, string seasons) DateTime.TryParse(showInfo.premiered, out firstAir); string fullShowName = $"{showInfo.name} ({firstAir.Year})"; + // For some reason the poster path is always http + var posterPath = showInfo.image?.medium.Replace("http:", "https:"); var model = new RequestedModel { Type = RequestType.TvShow, Overview = showInfo.summary.RemoveHtml(), - PosterPath = showInfo.image?.medium, + PosterPath = posterPath, Title = showInfo.name, ReleaseDate = firstAir, Status = showInfo.status, From 7cbea541cef12d020243a18888883c9e0cc2865a Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Fri, 20 Jan 2017 08:13:19 +0000 Subject: [PATCH 12/41] Fixed --- Ombi.UI/Modules/RequestsModule.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Ombi.UI/Modules/RequestsModule.cs b/Ombi.UI/Modules/RequestsModule.cs index 9c5460323..4faefa206 100644 --- a/Ombi.UI/Modules/RequestsModule.cs +++ b/Ombi.UI/Modules/RequestsModule.cs @@ -245,8 +245,7 @@ private async Task GetTvShows() Status = tv.Status, ImdbId = tv.ImdbId, Id = tv.Id, - //PosterPath = tv.PosterPath.Contains("http:") ? tv.PosterPath.Replace("http:", "https:") : tv.PosterPath, - PosterPath = tv.PosterPath, + PosterPath = tv.PosterPath.Contains("http:") ? tv.PosterPath.Replace("http:", "https:") : tv.PosterPath, // We make the poster path https on request, but this is just incase ReleaseDate = tv.ReleaseDate, ReleaseDateTicks = tv.ReleaseDate.Ticks, RequestedDate = tv.RequestedDate, From 9886c404992353191b58dfe9edfaedf65f5c2ee0 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Fri, 20 Jan 2017 15:56:49 +0000 Subject: [PATCH 13/41] Added a bunch of categories for tv search similar to what we have for movies. --- Ombi.Api.Interfaces/ITraktApi.cs | 16 + .../Ombi.Api.Interfaces.csproj | 9 + Ombi.Api.Interfaces/packages.config | 2 + Ombi.Api/Ombi.Api.csproj | 5 + Ombi.Api/TraktApi.cs | 51 ++ Ombi.Api/packages.config | 1 + Ombi.Helpers/DateTimeHelper.cs | 7 + Ombi.UI/Content/search.js | 59 ++- Ombi.UI/Models/SearchTvShowViewModel.cs | 15 + Ombi.UI/Modules/SearchModule.cs | 211 +++++++- Ombi.UI/NinjectModules/ApiModule.cs | 1 + Ombi.UI/Ombi.UI.csproj | 4 + Ombi.UI/Views/Search/Index.cshtml | 474 +++++++++--------- Ombi.UI/packages.config | 1 + 14 files changed, 624 insertions(+), 232 deletions(-) create mode 100644 Ombi.Api.Interfaces/ITraktApi.cs create mode 100644 Ombi.Api/TraktApi.cs diff --git a/Ombi.Api.Interfaces/ITraktApi.cs b/Ombi.Api.Interfaces/ITraktApi.cs new file mode 100644 index 000000000..ed20f039c --- /dev/null +++ b/Ombi.Api.Interfaces/ITraktApi.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using TraktApiSharp.Enums; +using TraktApiSharp.Objects.Get.Shows; +using TraktApiSharp.Objects.Get.Shows.Common; + +namespace Ombi.Api.Interfaces +{ + public interface ITraktApi + { + Task> GetAnticipatedShows(int? page = default(int?), int? limitPerPage = default(int?)); + Task> GetMostWatchesShows(TraktTimePeriod period = null, int? page = default(int?), int? limitPerPage = default(int?)); + Task> GetPopularShows(int? page = default(int?), int? limitPerPage = default(int?)); + Task> GetTrendingShows(int? page = default(int?), int? limitPerPage = default(int?)); + } +} \ No newline at end of file diff --git a/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj b/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj index 1cc18964a..c8c1ca938 100644 --- a/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj +++ b/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj @@ -31,6 +31,10 @@ 4 + + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + ..\packages\RestSharp.105.2.3\lib\net45\RestSharp.dll True @@ -43,6 +47,10 @@ + + ..\packages\TraktApiSharp.0.8.0\lib\portable-net45+netcore45+wpa81\TraktApiSharp.dll + True + @@ -58,6 +66,7 @@ + diff --git a/Ombi.Api.Interfaces/packages.config b/Ombi.Api.Interfaces/packages.config index a63cb4deb..5ac87cab2 100644 --- a/Ombi.Api.Interfaces/packages.config +++ b/Ombi.Api.Interfaces/packages.config @@ -1,4 +1,6 @@  + + \ No newline at end of file diff --git a/Ombi.Api/Ombi.Api.csproj b/Ombi.Api/Ombi.Api.csproj index 27a889451..b9e674cc2 100644 --- a/Ombi.Api/Ombi.Api.csproj +++ b/Ombi.Api/Ombi.Api.csproj @@ -66,12 +66,17 @@ ..\packages\TMDbLib.0.9.0.0-alpha\lib\net45\TMDbLib.dll True + + ..\packages\TraktApiSharp.0.8.0\lib\portable-net45+netcore45+wpa81\TraktApiSharp.dll + True + + diff --git a/Ombi.Api/TraktApi.cs b/Ombi.Api/TraktApi.cs new file mode 100644 index 000000000..ae2ff11b2 --- /dev/null +++ b/Ombi.Api/TraktApi.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Ombi.Api.Interfaces; +using Ombi.Helpers; +using TraktApiSharp; +using TraktApiSharp.Enums; +using TraktApiSharp.Objects.Get.Shows; +using TraktApiSharp.Objects.Get.Shows.Common; +using TraktApiSharp.Requests.Params; + +namespace Ombi.Api +{ + public class TraktApi : ITraktApi + { + private TraktClient Client { get; } + + private static readonly string Encrypted = "z/56wM/oEkkCWEvSIZCrzQyUvvqmafQ3njqf0UNK5xuKbNYh5Wz8ocoG2QDa5y1DBkozLaKsGxORmAB1XUvwbnom8DVNo9gE++9GTuwxmGlLDD318PXpRmYmpKqNwFSKRZgF6ewiY9qR4t3iG0pGQwPA08FK3+H7kpOKAGJNR9RMDP9wwB6Vl4DuOiZb9/DETjzZ+/zId0ZqimrbN+PLrg=="; + private readonly string _apiKey = StringCipher.Decrypt(Encrypted, "ApiKey"); + public TraktApi() + { + Client = new TraktClient(_apiKey); + } + + public async Task> GetPopularShows(int? page = null, int? limitPerPage = null) + { + var popular = await Client.Shows.GetPopularShowsAsync(new TraktExtendedInfo { Full = true }, null, page ?? 1, limitPerPage ?? 10); + return popular.Items; + } + + public async Task> GetTrendingShows(int? page = null, int? limitPerPage = null) + { + var trendingShowsTop10 = await Client.Shows.GetTrendingShowsAsync(new TraktExtendedInfo { Full = true }, null, page ?? 1, limitPerPage ?? 10); + return trendingShowsTop10.Items; + } + + public async Task> GetAnticipatedShows(int? page = null, int? limitPerPage = null) + { + var anticipatedShows = await Client.Shows.GetMostAnticipatedShowsAsync(new TraktExtendedInfo { Full = true }, null, page ?? 1, limitPerPage ?? 10); + return anticipatedShows.Items; + } + + public async Task> GetMostWatchesShows(TraktTimePeriod period = null, int? page = null, int? limitPerPage = null) + { + var anticipatedShows = await Client.Shows.GetMostWatchedShowsAsync(period ?? TraktTimePeriod.Monthly, new TraktExtendedInfo { Full = true }, null, page ?? 1, limitPerPage ?? 10); + return anticipatedShows.Items; + } + } +} diff --git a/Ombi.Api/packages.config b/Ombi.Api/packages.config index ce5ccf24e..a20220585 100644 --- a/Ombi.Api/packages.config +++ b/Ombi.Api/packages.config @@ -8,4 +8,5 @@ + \ No newline at end of file diff --git a/Ombi.Helpers/DateTimeHelper.cs b/Ombi.Helpers/DateTimeHelper.cs index d8dbc681a..b25972ddf 100644 --- a/Ombi.Helpers/DateTimeHelper.cs +++ b/Ombi.Helpers/DateTimeHelper.cs @@ -46,5 +46,12 @@ public static DateTime UnixTimeStampToDateTime(this int unixTimeStamp) dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime(); return dtDateTime; } + + public static long ToJavascriptTimestamp(this DateTime input) + { + var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + var time = input.Subtract(new TimeSpan(epoch.Ticks)); + return (long)(time.Ticks / 10000); + } } } diff --git a/Ombi.UI/Content/search.js b/Ombi.UI/Content/search.js index 8b9c10109..14f675562 100644 --- a/Ombi.UI/Content/search.js +++ b/Ombi.UI/Content/search.js @@ -72,6 +72,25 @@ $(function () { moviesInTheaters(); }); + // TV DropDown + $('#popularShows').on('click', function (e) { + e.preventDefault(); + popularShows(); + }); + + $('#trendingShows').on('click', function (e) { + e.preventDefault(); + trendingTv(); + }); + $('#mostWatchedShows').on('click', function (e) { + e.preventDefault(); + mostwatchedTv(); + }); + $('#anticipatedShows').on('click', function (e) { + e.preventDefault(); + anticipatedTv(); + }); + // Type in TV search $("#tvSearchContent").on("input", function () { if (searchTimer) { @@ -293,6 +312,23 @@ $(function () { getMovies(url); } + function popularShows() { + var url = createBaseUrl(base, '/search/tv/popular'); + getTvShows(url, true); + } + function anticipatedTv() { + var url = createBaseUrl(base, '/search/tv/anticipated'); + getTvShows(url, true); + } + function trendingTv() { + var url = createBaseUrl(base, '/search/tv/trending'); + getTvShows(url, true); + } + function mostwatchedTv() { + var url = createBaseUrl(base, '/search/tv/mostwatched'); + getTvShows(url, true); + } + function getMovies(url) { resetMovies(); @@ -323,10 +359,10 @@ $(function () { var query = $("#tvSearchContent").val(); var url = createBaseUrl(base, '/search/tv/'); - query ? getTvShows(url + query) : resetTvShows(); + query ? getTvShows(url + query, false) : resetTvShows(); } - function getTvShows(url) { + function getTvShows(url, loadImage) { resetTvShows(); $('#tvSearchButton').attr("class", "fa fa-spinner fa-spin"); @@ -338,7 +374,9 @@ $(function () { $("#tvList").append(html); checkNetflix(context.title, context.id); - + if (loadImage) { + getTvPoster(result.id); + } }); } else { @@ -406,6 +444,16 @@ $(function () { }); }; + function getTvPoster(theTvDbId) { + + var url = createBaseUrl(base, '/search/tv/poster/'); + $.ajax(url + theTvDbId).success(function (result) { + if (result) { + $('#' + theTvDbId + "imgDiv").html(" poster"); + } + }); + }; + function buildMovieContext(result) { var date = new Date(result.releaseDate); var year = date.getFullYear(); @@ -432,6 +480,7 @@ $(function () { var date = new Date(result.firstAired); var year = date.getFullYear(); var context = { + status : result.status, posterPath: result.banner, id: result.id, title: result.seriesName, @@ -448,7 +497,9 @@ $(function () { tvPartialAvailable: result.tvPartialAvailable, disableTvRequestsByEpisode: result.disableTvRequestsByEpisode, disableTvRequestsBySeason: result.disableTvRequestsBySeason, - enableTvRequestsForOnlySeries: result.enableTvRequestsForOnlySeries + enableTvRequestsForOnlySeries: result.enableTvRequestsForOnlySeries, + trailer: result.trailer, + homepage: result.homepage }; return context; diff --git a/Ombi.UI/Models/SearchTvShowViewModel.cs b/Ombi.UI/Models/SearchTvShowViewModel.cs index faa6333ba..29380a835 100644 --- a/Ombi.UI/Models/SearchTvShowViewModel.cs +++ b/Ombi.UI/Models/SearchTvShowViewModel.cs @@ -58,5 +58,20 @@ public SearchTvShowViewModel() public bool DisableTvRequestsByEpisode { get; set; } public bool DisableTvRequestsBySeason { get; set; } public bool EnableTvRequestsForOnlySeries { get; set; } + + /// + /// This is used from the Trakt API + /// + /// + /// The trailer. + /// + public string Trailer { get; set; } + /// + /// This is used from the Trakt API + /// + /// + /// The trailer. + /// + public string Homepage { get; set; } } } \ No newline at end of file diff --git a/Ombi.UI/Modules/SearchModule.cs b/Ombi.UI/Modules/SearchModule.cs index 10be9bfdb..63cc4c03c 100644 --- a/Ombi.UI/Modules/SearchModule.cs +++ b/Ombi.UI/Modules/SearchModule.cs @@ -58,6 +58,7 @@ using Ombi.UI.Helpers; using Ombi.UI.Models; using TMDbLib.Objects.General; +using TraktApiSharp.Objects.Get.Shows; using Action = Ombi.Helpers.Analytics.Action; using EpisodesModel = Ombi.Store.EpisodesModel; using ISecurityExtensions = Ombi.Core.ISecurityExtensions; @@ -76,7 +77,7 @@ public SearchModule(ICacheProvider cache, ISettingsService plexService, ISettingsService auth, IRepository u, ISettingsService email, IIssueService issue, IAnalytics a, IRepository rl, ITransientFaultQueue tfQueue, IRepository content, - ISecurityExtensions security, IMovieSender movieSender, IRadarrCacher radarrCacher) + ISecurityExtensions security, IMovieSender movieSender, IRadarrCacher radarrCacher, ITraktApi traktApi) : base("search", prSettings, security) { Auth = auth; @@ -109,6 +110,7 @@ public SearchModule(ICacheProvider cache, MovieSender = movieSender; WatcherCacher = watcherCacher; RadarrCacher = radarrCacher; + TraktApi = traktApi; Get["SearchIndex", "/", true] = async (x, ct) => await RequestLoad(); @@ -120,6 +122,13 @@ public SearchModule(ICacheProvider cache, 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); @@ -129,6 +138,7 @@ public SearchModule(ICacheProvider cache, 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; } @@ -190,6 +200,17 @@ private async Task SearchMovie(string searchTerm) 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; @@ -322,6 +343,186 @@ private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests 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 = Checker.GetPlexTvShows(content).ToList(); + + foreach (var show in shows) + { + if (plexSettings.AdvancedSearch) + { + providerId = show.Id.ToString(); + } + + var plexShow = Checker.GetTvShow(plexTvShows.ToArray(), show.SeriesName, show.FirstAired?.Substring(0, 4), + providerId); + if (plexShow != null) + { + show.Available = true; + show.PlexUrl = plexShow.Url; + } + else + { + if (dbTv.ContainsKey(show.Id)) + { + var dbt = dbTv[show.Id]; + + show.Requested = true; + show.Episodes = dbt.Episodes.ToList(); + show.Approved = dbt.Approved; + } + if (sonarrCached.Select(x => x.TvdbId).Contains(show.Id) || sickRageCache.Contains(show.Id)) + // compare to the sonarr/sickrage db + { + show.Requested = true; + } + } + } + return shows; + } + private async Task SearchTvShow(string searchTerm) { @@ -1401,5 +1602,13 @@ public bool ShouldAutoApprove(RequestType requestType) return false; } } + + private enum ShowSearchType + { + Popular, + Anticipated, + MostWatched, + Trending + } } } diff --git a/Ombi.UI/NinjectModules/ApiModule.cs b/Ombi.UI/NinjectModules/ApiModule.cs index 873a3c527..1a45764c7 100644 --- a/Ombi.UI/NinjectModules/ApiModule.cs +++ b/Ombi.UI/NinjectModules/ApiModule.cs @@ -49,6 +49,7 @@ public override void Load() Bind().To(); Bind().To(); Bind().To(); + Bind().To(); } } } \ No newline at end of file diff --git a/Ombi.UI/Ombi.UI.csproj b/Ombi.UI/Ombi.UI.csproj index ce3f1453b..22dd6175d 100644 --- a/Ombi.UI/Ombi.UI.csproj +++ b/Ombi.UI/Ombi.UI.csproj @@ -206,6 +206,10 @@ ..\packages\TMDbLib.0.9.0.0-alpha\lib\net45\TMDbLib.dll True + + ..\packages\TraktApiSharp.0.8.0\lib\portable-net45+netcore45+wpa81\TraktApiSharp.dll + True + diff --git a/Ombi.UI/Views/Search/Index.cshtml b/Ombi.UI/Views/Search/Index.cshtml index 59fc41464..ec31261e1 100644 --- a/Ombi.UI/Views/Search/Index.cshtml +++ b/Ombi.UI/Views/Search/Index.cshtml @@ -75,9 +75,20 @@ } - + +
+
+ - - + -