mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-20 21:33:15 -07:00
commit
1d242705c3
20 changed files with 692 additions and 258 deletions
16
Ombi.Api.Interfaces/ITraktApi.cs
Normal file
16
Ombi.Api.Interfaces/ITraktApi.cs
Normal file
|
@ -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" />
|
||||
|
|
51
Ombi.Api/TraktApi.cs
Normal file
51
Ombi.Api/TraktApi.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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,8 +497,11 @@ $(function () {
|
|||
tvPartialAvailable: result.tvPartialAvailable,
|
||||
disableTvRequestsByEpisode: result.disableTvRequestsByEpisode,
|
||||
disableTvRequestsBySeason: result.disableTvRequestsBySeason,
|
||||
enableTvRequestsForOnlySeries: result.enableTvRequestsForOnlySeries
|
||||
};
|
||||
enableTvRequestsForOnlySeries: result.enableTvRequestsForOnlySeries,
|
||||
trailer: result.trailer,
|
||||
homepage: result.homepage,
|
||||
firstAired: Humanize(result.firstAired)
|
||||
};
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
|
@ -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,7 +245,7 @@ namespace Ombi.UI.Modules
|
|||
Status = tv.Status,
|
||||
ImdbId = tv.ImdbId,
|
||||
Id = tv.Id,
|
||||
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,
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
||||
|
@ -693,11 +894,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,
|
||||
|
@ -1399,5 +1602,13 @@ namespace Ombi.UI.Modules
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private enum ShowSearchType
|
||||
{
|
||||
Popular,
|
||||
Anticipated,
|
||||
MostWatched,
|
||||
Trending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,286 @@
|
|||
</div>
|
||||
}
|
||||
|
||||
<!-- Movie and TV Results template -->
|
||||
<script id="search-template" type="text/x-handlebars-template">
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
<!-- Movie and TV Results template -->
|
||||
<script id="search-template" type="text/x-handlebars-template">
|
||||
<div class="row">
|
||||
<div id="{{id}}imgDiv" 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}}
|
||||
{{#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>
|
||||
<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}}
|
||||
</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 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}}
|
||||
{{#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>
|
||||
<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}}
|
||||
<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}}
|
||||
{{#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>
|
||||
{{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>
|
||||
{{#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>
|
||||
{{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>
|
||||
{{/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}}
|
||||
{{#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>
|
||||
{{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>
|
||||
{{#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>
|
||||
{{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>
|
||||
{{/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>
|
||||
</div>
|
||||
</form>
|
||||
{{/if_eq}}
|
||||
</div>
|
||||
|
||||
</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 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}}
|
||||
</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>
|
||||
<hr />
|
||||
|
||||
</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>
|
||||
|
||||
<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 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>
|
||||
{{/if_eq}}
|
||||
{{/if_eq}}
|
||||
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</form>
|
||||
<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>
|
||||
<hr />
|
||||
|
||||
</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>
|
||||
|
||||
<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>
|
||||
|
||||
<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 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>
|
||||
</script>
|
||||
|
||||
<script id="seasonNumber-template" type="text/x-handlebars-template">
|
||||
|
||||
<div id="seasonNumber{{seasonNumber}}" class="col-md-12">
|
||||
<strong>@UI.Search_Season {{seasonNumber}}</strong>
|
||||
</div>
|
||||
|
||||
|
||||
</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 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>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
@Html.LoadSearchAssets()
|
||||
<script id="seasonNumber-template" type="text/x-handlebars-template">
|
||||
|
||||
<div id="seasonNumber{{seasonNumber}}" class="col-md-12">
|
||||
<strong>@UI.Search_Season {{seasonNumber}}</strong>
|
||||
</div>
|
||||
|
||||
|
||||
</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>
|
||||
|
||||
@Html.LoadSearchAssets()
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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…
Add table
Add a link
Reference in a new issue