From 2976e87cc56d0cba73eef46c55de6ae29a46366f Mon Sep 17 00:00:00 2001 From: tidusjar <jamie.rees@outlook.com> Date: Wed, 18 Jan 2017 21:30:19 +0000 Subject: [PATCH 1/7] #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 @@ namespace Ombi.Services.Jobs 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 @@ namespace Ombi.Services.Notification 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" <Jamie.Rees> Date: Thu, 19 Jan 2017 08:19:23 +0000 Subject: [PATCH 2/7] 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 @@ namespace Ombi.UI.Modules 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" <Jamie.Rees> Date: Thu, 19 Jan 2017 08:45:13 +0000 Subject: [PATCH 3/7] #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 @@ namespace Ombi.UI.Modules 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<PlexUsers> IsPlexUser(string username) @@ -318,6 +333,21 @@ namespace Ombi.UI.Modules 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 @@ <legend>User Management Settings</legend> <span>Here you can manage the default permissions and features that your users get</span> + <small> + 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. + </small> <h3>Permissions</h3> From 05fbdf2a384f6a07287d95ecef928d00f5df0499 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" <Jamie.Rees> Date: Fri, 20 Jan 2017 08:11:22 +0000 Subject: [PATCH 4/7] 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 @@ namespace Ombi.UI.Modules } + + var canManageRequest = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests); var viewModel = dbTv.Select(tv => new RequestViewModel { @@ -243,6 +245,7 @@ namespace Ombi.UI.Modules 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 @@ namespace Ombi.UI.Modules DateTime.TryParse(showInfo.premiered, out firstAir); string fullShowName = $"{showInfo.name} ({firstAir.Year})"; + // For some reason the poster path is always http + var posterPath = showInfo.image?.medium.Replace("http:", "https:"); var model = new RequestedModel { Type = RequestType.TvShow, Overview = showInfo.summary.RemoveHtml(), - PosterPath = showInfo.image?.medium, + PosterPath = posterPath, Title = showInfo.name, ReleaseDate = firstAir, Status = showInfo.status, From 7cbea541cef12d020243a18888883c9e0cc2865a Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" <Jamie.Rees> Date: Fri, 20 Jan 2017 08:13:19 +0000 Subject: [PATCH 5/7] 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 @@ namespace Ombi.UI.Modules 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" <Jamie.Rees> Date: Fri, 20 Jan 2017 15:56:49 +0000 Subject: [PATCH 6/7] 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<IEnumerable<TraktMostAnticipatedShow>> GetAnticipatedShows(int? page = default(int?), int? limitPerPage = default(int?)); + Task<IEnumerable<TraktMostWatchedShow>> GetMostWatchesShows(TraktTimePeriod period = null, int? page = default(int?), int? limitPerPage = default(int?)); + Task<IEnumerable<TraktShow>> GetPopularShows(int? page = default(int?), int? limitPerPage = default(int?)); + Task<IEnumerable<TraktTrendingShow>> 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 @@ <WarningLevel>4</WarningLevel> </PropertyGroup> <ItemGroup> + <Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> + <HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> + <Private>True</Private> + </Reference> <Reference Include="RestSharp, Version=105.2.3.0, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\RestSharp.105.2.3\lib\net45\RestSharp.dll</HintPath> <Private>True</Private> @@ -43,6 +47,10 @@ <Reference Include="System.Data" /> <Reference Include="System.Net.Http" /> <Reference Include="System.Xml" /> + <Reference Include="TraktApiSharp, Version=0.8.0.0, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>..\packages\TraktApiSharp.0.8.0\lib\portable-net45+netcore45+wpa81\TraktApiSharp.dll</HintPath> + <Private>True</Private> + </Reference> </ItemGroup> <ItemGroup> <Compile Include="IApiRequest.cs" /> @@ -58,6 +66,7 @@ <Compile Include="IPushoverApi.cs" /> <Compile Include="ISickRageApi.cs" /> <Compile Include="ISonarrApi.cs" /> + <Compile Include="ITraktApi.cs" /> <Compile Include="IWatcherApi.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> 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 @@ <?xml version="1.0" encoding="utf-8"?> <packages> + <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net45" /> <package id="RestSharp" version="105.2.3" targetFramework="net45" /> + <package id="TraktApiSharp" version="0.8.0" targetFramework="net45" /> </packages> \ 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 @@ <HintPath>..\packages\TMDbLib.0.9.0.0-alpha\lib\net45\TMDbLib.dll</HintPath> <Private>True</Private> </Reference> + <Reference Include="TraktApiSharp, Version=0.8.0.0, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>..\packages\TraktApiSharp.0.8.0\lib\portable-net45+netcore45+wpa81\TraktApiSharp.dll</HintPath> + <Private>True</Private> + </Reference> </ItemGroup> <ItemGroup> <Compile Include="ApiRequest.cs" /> <Compile Include="DiscordApi.cs" /> <Compile Include="NetflixRouletteApi.cs" /> <Compile Include="RadarrApi.cs" /> + <Compile Include="TraktApi.cs" /> <Compile Include="WatcherApi.cs" /> <Compile Include="MusicBrainzApi.cs" /> <Compile Include="SlackApi.cs" /> 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<IEnumerable<TraktShow>> 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<IEnumerable<TraktTrendingShow>> 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<IEnumerable<TraktMostAnticipatedShow>> 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<IEnumerable<TraktMostWatchedShow>> 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 @@ <package id="RestSharp" version="105.2.3" targetFramework="net45" /> <package id="System.Net.Http" version="4.0.0" targetFramework="net45" /> <package id="TMDbLib" version="0.9.0.0-alpha" targetFramework="net45" /> + <package id="TraktApiSharp" version="0.8.0" targetFramework="net45" /> </packages> \ 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 @@ namespace Ombi.Helpers 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(" <img class='img-responsive' src='" + result + "' width='150' alt='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 @@ namespace Ombi.UI.Models public bool DisableTvRequestsByEpisode { get; set; } public bool DisableTvRequestsBySeason { get; set; } public bool EnableTvRequestsForOnlySeries { get; set; } + + /// <summary> + /// This is used from the Trakt API + /// </summary> + /// <value> + /// The trailer. + /// </value> + public string Trailer { get; set; } + /// <summary> + /// This is used from the Trakt API + /// </summary> + /// <value> + /// The trailer. + /// </value> + 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.Store.Repository; using Ombi.UI.Helpers; using Ombi.UI.Models; using TMDbLib.Objects.General; +using TraktApiSharp.Objects.Get.Shows; using Action = Ombi.Helpers.Analytics.Action; using EpisodesModel = Ombi.Store.EpisodesModel; using ISecurityExtensions = Ombi.Core.ISecurityExtensions; @@ -76,7 +77,7 @@ namespace Ombi.UI.Modules ISettingsService<PlexSettings> plexService, ISettingsService<AuthenticationSettings> auth, IRepository<UsersToNotify> u, ISettingsService<EmailNotificationSettings> email, IIssueService issue, IAnalytics a, IRepository<RequestLimit> rl, ITransientFaultQueue tfQueue, IRepository<PlexContent> content, - ISecurityExtensions security, IMovieSender movieSender, IRadarrCacher radarrCacher) + ISecurityExtensions security, IMovieSender movieSender, IRadarrCacher radarrCacher, ITraktApi traktApi) : base("search", prSettings, security) { Auth = auth; @@ -109,6 +110,7 @@ namespace Ombi.UI.Modules MovieSender = movieSender; WatcherCacher = watcherCacher; RadarrCacher = radarrCacher; + TraktApi = traktApi; Get["SearchIndex", "/", true] = async (x, ct) => await RequestLoad(); @@ -120,6 +122,13 @@ namespace Ombi.UI.Modules Get["movie/upcoming", true] = async (x, ct) => await UpcomingMovies(); Get["movie/playing", true] = async (x, ct) => await CurrentlyPlayingMovies(); + Get["tv/popular", true] = async (x, ct) => await ProcessShows(ShowSearchType.Popular); + Get["tv/trending", true] = async (x, ct) => await ProcessShows(ShowSearchType.Trending); + Get["tv/mostwatched", true] = async (x, ct) => await ProcessShows(ShowSearchType.MostWatched); + Get["tv/anticipated", true] = async (x, ct) => await ProcessShows(ShowSearchType.Anticipated); + + Get["tv/poster/{id}"] = p => GetTvPoster((int)p.id); + Post["request/movie", true] = async (x, ct) => await RequestMovie((int)Request.Form.movieId); Post["request/tv", true] = async (x, ct) => await RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons); @@ -129,6 +138,7 @@ namespace Ombi.UI.Modules Get["/seasons"] = x => GetSeasons(); Get["/episodes", true] = async (x, ct) => await GetEpisodes(); } + private ITraktApi TraktApi { get; } private IWatcherCacher WatcherCacher { get; } private IMovieSender MovieSender { get; } private IRepository<PlexContent> PlexContentRepository { get; } @@ -190,6 +200,17 @@ namespace Ombi.UI.Modules return await ProcessMovies(MovieSearchType.Search, searchTerm); } + private Response GetTvPoster(int theTvDbId) + { + var result = TvApi.ShowLookupByTheTvDbId(theTvDbId); + + var banner = result.image?.medium; + if (!string.IsNullOrEmpty(banner)) + { + banner = banner.Replace("http", "https"); // Always use the Https banners + } + return banner; + } private async Task<Response> ProcessMovies(MovieSearchType searchType, string searchTerm) { List<MovieResult> apiMovies; @@ -322,6 +343,186 @@ namespace Ombi.UI.Modules return true; } + private async Task<Response> ProcessShows(ShowSearchType type) + { + var shows = new List<SearchTvShowViewModel>(); + var prSettings = await PrService.GetSettingsAsync(); + switch (type) + { + case ShowSearchType.Popular: + Analytics.TrackEventAsync(Category.Search, Action.TvShow, "Popular", Username, CookieHelper.GetAnalyticClientId(Cookies)); + var popularShows = await TraktApi.GetPopularShows(); + + foreach (var popularShow in popularShows) + { + var theTvDbId = int.Parse(popularShow.Ids.Tvdb.ToString()); + + var model = new SearchTvShowViewModel + { + FirstAired = popularShow.FirstAired?.ToString("yyyy-MM-ddTHH:mm:ss"), + Id = theTvDbId, + ImdbId = popularShow.Ids.Imdb, + Network = popularShow.Network, + Overview = popularShow.Overview.RemoveHtml(), + Rating = popularShow.Rating.ToString(), + Runtime = popularShow.Runtime.ToString(), + SeriesName = popularShow.Title, + Status = popularShow.Status.DisplayName, + DisableTvRequestsByEpisode = prSettings.DisableTvRequestsByEpisode, + DisableTvRequestsBySeason = prSettings.DisableTvRequestsBySeason, + EnableTvRequestsForOnlySeries = (prSettings.DisableTvRequestsByEpisode && prSettings.DisableTvRequestsBySeason), + Trailer = popularShow.Trailer, + Homepage = popularShow.Homepage + }; + shows.Add(model); + } + shows = await MapToTvModel(shows, prSettings); + break; + case ShowSearchType.Anticipated: + Analytics.TrackEventAsync(Category.Search, Action.TvShow, "Anticipated", Username, CookieHelper.GetAnalyticClientId(Cookies)); + var anticipated = await TraktApi.GetAnticipatedShows(); + foreach (var anticipatedShow in anticipated) + { + var show = anticipatedShow.Show; + var theTvDbId = int.Parse(show.Ids.Tvdb.ToString()); + + var model = new SearchTvShowViewModel + { + FirstAired = show.FirstAired?.ToString("yyyy-MM-ddTHH:mm:ss"), + Id = theTvDbId, + ImdbId = show.Ids.Imdb, + Network = show.Network ?? string.Empty, + Overview = show.Overview?.RemoveHtml() ?? string.Empty, + Rating = show.Rating.ToString(), + Runtime = show.Runtime.ToString(), + SeriesName = show.Title, + Status = show.Status?.DisplayName ?? string.Empty, + DisableTvRequestsByEpisode = prSettings.DisableTvRequestsByEpisode, + DisableTvRequestsBySeason = prSettings.DisableTvRequestsBySeason, + EnableTvRequestsForOnlySeries = (prSettings.DisableTvRequestsByEpisode && prSettings.DisableTvRequestsBySeason), + Trailer = show.Trailer, + Homepage = show.Homepage + }; + shows.Add(model); + } + shows = await MapToTvModel(shows, prSettings); + break; + case ShowSearchType.MostWatched: + Analytics.TrackEventAsync(Category.Search, Action.TvShow, "MostWatched", Username, CookieHelper.GetAnalyticClientId(Cookies)); + var mostWatched = await TraktApi.GetMostWatchesShows(); + foreach (var watched in mostWatched) + { + var show = watched.Show; + var theTvDbId = int.Parse(show.Ids.Tvdb.ToString()); + var model = new SearchTvShowViewModel + { + FirstAired = show.FirstAired?.ToString("yyyy-MM-ddTHH:mm:ss"), + Id = theTvDbId, + ImdbId = show.Ids.Imdb, + Network = show.Network, + Overview = show.Overview.RemoveHtml(), + Rating = show.Rating.ToString(), + Runtime = show.Runtime.ToString(), + SeriesName = show.Title, + Status = show.Status.DisplayName, + DisableTvRequestsByEpisode = prSettings.DisableTvRequestsByEpisode, + DisableTvRequestsBySeason = prSettings.DisableTvRequestsBySeason, + EnableTvRequestsForOnlySeries = (prSettings.DisableTvRequestsByEpisode && prSettings.DisableTvRequestsBySeason), + Trailer = show.Trailer, + Homepage = show.Homepage + }; + shows.Add(model); + } + shows = await MapToTvModel(shows, prSettings); + break; + case ShowSearchType.Trending: + Analytics.TrackEventAsync(Category.Search, Action.TvShow, "Trending", Username, CookieHelper.GetAnalyticClientId(Cookies)); + var trending = await TraktApi.GetTrendingShows(); + foreach (var watched in trending) + { + var show = watched.Show; + var theTvDbId = int.Parse(show.Ids.Tvdb.ToString()); + var model = new SearchTvShowViewModel + { + FirstAired = show.FirstAired?.ToString("yyyy-MM-ddTHH:mm:ss"), + Id = theTvDbId, + ImdbId = show.Ids.Imdb, + Network = show.Network, + Overview = show.Overview.RemoveHtml(), + Rating = show.Rating.ToString(), + Runtime = show.Runtime.ToString(), + SeriesName = show.Title, + Status = show.Status.DisplayName, + DisableTvRequestsByEpisode = prSettings.DisableTvRequestsByEpisode, + DisableTvRequestsBySeason = prSettings.DisableTvRequestsBySeason, + EnableTvRequestsForOnlySeries = (prSettings.DisableTvRequestsByEpisode && prSettings.DisableTvRequestsBySeason), + Trailer = show.Trailer, + Homepage = show.Homepage + }; + shows.Add(model); + } + shows = await MapToTvModel(shows, prSettings); + break; + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + + + return Response.AsJson(shows); + } + + private async Task<List<SearchTvShowViewModel>> MapToTvModel(List<SearchTvShowViewModel> shows, PlexRequestSettings prSettings) + { + + var plexSettings = await PlexService.GetSettingsAsync(); + + var providerId = string.Empty; + // Get the requests + var allResults = await RequestService.GetAllAsync(); + allResults = allResults.Where(x => x.Type == RequestType.TvShow); + var distinctResults = allResults.DistinctBy(x => x.ProviderId); + var dbTv = distinctResults.ToDictionary(x => x.ProviderId); + + // Check the external applications + var sonarrCached = SonarrCacher.QueuedIds().ToList(); + var sickRageCache = SickRageCacher.QueuedIds(); // consider just merging sonarr/sickrage arrays + var content = PlexContentRepository.GetAll(); + var plexTvShows = Checker.GetPlexTvShows(content).ToList(); + + foreach (var show in shows) + { + if (plexSettings.AdvancedSearch) + { + providerId = show.Id.ToString(); + } + + var plexShow = Checker.GetTvShow(plexTvShows.ToArray(), show.SeriesName, show.FirstAired?.Substring(0, 4), + providerId); + if (plexShow != null) + { + show.Available = true; + show.PlexUrl = plexShow.Url; + } + else + { + if (dbTv.ContainsKey(show.Id)) + { + var dbt = dbTv[show.Id]; + + show.Requested = true; + show.Episodes = dbt.Episodes.ToList(); + show.Approved = dbt.Approved; + } + if (sonarrCached.Select(x => x.TvdbId).Contains(show.Id) || sickRageCache.Contains(show.Id)) + // compare to the sonarr/sickrage db + { + show.Requested = true; + } + } + } + return shows; + } + private async Task<Response> SearchTvShow(string searchTerm) { @@ -1401,5 +1602,13 @@ namespace Ombi.UI.Modules 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 @@ namespace Ombi.UI.NinjectModules Bind<INetflixApi>().To<NetflixRouletteApi>(); Bind<IDiscordApi>().To<DiscordApi>(); Bind<IRadarrApi>().To<RadarrApi>(); + Bind<ITraktApi>().To<TraktApi>(); } } } \ 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 @@ <HintPath>..\packages\TMDbLib.0.9.0.0-alpha\lib\net45\TMDbLib.dll</HintPath> <Private>True</Private> </Reference> + <Reference Include="TraktApiSharp, Version=0.8.0.0, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>..\packages\TraktApiSharp.0.8.0\lib\portable-net45+netcore45+wpa81\TraktApiSharp.dll</HintPath> + <Private>True</Private> + </Reference> </ItemGroup> <ItemGroup> <Compile Include="Authentication\CustomAuthenticationConfiguration.cs" /> 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 @@ <!-- TV tab --> <div role="tabpanel" class="tab-pane" id="TvShowTab"> <div class="input-group"> - <input id="tvSearchContent" type="text" class="form-control form-control-custom form-control-search"> + <input id="tvSearchContent" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons"> <div class="input-group-addon"> - <i id="tvSearchButton" class="fa fa-search"></i> + <div class="btn-group"> + <a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> + @UI.Search_Suggestions + <i class="fa fa-chevron-down"></i> + </a> + <ul class="dropdown-menu"> + <li><a id="popularShows" href="#">Popular Shows</a></li> + <li><a id="trendingShows" href="#">Trending Shows</a></li> + <li><a id="mostWatchedShows" href="#">Most Watched Shows</a></li> + <li><a id="anticipatedShows" href="#">Most Anticipated Shows</a></li> + </ul> + </div><i id="tvSearchButton" class="fa fa-search"></i> </div> </div> <br /> @@ -106,274 +117,283 @@ </div> } -<!-- Movie and TV Results template --> + <!-- Movie and TV Results template --> <script id="search-template" type="text/x-handlebars-template"> - <div class="row"> - <div class="col-sm-2"> - - {{#if_eq type "movie"}} - {{#if posterPath}} - <img class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{posterPath}}" alt="poster"> - {{/if}} - {{/if_eq}} - {{#if_eq type "tv"}} - {{#if posterPath}} - <img class="img-responsive" width="150" src="{{posterPath}}" alt="poster"> - {{/if}} - {{/if_eq}} + <div class="row"> + <div id="{{id}}imgDiv" class="col-sm-2"> - </div> - <div class="col-sm-5 "> - <div> - {{#if_eq type "movie"}} - <a href="https://www.themoviedb.org/movie/{{id}}/" target="_blank"> - <h4>{{title}} ({{year}})</h4> - </a> - {{else}} - <a href="http://www.imdb.com/title/{{imdb}}/" target="_blank"> - <h4>{{title}} ({{year}})</h4> - </a> - {{/if_eq}} - {{#if available}} - <span class="label label-success">@UI.Search_Available_on_plex</span> - {{else}} - {{#if approved}} - <span class="label label-info">@UI.Search_Processing_Request</span> - {{else if requested}} - <span class="label label-warning">@UI.Search_Pending_approval</span> - {{else}} - <span class="label label-danger">@UI.Search_Not_Requested_Yet</span> - {{/if}} - {{/if}} - - <span id="{{id}}netflixTab"></span> + {{#if_eq type "movie"}} + {{#if posterPath}} + <img class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{posterPath}}" alt="poster"> + {{/if}} + {{/if_eq}} + {{#if_eq type "tv"}} + {{#if posterPath}} + <img class="img-responsive" width="150" src="{{posterPath}}" alt="poster"> + {{/if}} + {{/if_eq}} - <br /> - <br /> - </div> - <p>{{overview}}</p> - </div> - <div class="col-sm-2 col-sm-push-3"> - <form method="POST" action="@url/search/request/{{type}}" id="form{{id}}"> - <input name="{{type}}Id" type="text" value="{{id}}" hidden="hidden" /> - {{#if_eq type "movie"}} - {{#if_eq available true}} + </div> + <div class="col-sm-5 "> + <div> + {{#if_eq type "movie"}} + <a href="https://www.themoviedb.org/movie/{{id}}/" target="_blank"> + <h4>{{title}} ({{year}})</h4> + </a> + {{else}} + <a href="http://www.imdb.com/title/{{imdb}}/" target="_blank"> + <h4>{{title}} ({{year}})</h4> + </a> + {{/if_eq}} + {{#if status}} + <span class="label label-info" target="_blank">{{status}}</span> + {{/if}} + {{#if homepage}} + <a href="{{homepage}}" target="_blank"><span class="label label-info">HomePage</span></a> + {{/if}} + {{#if trailer}} + <a href="{{trailer}}" target="_blank"><span class="label label-info">Trailer</span></a> + {{/if}} + {{#if available}} + <span class="label label-success">@UI.Search_Available_on_plex</span> + {{else}} + {{#if approved}} + <span class="label label-info">@UI.Search_Processing_Request</span> + {{else if requested}} + <span class="label label-warning">@UI.Search_Pending_approval</span> + {{else}} + <span class="label label-danger">@UI.Search_Not_Requested_Yet</span> + {{/if}} + {{/if}} + + <span id="{{id}}netflixTab"></span> + + <br /> + <br /> + </div> + <p>{{overview}}</p> + </div> + <div class="col-sm-2 col-sm-push-3"> + <form method="POST" action="@url/search/request/{{type}}" id="form{{id}}"> + <input name="{{type}}Id" type="text" value="{{id}}" hidden="hidden" /> + {{#if_eq type "movie"}} + {{#if_eq available true}} <button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Available</button> <br /> <br /> <a style="text-align: right" class="btn btn-sm btn-primary-outline" href="{{url}}" target="_blank"><i class="fa fa-eye"></i> @UI.Search_ViewInPlex</a> - {{else}} + {{else}} {{#if_eq requested true}} - <button style="text-align: right" class="btn btn-primary-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Requested</button> + <button style="text-align: right" class="btn btn-primary-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Requested</button> {{else}} - <button id="{{id}}" style="text-align: right" class="btn btn-primary-outline requestMovie" type="submit"><i class="fa fa-plus"></i> @UI.Search_Request</button> + <button id="{{id}}" style="text-align: right" class="btn btn-primary-outline requestMovie" type="submit"><i class="fa fa-plus"></i> @UI.Search_Request</button> + {{/if_eq}} + {{/if_eq}} {{/if_eq}} - {{/if_eq}} - {{/if_eq}} - {{#if_eq type "tv"}} - {{#if_eq tvFullyAvailable true}} + {{#if_eq type "tv"}} + {{#if_eq tvFullyAvailable true}} @*//TODO Not used yet*@ <button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Available</button><br /> - {{else}} + {{else}} {{#if_eq enableTvRequestsForOnlySeries true}} - <button id="{{id}}" style="text-align: right" class="btn {{#if available}}btn-success-outline{{else}}btn-primary-outline{{/if}} btn-primary-outline dropdownTv" season-select="0" type="button"><i class="fa fa-plus"></i> @UI.Search_Request</button> + <button id="{{id}}" style="text-align: right" class="btn {{#if available}}btn-success-outline{{else}}btn-primary-outline{{/if}} btn-primary-outline dropdownTv" season-select="0" type="button"><i class="fa fa-plus"></i> @UI.Search_Request</button> {{else}} - <div class="dropdown"> - <button id="{{id}}" class="btn {{#if available}}btn-success-outline{{else}}btn-primary-outline{{/if}} dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> - <i class="fa fa-plus"></i> {{#if available}}@UI.Search_Available{{else}}@UI.Search_Request {{/if}} - <span class="caret"></span> - </button> - <ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> - <li><a id="{{id}}" season-select="0" class="dropdownTv " href="#">@UI.Search_AllSeasons</a></li> - {{#if_eq disableTvRequestsBySeason false}} - <li><a id="{{id}}" season-select="1" class="dropdownTv" href="#">@UI.Search_FirstSeason</a></li> - <li><a id="{{id}}" season-select="2" class="dropdownTv" href="#">@UI.Search_LatestSeason</a></li> - <li><a id="SeasonSelect" data-identifier="{{id}}" data-toggle="modal" data-target="#seasonsModal" href="#">@UI.Search_SelectSeason...</a></li> - {{/if_eq}} - {{#if_eq disableTvRequestsByEpisode false}} - <li><a id="EpisodeSelect" data-identifier="{{id}}" data-toggle="modal" data-target="#episodesModal" href="#">@UI.Search_SelectEpisode...</a></li> - {{/if_eq}} - </ul> - </div> + <div class="dropdown"> + <button id="{{id}}" class="btn {{#if available}}btn-success-outline{{else}}btn-primary-outline{{/if}} dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> + <i class="fa fa-plus"></i> {{#if available}}@UI.Search_Available{{else}}@UI.Search_Request {{/if}} + <span class="caret"></span> + </button> + <ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> + <li><a id="{{id}}" season-select="0" class="dropdownTv " href="#">@UI.Search_AllSeasons</a></li> + {{#if_eq disableTvRequestsBySeason false}} + <li><a id="{{id}}" season-select="1" class="dropdownTv" href="#">@UI.Search_FirstSeason</a></li> + <li><a id="{{id}}" season-select="2" class="dropdownTv" href="#">@UI.Search_LatestSeason</a></li> + <li><a id="SeasonSelect" data-identifier="{{id}}" data-toggle="modal" data-target="#seasonsModal" href="#">@UI.Search_SelectSeason...</a></li> + {{/if_eq}} + {{#if_eq disableTvRequestsByEpisode false}} + <li><a id="EpisodeSelect" data-identifier="{{id}}" data-toggle="modal" data-target="#episodesModal" href="#">@UI.Search_SelectEpisode...</a></li> + {{/if_eq}} + </ul> + </div> + {{/if_eq}} + {{#if available}} + <br /> + <a style="text-align: right" class="btn btn-sm btn-primary-outline" href="{{url}}" target="_blank"><i class="fa fa-eye"></i> @UI.Search_ViewInPlex</a> + {{/if}} + {{/if_eq}} {{/if_eq}} - {{#if available}} - <br /> - <a style="text-align: right" class="btn btn-sm btn-primary-outline" href="{{url}}" target="_blank"><i class="fa fa-eye"></i> @UI.Search_ViewInPlex</a> - {{/if}} - {{/if_eq}} - {{/if_eq}} - <br /> - </form> - {{#if_eq available true}} - <form method="POST" action="@url/issues/nonrequestissue/" id="report{{id}}"> - <input name="providerId" type="text" value="{{id}}" hidden="hidden" /> - <input name="type" type="text" value="{{type}}" hidden="hidden" /> - <div class="dropdown"> - <button id="{{id}}" class="btn btn-sm btn-danger-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> - <i class="fa fa-exclamation"></i> @UI.Search_ReportIssue - <span class="caret"></span> - </button> - <ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> - <li><a id="{{id}}" issue-select="0" class="dropdownIssue" href="#">@UI.Issues_WrongAudio</a></li> - <li><a id="{{id}}" issue-select="1" class="dropdownIssue" href="#">@UI.Issues_NoSubs</a></li> - <li><a id="{{id}}" issue-select="2" class="dropdownIssue" href="#">@UI.Issues_WrongContent</a></li> - <li><a id="{{id}}" issue-select="3" class="dropdownIssue" href="#">@UI.Issues_Playback</a></li> - <li><a id="{{id}}" issue-select="4" class="dropdownIssue" data-identifier="{{id}}" data-type="{{type}}" href="#" data-toggle="modal" data-target="#issuesModal">@UI.Issues_Other</a></li> - </ul> + <br /> + </form> + {{#if_eq available true}} + <form method="POST" action="@url/issues/nonrequestissue/" id="report{{id}}"> + <input name="providerId" type="text" value="{{id}}" hidden="hidden" /> + <input name="type" type="text" value="{{type}}" hidden="hidden" /> + <div class="dropdown"> + <button id="{{id}}" class="btn btn-sm btn-danger-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> + <i class="fa fa-exclamation"></i> @UI.Search_ReportIssue + <span class="caret"></span> + </button> + <ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> + <li><a id="{{id}}" issue-select="0" class="dropdownIssue" href="#">@UI.Issues_WrongAudio</a></li> + <li><a id="{{id}}" issue-select="1" class="dropdownIssue" href="#">@UI.Issues_NoSubs</a></li> + <li><a id="{{id}}" issue-select="2" class="dropdownIssue" href="#">@UI.Issues_WrongContent</a></li> + <li><a id="{{id}}" issue-select="3" class="dropdownIssue" href="#">@UI.Issues_Playback</a></li> + <li><a id="{{id}}" issue-select="4" class="dropdownIssue" data-identifier="{{id}}" data-type="{{type}}" href="#" data-toggle="modal" data-target="#issuesModal">@UI.Issues_Other</a></li> + </ul> + </div> + </form> + {{/if_eq}} </div> - </form> - {{/if_eq}} - </div> - </div> - <hr /> -</script> + </div> + <hr /> + </script> -<!-- Music Results template --> -<script id="music-template" type="text/x-handlebars-template"> - <div class="row"> - <div id="{{id}}imageDiv" class="col-sm-2"> - {{#if coverArtUrl}} - <img id="{{id}}cover" class="img-responsive" src="{{coverArtUrl}}" width="150" alt="poster"> - {{/if}} - </div> - <div class="col-sm-5 "> - <div> - <a href="https://musicbrainz.org/release/{{id}}" target="_blank"> - <h4> - {{artist}} - {{title}} - {{#if year}} - ({{year}}) - {{/if}} - </h4> - </a> + <!-- Music Results template --> + <script id="music-template" type="text/x-handlebars-template"> + <div class="row"> + <div id="{{id}}imageDiv" class="col-sm-2"> + {{#if coverArtUrl}} + <img id="{{id}}cover" class="img-responsive" src="{{coverArtUrl}}" width="150" alt="poster"> + {{/if}} + </div> + <div class="col-sm-5 "> + <div> + <a href="https://musicbrainz.org/release/{{id}}" target="_blank"> + <h4> + {{artist}} - {{title}} + {{#if year}} + ({{year}}) + {{/if}} + </h4> + </a> - </div> - <p>{{overview}}</p> - </div> - <div class="col-sm-2 col-sm-push-3"> - <form method="POST" action="@url/search/request/{{type}}" id="form{{id}}"> - <input name="{{type}}Id" type="text" value="{{id}}" hidden="hidden" /> - {{#if_eq available true}} - <button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Available</button><br /> - <a style="text-align: right" class="btn btn-sm btn-primary-outline" href="{{url}}" target="_blank"><i class="fa fa-eye"></i> @UI.Search_ViewInPlex</a> - {{else}} - {{#if_eq requested true}} - <button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Requested</button> - {{else}} - <button id="{{id}}" style="text-align: right" class="btn btn-primary-outline requestAlbum" type="submit"><i class="fa fa-plus"></i> @UI.Search_Request</button> - {{/if_eq}} - {{/if_eq}} - <br /> - <small class="row">@UI.Search_TrackCount: {{trackCount}}</small> - <small class="row">@UI.Search_Country: {{country}}</small> - </form> - </div> + </div> + <p>{{overview}}</p> + </div> + <div class="col-sm-2 col-sm-push-3"> + <form method="POST" action="@url/search/request/{{type}}" id="form{{id}}"> + <input name="{{type}}Id" type="text" value="{{id}}" hidden="hidden" /> + {{#if_eq available true}} + <button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Available</button><br /> + <a style="text-align: right" class="btn btn-sm btn-primary-outline" href="{{url}}" target="_blank"><i class="fa fa-eye"></i> @UI.Search_ViewInPlex</a> + {{else}} + {{#if_eq requested true}} + <button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Requested</button> + {{else}} + <button id="{{id}}" style="text-align: right" class="btn btn-primary-outline requestAlbum" type="submit"><i class="fa fa-plus"></i> @UI.Search_Request</button> + {{/if_eq}} + {{/if_eq}} + <br /> + <small class="row">@UI.Search_TrackCount: {{trackCount}}</small> + <small class="row">@UI.Search_Country: {{country}}</small> + </form> + </div> - </div> - <hr /> + </div> + <hr /> -</script> + </script> -<div class="modal fade" id="seasonsModal"> - <div class="modal-dialog"> - <div class="modal-content"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> - <h4 class="modal-title">@UI.Search_Modal_SeasonsTitle</h4> - </div> - <div class="modal-body" id="seasonsBody"> + <div class="modal fade" id="seasonsModal"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title">@UI.Search_Modal_SeasonsTitle</h4> + </div> + <div class="modal-body" id="seasonsBody"> - </div> + </div> - <div hidden="hidden" id="selectedSeasonsId"></div> - <div class="modal-footer"> - <button type="button" class="btn btn-default" data-dismiss="modal">@UI.Common_Close</button> - <button type="button" id="seasonsRequest" class="btn btn-primary">@UI.Search_Request</button> + <div hidden="hidden" id="selectedSeasonsId"></div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">@UI.Common_Close</button> + <button type="button" id="seasonsRequest" class="btn btn-primary">@UI.Search_Request</button> + </div> + </div> </div> </div> - </div> -</div> - -<div class="modal fade" id="episodesModal"> - <div class="modal-dialog modal-lg"> - <div class="modal-content col-md-12"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> - <h4 class="modal-title">@UI.Search_Modal_SeasonsTitle</h4> - </div> - <div class="text-center" id="episodeModalLoading"><i class="fa fa-5x fa-spinner fa-spin"></i></div> - <div class="modal-body" id="episodesBody"> - - </div> - <div hidden="hidden" id="selectedEpisodeId"></div> - <div hidden="hidden" id="episodeTvID"></div> - <div class="modal-footer col-md-12"> - <button type="button" class="btn btn-default btn-default-outline" data-dismiss="modal">@UI.Common_Close</button> - <button type="button" id="episodesRequest" class="btn btn-primary">@UI.Search_Request</button> + <div class="modal fade" id="episodesModal"> + <div class="modal-dialog modal-lg"> + <div class="modal-content col-md-12"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title">@UI.Search_Modal_SeasonsTitle</h4> + </div> + <div class="text-center" id="episodeModalLoading"><i class="fa fa-5x fa-spinner fa-spin"></i></div> + <div class="modal-body" id="episodesBody"> + + </div> + + <div hidden="hidden" id="selectedEpisodeId"></div> + <div hidden="hidden" id="episodeTvID"></div> + <div class="modal-footer col-md-12"> + <button type="button" class="btn btn-default btn-default-outline" data-dismiss="modal">@UI.Common_Close</button> + <button type="button" id="episodesRequest" class="btn btn-primary">@UI.Search_Request</button> + </div> + </div> </div> </div> - </div> -</div> -<div class="modal fade" id="issuesModal"> - <div class="modal-dialog"> - <div class="modal-content"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button> - <h4 class="modal-title">@UI.Issues_Modal_Title</h4> - </div> - <form method="POST" action="@url/issues/nonrequestissuecomment" id="commentForm"> - <div class="modal-body"> - <input id="providerIdModal" name="providerId" class="providerId" type="text" hidden="hidden" value="" /> - <input name="issue" class="issue" type="text" hidden="hidden" value="" /> - <input id="typeModal" name="type" class="type" type="text" hidden="hidden" value="" /> - <textarea class="form-control form-control-custom" rows="3" id="commentArea" name="commentArea"></textarea> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-danger-outline" data-dismiss="modal">@UI.Common_Close</button> - <button type="button" class="btn btn-primary-outline theSaveButton" data-dismiss="modal">@UI.Issues_Modal_Save</button> + <div class="modal fade" id="issuesModal"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button> + <h4 class="modal-title">@UI.Issues_Modal_Title</h4> + </div> + <form method="POST" action="@url/issues/nonrequestissuecomment" id="commentForm"> + <div class="modal-body"> + <input id="providerIdModal" name="providerId" class="providerId" type="text" hidden="hidden" value="" /> + <input name="issue" class="issue" type="text" hidden="hidden" value="" /> + <input id="typeModal" name="type" class="type" type="text" hidden="hidden" value="" /> + <textarea class="form-control form-control-custom" rows="3" id="commentArea" name="commentArea"></textarea> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-danger-outline" data-dismiss="modal">@UI.Common_Close</button> + <button type="button" class="btn btn-primary-outline theSaveButton" data-dismiss="modal">@UI.Issues_Modal_Save</button> + </div> + </form> </div> - </form> + </div> </div> - </div> -</div> -<script id="seasons-template" type="text/x-handlebars-template"> - <div class="form-group"> - <div class="checkbox"> - <input type="checkbox" class="selectedSeasons" id="{{id}}" name="{{id}}"><label for="{{id}}">@UI.Search_Season {{id}}</label> - </div> - </div> -</script> + <script id="seasons-template" type="text/x-handlebars-template"> + <div class="form-group"> + <div class="checkbox"> + <input type="checkbox" class="selectedSeasons" id="{{id}}" name="{{id}}"><label for="{{id}}">@UI.Search_Season {{id}}</label> + </div> + </div> + </script> -<script id="seasonNumber-template" type="text/x-handlebars-template"> + <script id="seasonNumber-template" type="text/x-handlebars-template"> - <div id="seasonNumber{{seasonNumber}}" class="col-md-12"> - <strong>@UI.Search_Season {{seasonNumber}}</strong> - </div> + <div id="seasonNumber{{seasonNumber}}" class="col-md-12"> + <strong>@UI.Search_Season {{seasonNumber}}</strong> + </div> -</script> + </script> -<script id="episode-template" type="text/x-handlebars-template"> - <div class="form-group col-md-6"> - <div class="checkbox" style="margin-bottom:0px; margin-top:0px;"> - {{#if_eq requested true}} - <input type="checkbox" checked="checked" disabled="disabled" class="selectedEpisodes" id="{{episodeId}}" epNumber="{{number}}" epSeason="{{season}}" name="{{episodeId}}"><label for="{{episodeId}}">{{number}}. {{name}}</label> - {{else}} - <input type="checkbox" class="selectedEpisodes" id="{{episodeId}}" epNumber="{{number}}" epSeason="{{season}}" name="{{episodeId}}"><label for="{{episodeId}}">{{number}}. {{name}}</label> - {{/if_eq}} - </div> - </div> + <script id="episode-template" type="text/x-handlebars-template"> + <div class="form-group col-md-6"> + <div class="checkbox" style="margin-bottom:0px; margin-top:0px;"> + {{#if_eq requested true}} + <input type="checkbox" checked="checked" disabled="disabled" class="selectedEpisodes" id="{{episodeId}}" epNumber="{{number}}" epSeason="{{season}}" name="{{episodeId}}"><label for="{{episodeId}}">{{number}}. {{name}}</label> + {{else}} + <input type="checkbox" class="selectedEpisodes" id="{{episodeId}}" epNumber="{{number}}" epSeason="{{season}}" name="{{episodeId}}"><label for="{{episodeId}}">{{number}}. {{name}}</label> + {{/if_eq}} + </div> + </div> -</script> + </script> -@Html.LoadSearchAssets() + @Html.LoadSearchAssets() diff --git a/Ombi.UI/packages.config b/Ombi.UI/packages.config index 5968f8795..c70ff2960 100644 --- a/Ombi.UI/packages.config +++ b/Ombi.UI/packages.config @@ -53,4 +53,5 @@ <package id="System.Runtime.Extensions" version="4.0.0" targetFramework="net45" /> <package id="System.Text.RegularExpressions" version="4.0.0" targetFramework="net45" /> <package id="TMDbLib" version="0.9.0.0-alpha" targetFramework="net45" /> + <package id="TraktApiSharp" version="0.8.0" targetFramework="net45" /> </packages> \ No newline at end of file From d17c0ef88ada9be7c8e989c2d3144517c05f1b34 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" <Jamie.Rees> Date: Fri, 20 Jan 2017 16:07:52 +0000 Subject: [PATCH 7/7] Done #924 --- Ombi.UI/Content/search.js | 7 ++++--- Ombi.UI/Views/Search/Index.cshtml | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Ombi.UI/Content/search.js b/Ombi.UI/Content/search.js index 14f675562..b8f9f5504 100644 --- a/Ombi.UI/Content/search.js +++ b/Ombi.UI/Content/search.js @@ -480,7 +480,7 @@ $(function () { var date = new Date(result.firstAired); var year = date.getFullYear(); var context = { - status : result.status, + status: result.status, posterPath: result.banner, id: result.id, title: result.seriesName, @@ -499,8 +499,9 @@ $(function () { disableTvRequestsBySeason: result.disableTvRequestsBySeason, enableTvRequestsForOnlySeries: result.enableTvRequestsForOnlySeries, trailer: result.trailer, - homepage: result.homepage - }; + homepage: result.homepage, + firstAired: Humanize(result.firstAired) + }; return context; } diff --git a/Ombi.UI/Views/Search/Index.cshtml b/Ombi.UI/Views/Search/Index.cshtml index ec31261e1..efb9228ad 100644 --- a/Ombi.UI/Views/Search/Index.cshtml +++ b/Ombi.UI/Views/Search/Index.cshtml @@ -118,7 +118,7 @@ } <!-- Movie and TV Results template --> - <script id="search-template" type="text/x-handlebars-template"> + <script id="search-template" type="text/x-handlebars-template"> <div class="row"> <div id="{{id}}imgDiv" class="col-sm-2"> @@ -129,7 +129,7 @@ {{/if_eq}} {{#if_eq type "tv"}} {{#if posterPath}} - <img class="img-responsive" width="150" src="{{posterPath}}" alt="poster"> + <img class="img-responsive" width="150" src="{{posterPath}}" alt="poster"> {{/if}} {{/if_eq}} @@ -154,6 +154,9 @@ {{#if trailer}} <a href="{{trailer}}" target="_blank"><span class="label label-info">Trailer</span></a> {{/if}} + {{#if firstAired}} + <span class="label label-info" target="_blank">First Aired: {{firstAired}}</span> + {{/if}} {{#if available}} <span class="label label-success">@UI.Search_Available_on_plex</span> {{else}}