Added a bunch of categories for tv search similar to what we have for movies.

pull/963/head
Jamie.Rees 8 years ago
parent 7cbea541ce
commit 9886c40499

@ -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?));
}
}

@ -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>

@ -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>

@ -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" />

@ -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;
}
}
}

@ -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>

@ -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);
}
}
}

@ -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;

@ -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; }
}
}

@ -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
}
}
}

@ -49,6 +49,7 @@ namespace Ombi.UI.NinjectModules
Bind<INetflixApi>().To<NetflixRouletteApi>();
Bind<IDiscordApi>().To<DiscordApi>();
Bind<IRadarrApi>().To<RadarrApi>();
Bind<ITraktApi>().To<TraktApi>();
}
}
}

@ -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" />

@ -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">&times;</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">&times;</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">&times;</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">&times;</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()

@ -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>
Loading…
Cancel
Save