pull/1425/head
Jamie.Rees 8 years ago
parent 3935dcb811
commit 8b64c18ace

@ -0,0 +1,15 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.TvMaze.Models;
namespace Ombi.Api.TvMaze
{
public interface ITvMazeApi
{
Task<IEnumerable<TvMazeEpisodes>> EpisodeLookup(int showId);
Task<List<TvMazeSeasons>> GetSeasons(int id);
Task<List<TvMazeSearch>> Search(string searchTerm);
Task<TvMazeShow> ShowLookup(int showId);
Task<TvMazeShow> ShowLookupByTheTvDbId(int theTvDbId);
}
}

@ -0,0 +1,9 @@
namespace Ombi.Api.TvMaze.Models
{
public class Country
{
public string code { get; set; }
public string name { get; set; }
public string timezone { get; set; }
}
}

@ -0,0 +1,9 @@
namespace Ombi.Api.TvMaze.Models
{
public class Externals
{
public string imdb { get; set; }
public int? thetvdb { get; set; }
public int? tvrage { get; set; }
}
}

@ -0,0 +1,8 @@
namespace Ombi.Api.TvMaze.Models
{
public class Image
{
public string medium { get; set; }
public string original { get; set; }
}
}

@ -0,0 +1,9 @@
namespace Ombi.Api.TvMaze.Models
{
public class Links
{
public Nextepisode nextepisode { get; set; }
public Previousepisode previousepisode { get; set; }
public Self self { get; set; }
}
}

@ -0,0 +1,9 @@
namespace Ombi.Api.TvMaze.Models
{
public class Network
{
public Country country { get; set; }
public int id { get; set; }
public string name { get; set; }
}
}

@ -0,0 +1,7 @@
namespace Ombi.Api.TvMaze.Models
{
public class Nextepisode
{
public string href { get; set; }
}
}

@ -0,0 +1,7 @@
namespace Ombi.Api.TvMaze.Models
{
public class Previousepisode
{
public string href { get; set; }
}
}

@ -0,0 +1,7 @@
namespace Ombi.Api.TvMaze.Models
{
public class Rating
{
public double? average { get; set; }
}
}

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Ombi.Api.TvMaze.Models
{
public class Schedule
{
public List<object> days { get; set; }
public string time { get; set; }
}
}

@ -0,0 +1,7 @@
namespace Ombi.Api.TvMaze.Models
{
public class Self
{
public string href { get; set; }
}
}

@ -0,0 +1,27 @@
using System.Collections.Generic;
namespace Ombi.Api.TvMaze.Models
{
public class Show
{
public Links _links { get; set; }
public Externals externals { get; set; }
public List<object> genres { get; set; }
public int id { get; set; }
public Image image { get; set; }
public string language { get; set; }
public string name { get; set; }
public Network network { get; set; }
public string premiered { get; set; }
public Rating rating { get; set; }
public int? runtime { get; set; }
public Schedule schedule { get; set; }
public string status { get; set; }
public string summary { get; set; }
public string type { get; set; }
public int updated { get; set; }
public string url { get; set; }
public object webChannel { get; set; }
public int weight { get; set; }
}
}

@ -0,0 +1,8 @@
namespace Ombi.Api.TvMaze.Models
{
public class TvMazeSearch
{
public double score { get; set; }
public Show show { get; set; }
}
}

@ -0,0 +1,7 @@
namespace Ombi.Api.TvMaze.Models
{
public class TvMazeSeasons : TvMazeShow
{
public int number { get; set; }
}
}

@ -0,0 +1,18 @@
namespace Ombi.Api.TvMaze.Models
{
public class TvMazeEpisodes
{
public int id { get; set; }
public string url { get; set; }
public string name { get; set; }
public int season { get; set; }
public int number { get; set; }
public string airdate { get; set; }
public string airtime { get; set; }
public string airstamp { get; set; }
public int runtime { get; set; }
public Image image { get; set; }
public string summary { get; set; }
public Links _links { get; set; }
}
}

@ -0,0 +1,89 @@
using System.Collections.Generic;
namespace Ombi.Api.TvMaze.Models
{
public class TvMazeShow
{
public TvMazeShow()
{
Season = new List<TvMazeCustomSeason>();
}
public int id { get; set; }
public string url { get; set; }
public string name { get; set; }
public string type { get; set; }
public string language { get; set; }
public List<string> genres { get; set; }
public string status { get; set; }
public double runtime { get; set; }
public string premiered { get; set; }
public Schedule schedule { get; set; }
public Rating rating { get; set; }
public int weight { get; set; }
public Network network { get; set; }
public object webChannel { get; set; }
public Externals externals { get; set; }
public Image image { get; set; }
public string summary { get; set; }
public int updated { get; set; }
public Links _links { get; set; }
public List<TvMazeCustomSeason> Season { get; set; }
public Embedded _embedded { get; set; }
}
public class TvMazeCustomSeason
{
public int SeasonNumber { get; set; }
public int EpisodeNumber { get; set; }
}
public class Season
{
public int id { get; set; }
public string url { get; set; }
public int number { get; set; }
public string name { get; set; }
public int? episodeOrder { get; set; }
public string premiereDate { get; set; }
public string endDate { get; set; }
public Network2 network { get; set; }
public object webChannel { get; set; }
public Image2 image { get; set; }
public string summary { get; set; }
public Links2 _links { get; set; }
}
public class Country2
{
public string name { get; set; }
public string code { get; set; }
public string timezone { get; set; }
}
public class Network2
{
public int id { get; set; }
public string name { get; set; }
public Country2 country { get; set; }
}
public class Image2
{
public string medium { get; set; }
public string original { get; set; }
}
public class Self2
{
public string href { get; set; }
}
public class Links2
{
public Self2 self { get; set; }
}
public class Embedded
{
public List<Season> seasons { get; set; }
}
}

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Ombi.Api.TvMaze.Models;
namespace Ombi.Api.TvMaze
{
public class TvMazeApi : ITvMazeApi
{
public TvMazeApi()
{
Api = new Ombi.Api.Api();
//Mapper = mapper;
}
private string Uri = "http://api.tvmaze.com";
private Api Api { get; }
public async Task<List<TvMazeSearch>> Search(string searchTerm)
{
var request = new Request("search/shows", Uri, HttpMethod.Get);
request.AddQueryString("q", searchTerm);
request.AddHeader("Content-Type", "application/json");
return await Api.Request<List<TvMazeSearch>>(request);
}
public async Task<TvMazeShow> ShowLookup(int showId)
{
var request = new Request($"shows/{showId}", Uri, HttpMethod.Get);
request.AddHeader("Content-Type", "application/json");
return await Api.Request<TvMazeShow>(request);
}
public async Task<IEnumerable<TvMazeEpisodes>> EpisodeLookup(int showId)
{
var request = new Request($"shows/{showId}/episodes", Uri, HttpMethod.Get);
request.AddHeader("Content-Type", "application/json");
return await Api.Request<List<TvMazeEpisodes>>(request);
}
public async Task<TvMazeShow> ShowLookupByTheTvDbId(int theTvDbId)
{
var request = new Request($"lookup/shows?thetvdb={theTvDbId}", Uri, HttpMethod.Get);
request.AddHeader("Content-Type", "application/json");
try
{
var obj = await Api.Request<TvMazeShow>(request);
var episodes = await EpisodeLookup(obj.id);
foreach (var e in episodes)
{
obj.Season.Add(new TvMazeCustomSeason
{
SeasonNumber = e.season,
EpisodeNumber = e.number
});
}
return obj;
}
catch (Exception e)
{
// TODO
return null;
}
}
public async Task<List<TvMazeSeasons>> GetSeasons(int id)
{
var request = new Request($"shows/{id}/seasons", Uri, HttpMethod.Get);
request.AddHeader("Content-Type", "application/json");
return await Api.Request<List<TvMazeSeasons>>(request);
}
}
}

@ -78,6 +78,29 @@ namespace Ombi.Api
ContentHeaders.Add(new KeyValuePair<string, string>(key, value)); ContentHeaders.Add(new KeyValuePair<string, string>(key, value));
} }
public void AddQueryString(string key, string value)
{
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) return;
var builder = new UriBuilder(_modified);
var startingTag = string.Empty;
var hasQuery = false;
if (string.IsNullOrEmpty(builder.Query))
{
startingTag = "?";
}
else
{
hasQuery = true;
startingTag = builder.Query.Contains("?") ? "&" : "?";
}
builder.Query = hasQuery
? $"{builder.Query}{startingTag}{key}={value}"
: $"{startingTag}{key}={value}";
_modified = builder.Uri;
}
public void AddJsonBody(object obj) public void AddJsonBody(object obj)
{ {
JsonBody = obj; JsonBody = obj;

@ -29,6 +29,8 @@ namespace Ombi.Core.Claims
public static class OmbiClaims public static class OmbiClaims
{ {
public const string Admin = nameof(Admin); public const string Admin = nameof(Admin);
public const string AutoApproveMovie = nameof(AutoApproveMovie);
public const string AutoApproveTv = nameof(AutoApproveTv);
} }
} }

@ -1,7 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ombi.Core.Claims;
using Ombi.Core.Engine.Interfaces; using Ombi.Core.Engine.Interfaces;
using Ombi.Core.IdentityResolver; using Ombi.Core.IdentityResolver;
using Ombi.Core.Models.Requests; using Ombi.Core.Models.Requests;
@ -13,7 +15,7 @@ namespace Ombi.Core.Engine
{ {
public abstract class BaseMediaEngine : BaseEngine public abstract class BaseMediaEngine : BaseEngine
{ {
protected BaseMediaEngine(IUserIdentityManager identity, IRequestService service) : base(identity) protected BaseMediaEngine(IPrincipal identity, IRequestService service) : base(identity)
{ {
RequestService = service; RequestService = service;
} }
@ -36,5 +38,43 @@ namespace Ombi.Core.Engine
} }
return _dbMovies; return _dbMovies;
} }
public async Task<IEnumerable<RequestViewModel>> GetRequests()
{
var allRequests = await RequestService.GetAllAsync();
var viewModel = MapToVm(allRequests);
return viewModel;
}
protected IEnumerable<RequestViewModel> MapToVm(IEnumerable<RequestModel> model)
{
return model.Select(movie => new RequestViewModel
{
ProviderId = movie.ProviderId,
Type = movie.Type,
Status = movie.Status,
ImdbId = movie.ImdbId,
Id = movie.Id,
PosterPath = movie.PosterPath,
ReleaseDate = movie.ReleaseDate,
RequestedDate = movie.RequestedDate,
Released = DateTime.Now > movie.ReleaseDate,
Approved = movie.Available || movie.Approved,
Title = movie.Title,
Overview = movie.Overview,
RequestedUsers = movie.AllUsers.ToArray(),
ReleaseYear = movie.ReleaseDate.Year.ToString(),
Available = movie.Available,
Admin = HasRole(OmbiClaims.Admin),
IssueId = movie.IssueId,
Denied = movie.Denied,
DeniedReason = movie.DeniedReason,
//Qualities = qualities.ToArray(),
//HasRootFolders = rootFolders.Any(),
//RootFolders = rootFolders.ToArray(),
//CurrentRootPath = radarr.Enabled ? GetRootPath(movie.RootFolderSelected, radarr).Result : null
}).ToList();
}
} }
} }

@ -1,22 +1,56 @@
using System.Threading.Tasks; using System.Security.Principal;
using Ombi.Core.IdentityResolver; using Ombi.Core.Claims;
using Ombi.Core.Models; using Ombi.Core.Settings.Models;
using Ombi.Store.Entities;
namespace Ombi.Core.Engine.Interfaces namespace Ombi.Core.Engine.Interfaces
{ {
public abstract class BaseEngine public abstract class BaseEngine
{ {
protected BaseEngine(IUserIdentityManager identity) protected BaseEngine(IPrincipal user)
{ {
UserIdentity = identity; User = user;
} }
protected IPrincipal User { get; }
protected IUserIdentityManager UserIdentity { get; } protected string Username => User.Identity.Name;
protected async Task<UserDto> GetUser(string username) protected bool HasRole(string roleName)
{ {
return User.IsInRole(roleName);
} }
protected bool ShouldSendNotification(RequestType type)
{
var sendNotification = !ShouldAutoApprove(type); /*|| !prSettings.IgnoreNotifyForAutoApprovedRequests;*/
if (HasRole(OmbiClaims.Admin))
{
sendNotification = false; // Don't bother sending a notification if the user is an admin
}
return sendNotification;
}
public bool ShouldAutoApprove(RequestType requestType)
{
var admin = HasRole(OmbiClaims.Admin);
// if the user is an admin, they go ahead and allow auto-approval
if (admin) return true;
// check by request type if the category requires approval or not
switch (requestType)
{
case RequestType.Movie:
return HasRole(OmbiClaims.AutoApproveMovie);
case RequestType.TvShow:
return HasRole(OmbiClaims.AutoApproveTv);
default:
return false;
}
}
} }
} }

@ -1,4 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks; using System.Threading.Tasks;
using AutoMapper; using AutoMapper;
using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb;
@ -13,10 +15,10 @@ using Ombi.Store.Entities;
namespace Ombi.Core.Engine namespace Ombi.Core.Engine
{ {
public class MovieEngine : BaseMediaEngine, IMovieEngine public class MovieSearchEngine : BaseMediaEngine, IMovieEngine
{ {
public MovieEngine(IUserIdentityManager identity, IRequestService service, IMovieDbApi movApi, IMapper mapper, ISettingsService<PlexSettings> plexSettings, ISettingsService<EmbySettings> embySettings) public MovieSearchEngine(IPrincipal identity, IRequestService service, IMovieDbApi movApi, IMapper mapper, ISettingsService<PlexSettings> plexSettings, ISettingsService<EmbySettings> embySettings)
: base(identity, service) : base(identity, service)
{ {
MovieApi = movApi; MovieApi = movApi;
@ -32,14 +34,26 @@ namespace Ombi.Core.Engine
public async Task<IEnumerable<SearchMovieViewModel>> LookupImdbInformation(IEnumerable<SearchMovieViewModel> movies) public async Task<IEnumerable<SearchMovieViewModel>> LookupImdbInformation(IEnumerable<SearchMovieViewModel> movies)
{ {
var searchMovieViewModels
= movies as IList<SearchMovieViewModel> ?? movies.ToList();
if (searchMovieViewModels == null || !searchMovieViewModels.Any())
{
return new List<SearchMovieViewModel>();
}
var retVal = new List<SearchMovieViewModel>(); var retVal = new List<SearchMovieViewModel>();
Dictionary<int, RequestModel> dbMovies = await GetRequests(RequestType.Movie); Dictionary<int, RequestModel> dbMovies = await GetRequests(RequestType.Movie);
foreach (var m in movies)
var plexSettings = await PlexSettings.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
foreach (var m in searchMovieViewModels)
{ {
var movieInfo = await MovieApi.GetMovieInformationWithVideo(m.Id); var movieInfo = await MovieApi.GetMovieInformationWithVideo(m.Id);
var viewMovie = Mapper.Map<SearchMovieViewModel>(movieInfo); var viewMovie = Mapper.Map<SearchMovieViewModel>(movieInfo);
retVal.Add(await ProcessSingleMovie(viewMovie, dbMovies)); retVal.Add(await ProcessSingleMovie(viewMovie, dbMovies, plexSettings, embySettings));
} }
return retVal; return retVal;
} }
@ -97,21 +111,21 @@ namespace Ombi.Core.Engine
{ {
var viewMovies = new List<SearchMovieViewModel>(); var viewMovies = new List<SearchMovieViewModel>();
Dictionary<int, RequestModel> dbMovies = await GetRequests(RequestType.Movie); Dictionary<int, RequestModel> dbMovies = await GetRequests(RequestType.Movie);
var plexSettings = await PlexSettings.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
foreach (var movie in movies) foreach (var movie in movies)
{ {
viewMovies.Add(await ProcessSingleMovie(movie, dbMovies)); viewMovies.Add(await ProcessSingleMovie(movie, dbMovies, plexSettings, embySettings));
} }
return viewMovies; return viewMovies;
} }
private async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie, private async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie,
Dictionary<int, RequestModel> existingRequests) Dictionary<int, RequestModel> existingRequests, PlexSettings plexSettings, EmbySettings embySettings)
{ {
var plexSettings = await PlexSettings.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
if (plexSettings.Enable) if (plexSettings.Enable)
{ {
// var content = PlexContentRepository.GetAll(); // var content = PlexContentRepository.GetAll();
@ -152,10 +166,10 @@ namespace Ombi.Core.Engine
return viewMovie; return viewMovie;
} }
private async Task<SearchMovieViewModel> ProcessSingleMovie(MovieSearchResult movie, Dictionary<int, RequestModel> existingRequests) private async Task<SearchMovieViewModel> ProcessSingleMovie(MovieSearchResult movie, Dictionary<int, RequestModel> existingRequests, PlexSettings plexSettings, EmbySettings embySettings)
{ {
var viewMovie = Mapper.Map<SearchMovieViewModel>(movie); var viewMovie = Mapper.Map<SearchMovieViewModel>(movie);
return await ProcessSingleMovie(viewMovie, existingRequests); return await ProcessSingleMovie(viewMovie, existingRequests, plexSettings, embySettings);
} }
} }
} }

@ -2,26 +2,24 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb;
using Ombi.Core.Models.Requests; using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search; using Ombi.Core.Models.Search;
using Ombi.Core.Requests.Models; using Ombi.Core.Requests.Models;
using Ombi.Store.Entities; using Ombi.Store.Entities;
using Ombi.TheMovieDbApi;
using Ombi.Helpers; using Ombi.Helpers;
namespace Ombi.Core.Engine namespace Ombi.Core.Engine
{ {
public class RequestEngine : IRequestEngine public class RequestEngine : BaseMediaEngine, IRequestEngine
{ {
public RequestEngine(IMovieDbApi movieApi, IRequestService requestService) public RequestEngine(IMovieDbApi movieApi, IRequestService requestService, IPrincipal user) : base(user, requestService)
{ {
MovieApi = movieApi; MovieApi = movieApi;
RequestService = requestService;
} }
private IMovieDbApi MovieApi { get; } private IMovieDbApi MovieApi { get; }
private IRequestService RequestService { get; }
public async Task<RequestEngineResult> RequestMovie(SearchMovieViewModel model) public async Task<RequestEngineResult> RequestMovie(SearchMovieViewModel model)
{ {
var movieInfo = await MovieApi.GetMovieInformation(model.Id); var movieInfo = await MovieApi.GetMovieInformation(model.Id);
@ -30,7 +28,8 @@ namespace Ombi.Core.Engine
return new RequestEngineResult return new RequestEngineResult
{ {
RequestAdded = false, RequestAdded = false,
Message = "There was an issue adding this movie!" Message = "There was an issue adding this movie!",
ErrorMessage = $"TheMovieDb didn't have any information for ID {model.Id}"
}; };
} }
var fullMovieName = var fullMovieName =
@ -42,7 +41,7 @@ namespace Ombi.Core.Engine
return new RequestEngineResult return new RequestEngineResult
{ {
RequestAdded = false, RequestAdded = false,
Message = "This has already been requested" Message = $"{fullMovieName} has already been requested"
}; };
} }
@ -86,7 +85,7 @@ namespace Ombi.Core.Engine
Status = movieInfo.Status, Status = movieInfo.Status,
RequestedDate = DateTime.UtcNow, RequestedDate = DateTime.UtcNow,
Approved = false, Approved = false,
//RequestedUsers = new List<string> { Username }, RequestedUsers = new List<string> { Username },
Issues = IssueState.None, Issues = IssueState.None,
}; };
@ -94,7 +93,7 @@ namespace Ombi.Core.Engine
{ {
if (ShouldAutoApprove(RequestType.Movie)) if (ShouldAutoApprove(RequestType.Movie))
{ {
// model.Approved = true; model.Approved = true;
// var result = await MovieSender.Send(model); // var result = await MovieSender.Send(model);
// if (result.Result) // if (result.Result)
@ -154,43 +153,24 @@ namespace Ombi.Core.Engine
return null; return null;
} }
public bool ShouldAutoApprove(RequestType requestType)
{
//var admin = Security.HasPermissions(Context.CurrentUser, Permissions.Administrator);
//// if the user is an admin, they go ahead and allow auto-approval
//if (admin) return true;
//// check by request type if the category requires approval or not private async Task<RequestEngineResult> AddRequest(RequestModel model, string message)
//switch (requestType)
//{
// case RequestType.Movie:
// return Security.HasPermissions(User, Permissions.AutoApproveMovie);
// case RequestType.TvShow:
// return Security.HasPermissions(User, Permissions.AutoApproveTv);
// case RequestType.Album:
// return Security.HasPermissions(User, Permissions.AutoApproveAlbum);
// default:
// return false;
return false;
}
private async Task<RequestEngineResult> AddRequest(RequestModel model, /*PlexRequestSettings settings,*/ string message)
{ {
await RequestService.AddRequestAsync(model); await RequestService.AddRequestAsync(model);
//if (ShouldSendNotification(model.Type, settings)) if (ShouldSendNotification(model.Type))
//{ {
// var notificationModel = new NotificationModel // var notificationModel = new NotificationModel
// { // {
// Title = model.Title, // Title = model.Title,
// User = Username, // User = Username,
// DateTime = DateTime.Now, // DateTime = DateTime.Now,
// NotificationType = NotificationType.NewRequest, // NotificationType = NotificationType.NewRequest,
// RequestType = model.Type, // RequestType = model.Type,
// ImgSrc = model.Type == RequestType.Movie ? $"https://image.tmdb.org/t/p/w300/{model.PosterPath}" : model.PosterPath // ImgSrc = model.Type == RequestType.Movie ? $"https://image.tmdb.org/t/p/w300/{model.PosterPath}" : model.PosterPath
// }; // };
// await NotificationService.Publish(notificationModel); // await NotificationService.Publish(notificationModel);
//} }
//var limit = await RequestLimitRepo.GetAllAsync(); //var limit = await RequestLimitRepo.GetAllAsync();
//var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == model.Type); //var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == model.Type);
@ -213,13 +193,6 @@ namespace Ombi.Core.Engine
return new RequestEngineResult{RequestAdded = true}; return new RequestEngineResult{RequestAdded = true};
} }
public async Task<IEnumerable<RequestViewModel>> GetRequests()
{
var allRequests = await RequestService.GetAllAsync();
var viewModel = MapToVm(allRequests);
return viewModel;
}
public async Task<IEnumerable<RequestViewModel>> GetRequests(int count, int position) public async Task<IEnumerable<RequestViewModel>> GetRequests(int count, int position)
{ {
var allRequests = await RequestService.GetAllAsync(count, position); var allRequests = await RequestService.GetAllAsync(count, position);
@ -264,34 +237,6 @@ namespace Ombi.Core.Engine
} }
private IEnumerable<RequestViewModel> MapToVm(IEnumerable<RequestModel> model)
{
return model.Select(movie => new RequestViewModel
{
ProviderId = movie.ProviderId,
Type = movie.Type,
Status = movie.Status,
ImdbId = movie.ImdbId,
Id = movie.Id,
PosterPath = movie.PosterPath,
ReleaseDate = movie.ReleaseDate,
RequestedDate = movie.RequestedDate,
Released = DateTime.Now > movie.ReleaseDate,
Approved = movie.Available || movie.Approved,
Title = movie.Title,
Overview = movie.Overview,
RequestedUsers = movie.AllUsers.ToArray(),
ReleaseYear = movie.ReleaseDate.Year.ToString(),
Available = movie.Available,
Admin = false,
IssueId = movie.IssueId,
Denied = movie.Denied,
DeniedReason = movie.DeniedReason,
//Qualities = qualities.ToArray(),
//HasRootFolders = rootFolders.Any(),
//RootFolders = rootFolders.ToArray(),
//CurrentRootPath = radarr.Enabled ? GetRootPath(movie.RootFolderSelected, radarr).Result : null
}).ToList();
}
} }
} }

@ -4,5 +4,7 @@
{ {
public bool RequestAdded { get; set; } public bool RequestAdded { get; set; }
public string Message { get; set; } public string Message { get; set; }
public bool IsError => !string.IsNullOrEmpty(ErrorMessage);
public string ErrorMessage { get; set; }
} }
} }

@ -0,0 +1,29 @@
using System.Security.Principal;
using AutoMapper;
using Ombi.Api.TvMaze;
using Ombi.Core.Requests.Models;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
namespace Ombi.Core.Engine
{
public class TvSearchEngine : BaseMediaEngine
{
public TvSearchEngine(IPrincipal identity, IRequestService service, ITvMazeApi tvMaze, IMapper mapper, ISettingsService<PlexSettings> plexSettings, ISettingsService<EmbySettings> embySettings)
: base(identity, service)
{
TvMazeApi = tvMaze;
Mapper = mapper;
PlexSettings = plexSettings;
EmbySettings = embySettings;
}
private ITvMazeApi TvMazeApi { get; }
private IMapper Mapper { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
}
}

@ -16,6 +16,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj" />
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" /> <ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" />
<ProjectReference Include="..\Ombi.Store\Ombi.Store.csproj" /> <ProjectReference Include="..\Ombi.Store\Ombi.Store.csproj" />
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj" /> <ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj" />

@ -0,0 +1,22 @@
using System;
using Newtonsoft.Json;
namespace Ombi.Core.Settings.Models
{
public class LandingPageSettings : Settings
{
public bool Enabled { get; set; }
public bool BeforeLogin { get; set; }
[JsonIgnore]
public bool AfterLogin => !BeforeLogin;
public bool NoticeEnabled => !string.IsNullOrEmpty(NoticeText);
public string NoticeText { get; set; }
public string NoticeBackgroundColor { get; set; }
public bool TimeLimit { get; set; }
public DateTime StartDateTime { get; set; }
public DateTime EndDateTime { get; set; }
}
}

@ -1,13 +1,16 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Security.Principal;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Ombi.Api.Emby; using Ombi.Api.Emby;
using Ombi.Api.Plex; using Ombi.Api.Plex;
using Ombi.Api.Sonarr; using Ombi.Api.Sonarr;
using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb;
using Ombi.Api.TvMaze;
using Ombi.Core; using Ombi.Core;
using Ombi.Core.Engine; using Ombi.Core.Engine;
using Ombi.Core.IdentityResolver; using Ombi.Core.IdentityResolver;
@ -38,7 +41,7 @@ namespace Ombi.DependencyInjection
public static IServiceCollection RegisterEngines(this IServiceCollection services) public static IServiceCollection RegisterEngines(this IServiceCollection services)
{ {
services.AddTransient<IMovieEngine, MovieEngine>(); services.AddTransient<IMovieEngine, MovieSearchEngine>();
services.AddTransient<IRequestEngine, RequestEngine>(); services.AddTransient<IRequestEngine, RequestEngine>();
return services; return services;
} }
@ -49,6 +52,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IPlexApi, PlexApi>(); services.AddTransient<IPlexApi, PlexApi>();
services.AddTransient<IEmbyApi, EmbyApi>(); services.AddTransient<IEmbyApi, EmbyApi>();
services.AddTransient<ISonarrApi, SonarrApi>(); services.AddTransient<ISonarrApi, SonarrApi>();
services.AddTransient<ITvMazeApi, TvMazeApi>();
return services; return services;
} }
@ -80,6 +84,7 @@ namespace Ombi.DependencyInjection
public static IServiceCollection RegisterIdentity(this IServiceCollection services) public static IServiceCollection RegisterIdentity(this IServiceCollection services)
{ {
services.AddTransient<IUserIdentityManager, UserIdentityManager>(); services.AddTransient<IUserIdentityManager, UserIdentityManager>();
services.AddAuthorization(auth => services.AddAuthorization(auth =>
{ {

@ -14,6 +14,7 @@
<ProjectReference Include="..\Ombi.Api.Emby\Ombi.Api.Emby.csproj" /> <ProjectReference Include="..\Ombi.Api.Emby\Ombi.Api.Emby.csproj" />
<ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" /> <ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" />
<ProjectReference Include="..\Ombi.Api.Sonarr\Ombi.Api.Sonarr.csproj" /> <ProjectReference Include="..\Ombi.Api.Sonarr\Ombi.Api.Sonarr.csproj" />
<ProjectReference Include="..\Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj" />
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj" /> <ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj" />
<ProjectReference Include="..\Ombi.Schedule\Ombi.Schedule.csproj" /> <ProjectReference Include="..\Ombi.Schedule\Ombi.Schedule.csproj" />
</ItemGroup> </ItemGroup>

@ -86,13 +86,15 @@ namespace Ombi.Helpers
public static string EncryptString(string text, string keyString) public static string EncryptString(string text, string keyString)
{ {
var result = AesEncryption.EncryptWithPassword(text, keyString); var t = Encoding.UTF8.GetBytes(text);
var result = Convert.ToBase64String(t);
return result; return result;
} }
public static string DecryptString(string cipherText, string keyString) public static string DecryptString(string cipherText, string keyString)
{ {
var result = AesEncryption.DecryptWithPassword(cipherText, keyString); var textAsBytes = Convert.FromBase64String(cipherText);
var result = Encoding.UTF8.GetString(textAsBytes);
return result; return result;
} }
} }

@ -17,7 +17,7 @@ namespace Ombi.Api.TheMovieDb
} }
private IMapper Mapper { get; } private IMapper Mapper { get; }
private readonly string ApiToken = StringCipher.DecryptString("bqCrDAQABAC/mJT6JXNdlQ1boOjsHeQgyk7gcNv7tUFtwxoVEnYvqS+UdgfgoyXnBz2F6LJnKX8xGtXbzLsf6pbxDkxna6zvunivxAcAHewo2zTPjoUB5igeMB8d93fx0WO9IhGtq8oGXv++xfaXfTY3aN5NV7JmF6ziAAAAAD1e5VjRPSLOYTyJ3Hbw9bDsE/4FGxYIrvxVkqDMl1vAosOeTi+0kKPFloF6k2ptTw==", "Ombiv3SettingsEncryptionPassword"); private readonly string ApiToken = "b8eabaf5608b88d0298aa189dd90bf00";
private static readonly string BaseUri ="http://api.themoviedb.org/3/"; private static readonly string BaseUri ="http://api.themoviedb.org/3/";
private Ombi.Api.Api Api { get; } private Ombi.Api.Api Api { get; }

@ -42,7 +42,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Sonarr", "Ombi.Api
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{6F42AB98-9196-44C4-B888-D5E409F415A1}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{6F42AB98-9196-44C4-B888-D5E409F415A1}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Core.Tests", "Ombi.Core.Tests\Ombi.Core.Tests.csproj", "{2836861C-1185-4E56-A0C2-F4EB541C74AE}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.TvMaze", "Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj", "{0E8EF835-E4F0-4EE5-A2B6-678DEE973721}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -98,10 +98,10 @@ Global
{CFB5E008-D0D0-43C0-AA06-89E49D17F384}.Debug|Any CPU.Build.0 = Debug|Any CPU {CFB5E008-D0D0-43C0-AA06-89E49D17F384}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CFB5E008-D0D0-43C0-AA06-89E49D17F384}.Release|Any CPU.ActiveCfg = Release|Any CPU {CFB5E008-D0D0-43C0-AA06-89E49D17F384}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CFB5E008-D0D0-43C0-AA06-89E49D17F384}.Release|Any CPU.Build.0 = Release|Any CPU {CFB5E008-D0D0-43C0-AA06-89E49D17F384}.Release|Any CPU.Build.0 = Release|Any CPU
{2836861C-1185-4E56-A0C2-F4EB541C74AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0E8EF835-E4F0-4EE5-A2B6-678DEE973721}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2836861C-1185-4E56-A0C2-F4EB541C74AE}.Debug|Any CPU.Build.0 = Debug|Any CPU {0E8EF835-E4F0-4EE5-A2B6-678DEE973721}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2836861C-1185-4E56-A0C2-F4EB541C74AE}.Release|Any CPU.ActiveCfg = Release|Any CPU {0E8EF835-E4F0-4EE5-A2B6-678DEE973721}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2836861C-1185-4E56-A0C2-F4EB541C74AE}.Release|Any CPU.Build.0 = Release|Any CPU {0E8EF835-E4F0-4EE5-A2B6-678DEE973721}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -114,6 +114,6 @@ Global
{2E1A7B91-F29B-42BC-8F1E-1CF2DCC389BA} = {9293CA11-360A-4C20-A674-B9E794431BF5} {2E1A7B91-F29B-42BC-8F1E-1CF2DCC389BA} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{08FF107D-31E1-470D-AF86-E09B015CEE06} = {9293CA11-360A-4C20-A674-B9E794431BF5} {08FF107D-31E1-470D-AF86-E09B015CEE06} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{CFB5E008-D0D0-43C0-AA06-89E49D17F384} = {9293CA11-360A-4C20-A674-B9E794431BF5} {CFB5E008-D0D0-43C0-AA06-89E49D17F384} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{2836861C-1185-4E56-A0C2-F4EB541C74AE} = {6F42AB98-9196-44C4-B888-D5E409F415A1} {0E8EF835-E4F0-4EE5-A2B6-678DEE973721} = {9293CA11-360A-4C20-A674-B9E794431BF5}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

@ -39,6 +39,7 @@ namespace Ombi.Controllers
[HttpGet("search/{searchTerm}")] [HttpGet("search/{searchTerm}")]
public async Task<IEnumerable<RequestViewModel>> Search(string searchTerm) public async Task<IEnumerable<RequestViewModel>> Search(string searchTerm)
{ {
return await RequestEngine.SearchRequest(searchTerm); return await RequestEngine.SearchRequest(searchTerm);
} }

@ -29,7 +29,6 @@ namespace Ombi.Controllers
public async Task<bool> OmbiSettings([FromBody]OmbiSettings ombi) public async Task<bool> OmbiSettings([FromBody]OmbiSettings ombi)
{ {
return await Save(ombi); return await Save(ombi);
} }
[HttpGet("plex")] [HttpGet("plex")]
@ -56,6 +55,18 @@ namespace Ombi.Controllers
return await Save(emby); return await Save(emby);
} }
[HttpGet("landingpage")]
public async Task<LandingPageSettings> LandingPageSettings()
{
return await Get<LandingPageSettings>();
}
[HttpPost("landingpage")]
public async Task<bool> LandingPageSettings([FromBody]LandingPageSettings settings)
{
return await Save(settings);
}
private async Task<T> Get<T>() private async Task<T> Get<T>()
{ {

@ -1,10 +1,14 @@
using AutoMapper; using System;
using System.Configuration;
using System.Security.Principal;
using AutoMapper;
using AutoMapper.EquivalencyExpression; using AutoMapper.EquivalencyExpression;
using Hangfire; using Hangfire;
using Hangfire.MemoryStorage; using Hangfire.MemoryStorage;
using Hangfire.RecurringJobExtensions; using Hangfire.RecurringJobExtensions;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.StaticFiles; using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -42,6 +46,10 @@ namespace Ombi
}); });
services.RegisterDependencies(); // Ioc and EF services.RegisterDependencies(); // Ioc and EF
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
//services.AddTransient<IPrincipal>(new InjectionFactory(u => HttpContext.Current.User));
services.AddScoped<IPrincipal>(sp => sp.GetService<IHttpContextAccessor>().HttpContext.User);
services.AddHangfire(x => services.AddHangfire(x =>
{ {

@ -11,6 +11,7 @@ import { HttpModule } from '@angular/http';
import { InfiniteScrollModule } from 'ngx-infinite-scroll' import { InfiniteScrollModule } from 'ngx-infinite-scroll'
import { SearchComponent } from './search/search.component'; import { SearchComponent } from './search/search.component';
import { MovieSearchComponent } from './search/moviesearch.component';
import { RequestComponent } from './requests/request.component'; import { RequestComponent } from './requests/request.component';
import { LoginComponent } from './login/login.component'; import { LoginComponent } from './login/login.component';
import { PageNotFoundComponent } from './errors/not-found.component'; import { PageNotFoundComponent } from './errors/not-found.component';
@ -64,7 +65,8 @@ const routes: Routes = [
PageNotFoundComponent, PageNotFoundComponent,
SearchComponent, SearchComponent,
RequestComponent, RequestComponent,
LoginComponent LoginComponent,
MovieSearchComponent
], ],
providers: [ providers: [
SearchService, SearchService,

@ -36,4 +36,15 @@ export interface ISonarrSettings extends IExternalSettings {
seasonFolders: boolean, seasonFolders: boolean,
rootPath: string, rootPath: string,
fullRootPath:string fullRootPath:string
}
export interface ILandingPageSettings extends ISettings {
enabled: boolean,
beforeLogin: boolean,
afterLogin: boolean,
noticeText: string,
noticeBackgroundColor: string,
timeLimit: boolean,
startDateTime: Date,
endDateTime:Date,
} }

@ -0,0 +1,154 @@
<!-- Movie tab -->
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
<div class="input-group">
<input id="movieSearchContent" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)">
<div class="input-group-addon">
<div class="btn-group">
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Suggestions
<i class="fa fa-chevron-down"></i>
</a>
<ul class="dropdown-menu">
<li><a (click)="popularMovies()">Popular Movies</a></li>
<li><a (click)="upcomingMovies()">Upcoming Movies</a></li>
<li><a (click)="topRatedMovies()">Top Rated Movies</a></li>
<li><a (click)="nowPlayingMovies()">Now Playing Movies</a></li>
</ul>
</div>
<i id="movieSearchButton" class="fa fa-search"></i>
</div>
</div>
<br />
<br />
<!-- Movie content -->
<div id="movieList">
<div *ngFor="let result of movieResults">
<div class="row">
<div id="{{id}}imgDiv" class="col-sm-2">
<img *ngIf="result.posterPath" class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{result.posterPath}}" alt="poster">
</div>
<div class="col-sm-8">
<div>
<a href="https://www.themoviedb.org/movie/{{result.id}}/" target="_blank">
<h4>{{result.title}} ({{result.releaseDate | date: 'yyyy'}})</h4>
</a>
<span *ngIf="result.firstAired" class="label label-info" target="_blank">Air Date: {{result.firstAired | date: 'dd/MM/yyyy'}}</span>
<span *ngIf="result.releaseDate" class="label label-info" target="_blank">Release Date: {{result.releaseDate | date: 'dd/MM/yyyy'}}</span>
<span *ngIf="result.available" class="label label-success">Available</span>
<span *ngIf="result.approved && !result.available" class="label label-info">Processing Request</span>
<div *ngIf="result.requested && !result.available; then requested else notRequested"></div>
<template #requested>
<span *ngIf="!result.available" class="label label-warning">Pending Approval</span>
</template>
<template #notRequested>
<span *ngIf="!result.available" class="label label-danger">Not Yet Requested</span>
</template>
<span id="{{id}}netflixTab"></span>
<a *ngIf="result.homepage" href="{{result.homepage}}" target="_blank"><span class="label label-info">HomePage</span></a>
<a *ngIf="result.trailer" href="{{result.trailer}}" target="_blank"><span class="label label-info">Trailer</span></a>
<br />
<br />
</div>
<p style="font-size:0.9rem !important">{{result.overview}}</p>
</div>
<div class="col-sm-2">
<input name="{{type}}Id" type="text" value="{{result.id}}" hidden="hidden" />
<div *ngIf="result.available">
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> Available</button>
<div *ngIf="result.url">
<br />
<br />
<a style="text-align: right" class="btn btn-sm btn-primary-outline" href="{{result.url}}" target="_blank"><i class="fa fa-eye"></i> View In Plex</a>
</div>
</div>
<div *ngIf="result.requested; then requestedBtn else notRequestedBtn"></div>
<template #requestedBtn>
<button style="text-align: right" class="btn btn-primary-outline disabled" [disabled]><i class="fa fa-check"></i> Requested</button>
</template>
<template #notRequestedBtn>
<button id="{{result.id}}" style="text-align: right" class="btn btn-primary-outline" (click)="request(result)"><i class="fa fa-plus"></i> Request</button>
</template>
<!--{{#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 dropdownTv{{/if}} btn-primary-outline" season-select="0" type="button" {{#if available}} disabled{{/if}}><i class="fa fa-plus"></i> {{#if available}}@UI.Search_Available{{else}}@UI.Search_Request{{/if}}</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}}
{{#if url}}
<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}}
{{/if_eq}}
{{/if_eq}}-->
<br />
<div *ngIf="result.available">
<input name="providerId" type="text" value="{{id}}" hidden="hidden" />
<input name="type" type="text" value="{{type}}" hidden="hidden" />
<div class="dropdown">
<button 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 issue-select="0" class="dropdownIssue" href="#">WrongAudio</a></li>
<li><a issue-select="1" class="dropdownIssue" href="#">NoSubs</a></li>
<li><a issue-select="2" class="dropdownIssue" href="#">WrongContent</a></li>
<li><a issue-select="3" class="dropdownIssue" href="#">Playback</a></li>
<li><a issue-select="4" class="dropdownIssue" href="#" data-toggle="modal" data-target="#issuesModal">Other</a></li>
</ul>
</div>
</div>
</div>
</div>
<hr />
</div>
</div>
</div>

@ -0,0 +1,110 @@
import { Component, OnInit } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/map';
import { SearchService } from '../services/search.service';
import { RequestService } from '../services/request.service';
import { NotificationService } from '../services/notification.service';
import { ISearchMovieResult } from '../interfaces/ISearchMovieResult';
import { IRequestEngineResult } from '../interfaces/IRequestEngineResult';
@Component({
selector: 'movie-search',
moduleId: module.id,
templateUrl: './moviesearch.component.html',
})
export class MovieSearchComponent implements OnInit {
searchText: string;
searchChanged: Subject<string> = new Subject<string>();
movieResults: ISearchMovieResult[];
result: IRequestEngineResult;
constructor(private searchService: SearchService, private requestService: RequestService, private notificationService: NotificationService) {
this.searchChanged
.debounceTime(600) // Wait Xms afterthe last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.subscribe(x => {
this.searchText = x as string;
if (this.searchText === "") {
this.clearResults();
return;
}
this.searchService.searchMovie(this.searchText).subscribe(x => {
this.movieResults = x;
// Now let's load some exta info including IMDBId
// This way the search is fast at displaying results.
this.getExtaInfo();
});
});
}
ngOnInit(): void {
this.searchText = "";
this.movieResults = [];
this.result = {
message: "",
requestAdded: false
}
}
search(text: any) {
this.searchChanged.next(text.target.value);
}
request(searchResult: ISearchMovieResult) {
searchResult.requested = true;
this.requestService.requestMovie(searchResult).subscribe(x => {
this.result = x;
if (this.result.requestAdded) {
this.notificationService.success("Request Added",
`Request for ${searchResult.title} has been added successfully`);
} else {
this.notificationService.warning("Request Added", this.result.message);
}
});
}
popularMovies() {
this.clearResults();
this.searchService.popularMovies().subscribe(x => {
this.movieResults = x;
this.getExtaInfo();
});
}
nowPlayingMovies() {
this.clearResults();
this.searchService.nowPlayingMovies().subscribe(x => {
this.movieResults = x;
this.getExtaInfo();
});
}
topRatedMovies() {
this.clearResults();
this.searchService.topRatedMovies().subscribe(x => {
this.movieResults = x;
this.getExtaInfo();
});
}
upcomingMovies() {
this.clearResults();
this.searchService.upcomignMovies().subscribe(x => {
this.movieResults = x;
this.getExtaInfo();
});
}
private getExtaInfo() {
this.searchService.extraInfo(this.movieResults).subscribe(m => this.movieResults = m);
}
private clearResults() {
this.movieResults = [];
}
}

@ -30,32 +30,7 @@
<!-- Tab panes --> <!-- Tab panes -->
<div class="tab-content"> <div class="tab-content">
<!-- Movie tab --> <movie-search></movie-search>
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
<div class="input-group">
<input id="movieSearchContent" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)">
<div class="input-group-addon">
<div class="btn-group">
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Suggestions
<i class="fa fa-chevron-down"></i>
</a>
<ul class="dropdown-menu">
<li><a (click)="popularMovies()">Popular Movies</a></li>
<li><a (click)="upcomingMovies()">Upcoming Movies</a></li>
<li><a (click)="topRatedMovies()">Top Rated Movies</a></li>
<li><a (click)="nowPlayingMovies()">Now Playing Movies</a></li>
</ul>
</div>
<i id="movieSearchButton" class="fa fa-search"></i>
</div>
</div>
<br />
<br />
<!-- Movie content -->
<div id="movieList">
</div>
</div>
<!-- Actors tab --> <!-- Actors tab -->
<div role="tabpanel" class="tab-pane" id="ActorsTab"> <div role="tabpanel" class="tab-pane" id="ActorsTab">
@ -87,7 +62,7 @@
<i class="fa fa-chevron-down"></i> <i class="fa fa-chevron-down"></i>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a id="popularShows" >Popular Shows</a></li> <li><a id="popularShows">Popular Shows</a></li>
<li><a id="trendingShows" href="#">Trending 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="mostWatchedShows" href="#">Most Watched Shows</a></li>
<li><a id="anticipatedShows" href="#">Most Anticipated Shows</a></li> <li><a id="anticipatedShows" href="#">Most Anticipated Shows</a></li>
@ -101,146 +76,6 @@
<div id="tvList"> <div id="tvList">
</div> </div>
</div> </div>
</div>
<!-- Music tab -->
<!-- <div role="tabpanel" class="tab-pane" id="MusicTab">
<div class="input-group">
<input id="musicSearchContent" type="text" class="form-control form-control-custom form-control-search">
<div class="input-group-addon">
<i id="musicSearchButton" class="fa fa-search"></i>
</div>
</div>
<br />
<br />
<div id="musicList">
</div>
</div>
-->
<div *ngFor="let result of movieResults">
<div class="row">
<div id="{{id}}imgDiv" class="col-sm-2">
<img *ngIf="result.posterPath" class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{result.posterPath}}" alt="poster">
</div>
<div class="col-sm-8">
<div>
<a href="https://www.themoviedb.org/movie/{{result.id}}/" target="_blank">
<h4>{{result.title}} ({{result.releaseDate | date: 'yyyy'}})</h4>
</a>
<span *ngIf="result.firstAired" class="label label-info" target="_blank">Air Date: {{result.firstAired | date: 'dd/MM/yyyy'}}</span>
<span *ngIf="result.releaseDate" class="label label-info" target="_blank">Release Date: {{result.releaseDate | date: 'dd/MM/yyyy'}}</span>
<span *ngIf="result.available" class="label label-success">Available</span>
<span *ngIf="result.approved && !result.available" class="label label-info">Processing Request</span>
<div *ngIf="result.requested && !result.available; then requested else notRequested"></div>
<template #requested>
<span *ngIf="!result.available" class="label label-warning">Pending Approval</span>
</template>
<template #notRequested>
<span *ngIf="!result.available" class="label label-danger">Not Yet Requested</span>
</template>
<span id="{{id}}netflixTab"></span>
<a *ngIf="result.homepage" href="{{result.homepage}}" target="_blank"><span class="label label-info">HomePage</span></a>
<a *ngIf="result.trailer" href="{{result.trailer}}" target="_blank"><span class="label label-info">Trailer</span></a>
<br />
<br />
</div>
<p style="font-size:0.9rem !important">{{result.overview}}</p>
</div>
<div class="col-sm-2">
<input name="{{type}}Id" type="text" value="{{result.id}}" hidden="hidden" />
<div *ngIf="result.available">
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> Available</button>
<div *ngIf="result.url">
<br />
<br />
<a style="text-align: right" class="btn btn-sm btn-primary-outline" href="{{result.url}}" target="_blank"><i class="fa fa-eye"></i> View In Plex</a>
</div>
</div>
<div *ngIf="result.requested; then requestedBtn else notRequestedBtn"></div>
<template #requestedBtn>
<button style="text-align: right" class="btn btn-primary-outline disabled" [disabled]><i class="fa fa-check"></i> Requested</button>
</template>
<template #notRequestedBtn>
<button id="{{result.id}}" style="text-align: right" class="btn btn-primary-outline" (click)="request(result)"><i class="fa fa-plus"></i> Request</button>
</template>
<!--{{#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 dropdownTv{{/if}} btn-primary-outline" season-select="0" type="button" {{#if available}} disabled{{/if}}><i class="fa fa-plus"></i> {{#if available}}@UI.Search_Available{{else}}@UI.Search_Request{{/if}}</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}}
{{#if url}}
<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}}
{{/if_eq}}
{{/if_eq}}-->
<br />
<div *ngIf="result.available">
<input name="providerId" type="text" value="{{id}}" hidden="hidden" />
<input name="type" type="text" value="{{type}}" hidden="hidden" />
<div class="dropdown">
<button 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 issue-select="0" class="dropdownIssue" href="#">@UI.Issues_WrongAudio</a></li>
<li><a issue-select="1" class="dropdownIssue" href="#">@UI.Issues_NoSubs</a></li>
<li><a issue-select="2" class="dropdownIssue" href="#">@UI.Issues_WrongContent</a></li>
<li><a issue-select="3" class="dropdownIssue" href="#">@UI.Issues_Playback</a></li>
<li><a issue-select="4" class="dropdownIssue" href="#" data-toggle="modal" data-target="#issuesModal">@UI.Issues_Other</a></li>
</ul>
</div>
</div>
</div>
</div>
<hr />
</div>

@ -3,7 +3,7 @@ import { AuthHttp } from 'angular2-jwt';
import { Observable } from 'rxjs/Rx'; import { Observable } from 'rxjs/Rx';
import { ServiceAuthHelpers } from './service.helpers'; import { ServiceAuthHelpers } from './service.helpers';
import { IOmbiSettings, IEmbySettings, IPlexSettings, ISonarrSettings } from '../interfaces/ISettings'; import { IOmbiSettings, IEmbySettings, IPlexSettings, ISonarrSettings,ILandingPageSettings } from '../interfaces/ISettings';
@Injectable() @Injectable()
export class SettingsService extends ServiceAuthHelpers { export class SettingsService extends ServiceAuthHelpers {
@ -42,5 +42,12 @@ export class SettingsService extends ServiceAuthHelpers {
saveSonarr(settings: ISonarrSettings): Observable<boolean> { saveSonarr(settings: ISonarrSettings): Observable<boolean> {
return this.http.post(`${this.url}/Sonarr`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData); return this.http.post(`${this.url}/Sonarr`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData);
} }
getLandingPage(): Observable<ILandingPageSettings> {
return this.http.get(`${this.url}/LandingPage`).map(this.extractData);
}
saveLandingPage(settings: ILandingPageSettings): Observable<boolean> {
return this.http.post(`${this.url}/LandingPage`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData);
}
} }

@ -0,0 +1,41 @@
<div class="col-sm-8 col-sm-push-1">
<fieldset>
<legend>Landing Page Configuration</legend>
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="enable" name="enable" [(ngModel)]="settings.enable" ng-checked="settings.enable">
<label for="enable">Enable</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="BeforeLogin" name="BeforeLogin" [(ngModel)]="settings.beforeLogin" ng-checked="settings.beforeLogin">
<label for="BeforeLogin">Show before the login</label>
</div>
<small>If enabled then this will show the landing page before the login page, if this is disabled the user will log in first and then see the landing page.</small>
</div>
<p class="form-group">Notice Message</p>
<div class="form-group">
<div>
<textarea rows="4" type="text" class="form-control-custom form-control " id="NoticeMessage" name="NoticeMessage" placeholder="e.g. The server will be down for maintaince (HTML is allowed)" [(ngModel)]="settings.noticeText" >{{noticeText}}</textarea>
</div>
</div>
<p class="form-group">Notice Preview:</p>
<div class="form-group">
<div [innerHTML]="settings.noticeText"></div>
</div>
<div class="form-group">
<div>
<button (click)="save()" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
</div>
</div>
</fieldset>
</div>

@ -0,0 +1,31 @@
import { Component, OnInit } from '@angular/core';
import { ILandingPageSettings } from '../../interfaces/ISettings'
import { SettingsService } from '../../services/settings.service';
import { NotificationService } from "../../services/notification.service";
@Component({
selector: 'ombi',
moduleId: module.id,
templateUrl: './landingpage.component.html',
})
export class LandingPageComponent implements OnInit {
constructor(private settingsService: SettingsService, private notificationService: NotificationService) { }
settings: ILandingPageSettings;
ngOnInit(): void {
this.settingsService.getLandingPage().subscribe(x => this.settings = x);
}
save() {
this.settingsService.saveLandingPage(this.settings).subscribe(x => {
if (x) {
this.notificationService.success("Settings Saved", "Successfully saved the Landing Page settings");
} else {
this.notificationService.success("Settings Saved", "There was an error when saving the Landing Page settings");
}
});
}
}

@ -11,6 +11,7 @@ import { SonarrService } from '../services/applications/sonarr.service';
import { OmbiComponent } from './ombi/ombi.component' import { OmbiComponent } from './ombi/ombi.component'
import { PlexComponent } from './plex/plex.component' import { PlexComponent } from './plex/plex.component'
import { EmbyComponent } from './emby/emby.component' import { EmbyComponent } from './emby/emby.component'
import { LandingPageComponent } from './landingpage/landingpage.component'
import { SettingsMenuComponent } from './settingsmenu.component'; import { SettingsMenuComponent } from './settingsmenu.component';
@ -20,6 +21,7 @@ const routes: Routes = [
{ path: 'Settings/Ombi', component: OmbiComponent, canActivate: [AuthGuard] }, { path: 'Settings/Ombi', component: OmbiComponent, canActivate: [AuthGuard] },
{ path: 'Settings/Plex', component: PlexComponent, canActivate: [AuthGuard] }, { path: 'Settings/Plex', component: PlexComponent, canActivate: [AuthGuard] },
{ path: 'Settings/Emby', component: EmbyComponent, canActivate: [AuthGuard] }, { path: 'Settings/Emby', component: EmbyComponent, canActivate: [AuthGuard] },
{ path: 'Settings/LandingPage', component: LandingPageComponent, canActivate: [AuthGuard] },
]; ];
@NgModule({ @NgModule({
@ -37,6 +39,7 @@ const routes: Routes = [
OmbiComponent, OmbiComponent,
PlexComponent, PlexComponent,
EmbyComponent, EmbyComponent,
LandingPageComponent,
], ],
exports: [ exports: [
RouterModule RouterModule

@ -32,9 +32,11 @@ export class CreateAdminComponent {
this.settings.getOmbi().subscribe(ombi => { this.settings.getOmbi().subscribe(ombi => {
ombi.wizard = true; ombi.wizard = true;
this.settings.saveOmbi(ombi).subscribe(); this.settings.saveOmbi(ombi).subscribe(x => {
this.router.navigate(['search']);
});
this.router.navigate(['search']);
}); });
}); });

Loading…
Cancel
Save