mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-14 02:26:55 -07:00
Merge branch 'develop' into patch-1
This commit is contained in:
commit
3c3807493b
92 changed files with 14526 additions and 1374 deletions
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -3,9 +3,22 @@
|
|||
|
||||
## v3.0.4119 (2019-1-09)
|
||||
|
||||
|
||||
### **New Features**
|
||||
|
||||
- Added a new Custom Page, this will allow you to completely change the page via a WYSIWYG editor! [TidusJar]
|
||||
- Update CHANGELOG.md. [Jamie]
|
||||
|
||||
- Added a page where the admin can write/style/basically do whatever they want with e.g. FAQ for the users #2715 This needs to be enabled in the Customization Settings and then it's all configured on the page. [TidusJar]
|
||||
|
||||
- Updated the AspnetCore.App package to remove the CVE-2019-0564 vulnerability. [TidusJar]
|
||||
|
||||
- Added a global language flag that now applies to the search by default. [tidusjar]
|
||||
|
||||
- Updated the frontend packages (Using Angular 7 now) [TidusJar]
|
||||
|
||||
- Added capture of anonymous analytical data. [tidusjar]
|
||||
|
||||
- Added {AvailableDate} as a Notification Variable, this is the date the request was marked as available. See here: https://github.com/tidusjar/Ombi/wiki/Notification-Template-Variables. [tidusjar]
|
||||
|
||||
- Added the ability to search movies via the movie db with a different language! [tidusjar]
|
||||
|
||||
|
@ -26,15 +39,31 @@
|
|||
- Updated boostrap #2694. [TidusJar]
|
||||
|
||||
- Added the ability to deny a request with a reason. [TidusJar]
|
||||
|
||||
- Updated to .net core 2.2 and included a linux-arm64 build. [aptalca]
|
||||
|
||||
- Make the newsletter BCC the users rather than creating a million newsletters (Hopefully will stop SMTP providers from marking as spam). This does mean that the custom user customization in the newsletter will no longer work. [TidusJar]
|
||||
|
||||
|
||||
- New translations [TidusJar]
|
||||
|
||||
### **Fixes**
|
||||
|
||||
- There is now a new Job in ombi that will clear out the Plex/Emby data and recache. This will prevent the issues going forward that we have when Ombi and the Media server fall out of sync with deletions/updates #2641 #2362 #1566. [TidusJar]
|
||||
|
||||
- Potentially fix #2726. [TidusJar]
|
||||
|
||||
- New translations en.json (Spanish) [Jamie]
|
||||
|
||||
- New translations en.json (Spanish) [Jamie]
|
||||
|
||||
- New translations en.json (Dutch) [Jamie]
|
||||
|
||||
- Fixed #2725 and #2721. [TidusJar]
|
||||
|
||||
- Made the newsletter use the default lanuage code set in the Ombi settings for movie information. [TidusJar]
|
||||
|
||||
- Save the language code against the request so we can use it later e.g. Sending to the DVR apps. [tidusjar]
|
||||
|
||||
- Fixed #2716. [tidusjar]
|
||||
|
||||
- If we don't know the Plex agent, then see if it's a ImdbId, if it's not check the string for any episode and season hints #2695. [tidusjar]
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace Ombi.Api
|
|||
{
|
||||
if (!request.IgnoreErrors)
|
||||
{
|
||||
LogError(request, httpResponseMessage);
|
||||
await LogError(request, httpResponseMessage);
|
||||
}
|
||||
|
||||
if (request.Retry)
|
||||
|
@ -105,7 +105,7 @@ namespace Ombi.Api
|
|||
{
|
||||
if (!request.IgnoreErrors)
|
||||
{
|
||||
LogError(request, httpResponseMessage);
|
||||
await LogError(request, httpResponseMessage);
|
||||
}
|
||||
}
|
||||
// do something with the response
|
||||
|
@ -126,7 +126,7 @@ namespace Ombi.Api
|
|||
{
|
||||
if (!request.IgnoreErrors)
|
||||
{
|
||||
LogError(request, httpResponseMessage);
|
||||
await LogError(request, httpResponseMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -149,10 +149,15 @@ namespace Ombi.Api
|
|||
}
|
||||
}
|
||||
|
||||
private void LogError(Request request, HttpResponseMessage httpResponseMessage)
|
||||
private async Task LogError(Request request, HttpResponseMessage httpResponseMessage)
|
||||
{
|
||||
Logger.LogError(LoggingEvents.Api,
|
||||
$"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}, RequestUri: {request.FullUri}");
|
||||
if (Logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
var content = await httpResponseMessage.Content.ReadAsStringAsync();
|
||||
Logger.LogDebug(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
106
src/Ombi.Core/Engine/Demo/DemoMovieSearchEngine.cs
Normal file
106
src/Ombi.Core/Engine/Demo/DemoMovieSearchEngine.cs
Normal file
|
@ -0,0 +1,106 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AutoMapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Ombi.Api.TheMovieDb;
|
||||
using Ombi.Api.TheMovieDb.Models;
|
||||
using Ombi.Config;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Core.Models.Requests;
|
||||
using Ombi.Core.Models.Search;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
|
||||
namespace Ombi.Core.Engine.Demo
|
||||
{
|
||||
public class DemoMovieSearchEngine : MovieSearchEngine, IDemoMovieSearchEngine
|
||||
{
|
||||
public DemoMovieSearchEngine(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper,
|
||||
ILogger<MovieSearchEngine> logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService<OmbiSettings> s,
|
||||
IRepository<RequestSubscription> sub, IOptions<DemoLists> lists)
|
||||
: base(identity, service, movApi, mapper, logger, r, um, mem, s, sub)
|
||||
{
|
||||
_demoLists = lists.Value;
|
||||
}
|
||||
|
||||
private readonly DemoLists _demoLists;
|
||||
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> Search(string search)
|
||||
{
|
||||
var result = await MovieApi.SearchMovie(search, null, "en");
|
||||
|
||||
for (var i = 0; i < result.Count; i++)
|
||||
{
|
||||
if (!_demoLists.Movies.Contains(result[i].Id))
|
||||
{
|
||||
result.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
if(result.Count > 0)
|
||||
return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies()
|
||||
{
|
||||
var rand = new Random();
|
||||
var responses = new List<SearchMovieViewModel>();
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var item = rand.Next(_demoLists.Movies.Length);
|
||||
var movie = _demoLists.Movies[item];
|
||||
if (responses.Any(x => x.Id == movie))
|
||||
{
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
var movieResult = await MovieApi.GetMovieInformationWithExtraInfo(movie);
|
||||
var viewMovie = Mapper.Map<SearchMovieViewModel>(movieResult);
|
||||
|
||||
responses.Add(await ProcessSingleMovie(viewMovie));
|
||||
}
|
||||
|
||||
return responses;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> PopularMovies()
|
||||
{
|
||||
return await NowPlayingMovies();
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies()
|
||||
{
|
||||
return await NowPlayingMovies();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies()
|
||||
{
|
||||
|
||||
return await NowPlayingMovies();
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDemoMovieSearchEngine
|
||||
{
|
||||
Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies();
|
||||
|
||||
Task<IEnumerable<SearchMovieViewModel>> PopularMovies();
|
||||
|
||||
Task<IEnumerable<SearchMovieViewModel>> Search(string search);
|
||||
|
||||
Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies();
|
||||
|
||||
Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies();
|
||||
|
||||
}
|
||||
}
|
96
src/Ombi.Core/Engine/Demo/DemoTvSearchEngine.cs
Normal file
96
src/Ombi.Core/Engine/Demo/DemoTvSearchEngine.cs
Normal file
|
@ -0,0 +1,96 @@
|
|||
using AutoMapper;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Ombi.Api.Trakt;
|
||||
using Ombi.Api.TvMaze;
|
||||
using Ombi.Config;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Core.Models.Requests;
|
||||
using Ombi.Core.Models.Search;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Core.Engine.Demo
|
||||
{
|
||||
public class DemoTvSearchEngine : TvSearchEngine, IDemoTvSearchEngine
|
||||
{
|
||||
|
||||
public DemoTvSearchEngine(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper,
|
||||
ISettingsService<PlexSettings> plexSettings, ISettingsService<EmbySettings> embySettings, IPlexContentRepository repo,
|
||||
IEmbyContentRepository embyRepo, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, ICacheService memCache,
|
||||
ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub, IOptions<DemoLists> lists)
|
||||
: base(identity, service, tvMaze, mapper, plexSettings, embySettings, repo, embyRepo, trakt, r, um, memCache, s, sub)
|
||||
{
|
||||
_demoLists = lists.Value;
|
||||
}
|
||||
|
||||
private readonly DemoLists _demoLists;
|
||||
|
||||
public async Task<IEnumerable<SearchTvShowViewModel>> Search(string search)
|
||||
{
|
||||
var searchResult = await TvMazeApi.Search(search);
|
||||
|
||||
for (var i = 0; i < searchResult.Count; i++)
|
||||
{
|
||||
if (!_demoLists.TvShows.Contains(searchResult[i].show?.externals?.thetvdb ?? 0))
|
||||
{
|
||||
searchResult.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (searchResult != null)
|
||||
{
|
||||
var retVal = new List<SearchTvShowViewModel>();
|
||||
foreach (var tvMazeSearch in searchResult)
|
||||
{
|
||||
if (tvMazeSearch.show.externals == null || !(tvMazeSearch.show.externals?.thetvdb.HasValue ?? false))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
retVal.Add(ProcessResult(tvMazeSearch));
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SearchTvShowViewModel>> NowPlayingMovies()
|
||||
{
|
||||
var rand = new Random();
|
||||
var responses = new List<SearchTvShowViewModel>();
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var item = rand.Next(_demoLists.TvShows.Length);
|
||||
var tv = _demoLists.TvShows[item];
|
||||
if (responses.Any(x => x.Id == tv))
|
||||
{
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
var movieResult = await TvMazeApi.ShowLookup(tv);
|
||||
responses.Add(ProcessResult(movieResult));
|
||||
}
|
||||
|
||||
return responses;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public interface IDemoTvSearchEngine
|
||||
{
|
||||
Task<IEnumerable<SearchTvShowViewModel>> Search(string search);
|
||||
Task<IEnumerable<SearchTvShowViewModel>> NowPlayingMovies();
|
||||
}
|
||||
}
|
|
@ -19,5 +19,6 @@ namespace Ombi.Core
|
|||
Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId, string langCode = null);
|
||||
|
||||
Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId, string langCode);
|
||||
Task<IEnumerable<SearchMovieViewModel>> SearchActor(string search, string langaugeCode);
|
||||
}
|
||||
}
|
|
@ -83,7 +83,8 @@ namespace Ombi.Core.Engine
|
|||
Approved = false,
|
||||
RequestedUserId = userDetails.Id,
|
||||
Background = movieInfo.BackdropPath,
|
||||
LangCode = model.LanguageCode
|
||||
LangCode = model.LanguageCode,
|
||||
RequestedByAlias = model.RequestedByAlias
|
||||
};
|
||||
|
||||
var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US");
|
||||
|
@ -325,6 +326,7 @@ namespace Ombi.Core.Engine
|
|||
|
||||
return new RequestEngineResult
|
||||
{
|
||||
Result = true,
|
||||
Message = "Request successfully deleted",
|
||||
};
|
||||
}
|
||||
|
|
|
@ -31,11 +31,11 @@ namespace Ombi.Core.Engine
|
|||
Logger = logger;
|
||||
}
|
||||
|
||||
private IMovieDbApi MovieApi { get; }
|
||||
private IMapper Mapper { get; }
|
||||
protected IMovieDbApi MovieApi { get; }
|
||||
protected IMapper Mapper { get; }
|
||||
private ILogger<MovieSearchEngine> Logger { get; }
|
||||
|
||||
private const int MovieLimit = 10;
|
||||
protected const int MovieLimit = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Lookups the imdb information.
|
||||
|
@ -54,8 +54,6 @@ namespace Ombi.Core.Engine
|
|||
/// <summary>
|
||||
/// Searches the specified movie.
|
||||
/// </summary>
|
||||
/// <param name="search">The search.</param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> Search(string search, int? year, string langaugeCode)
|
||||
{
|
||||
langaugeCode = await DefaultLanguageCode(langaugeCode);
|
||||
|
@ -68,6 +66,33 @@ namespace Ombi.Core.Engine
|
|||
return null;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> SearchActor(string search, string langaugeCode)
|
||||
{
|
||||
langaugeCode = await DefaultLanguageCode(langaugeCode);
|
||||
var people = await MovieApi.SearchByActor(search, langaugeCode);
|
||||
var person = people?.results?.Count > 0 ? people.results.FirstOrDefault() : null;
|
||||
|
||||
var resultSet = new List<SearchMovieViewModel>();
|
||||
if (person == null)
|
||||
{
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
// Get this person movie credits
|
||||
var credits = await MovieApi.GetActorMovieCredits(person.id, langaugeCode);
|
||||
// Grab results from both cast and crew, prefer items in cast. we can handle directors like this.
|
||||
var movieResults = (from role in credits.cast select new { Id = role.id, Title = role.title, ReleaseDate = role.release_date }).ToList();
|
||||
movieResults.AddRange((from job in credits.crew select new { Id = job.id, Title = job.title, ReleaseDate = job.release_date }).ToList());
|
||||
|
||||
movieResults = movieResults.Take(10).ToList();
|
||||
foreach (var movieResult in movieResults)
|
||||
{
|
||||
resultSet.Add(await LookupImdbInformation(movieResult.Id, langaugeCode));
|
||||
}
|
||||
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get similar movies to the id passed in
|
||||
/// </summary>
|
||||
|
@ -159,7 +184,7 @@ namespace Ombi.Core.Engine
|
|||
return null;
|
||||
}
|
||||
|
||||
private async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse(
|
||||
protected async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse(
|
||||
IEnumerable<MovieSearchResult> movies)
|
||||
{
|
||||
var viewMovies = new List<SearchMovieViewModel>();
|
||||
|
@ -170,24 +195,25 @@ namespace Ombi.Core.Engine
|
|||
return viewMovies;
|
||||
}
|
||||
|
||||
private async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie, bool lookupExtraInfo = false)
|
||||
protected async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie, bool lookupExtraInfo = false)
|
||||
{
|
||||
if (lookupExtraInfo)
|
||||
if (lookupExtraInfo && viewMovie.ImdbId.IsNullOrEmpty())
|
||||
{
|
||||
var showInfo = await MovieApi.GetMovieInformation(viewMovie.Id);
|
||||
viewMovie.Id = showInfo.Id; // TheMovieDbId
|
||||
viewMovie.ImdbId = showInfo.ImdbId;
|
||||
var usDates = viewMovie.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US");
|
||||
viewMovie.DigitalReleaseDate = usDates?.ReleaseDate?.FirstOrDefault(x => x.Type == ReleaseDateType.Digital)?.ReleaseDate;
|
||||
}
|
||||
|
||||
var usDates = viewMovie.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US");
|
||||
viewMovie.DigitalReleaseDate = usDates?.ReleaseDate?.FirstOrDefault(x => x.Type == ReleaseDateType.Digital)?.ReleaseDate;
|
||||
|
||||
viewMovie.TheMovieDbId = viewMovie.Id.ToString();
|
||||
|
||||
await RunSearchRules(viewMovie);
|
||||
|
||||
// This requires the rules to be run first to populate the RequestId property
|
||||
await CheckForSubscription(viewMovie);
|
||||
|
||||
|
||||
return viewMovie;
|
||||
}
|
||||
|
||||
|
|
|
@ -83,7 +83,8 @@ namespace Ombi.Core.Engine
|
|||
Title = album.title,
|
||||
Disk = album.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url,
|
||||
Cover = album.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url,
|
||||
ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty
|
||||
ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty,
|
||||
RequestedByAlias = model.RequestedByAlias
|
||||
};
|
||||
if (requestModel.Cover.IsNullOrEmpty())
|
||||
{
|
||||
|
|
|
@ -40,8 +40,8 @@ namespace Ombi.Core.Engine
|
|||
EmbyContentRepo = embyRepo;
|
||||
}
|
||||
|
||||
private ITvMazeApi TvMazeApi { get; }
|
||||
private IMapper Mapper { get; }
|
||||
protected ITvMazeApi TvMazeApi { get; }
|
||||
protected IMapper Mapper { get; }
|
||||
private ISettingsService<PlexSettings> PlexSettings { get; }
|
||||
private ISettingsService<EmbySettings> EmbySettings { get; }
|
||||
private IPlexContentRepository PlexContentRepo { get; }
|
||||
|
@ -99,7 +99,7 @@ namespace Ombi.Core.Engine
|
|||
{
|
||||
Url = e.url,
|
||||
Title = e.name,
|
||||
AirDate = DateTime.Parse(e.airstamp ?? DateTime.MinValue.ToString()),
|
||||
AirDate = e.airstamp.HasValue() ? DateTime.Parse(e.airstamp) : DateTime.MinValue,
|
||||
EpisodeNumber = e.number,
|
||||
|
||||
});
|
||||
|
@ -112,7 +112,7 @@ namespace Ombi.Core.Engine
|
|||
{
|
||||
Url = e.url,
|
||||
Title = e.name,
|
||||
AirDate = DateTime.Parse(e.airstamp ?? DateTime.MinValue.ToString()),
|
||||
AirDate = e.airstamp.HasValue() ? DateTime.Parse(e.airstamp) : DateTime.MinValue,
|
||||
EpisodeNumber = e.number,
|
||||
});
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ namespace Ombi.Core.Engine
|
|||
return processed;
|
||||
}
|
||||
|
||||
private IEnumerable<SearchTvShowViewModel> ProcessResults<T>(IEnumerable<T> items)
|
||||
protected IEnumerable<SearchTvShowViewModel> ProcessResults<T>(IEnumerable<T> items)
|
||||
{
|
||||
var retVal = new List<SearchTvShowViewModel>();
|
||||
foreach (var tvMazeSearch in items)
|
||||
|
@ -159,7 +159,7 @@ namespace Ombi.Core.Engine
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private SearchTvShowViewModel ProcessResult<T>(T tvMazeSearch)
|
||||
protected SearchTvShowViewModel ProcessResult<T>(T tvMazeSearch)
|
||||
{
|
||||
return Mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@ namespace Ombi.Core.Helpers
|
|||
SeasonRequests = new List<SeasonRequests>(),
|
||||
Title = ShowInfo.name,
|
||||
ReleaseYear = FirstAir,
|
||||
RequestedByAlias = model.RequestedByAlias,
|
||||
SeriesType = ShowInfo.genres.Any( s => s.Equals("Anime", StringComparison.InvariantCultureIgnoreCase)) ? SeriesType.Anime : SeriesType.Standard
|
||||
};
|
||||
|
||||
|
|
|
@ -24,11 +24,20 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Ombi.Core.Models.Requests
|
||||
{
|
||||
public class MovieRequestViewModel
|
||||
{
|
||||
public int TheMovieDbId { get; set; }
|
||||
public string LanguageCode { get; set; } = "en";
|
||||
|
||||
/// <summary>
|
||||
/// This is only set from a HTTP Header
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string RequestedByAlias { get; set; }
|
||||
}
|
||||
}
|
|
@ -3,5 +3,6 @@
|
|||
public class MusicAlbumRequestViewModel
|
||||
{
|
||||
public string ForeignAlbumId { get; set; }
|
||||
public string RequestedByAlias { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Ombi.Core.Models.Requests
|
||||
{
|
||||
|
@ -9,6 +10,8 @@ namespace Ombi.Core.Models.Requests
|
|||
public bool FirstSeason { get; set; }
|
||||
public int TvDbId { get; set; }
|
||||
public List<SeasonsViewModel> Seasons { get; set; } = new List<SeasonsViewModel>();
|
||||
[JsonIgnore]
|
||||
public string RequestedByAlias { get; set; }
|
||||
}
|
||||
|
||||
public class SeasonsViewModel
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Models.Search;
|
||||
|
@ -87,11 +88,11 @@ namespace Ombi.Core.Rule.Rules.Search
|
|||
}
|
||||
}
|
||||
|
||||
if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.All(e => e.Available)))
|
||||
if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.All(e => e.Available && e.AirDate > DateTime.MinValue)))
|
||||
{
|
||||
request.FullyAvailable = true;
|
||||
}
|
||||
if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.Any(e => e.Available)))
|
||||
if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.Any(e => e.Available && e.AirDate > DateTime.MinValue)))
|
||||
{
|
||||
request.PartlyAvailable = true;
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ using Ombi.Updater;
|
|||
using PlexContentCacher = Ombi.Schedule.Jobs.Plex;
|
||||
using Ombi.Api.Telegram;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Core.Engine.Demo;
|
||||
using Ombi.Core.Processor;
|
||||
using Ombi.Schedule.Jobs.Lidarr;
|
||||
using Ombi.Schedule.Jobs.Plex.Interfaces;
|
||||
|
@ -92,6 +93,8 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<IMassEmailSender, MassEmailSender>();
|
||||
services.AddTransient<IPlexOAuthManager, PlexOAuthManager>();
|
||||
services.AddTransient<IVoteEngine, VoteEngine>();
|
||||
services.AddTransient<IDemoMovieSearchEngine, DemoMovieSearchEngine>();
|
||||
services.AddTransient<IDemoTvSearchEngine, DemoTvSearchEngine>();
|
||||
}
|
||||
public static void RegisterHttp(this IServiceCollection services)
|
||||
{
|
||||
|
|
11
src/Ombi.Helpers/DemoLists.cs
Normal file
11
src/Ombi.Helpers/DemoLists.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace Ombi.Config
|
||||
{
|
||||
public class DemoLists
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public int[] Movies { get; set; }
|
||||
public int[] TvShows { get; set; }
|
||||
}
|
||||
|
||||
|
||||
}
|
13
src/Ombi.Helpers/DemoSingleton.cs
Normal file
13
src/Ombi.Helpers/DemoSingleton.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace Ombi.Helpers
|
||||
{
|
||||
public class DemoSingleton
|
||||
{
|
||||
private static DemoSingleton instance;
|
||||
|
||||
private DemoSingleton() { }
|
||||
|
||||
public static DemoSingleton Instance => instance ?? (instance = new DemoSingleton());
|
||||
|
||||
public bool Demo { get; set; }
|
||||
}
|
||||
}
|
|
@ -7,11 +7,16 @@ namespace Ombi.Helpers
|
|||
{
|
||||
public class EmbyHelper
|
||||
{
|
||||
public static string GetEmbyMediaUrl(string mediaId)
|
||||
public static string GetEmbyMediaUrl(string mediaId, string customerServerUrl = null)
|
||||
{
|
||||
var url =
|
||||
$"http://app.emby.media/#!/itemdetails.html?id={mediaId}";
|
||||
return url;
|
||||
if (customerServerUrl.HasValue())
|
||||
{
|
||||
return $"{customerServerUrl}#!/itemdetails.html?id={mediaId}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"https://app.emby.media/#!/itemdetails.html?id={mediaId}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,5 +15,6 @@
|
|||
public const string Disabled = nameof(Disabled);
|
||||
public const string ReceivesNewsletter = nameof(ReceivesNewsletter);
|
||||
public const string ManageOwnRequests = nameof(ManageOwnRequests);
|
||||
public const string EditCustomPage = nameof(EditCustomPage);
|
||||
}
|
||||
}
|
|
@ -56,148 +56,42 @@ namespace Ombi.Notifications.Agents
|
|||
|
||||
protected override async Task NewRequest(NotificationOptions model, DiscordNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Discord, NotificationType.NewRequest, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.NewRequest} is disabled for {NotificationAgent.Discord}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.NewRequest);
|
||||
}
|
||||
|
||||
protected override async Task NewIssue(NotificationOptions model, DiscordNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Discord, NotificationType.Issue, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.Issue} is disabled for {NotificationAgent.Discord}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.Issue);
|
||||
}
|
||||
|
||||
protected override async Task IssueComment(NotificationOptions model, DiscordNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Discord, NotificationType.IssueComment, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.IssueComment} is disabled for {NotificationAgent.Discord}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.IssueComment);
|
||||
}
|
||||
|
||||
protected override async Task IssueResolved(NotificationOptions model, DiscordNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Discord, NotificationType.IssueResolved, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.IssueResolved} is disabled for {NotificationAgent.Discord}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.IssueResolved);
|
||||
}
|
||||
|
||||
protected override async Task AddedToRequestQueue(NotificationOptions model, DiscordNotificationSettings settings)
|
||||
{
|
||||
var user = string.Empty;
|
||||
var title = string.Empty;
|
||||
var image = string.Empty;
|
||||
if (model.RequestType == RequestType.Movie)
|
||||
{
|
||||
user = MovieRequest.RequestedUser.UserAlias;
|
||||
title = MovieRequest.Title;
|
||||
image = MovieRequest.PosterPath;
|
||||
}
|
||||
else if (model.RequestType == RequestType.TvShow)
|
||||
{
|
||||
user = TvRequest.RequestedUser.UserAlias;
|
||||
title = TvRequest.ParentRequest.Title;
|
||||
image = TvRequest.ParentRequest.PosterPath;
|
||||
}
|
||||
else if (model.RequestType == RequestType.Album)
|
||||
{
|
||||
user = AlbumRequest.RequestedUser.UserAlias;
|
||||
title = AlbumRequest.Title;
|
||||
image = AlbumRequest.Cover;
|
||||
}
|
||||
var message = $"Hello! The user '{user}' has requested {title} but it could not be added. This has been added into the requests queue and will keep retrying";
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = message
|
||||
};
|
||||
notification.Other.Add("image", image);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.ItemAddedToFaultQueue);
|
||||
}
|
||||
|
||||
protected override async Task RequestDeclined(NotificationOptions model, DiscordNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Discord, NotificationType.RequestDeclined, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestDeclined} is disabled for {NotificationAgent.Discord}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestDeclined);
|
||||
}
|
||||
|
||||
protected override async Task RequestApproved(NotificationOptions model, DiscordNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Discord, NotificationType.RequestApproved, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestApproved} is disabled for {NotificationAgent.Discord}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestApproved);
|
||||
}
|
||||
|
||||
protected override async Task AvailableRequest(NotificationOptions model, DiscordNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Discord, NotificationType.RequestAvailable, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestAvailable} is disabled for {NotificationAgent.Discord}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestAvailable);
|
||||
}
|
||||
|
||||
protected override async Task Send(NotificationMessage model, DiscordNotificationSettings settings)
|
||||
|
@ -242,5 +136,21 @@ namespace Ombi.Notifications.Agents
|
|||
};
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
private async Task Run(NotificationOptions model, DiscordNotificationSettings settings, NotificationType type)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Discord, type, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {type} is disabled for {NotificationAgent.Discord}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
await Send(notification, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,7 +89,6 @@ namespace Ombi.Notifications.Agents
|
|||
}
|
||||
else
|
||||
{
|
||||
|
||||
// Send to admin
|
||||
message.To = settings.AdminEmail;
|
||||
}
|
||||
|
@ -183,37 +182,21 @@ namespace Ombi.Notifications.Agents
|
|||
|
||||
protected override async Task AddedToRequestQueue(NotificationOptions model, EmailNotificationSettings settings)
|
||||
{
|
||||
var email = new EmailBasicTemplate();
|
||||
var user = string.Empty;
|
||||
var title = string.Empty;
|
||||
var img = string.Empty;
|
||||
if (model.RequestType == RequestType.Movie)
|
||||
if (!model.Recipient.HasValue())
|
||||
{
|
||||
user = MovieRequest.RequestedUser.UserAlias;
|
||||
title = MovieRequest.Title;
|
||||
img = $"https://image.tmdb.org/t/p/w300/{MovieRequest.PosterPath}";
|
||||
return;
|
||||
}
|
||||
else
|
||||
var message = await LoadTemplate(NotificationType.ItemAddedToFaultQueue, model, settings);
|
||||
if (message == null)
|
||||
{
|
||||
user = TvRequest.RequestedUser.UserAlias;
|
||||
title = TvRequest.ParentRequest.Title;
|
||||
img = TvRequest.ParentRequest.PosterPath;
|
||||
return;
|
||||
}
|
||||
|
||||
var html = email.LoadTemplate(
|
||||
$"{Customization.ApplicationName}: A request could not be added.",
|
||||
$"Hello! The user '{user}' has requested {title} but it could not be added. This has been added into the requests queue and will keep retrying", img, Customization.Logo);
|
||||
|
||||
var message = new NotificationMessage
|
||||
{
|
||||
Message = html,
|
||||
Subject = $"{Customization.ApplicationName}: A request could not be added",
|
||||
To = settings.AdminEmail,
|
||||
};
|
||||
|
||||
var plaintext = $"Hello! The user '{user}' has requested {title} but it could not be added. This has been added into the requests queue and will keep retrying";
|
||||
var plaintext = await LoadPlainTextMessage(NotificationType.ItemAddedToFaultQueue, model, settings);
|
||||
message.Other.Add("PlainTextBody", plaintext);
|
||||
|
||||
// Issues resolved should be sent to the user
|
||||
message.To = settings.AdminEmail;
|
||||
await Send(message, settings);
|
||||
}
|
||||
|
||||
|
|
|
@ -46,20 +46,7 @@ namespace Ombi.Notifications.Agents
|
|||
|
||||
protected override async Task NewRequest(NotificationOptions model, MattermostNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Mattermost, NotificationType.NewRequest, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.NewRequest} is disabled for {NotificationAgent.Mattermost}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
|
||||
AddOtherInformation(model, notification, parsed);
|
||||
//notification.Other.Add("overview", model.RequestType == RequestType.Movie ? base.MovieRequest.Overview : TvRequest.);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.NewRequest);
|
||||
}
|
||||
|
||||
private void AddOtherInformation(NotificationOptions model, NotificationMessage notification,
|
||||
|
@ -71,125 +58,37 @@ namespace Ombi.Notifications.Agents
|
|||
|
||||
protected override async Task NewIssue(NotificationOptions model, MattermostNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Mattermost, NotificationType.Issue, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.Issue} is disabled for {NotificationAgent.Mattermost}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
AddOtherInformation(model, notification, parsed);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.Issue);
|
||||
}
|
||||
|
||||
protected override async Task IssueComment(NotificationOptions model, MattermostNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Mattermost, NotificationType.IssueComment, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.IssueComment} is disabled for {NotificationAgent.Mattermost}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
AddOtherInformation(model, notification, parsed);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.IssueComment);
|
||||
}
|
||||
|
||||
protected override async Task IssueResolved(NotificationOptions model, MattermostNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Mattermost, NotificationType.IssueResolved, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.IssueResolved} is disabled for {NotificationAgent.Mattermost}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
AddOtherInformation(model, notification, parsed);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.IssueResolved);
|
||||
}
|
||||
|
||||
protected override async Task AddedToRequestQueue(NotificationOptions model, MattermostNotificationSettings settings)
|
||||
{
|
||||
var user = string.Empty;
|
||||
var title = string.Empty;
|
||||
var image = string.Empty;
|
||||
if (model.RequestType == RequestType.Movie)
|
||||
{
|
||||
user = MovieRequest.RequestedUser.UserAlias;
|
||||
title = MovieRequest.Title;
|
||||
image = MovieRequest.PosterPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
user = TvRequest.RequestedUser.UserAlias;
|
||||
title = TvRequest.ParentRequest.Title;
|
||||
image = TvRequest.ParentRequest.PosterPath;
|
||||
}
|
||||
var message = $"Hello! The user '{user}' has requested {title} but it could not be added. This has been added into the requests queue and will keep retrying";
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = message
|
||||
};
|
||||
notification.Other.Add("image", image);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.ItemAddedToFaultQueue);
|
||||
}
|
||||
|
||||
protected override async Task RequestDeclined(NotificationOptions model, MattermostNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Mattermost, NotificationType.RequestDeclined, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestDeclined} is disabled for {NotificationAgent.Mattermost}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
AddOtherInformation(model, notification, parsed);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestDeclined);
|
||||
}
|
||||
|
||||
protected override async Task RequestApproved(NotificationOptions model, MattermostNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Mattermost, NotificationType.RequestApproved, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestApproved} is disabled for {NotificationAgent.Mattermost}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
|
||||
AddOtherInformation(model, notification, parsed);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestApproved);
|
||||
}
|
||||
|
||||
protected override async Task AvailableRequest(NotificationOptions model, MattermostNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Mattermost, NotificationType.RequestAvailable, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestAvailable} is disabled for {NotificationAgent.Mattermost}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
AddOtherInformation(model, notification, parsed);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestAvailable);
|
||||
}
|
||||
|
||||
protected override async Task Send(NotificationMessage model, MattermostNotificationSettings settings)
|
||||
|
@ -228,5 +127,21 @@ namespace Ombi.Notifications.Agents
|
|||
};
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
private async Task Run(NotificationOptions model, MattermostNotificationSettings settings, NotificationType type)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Mattermost, type, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {type} is disabled for {NotificationAgent.Mattermost}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
AddOtherInformation(model, notification, parsed);
|
||||
await Send(notification, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,23 +130,18 @@ namespace Ombi.Notifications.Agents
|
|||
|
||||
protected override async Task AddedToRequestQueue(NotificationOptions model, MobileNotificationSettings settings)
|
||||
{
|
||||
string user;
|
||||
string title;
|
||||
if (model.RequestType == RequestType.Movie)
|
||||
|
||||
var parsed = await LoadTemplate(NotificationAgent.Mobile, NotificationType.ItemAddedToFaultQueue, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
user = MovieRequest.RequestedUser.UserAlias;
|
||||
title = MovieRequest.Title;
|
||||
_logger.LogInformation($"Template {NotificationType.ItemAddedToFaultQueue} is disabled for {NotificationAgent.Mobile}");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
user = TvRequest.RequestedUser.UserAlias;
|
||||
title = TvRequest.ParentRequest.Title;
|
||||
}
|
||||
var message = $"Hello! The user '{user}' has requested {title} but it could not be added. This has been added into the requests queue and will keep retrying";
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = message
|
||||
Message = parsed.Message,
|
||||
};
|
||||
|
||||
// Get admin devices
|
||||
var playerIds = await GetAdmins(NotificationType.Test);
|
||||
await Send(playerIds, notification, settings, model);
|
||||
|
@ -294,6 +289,5 @@ namespace Ombi.Notifications.Agents
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -44,131 +44,43 @@ namespace Ombi.Notifications.Agents
|
|||
|
||||
protected override async Task NewRequest(NotificationOptions model, PushbulletSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Pushbullet, NotificationType.NewRequest, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.NewRequest} is disabled for {NotificationAgent.Pushbullet}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.NewRequest);
|
||||
}
|
||||
|
||||
|
||||
protected override async Task NewIssue(NotificationOptions model, PushbulletSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Pushbullet, NotificationType.Issue, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.Issue} is disabled for {NotificationAgent.Pushbullet}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.Issue);
|
||||
}
|
||||
|
||||
protected override async Task IssueComment(NotificationOptions model, PushbulletSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Pushbullet, NotificationType.IssueComment, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.IssueComment} is disabled for {NotificationAgent.Pushbullet}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.IssueComment);
|
||||
}
|
||||
|
||||
protected override async Task IssueResolved(NotificationOptions model, PushbulletSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Pushbullet, NotificationType.IssueResolved, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.IssueResolved} is disabled for {NotificationAgent.Pushbullet}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.IssueResolved);
|
||||
}
|
||||
|
||||
protected override async Task AddedToRequestQueue(NotificationOptions model, PushbulletSettings settings)
|
||||
{
|
||||
string user;
|
||||
string title;
|
||||
if (model.RequestType == RequestType.Movie)
|
||||
{
|
||||
user = MovieRequest.RequestedUser.UserAlias;
|
||||
title = MovieRequest.Title;
|
||||
}
|
||||
else
|
||||
{
|
||||
user = TvRequest.RequestedUser.UserAlias;
|
||||
title = TvRequest.ParentRequest.Title;
|
||||
}
|
||||
var message = $"Hello! The user '{user}' has requested {title} but it could not be added. This has been added into the requests queue and will keep retrying";
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = message
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.ItemAddedToFaultQueue);
|
||||
}
|
||||
|
||||
protected override async Task RequestDeclined(NotificationOptions model, PushbulletSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Pushbullet, NotificationType.RequestDeclined, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestDeclined} is disabled for {NotificationAgent.Pushbullet}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestDeclined);
|
||||
}
|
||||
|
||||
protected override async Task RequestApproved(NotificationOptions model, PushbulletSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Pushbullet, NotificationType.RequestApproved, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestApproved} is disabled for {NotificationAgent.Pushbullet}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestApproved);
|
||||
}
|
||||
|
||||
protected override async Task AvailableRequest(NotificationOptions model, PushbulletSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Pushbullet, NotificationType.RequestAvailable, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestAvailable} is disabled for {NotificationAgent.Pushbullet}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestAvailable);
|
||||
}
|
||||
|
||||
protected override async Task Send(NotificationMessage model, PushbulletSettings settings)
|
||||
|
@ -192,5 +104,22 @@ namespace Ombi.Notifications.Agents
|
|||
};
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
private async Task Run(NotificationOptions model, PushbulletSettings settings, NotificationType type)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Pushbullet, type, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {type} is disabled for {NotificationAgent.Pushbullet}");
|
||||
return;
|
||||
}
|
||||
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
|
||||
await Send(notification, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,132 +45,42 @@ namespace Ombi.Notifications.Agents
|
|||
|
||||
protected override async Task NewRequest(NotificationOptions model, PushoverSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Pushover, NotificationType.NewRequest, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.NewRequest} is disabled for {NotificationAgent.Pushover}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.NewRequest);
|
||||
}
|
||||
|
||||
protected override async Task NewIssue(NotificationOptions model, PushoverSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Pushover, NotificationType.Issue, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.Issue} is disabled for {NotificationAgent.Pushover}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.Issue);
|
||||
}
|
||||
|
||||
protected override async Task IssueComment(NotificationOptions model, PushoverSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Pushover, NotificationType.IssueComment, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.IssueComment} is disabled for {NotificationAgent.Pushover}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.IssueComment);
|
||||
}
|
||||
|
||||
protected override async Task IssueResolved(NotificationOptions model, PushoverSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Pushover, NotificationType.IssueResolved, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.IssueResolved} is disabled for {NotificationAgent.Pushover}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.IssueResolved);
|
||||
}
|
||||
|
||||
protected override async Task AddedToRequestQueue(NotificationOptions model, PushoverSettings settings)
|
||||
{
|
||||
string user;
|
||||
string title;
|
||||
if (model.RequestType == RequestType.Movie)
|
||||
{
|
||||
user = MovieRequest.RequestedUser.UserAlias;
|
||||
title = MovieRequest.Title;
|
||||
}
|
||||
else
|
||||
{
|
||||
user = TvRequest.RequestedUser.UserAlias;
|
||||
title = TvRequest.ParentRequest.Title;
|
||||
}
|
||||
var message = $"Hello! The user '{user}' has requested {title} but it could not be added. This has been added into the requests queue and will keep retrying";
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = message
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.ItemAddedToFaultQueue);
|
||||
}
|
||||
|
||||
protected override async Task RequestDeclined(NotificationOptions model, PushoverSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Pushover, NotificationType.RequestDeclined, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestDeclined} is disabled for {NotificationAgent.Pushover}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestDeclined);
|
||||
}
|
||||
|
||||
protected override async Task RequestApproved(NotificationOptions model, PushoverSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Pushover, NotificationType.RequestApproved, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestApproved} is disabled for {NotificationAgent.Pushover}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestApproved);
|
||||
}
|
||||
|
||||
protected override async Task AvailableRequest(NotificationOptions model, PushoverSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Pushover, NotificationType.RequestAvailable, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestAvailable} is disabled for {NotificationAgent.Pushover}");
|
||||
return;
|
||||
}
|
||||
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestAvailable);
|
||||
}
|
||||
|
||||
protected override async Task Send(NotificationMessage model, PushoverSettings settings)
|
||||
|
@ -195,5 +105,21 @@ namespace Ombi.Notifications.Agents
|
|||
};
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
private async Task Run(NotificationOptions model, PushoverSettings settings, NotificationType type)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Pushover, type, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {type} is disabled for {NotificationAgent.Pushover}");
|
||||
return;
|
||||
}
|
||||
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,138 +54,42 @@ namespace Ombi.Notifications.Agents
|
|||
|
||||
protected override async Task NewRequest(NotificationOptions model, SlackNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Slack, NotificationType.NewRequest, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.NewRequest} is disabled for {NotificationAgent.Slack}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.NewRequest);
|
||||
}
|
||||
|
||||
protected override async Task NewIssue(NotificationOptions model, SlackNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Slack, NotificationType.Issue, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.Issue} is disabled for {NotificationAgent.Slack}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.Issue);
|
||||
}
|
||||
|
||||
protected override async Task IssueComment(NotificationOptions model, SlackNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Slack, NotificationType.IssueComment, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.IssueComment} is disabled for {NotificationAgent.Slack}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.IssueComment);
|
||||
}
|
||||
|
||||
protected override async Task IssueResolved(NotificationOptions model, SlackNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Slack, NotificationType.IssueResolved, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.IssueResolved} is disabled for {NotificationAgent.Slack}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.IssueResolved);
|
||||
}
|
||||
|
||||
protected override async Task AddedToRequestQueue(NotificationOptions model, SlackNotificationSettings settings)
|
||||
{
|
||||
var user = string.Empty;
|
||||
var title = string.Empty;
|
||||
if (model.RequestType == RequestType.Movie)
|
||||
{
|
||||
user = MovieRequest.RequestedUser.UserAlias;
|
||||
title = MovieRequest.Title;
|
||||
}
|
||||
else
|
||||
{
|
||||
user = TvRequest.RequestedUser.UserAlias;
|
||||
title = TvRequest.ParentRequest.Title;
|
||||
}
|
||||
var message = $"Hello! The user '{user}' has requested {title} but it could not be added. This has been added into the requests queue and will keep retrying";
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = message
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.ItemAddedToFaultQueue);
|
||||
}
|
||||
|
||||
protected override async Task RequestDeclined(NotificationOptions model, SlackNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Slack, NotificationType.RequestDeclined, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestDeclined} is disabled for {NotificationAgent.Slack}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestAvailable);
|
||||
}
|
||||
|
||||
protected override async Task RequestApproved(NotificationOptions model, SlackNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Slack, NotificationType.RequestApproved, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestApproved} is disabled for {NotificationAgent.Slack}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestApproved);
|
||||
}
|
||||
|
||||
protected override async Task AvailableRequest(NotificationOptions model, SlackNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Slack, NotificationType.RequestAvailable, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestAvailable} is disabled for {NotificationAgent.Slack}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestAvailable);
|
||||
}
|
||||
|
||||
protected override async Task Send(NotificationMessage model, SlackNotificationSettings settings)
|
||||
|
@ -218,5 +122,21 @@ namespace Ombi.Notifications.Agents
|
|||
};
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
private async Task Run(NotificationOptions model, SlackNotificationSettings settings, NotificationType type)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Slack, type, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {type} is disabled for {NotificationAgent.Slack}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
await Send(notification, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,134 +41,42 @@ namespace Ombi.Notifications.Agents
|
|||
|
||||
protected override async Task NewRequest(NotificationOptions model, TelegramSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Telegram, NotificationType.NewRequest, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.NewRequest} is disabled for {NotificationAgent.Telegram}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.NewRequest);
|
||||
}
|
||||
|
||||
protected override async Task NewIssue(NotificationOptions model, TelegramSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Telegram, NotificationType.Issue, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.Issue} is disabled for {NotificationAgent.Telegram}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.Issue);
|
||||
}
|
||||
|
||||
protected override async Task IssueComment(NotificationOptions model, TelegramSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Telegram, NotificationType.IssueComment, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.IssueComment} is disabled for {NotificationAgent.Telegram}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.IssueComment);
|
||||
}
|
||||
|
||||
protected override async Task IssueResolved(NotificationOptions model, TelegramSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Telegram, NotificationType.IssueResolved, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.IssueResolved} is disabled for {NotificationAgent.Telegram}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.IssueResolved);
|
||||
}
|
||||
|
||||
protected override async Task AddedToRequestQueue(NotificationOptions model, TelegramSettings settings)
|
||||
{
|
||||
var user = string.Empty;
|
||||
var title = string.Empty;
|
||||
var image = string.Empty;
|
||||
if (model.RequestType == RequestType.Movie)
|
||||
{
|
||||
user = MovieRequest.RequestedUser.UserAlias;
|
||||
title = MovieRequest.Title;
|
||||
image = MovieRequest.PosterPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
user = TvRequest.RequestedUser.UserAlias;
|
||||
title = TvRequest.ParentRequest.Title;
|
||||
image = TvRequest.ParentRequest.PosterPath;
|
||||
}
|
||||
var message = $"Hello! The user '{user}' has requested {title} but it could not be added. This has been added into the requests queue and will keep retrying";
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = message
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.ItemAddedToFaultQueue);
|
||||
}
|
||||
|
||||
protected override async Task RequestDeclined(NotificationOptions model, TelegramSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Telegram, NotificationType.RequestDeclined, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestDeclined} is disabled for {NotificationAgent.Telegram}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestDeclined);
|
||||
}
|
||||
|
||||
protected override async Task RequestApproved(NotificationOptions model, TelegramSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Telegram, NotificationType.RequestApproved, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestApproved} is disabled for {NotificationAgent.Telegram}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message ?? string.Empty,
|
||||
};
|
||||
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestApproved);
|
||||
}
|
||||
|
||||
protected override async Task AvailableRequest(NotificationOptions model, TelegramSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Telegram, NotificationType.RequestAvailable, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {NotificationType.RequestAvailable} is disabled for {NotificationAgent.Telegram}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
await Run(model, settings, NotificationType.RequestAvailable);
|
||||
}
|
||||
|
||||
protected override async Task Send(NotificationMessage model, TelegramSettings settings)
|
||||
|
@ -192,5 +100,20 @@ namespace Ombi.Notifications.Agents
|
|||
};
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
private async Task Run(NotificationOptions model, TelegramSettings settings, NotificationType type)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Telegram, type, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {type} is disabled for {NotificationAgent.Telegram}");
|
||||
return;
|
||||
}
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
|||
await _api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri);
|
||||
foreach (var item in movieInfo.Items)
|
||||
{
|
||||
await ProcessMovies(item, mediaToAdd);
|
||||
await ProcessMovies(item, mediaToAdd, server);
|
||||
}
|
||||
|
||||
processed++;
|
||||
|
@ -96,7 +96,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
|||
{
|
||||
processed++;
|
||||
// Regular movie
|
||||
await ProcessMovies(movie, mediaToAdd);
|
||||
await ProcessMovies(movie, mediaToAdd, server);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,7 +138,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
|||
Title = tvShow.Name,
|
||||
Type = EmbyMediaType.Series,
|
||||
EmbyId = tvShow.Id,
|
||||
Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id),
|
||||
Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id, server.ServerHostname),
|
||||
AddedAt = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
|||
await _repo.AddRange(mediaToAdd);
|
||||
}
|
||||
|
||||
private async Task ProcessMovies(EmbyMovie movieInfo, ICollection<EmbyContent> content)
|
||||
private async Task ProcessMovies(EmbyMovie movieInfo, ICollection<EmbyContent> content, EmbyServers server)
|
||||
{
|
||||
// Check if it exists
|
||||
var existingMovie = await _repo.GetByEmbyId(movieInfo.Id);
|
||||
|
@ -179,7 +179,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
|||
Title = movieInfo.Name,
|
||||
Type = EmbyMediaType.Movie,
|
||||
EmbyId = movieInfo.Id,
|
||||
Url = EmbyHelper.GetEmbyMediaUrl(movieInfo.Id),
|
||||
Url = EmbyHelper.GetEmbyMediaUrl(movieInfo.Id, server.ServerHostname),
|
||||
AddedAt = DateTime.UtcNow,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using Hangfire;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Api.Emby;
|
||||
using Ombi.Api.TheMovieDb;
|
||||
using Ombi.Api.TheMovieDb.Models;
|
||||
using Ombi.Api.TvMaze;
|
||||
|
@ -21,7 +22,8 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
{
|
||||
public RefreshMetadata(IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo,
|
||||
ILogger<RefreshMetadata> log, ITvMazeApi tvApi, ISettingsService<PlexSettings> plexSettings,
|
||||
IMovieDbApi movieApi, ISettingsService<EmbySettings> embySettings, IPlexAvailabilityChecker plexAvailability, IEmbyAvaliabilityChecker embyAvaliability)
|
||||
IMovieDbApi movieApi, ISettingsService<EmbySettings> embySettings, IPlexAvailabilityChecker plexAvailability, IEmbyAvaliabilityChecker embyAvaliability,
|
||||
IEmbyApi embyApi)
|
||||
{
|
||||
_plexRepo = plexRepo;
|
||||
_embyRepo = embyRepo;
|
||||
|
@ -32,6 +34,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
_embySettings = embySettings;
|
||||
_plexAvailabilityChecker = plexAvailability;
|
||||
_embyAvaliabilityChecker = embyAvaliability;
|
||||
_embyApi = embyApi;
|
||||
}
|
||||
|
||||
private readonly IPlexContentRepository _plexRepo;
|
||||
|
@ -43,6 +46,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
private readonly ITvMazeApi _tvApi;
|
||||
private readonly ISettingsService<PlexSettings> _plexSettings;
|
||||
private readonly ISettingsService<EmbySettings> _embySettings;
|
||||
private readonly IEmbyApi _embyApi;
|
||||
|
||||
public async Task Start()
|
||||
{
|
||||
|
@ -239,26 +243,51 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
await _plexRepo.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async Task StartEmbyMovies()
|
||||
private async Task StartEmbyMovies(EmbySettings settings)
|
||||
{
|
||||
var allMovies = _embyRepo.GetAll().Where(x =>
|
||||
x.Type == EmbyMediaType.Movie && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue()));
|
||||
int movieCount = 0;
|
||||
foreach (var movie in allMovies)
|
||||
{
|
||||
var hasImdb = movie.ImdbId.HasValue();
|
||||
var hasTheMovieDb = movie.TheMovieDbId.HasValue();
|
||||
movie.ImdbId.HasValue();
|
||||
movie.TheMovieDbId.HasValue();
|
||||
// Movies don't really use TheTvDb
|
||||
|
||||
if (!hasImdb)
|
||||
// Check if it even has 1 ID
|
||||
if (!movie.HasImdb && !movie.HasTheMovieDb)
|
||||
{
|
||||
var imdbId = await GetImdbId(hasTheMovieDb, false, movie.Title, movie.TheMovieDbId, string.Empty);
|
||||
// Ok this sucks,
|
||||
// The only think I can think that has happened is that we scanned Emby before Emby has got the metadata
|
||||
// So let's recheck emby to see if they have got the metadata now
|
||||
_log.LogInformation($"Movie {movie.Title} does not have a ImdbId or TheMovieDbId, so rechecking emby");
|
||||
foreach (var server in settings.Servers)
|
||||
{
|
||||
_log.LogInformation($"Checking server {server.Name} for upto date metadata");
|
||||
var movieInfo = await _embyApi.GetMovieInformation(movie.EmbyId, server.ApiKey, server.AdministratorId,
|
||||
server.FullUri);
|
||||
|
||||
if (movieInfo.ProviderIds?.Imdb.HasValue() ?? false)
|
||||
{
|
||||
movie.ImdbId = movieInfo.ProviderIds.Imdb;
|
||||
}
|
||||
|
||||
if (movieInfo.ProviderIds?.Tmdb.HasValue() ?? false)
|
||||
{
|
||||
movie.TheMovieDbId = movieInfo.ProviderIds.Tmdb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!movie.HasImdb)
|
||||
{
|
||||
var imdbId = await GetImdbId(movie.HasTheMovieDb, false, movie.Title, movie.TheMovieDbId, string.Empty);
|
||||
movie.ImdbId = imdbId;
|
||||
_embyRepo.UpdateWithoutSave(movie);
|
||||
}
|
||||
if (!hasTheMovieDb)
|
||||
if (!movie.HasTheMovieDb)
|
||||
{
|
||||
var id = await GetTheMovieDbId(false, hasImdb, string.Empty, movie.ImdbId, movie.Title, true);
|
||||
var id = await GetTheMovieDbId(false, movie.HasImdb, string.Empty, movie.ImdbId, movie.Title, true);
|
||||
movie.TheMovieDbId = id;
|
||||
_embyRepo.UpdateWithoutSave(movie);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Ombi.Core.Settings.Models.External
|
|||
public string Name { get; set; }
|
||||
public string ApiKey { get; set; }
|
||||
public string AdministratorId { get; set; }
|
||||
public string ServerHostname { get; set; }
|
||||
public bool EnableEpisodeSearching { get; set; }
|
||||
}
|
||||
}
|
|
@ -124,6 +124,16 @@ namespace Ombi.Store.Context
|
|||
SaveChanges();
|
||||
}
|
||||
|
||||
var editCustomPage = Roles.Where(x => x.Name == OmbiRoles.EditCustomPage);
|
||||
if (!editCustomPage.Any())
|
||||
{
|
||||
Roles.Add(new IdentityRole(OmbiRoles.EditCustomPage)
|
||||
{
|
||||
NormalizedName = OmbiRoles.EditCustomPage.ToUpper()
|
||||
});
|
||||
SaveChanges();
|
||||
}
|
||||
|
||||
// Make sure we have the API User
|
||||
var apiUserExists = Users.Any(x => x.UserName.Equals("Api", StringComparison.CurrentCultureIgnoreCase));
|
||||
if (!apiUserExists)
|
||||
|
@ -209,7 +219,15 @@ namespace Ombi.Store.Context
|
|||
};
|
||||
break;
|
||||
case NotificationType.ItemAddedToFaultQueue:
|
||||
continue;
|
||||
notificationToAdd = new NotificationTemplates
|
||||
{
|
||||
NotificationType = notificationType,
|
||||
Message = "Hello! The user '{UserName}' has requested {Title} but it could not be added. This has been added into the requests queue and will keep retrying",
|
||||
Subject = "Item Added To Retry Queue",
|
||||
Agent = agent,
|
||||
Enabled = true,
|
||||
};
|
||||
break;
|
||||
case NotificationType.WelcomeEmail:
|
||||
notificationToAdd = new NotificationTemplates
|
||||
{
|
||||
|
|
|
@ -66,5 +66,10 @@ namespace Ombi.Store.Context
|
|||
|
||||
SaveChanges();
|
||||
}
|
||||
|
||||
~SettingsContext()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ namespace Ombi.Store.Entities
|
|||
public bool IsEmbyConnect => UserType == UserType.EmbyUser && EmbyConnectUserId.HasValue();
|
||||
|
||||
[NotMapped]
|
||||
public string UserAlias => string.IsNullOrEmpty(Alias) ? UserName : Alias;
|
||||
public virtual string UserAlias => string.IsNullOrEmpty(Alias) ? UserName : Alias;
|
||||
|
||||
[NotMapped]
|
||||
public bool EmailLogin { get; set; }
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace Ombi.Store.Entities.Requests
|
|||
public DateTime MarkedAsDenied { get; set; }
|
||||
public string DeniedReason { get; set; }
|
||||
public RequestType RequestType { get; set; }
|
||||
public string RequestedByAlias { get; set; }
|
||||
|
||||
[ForeignKey(nameof(RequestedUserId))]
|
||||
public OmbiUser RequestedUser { get; set; }
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Globalization;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
|
||||
|
@ -27,9 +28,10 @@ namespace Ombi.Store.Repository.Requests
|
|||
public bool Approved { get; set; }
|
||||
public bool Requested { get; set; }
|
||||
|
||||
|
||||
public int SeasonId { get; set; }
|
||||
[ForeignKey(nameof(SeasonId))]
|
||||
public SeasonRequests Season { get; set; }
|
||||
|
||||
[NotMapped] public string AirDateDisplay => AirDate == DateTime.MinValue ? "Unknown" : AirDate.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
1212
src/Ombi.Store/Migrations/20190116212601_RequestedByAlias.Designer.cs
generated
Normal file
1212
src/Ombi.Store/Migrations/20190116212601_RequestedByAlias.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
40
src/Ombi.Store/Migrations/20190116212601_RequestedByAlias.cs
Normal file
40
src/Ombi.Store/Migrations/20190116212601_RequestedByAlias.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Ombi.Store.Migrations
|
||||
{
|
||||
public partial class RequestedByAlias : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "RequestedByAlias",
|
||||
table: "MovieRequests",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "RequestedByAlias",
|
||||
table: "ChildRequests",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "RequestedByAlias",
|
||||
table: "AlbumRequests",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RequestedByAlias",
|
||||
table: "MovieRequests");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RequestedByAlias",
|
||||
table: "ChildRequests");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RequestedByAlias",
|
||||
table: "AlbumRequests");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ namespace Ombi.Store.Migrations
|
|||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.2.0-rtm-35687");
|
||||
.HasAnnotation("ProductVersion", "2.2.1-servicing-10028");
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
|
@ -583,6 +583,8 @@ namespace Ombi.Store.Migrations
|
|||
|
||||
b.Property<int>("RequestType");
|
||||
|
||||
b.Property<string>("RequestedByAlias");
|
||||
|
||||
b.Property<DateTime>("RequestedDate");
|
||||
|
||||
b.Property<string>("RequestedUserId");
|
||||
|
@ -621,6 +623,8 @@ namespace Ombi.Store.Migrations
|
|||
|
||||
b.Property<int>("RequestType");
|
||||
|
||||
b.Property<string>("RequestedByAlias");
|
||||
|
||||
b.Property<DateTime>("RequestedDate");
|
||||
|
||||
b.Property<string>("RequestedUserId");
|
||||
|
@ -749,6 +753,8 @@ namespace Ombi.Store.Migrations
|
|||
|
||||
b.Property<int>("RequestType");
|
||||
|
||||
b.Property<string>("RequestedByAlias");
|
||||
|
||||
b.Property<DateTime>("RequestedDate");
|
||||
|
||||
b.Property<string>("RequestedUserId");
|
||||
|
|
|
@ -1,71 +1,71 @@
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Ombi.Api.Emby;
|
||||
using Ombi.Api.Plex;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Models.Identity;
|
||||
using Ombi.Store.Context;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
//using System;
|
||||
//using Microsoft.AspNetCore.Builder;
|
||||
//using Microsoft.AspNetCore.Hosting;
|
||||
//using Microsoft.AspNetCore.Http;
|
||||
//using Microsoft.AspNetCore.Http.Features.Authentication;
|
||||
//using Microsoft.AspNetCore.Identity;
|
||||
//using Microsoft.Extensions.DependencyInjection;
|
||||
//using Microsoft.Extensions.Options;
|
||||
//using Moq;
|
||||
//using Ombi.Api.Emby;
|
||||
//using Ombi.Api.Plex;
|
||||
//using Ombi.Core.Authentication;
|
||||
//using Ombi.Core.Settings;
|
||||
//using Ombi.Core.Settings.Models.External;
|
||||
//using Ombi.Models.Identity;
|
||||
//using Ombi.Store.Context;
|
||||
//using Ombi.Store.Entities;
|
||||
//using Ombi.Store.Repository;
|
||||
|
||||
namespace Ombi.Tests
|
||||
{
|
||||
public class TestStartup
|
||||
{
|
||||
public IServiceProvider ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
var _plexApi = new Mock<IPlexApi>();
|
||||
var _embyApi = new Mock<IEmbyApi>();
|
||||
var _tokenSettings = new Mock<IOptions<TokenAuthentication>>();
|
||||
var _embySettings = new Mock<ISettingsService<EmbySettings>>();
|
||||
var _plexSettings = new Mock<ISettingsService<PlexSettings>>();
|
||||
var audit = new Mock<IAuditRepository>();
|
||||
var tokenRepo = new Mock<ITokenRepository>();
|
||||
//namespace Ombi.Tests
|
||||
//{
|
||||
// public class TestStartup
|
||||
// {
|
||||
// public IServiceProvider ConfigureServices(IServiceCollection services)
|
||||
// {
|
||||
// var _plexApi = new Mock<IPlexApi>();
|
||||
// var _embyApi = new Mock<IEmbyApi>();
|
||||
// var _tokenSettings = new Mock<IOptions<TokenAuthentication>>();
|
||||
// var _embySettings = new Mock<ISettingsService<EmbySettings>>();
|
||||
// var _plexSettings = new Mock<ISettingsService<PlexSettings>>();
|
||||
// var audit = new Mock<IAuditRepository>();
|
||||
// var tokenRepo = new Mock<ITokenRepository>();
|
||||
|
||||
services.AddEntityFrameworkInMemoryDatabase()
|
||||
.AddDbContext<OmbiContext>();
|
||||
services.AddIdentity<OmbiUser, IdentityRole>()
|
||||
.AddEntityFrameworkStores<OmbiContext>().AddUserManager<OmbiUserManager>();
|
||||
// services.AddEntityFrameworkInMemoryDatabase()
|
||||
// .AddDbContext<OmbiContext>();
|
||||
// services.AddIdentity<OmbiUser, IdentityRole>()
|
||||
// .AddEntityFrameworkStores<OmbiContext>().AddUserManager<OmbiUserManager>();
|
||||
|
||||
services.AddTransient(x => _plexApi.Object);
|
||||
services.AddTransient(x => _embyApi.Object);
|
||||
services.AddTransient(x => _tokenSettings.Object);
|
||||
services.AddTransient(x => _embySettings.Object);
|
||||
services.AddTransient(x => _plexSettings.Object);
|
||||
services.AddTransient(x => audit.Object);
|
||||
services.AddTransient(x => tokenRepo.Object);
|
||||
// Taken from https://github.com/aspnet/MusicStore/blob/dev/test/MusicStore.Test/ManageControllerTest.cs (and modified)
|
||||
var context = new DefaultHttpContext();
|
||||
context.Features.Set<IHttpAuthenticationFeature>(new HttpAuthenticationFeature());
|
||||
services.AddSingleton<IHttpContextAccessor>(h => new HttpContextAccessor { HttpContext = context });
|
||||
// services.AddTransient(x => _plexApi.Object);
|
||||
// services.AddTransient(x => _embyApi.Object);
|
||||
// services.AddTransient(x => _tokenSettings.Object);
|
||||
// services.AddTransient(x => _embySettings.Object);
|
||||
// services.AddTransient(x => _plexSettings.Object);
|
||||
// services.AddTransient(x => audit.Object);
|
||||
// services.AddTransient(x => tokenRepo.Object);
|
||||
// // Taken from https://github.com/aspnet/MusicStore/blob/dev/test/MusicStore.Test/ManageControllerTest.cs (and modified)
|
||||
// var context = new DefaultHttpContext();
|
||||
// context.Features.Set<IHttpAuthenticationFeature>(new HttpAuthenticationFeature());
|
||||
// services.AddSingleton<IHttpContextAccessor>(h => new HttpContextAccessor { HttpContext = context });
|
||||
|
||||
|
||||
services.Configure<IdentityOptions>(options =>
|
||||
{
|
||||
options.Password.RequireDigit = false;
|
||||
options.Password.RequiredLength = 1;
|
||||
options.Password.RequireLowercase = false;
|
||||
options.Password.RequireNonAlphanumeric = false;
|
||||
options.Password.RequireUppercase = false;
|
||||
options.User.AllowedUserNameCharacters = string.Empty;
|
||||
});
|
||||
// services.Configure<IdentityOptions>(options =>
|
||||
// {
|
||||
// options.Password.RequireDigit = false;
|
||||
// options.Password.RequiredLength = 1;
|
||||
// options.Password.RequireLowercase = false;
|
||||
// options.Password.RequireNonAlphanumeric = false;
|
||||
// options.Password.RequireUppercase = false;
|
||||
// options.User.AllowedUserNameCharacters = string.Empty;
|
||||
// });
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
// return services.BuildServiceProvider();
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
// public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
// {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
// }
|
||||
//}
|
|
@ -19,5 +19,7 @@ namespace Ombi.Api.TheMovieDb
|
|||
Task<FindResult> Find(string externalId, ExternalSource source);
|
||||
Task<TvExternals> GetTvExternals(int theMovieDbId);
|
||||
Task<TvInfo> GetTVInfo(string themoviedbid);
|
||||
Task<TheMovieDbContainer<ActorResult>> SearchByActor(string searchTerm, string langCode);
|
||||
Task<ActorCredits> GetActorMovieCredits(int actorId, string langCode);
|
||||
}
|
||||
}
|
51
src/Ombi.TheMovieDbApi/Models/ActorCredits.cs
Normal file
51
src/Ombi.TheMovieDbApi/Models/ActorCredits.cs
Normal file
|
@ -0,0 +1,51 @@
|
|||
namespace Ombi.Api.TheMovieDb.Models
|
||||
{
|
||||
public class ActorCredits
|
||||
{
|
||||
public Cast[] cast { get; set; }
|
||||
public Crew[] crew { get; set; }
|
||||
public int id { get; set; }
|
||||
}
|
||||
|
||||
public class Cast
|
||||
{
|
||||
public string character { get; set; }
|
||||
public string credit_id { get; set; }
|
||||
public string poster_path { get; set; }
|
||||
public int id { get; set; }
|
||||
public bool video { get; set; }
|
||||
public int vote_count { get; set; }
|
||||
public bool adult { get; set; }
|
||||
public string backdrop_path { get; set; }
|
||||
public int?[] genre_ids { get; set; }
|
||||
public string original_language { get; set; }
|
||||
public string original_title { get; set; }
|
||||
public float popularity { get; set; }
|
||||
public string title { get; set; }
|
||||
public float vote_average { get; set; }
|
||||
public string overview { get; set; }
|
||||
public string release_date { get; set; }
|
||||
}
|
||||
|
||||
public class Crew
|
||||
{
|
||||
public int id { get; set; }
|
||||
public string department { get; set; }
|
||||
public string original_language { get; set; }
|
||||
public string original_title { get; set; }
|
||||
public string job { get; set; }
|
||||
public string overview { get; set; }
|
||||
public int vote_count { get; set; }
|
||||
public bool video { get; set; }
|
||||
public string release_date { get; set; }
|
||||
public float vote_average { get; set; }
|
||||
public string title { get; set; }
|
||||
public float popularity { get; set; }
|
||||
public int?[] genre_ids { get; set; }
|
||||
public string backdrop_path { get; set; }
|
||||
public bool adult { get; set; }
|
||||
public string poster_path { get; set; }
|
||||
public string credit_id { get; set; }
|
||||
}
|
||||
|
||||
}
|
33
src/Ombi.TheMovieDbApi/Models/ActorSearchResult.cs
Normal file
33
src/Ombi.TheMovieDbApi/Models/ActorSearchResult.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
namespace Ombi.Api.TheMovieDb.Models
|
||||
{
|
||||
|
||||
public class ActorResult
|
||||
{
|
||||
public float popularity { get; set; }
|
||||
public int id { get; set; }
|
||||
public string profile_path { get; set; }
|
||||
public string name { get; set; }
|
||||
public Known_For[] known_for { get; set; }
|
||||
public bool adult { get; set; }
|
||||
}
|
||||
|
||||
public class Known_For
|
||||
{
|
||||
public float vote_average { get; set; }
|
||||
public int vote_count { get; set; }
|
||||
public int id { get; set; }
|
||||
public bool video { get; set; }
|
||||
public string media_type { get; set; }
|
||||
public string title { get; set; }
|
||||
public float popularity { get; set; }
|
||||
public string poster_path { get; set; }
|
||||
public string original_language { get; set; }
|
||||
public string original_title { get; set; }
|
||||
public int[] genre_ids { get; set; }
|
||||
public string backdrop_path { get; set; }
|
||||
public bool adult { get; set; }
|
||||
public string overview { get; set; }
|
||||
public string release_date { get; set; }
|
||||
}
|
||||
|
||||
}
|
|
@ -43,6 +43,27 @@ namespace Ombi.Api.TheMovieDb
|
|||
return await Api.Request<FindResult>(request);
|
||||
}
|
||||
|
||||
public async Task<TheMovieDbContainer<ActorResult>> SearchByActor(string searchTerm, string langCode)
|
||||
{
|
||||
var request = new Request($"search/person", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("query", searchTerm);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("language", langCode);
|
||||
|
||||
var result = await Api.Request<TheMovieDbContainer<ActorResult>>(request);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<ActorCredits> GetActorMovieCredits(int actorId, string langCode)
|
||||
{
|
||||
var request = new Request($"person/{actorId}/movie_credits", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("language", langCode);
|
||||
|
||||
var result = await Api.Request<ActorCredits>(request);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<List<TvSearchResult>> SearchTv(string searchTerm)
|
||||
{
|
||||
var request = new Request($"search/tv", BaseUri, HttpMethod.Get);
|
||||
|
|
|
@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
|
||||
namespace Ombi
|
||||
|
@ -98,6 +99,10 @@ namespace Ombi
|
|||
if (context.Request.Headers.Keys.Contains("UserName", StringComparer.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var username = context.Request.Headers["UserName"].FirstOrDefault();
|
||||
if (username.IsNullOrEmpty())
|
||||
{
|
||||
UseApiUser(context);
|
||||
}
|
||||
var um = context.RequestServices.GetService<OmbiUserManager>();
|
||||
var user = await um.Users.FirstOrDefaultAsync(x =>
|
||||
x.UserName.Equals(username, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
@ -114,13 +119,18 @@ namespace Ombi
|
|||
}
|
||||
else
|
||||
{
|
||||
var identity = new GenericIdentity("API");
|
||||
var principal = new GenericPrincipal(identity, new[] { "Admin", "ApiUser" });
|
||||
context.User = principal;
|
||||
UseApiUser(context);
|
||||
}
|
||||
|
||||
await next.Invoke(context);
|
||||
}
|
||||
}
|
||||
|
||||
private void UseApiUser(HttpContext context)
|
||||
{
|
||||
var identity = new GenericIdentity("API");
|
||||
var principal = new GenericPrincipal(identity, new[] { "Admin", "ApiUser" });
|
||||
context.User = principal;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import { NavigationStart, Router } from "@angular/router";
|
|||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { AuthService } from "./auth/auth.service";
|
||||
import { ILocalUser } from "./auth/IUserLogin";
|
||||
import { IdentityService, NotificationService } from "./services";
|
||||
import { CustomPageService, IdentityService, NotificationService } from "./services";
|
||||
import { JobService, SettingsService } from "./services";
|
||||
|
||||
import { ICustomizationSettings, ICustomPage } from "./interfaces";
|
||||
|
@ -35,7 +35,8 @@ export class AppComponent implements OnInit {
|
|||
private readonly jobService: JobService,
|
||||
public readonly translate: TranslateService,
|
||||
private readonly identityService: IdentityService,
|
||||
private readonly platformLocation: PlatformLocation) {
|
||||
private readonly platformLocation: PlatformLocation,
|
||||
private readonly customPageService: CustomPageService) {
|
||||
|
||||
const base = this.platformLocation.getBaseHrefFromDOM();
|
||||
if (base.length > 1) {
|
||||
|
@ -57,7 +58,7 @@ export class AppComponent implements OnInit {
|
|||
this.settingsService.getCustomization().subscribe(x => {
|
||||
this.customizationSettings = x;
|
||||
if(this.customizationSettings.useCustomPage) {
|
||||
this.settingsService.getCustomPage().subscribe(c => {
|
||||
this.customPageService.getCustomPage().subscribe(c => {
|
||||
this.customPageSettings = c;
|
||||
if(!this.customPageSettings.title) {
|
||||
this.customPageSettings.title = "Custom Page";
|
||||
|
|
|
@ -39,7 +39,7 @@ import { ImageService } from "./services";
|
|||
import { LandingPageService } from "./services";
|
||||
import { NotificationService } from "./services";
|
||||
import { SettingsService } from "./services";
|
||||
import { IssuesService, JobService, PlexTvService, StatusService } from "./services";
|
||||
import { CustomPageService, IssuesService, JobService, PlexTvService, StatusService } from "./services";
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "*", component: PageNotFoundComponent },
|
||||
|
@ -144,6 +144,7 @@ export function JwtTokenGetter() {
|
|||
JobService,
|
||||
IssuesService,
|
||||
PlexTvService,
|
||||
CustomPageService,
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { AuthService } from "../auth/auth.service";
|
||||
import { NotificationService, SettingsService } from "../services";
|
||||
import { CustomPageService, NotificationService } from "../services";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./custompage.component.html",
|
||||
|
@ -14,7 +14,7 @@ export class CustomPageComponent implements OnInit {
|
|||
public isEditing: boolean;
|
||||
public isAdmin: boolean;
|
||||
|
||||
constructor(private auth: AuthService, private settings: SettingsService, private fb: FormBuilder,
|
||||
constructor(private auth: AuthService, private settings: CustomPageService, private fb: FormBuilder,
|
||||
private notificationService: NotificationService,
|
||||
private sanitizer: DomSanitizer) {
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export class CustomPageComponent implements OnInit {
|
|||
fontAwesomeIcon: [x.fontAwesomeIcon, [Validators.required]],
|
||||
});
|
||||
});
|
||||
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||
this.isAdmin = this.auth.hasRole("EditCustomPage");
|
||||
}
|
||||
|
||||
public onSubmit() {
|
||||
|
|
|
@ -87,6 +87,7 @@ export interface IBaseRequest {
|
|||
requestedUser: IUser;
|
||||
canApprove: boolean;
|
||||
title: string;
|
||||
requestedByAlias: string;
|
||||
}
|
||||
|
||||
export interface ITvRequests {
|
||||
|
@ -145,6 +146,7 @@ export interface IEpisodesRequests {
|
|||
episodeNumber: number;
|
||||
title: string;
|
||||
airDate: Date;
|
||||
airDateDisplay: string;
|
||||
url: string;
|
||||
available: boolean;
|
||||
requested: boolean;
|
||||
|
|
|
@ -41,6 +41,7 @@ export interface IEmbyServer extends IExternalSettings {
|
|||
apiKey: string;
|
||||
administratorId: string;
|
||||
enableEpisodeSearching: boolean;
|
||||
serverHostname: string;
|
||||
}
|
||||
|
||||
export interface IPlexSettings extends ISettings {
|
||||
|
|
|
@ -161,7 +161,8 @@ export class LoginComponent implements OnDestroy, OnInit {
|
|||
}
|
||||
|
||||
}, err => {
|
||||
this.notify.error(err.statusText);
|
||||
console.log(err);
|
||||
this.notify.error(err.body);
|
||||
|
||||
this.router.navigate(["login"]);
|
||||
});
|
||||
|
|
|
@ -1,36 +1,39 @@
|
|||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<input type="text" id="search" class="form-control form-control-custom searchwidth" placeholder="Search" (keyup)="search($event)">
|
||||
<input type="text" id="search" class="form-control form-control-custom searchwidth" placeholder="Search"
|
||||
(keyup)="search($event)">
|
||||
<span class="input-group-btn">
|
||||
<button id="filterBtn" class="btn btn-sm btn-info-outline" (click)="filterDisplay = !filterDisplay">
|
||||
<i class="fa fa-filter"></i> {{ 'Requests.Filter' | translate }}
|
||||
</button>
|
||||
|
||||
|
||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="true">
|
||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fa fa-sort"></i> {{ 'Requests.Sort' | translate }}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu2">
|
||||
<li>
|
||||
<a (click)="setOrder(OrderType.RequestedDateAsc, $event)">{{ 'Requests.SortRequestDateAsc' | translate }}
|
||||
|
||||
<a (click)="setOrder(OrderType.RequestedDateAsc, $event)">{{ 'Requests.SortRequestDateAsc' |
|
||||
translate }}
|
||||
|
||||
</a>
|
||||
<a class="active" (click)="setOrder(OrderType.RequestedDateDesc, $event)">{{ 'Requests.SortRequestDateDesc' | translate }}
|
||||
<a class="active" (click)="setOrder(OrderType.RequestedDateDesc, $event)">{{
|
||||
'Requests.SortRequestDateDesc' | translate }}
|
||||
|
||||
</a>
|
||||
<a (click)="setOrder(OrderType.TitleAsc, $event)">{{ 'Requests.SortTitleAsc' | translate}}
|
||||
|
||||
|
||||
</a>
|
||||
<a (click)="setOrder(OrderType.TitleDesc, $event)">{{ 'Requests.SortTitleDesc' | translate}}
|
||||
|
||||
|
||||
</a>
|
||||
<a (click)="setOrder(OrderType.StatusAsc, $event)">{{ 'Requests.SortStatusAsc' | translate}}
|
||||
|
||||
|
||||
</a>
|
||||
<a (click)="setOrder(OrderType.StatusDesc, $event)">{{ 'Requests.SortStatusDesc' | translate}}
|
||||
|
||||
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
@ -58,16 +61,20 @@
|
|||
<div class="col-sm-5 small-padding">
|
||||
<div>
|
||||
<a href="http://www.imdb.com/title/{{request.imdbId}}/" target="_blank">
|
||||
<h4 class="request-title">{{request.title}} ({{request.releaseDate | amLocal | amDateFormat: 'YYYY'}})</h4>
|
||||
<h4 class="request-title">{{request.title}} ({{request.releaseDate | amLocal | amDateFormat:
|
||||
'YYYY'}})</h4>
|
||||
</a>
|
||||
</div>
|
||||
<br />
|
||||
<div class="request-info">
|
||||
<div class="request-by">
|
||||
<span>{{ 'Requests.RequestedBy' | translate }} </span>
|
||||
<span *ngIf="!isAdmin">{{request.requestedUser.userName}}</span>
|
||||
<span *ngIf="isAdmin && request.requestedUser.alias">{{request.requestedUser.alias}}</span>
|
||||
<span *ngIf="isAdmin && !request.requestedUser.alias">{{request.requestedUser.userName}}</span>
|
||||
<span *ngIf="request.requestedByAlias">{{request.requestedByAlias}}</span>
|
||||
<span *ngIf="!request.requestedByAlias">
|
||||
<span *ngIf="!isAdmin">{{request.requestedUser.userName}}</span>
|
||||
<span *ngIf="isAdmin && request.requestedUser.alias">{{request.requestedUser.alias}}</span>
|
||||
<span *ngIf="isAdmin && !request.requestedUser.alias">{{request.requestedUser.userName}}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="request-status">
|
||||
<span>{{ 'Requests.Status' | translate }} </span>
|
||||
|
@ -77,13 +84,11 @@
|
|||
<div class="requested-status">
|
||||
<span>{{ 'Requests.RequestStatus' | translate }} </span>
|
||||
<span *ngIf="request.available" class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span>
|
||||
<span *ngIf="request.approved && !request.available" id="processingRequestLabel" class="label label-info" [translate]="'Common.ProcessingRequest'"></span>
|
||||
<span *ngIf="request.approved && !request.available" id="processingRequestLabel" class="label label-info"
|
||||
[translate]="'Common.ProcessingRequest'"></span>
|
||||
<span *ngIf="request.denied" class="label label-danger" id="requestDeclinedLabel" [translate]="'Common.RequestDenied'"></span>
|
||||
<span *ngIf="request.deniedReason" title="{{request.deniedReason}}">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
</span>
|
||||
<span *ngIf="!request.approved && !request.availble && !request.denied" id="pendingApprovalLabel" class="label label-warning"
|
||||
[translate]="'Common.PendingApproval'"></span>
|
||||
<span *ngIf="!request.approved && !request.availble && !request.denied" id="pendingApprovalLabel"
|
||||
class="label label-warning" [translate]="'Common.PendingApproval'"></span>
|
||||
|
||||
</div>
|
||||
<div *ngIf="request.denied" id="requestDenied">
|
||||
|
@ -93,16 +98,21 @@
|
|||
</div>
|
||||
|
||||
|
||||
<div id="releaseDate">{{ 'Requests.TheatricalRelease' | translate: {date: request.releaseDate | amLocal | amDateFormat: 'LL'} }}</div>
|
||||
<div *ngIf="request.digitalReleaseDate" id="digitalReleaseDate">{{ 'Requests.DigitalRelease' | translate: {date: request.digitalReleaseDate | amLocal | amDateFormat: 'LL'} }}</div>
|
||||
<div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | amLocal | amDateFormat: 'LL'}}</div>
|
||||
<div id="releaseDate">{{ 'Requests.TheatricalRelease' | translate: {date: request.releaseDate |
|
||||
amLocal | amDateFormat: 'LL'} }}</div>
|
||||
<div *ngIf="request.digitalReleaseDate" id="digitalReleaseDate">{{ 'Requests.DigitalRelease' |
|
||||
translate: {date: request.digitalReleaseDate | amLocal | amDateFormat: 'LL'} }}</div>
|
||||
<div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | amLocal
|
||||
| amDateFormat: 'LL'}}</div>
|
||||
<br />
|
||||
</div>
|
||||
<div *ngIf="isAdmin">
|
||||
<div *ngIf="request.qualityOverrideTitle" class="quality-override">{{ 'Requests.QualityOverride' | translate }}
|
||||
<div *ngIf="request.qualityOverrideTitle" class="quality-override">{{ 'Requests.QualityOverride' |
|
||||
translate }}
|
||||
<span>{{request.qualityOverrideTitle}} </span>
|
||||
</div>
|
||||
<div *ngIf="request.rootPathOverrideTitle" class="root-override">{{ 'Requests.RootFolderOverride' | translate }}
|
||||
<div *ngIf="request.rootPathOverrideTitle" class="root-override">{{ 'Requests.RootFolderOverride' |
|
||||
translate }}
|
||||
<span>{{request.rootPathOverrideTitle}} </span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -112,10 +122,12 @@
|
|||
<div class="row">
|
||||
<div class="col-md-2 col-md-push-6">
|
||||
|
||||
<a *ngIf="request.showSubscribe && !request.subscribed" style="color:white" (click)="subscribe(request)" pTooltip="Subscribe for notifications">
|
||||
<a *ngIf="request.showSubscribe && !request.subscribed" style="color:white" (click)="subscribe(request)"
|
||||
pTooltip="Subscribe for notifications">
|
||||
<i class="fa fa-rss"></i>
|
||||
</a>
|
||||
<a *ngIf="request.showSubscribe && request.subscribed" style="color:red" (click)="unSubscribe(request)" pTooltip="Unsubscribe notification">
|
||||
<a *ngIf="request.showSubscribe && request.subscribed" style="color:red" (click)="unSubscribe(request)"
|
||||
pTooltip="Unsubscribe notification">
|
||||
<i class="fa fa-rss"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -123,7 +135,8 @@
|
|||
<div *ngIf="isAdmin">
|
||||
<div *ngIf="!request.approved" id="approveBtn">
|
||||
<form>
|
||||
<button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit">
|
||||
<button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve"
|
||||
type="submit">
|
||||
<i class="fa fa-plus"></i> {{ 'Common.Approve' | translate }}
|
||||
</button>
|
||||
</form>
|
||||
|
@ -133,7 +146,8 @@
|
|||
<button type="button" class="btn btn-sm btn-warning-outline">
|
||||
<i class="fa fa-plus"></i> {{ 'Requests.ChangeRootFolder' | translate }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
|
@ -149,7 +163,8 @@
|
|||
<button type="button" class="btn btn-sm btn-warning-outline">
|
||||
<i class="fa fa-plus"></i> {{ 'Requests.ChangeQualityProfile' | translate }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
|
@ -166,15 +181,15 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<form id="markBtnGroup">
|
||||
<button id="unavailableBtn" *ngIf="request.available" (click)="changeAvailability(request, false)" style="text-align: right"
|
||||
value="false" class="btn btn-sm btn-info-outline change">
|
||||
<button id="unavailableBtn" *ngIf="request.available" (click)="changeAvailability(request, false)"
|
||||
style="text-align: right" value="false" class="btn btn-sm btn-info-outline change">
|
||||
<i class="fa fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}
|
||||
</button>
|
||||
<button id="availableBtn" *ngIf="!request.available" (click)="changeAvailability(request, true)" style="text-align: right"
|
||||
value="true" class="btn btn-sm btn-success-outline change">
|
||||
<button id="availableBtn" *ngIf="!request.available" (click)="changeAvailability(request, true)"
|
||||
style="text-align: right" value="true" class="btn btn-sm btn-success-outline change">
|
||||
<i class="fa fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}
|
||||
</button>
|
||||
</form>
|
||||
|
@ -190,8 +205,8 @@
|
|||
</form>
|
||||
</div>
|
||||
<div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issuesBtn">
|
||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="true">
|
||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fa fa-plus"></i> {{ 'Requests.ReportIssue' | translate }}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
|
@ -204,8 +219,8 @@
|
|||
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
|
||||
|
||||
|
@ -216,11 +231,11 @@
|
|||
</div>
|
||||
|
||||
<p-dialog *ngIf="requestToDeny" header="Deny Request '{{requestToDeny.title}}''" [(visible)]="denyDisplay" [draggable]="false">
|
||||
<span>Please enter a rejection reason, the user will be notified of this:</span>
|
||||
<textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea>
|
||||
<span>Please enter a rejection reason, the user will be notified of this:</span>
|
||||
<textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea>
|
||||
<p-footer>
|
||||
<button type="button" (click)="denyRequest();" label="Reject" class="btn btn-success">Deny</button>
|
||||
<button type="button"(click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button>
|
||||
<button type="button" (click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button>
|
||||
</p-footer>
|
||||
</p-dialog>
|
||||
|
||||
|
|
|
@ -135,7 +135,7 @@ export class MovieRequestsComponent implements OnInit {
|
|||
public deny(request: IMovieRequests) {
|
||||
this.requestToDeny = request;
|
||||
this.denyDisplay = true;
|
||||
}
|
||||
}
|
||||
|
||||
public denyRequest() {
|
||||
this.requestService.denyMovie({ id: this.requestToDeny.id, reason: this.rejectionReason })
|
||||
|
@ -144,6 +144,10 @@ export class MovieRequestsComponent implements OnInit {
|
|||
if (x.result) {
|
||||
this.notificationService.success(
|
||||
`Request for ${this.requestToDeny.title} has been denied successfully`);
|
||||
const index = this.movieRequests.indexOf(this.requestToDeny, 0);
|
||||
if (index > -1) {
|
||||
this.movieRequests[index].denied = true;
|
||||
}
|
||||
} else {
|
||||
this.notificationService.warning("Request Denied", x.message ? x.message : x.errorMessage);
|
||||
this.requestToDeny.denied = false;
|
||||
|
|
|
@ -1,36 +1,39 @@
|
|||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<input type="text" id="search" class="form-control form-control-custom searchwidth" placeholder="Search" (keyup)="search($event)">
|
||||
<input type="text" id="search" class="form-control form-control-custom searchwidth" placeholder="Search"
|
||||
(keyup)="search($event)">
|
||||
<span class="input-group-btn">
|
||||
<button id="filterBtn" class="btn btn-sm btn-info-outline" (click)="filterDisplay = !filterDisplay">
|
||||
<i class="fa fa-filter"></i> {{ 'Requests.Filter' | translate }}
|
||||
</button>
|
||||
|
||||
|
||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="true">
|
||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fa fa-sort"></i> {{ 'Requests.Sort' | translate }}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu2">
|
||||
<li>
|
||||
<a (click)="setOrder(OrderType.RequestedDateAsc, $event)">{{ 'Requests.SortRequestDateAsc' | translate }}
|
||||
|
||||
<a (click)="setOrder(OrderType.RequestedDateAsc, $event)">{{ 'Requests.SortRequestDateAsc' |
|
||||
translate }}
|
||||
|
||||
</a>
|
||||
<a class="active" (click)="setOrder(OrderType.RequestedDateDesc, $event)">{{ 'Requests.SortRequestDateDesc' | translate }}
|
||||
<a class="active" (click)="setOrder(OrderType.RequestedDateDesc, $event)">{{
|
||||
'Requests.SortRequestDateDesc' | translate }}
|
||||
|
||||
</a>
|
||||
<a (click)="setOrder(OrderType.TitleAsc, $event)">{{ 'Requests.SortTitleAsc' | translate}}
|
||||
|
||||
|
||||
</a>
|
||||
<a (click)="setOrder(OrderType.TitleDesc, $event)">{{ 'Requests.SortTitleDesc' | translate}}
|
||||
|
||||
|
||||
</a>
|
||||
<a (click)="setOrder(OrderType.StatusAsc, $event)">{{ 'Requests.SortStatusAsc' | translate}}
|
||||
|
||||
|
||||
</a>
|
||||
<a (click)="setOrder(OrderType.StatusDesc, $event)">{{ 'Requests.SortStatusDesc' | translate}}
|
||||
|
||||
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
@ -45,7 +48,7 @@
|
|||
|
||||
|
||||
<div class="col-md-12">
|
||||
<div *ngFor="let request of albumRequests" class="col-md-4">
|
||||
<div *ngFor="let request of albumRequests" class="col-md-4">
|
||||
<div class="row">
|
||||
<div class="album-bg backdrop" [style.background-image]="request.background"></div>
|
||||
<div class="album-tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
|
||||
|
@ -53,15 +56,15 @@
|
|||
<div class="col-sm-12 small-padding">
|
||||
<img *ngIf="request.disk" class="img-responsive poster album-cover" src="{{request.disk}}" alt="poster">
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="col-sm-12 small-padding">
|
||||
<div>
|
||||
<h4>
|
||||
<a href="" target="_blank">
|
||||
{{request.title | truncate: 36}}
|
||||
{{request.title | truncate: 36}}
|
||||
</a>
|
||||
|
||||
|
||||
</h4>
|
||||
<h5>
|
||||
<a href="">
|
||||
|
@ -69,25 +72,29 @@
|
|||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="request-info">
|
||||
<div class="request-by">
|
||||
<span>{{ 'Requests.RequestedBy' | translate }} </span>
|
||||
<span *ngIf="!isAdmin">{{request.requestedUser.userName}}</span>
|
||||
<span *ngIf="isAdmin && request.requestedUser.alias">{{request.requestedUser.alias}}</span>
|
||||
<span *ngIf="isAdmin && !request.requestedUser.alias">{{request.requestedUser.userName}}</span>
|
||||
<span *ngIf="request.requestedByAlias">{{request.requestedByAlias}}</span>
|
||||
<span *ngIf="!request.requestedByAlias">
|
||||
<span *ngIf="!isAdmin">{{request.requestedUser.userName}}</span>
|
||||
<span *ngIf="isAdmin && request.requestedUser.alias">{{request.requestedUser.alias}}</span>
|
||||
<span *ngIf="isAdmin && !request.requestedUser.alias">{{request.requestedUser.userName}}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="requested-status">
|
||||
<span>{{ 'Requests.RequestStatus' | translate }} </span>
|
||||
<span *ngIf="request.available" class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span>
|
||||
<span *ngIf="request.approved && !request.available" id="processingRequestLabel" class="label label-info" [translate]="'Common.ProcessingRequest'"></span>
|
||||
<span *ngIf="request.approved && !request.available" id="processingRequestLabel" class="label label-info"
|
||||
[translate]="'Common.ProcessingRequest'"></span>
|
||||
<span *ngIf="request.denied" class="label label-danger" id="requestDeclinedLabel" [translate]="'Common.RequestDenied'"></span>
|
||||
<span *ngIf="request.deniedReason" title="{{request.deniedReason}}">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
</span>
|
||||
<span *ngIf="!request.approved && !request.availble && !request.denied" id="pendingApprovalLabel" class="label label-warning"
|
||||
[translate]="'Common.PendingApproval'"></span>
|
||||
<span *ngIf="!request.approved && !request.availble && !request.denied" id="pendingApprovalLabel"
|
||||
class="label label-warning" [translate]="'Common.PendingApproval'"></span>
|
||||
|
||||
</div>
|
||||
<div *ngIf="request.denied" id="requestDenied">
|
||||
|
@ -97,8 +104,10 @@
|
|||
</div>
|
||||
|
||||
|
||||
<div id="releaseDate">{{ 'Requests.ReleaseDate' | translate: {date: request.releaseDate | amLocal | amDateFormat: 'LL'} }}</div>
|
||||
<div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | amLocal | amDateFormat: 'LL'}}</div>
|
||||
<div id="releaseDate">{{ 'Requests.ReleaseDate' | translate: {date: request.releaseDate | amLocal |
|
||||
amDateFormat: 'LL'} }}</div>
|
||||
<div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | amLocal
|
||||
| amDateFormat: 'LL'}}</div>
|
||||
<br />
|
||||
</div>
|
||||
<!-- <div *ngIf="isAdmin">
|
||||
|
@ -125,8 +134,9 @@
|
|||
</div> -->
|
||||
<div *ngIf="isAdmin">
|
||||
<div *ngIf="!request.approved" id="approveBtn">
|
||||
<form class="col-md-6">
|
||||
<button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit">
|
||||
<form class="col-md-6">
|
||||
<button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve"
|
||||
type="submit">
|
||||
<i class="fa fa-plus"></i> {{ 'Common.Approve' | translate }}
|
||||
</button>
|
||||
</form>
|
||||
|
@ -169,15 +179,15 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<form id="markBtnGroup">
|
||||
<button id="unavailableBtn" *ngIf="request.available" (click)="changeAvailability(request, false)" style="text-align: right"
|
||||
value="false" class="btn btn-sm btn-info-outline change">
|
||||
<button id="unavailableBtn" *ngIf="request.available" (click)="changeAvailability(request, false)"
|
||||
style="text-align: right" value="false" class="btn btn-sm btn-info-outline change">
|
||||
<i class="fa fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}
|
||||
</button>
|
||||
<button id="availableBtn" *ngIf="!request.available" (click)="changeAvailability(request, true)" style="text-align: right"
|
||||
value="true" class="btn btn-sm btn-success-outline change">
|
||||
<button id="availableBtn" *ngIf="!request.available" (click)="changeAvailability(request, true)"
|
||||
style="text-align: right" value="true" class="btn btn-sm btn-success-outline change">
|
||||
<i class="fa fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}
|
||||
</button>
|
||||
</form>
|
||||
|
@ -193,8 +203,8 @@
|
|||
</form>
|
||||
</div>
|
||||
<div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issuesBtn">
|
||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="true">
|
||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fa fa-plus"></i> {{ 'Requests.ReportIssue' | translate }}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
|
@ -207,8 +217,8 @@
|
|||
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
|
||||
|
||||
|
@ -272,8 +282,8 @@
|
|||
<p-dialog *ngIf="requestToDeny" header="Deny Request '{{requestToDeny.title}}''" [(visible)]="denyDisplay" [draggable]="false">
|
||||
<span>Please enter a rejection reason, the user will be notified of this:</span>
|
||||
<textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea>
|
||||
<p-footer>
|
||||
<button type="button" (click)="denyRequest();" label="Reject" class="btn btn-success">Deny</button>
|
||||
<button type="button"(click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button>
|
||||
</p-footer>
|
||||
</p-dialog>
|
||||
<p-footer>
|
||||
<button type="button" (click)="denyRequest();" label="Reject" class="btn btn-success">Deny</button>
|
||||
<button type="button" (click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button>
|
||||
</p-footer>
|
||||
</p-dialog>
|
|
@ -23,17 +23,11 @@ export class RemainingRequestsComponent implements OnInit {
|
|||
}
|
||||
|
||||
public ngOnInit() {
|
||||
const self = this;
|
||||
|
||||
this.update();
|
||||
|
||||
this.quotaRefreshEvents.subscribe(() => {
|
||||
this.quotaRefreshEvents.subscribe(() => {
|
||||
this.update();
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
self.update();
|
||||
}, 60000);
|
||||
}
|
||||
|
||||
public update(): void {
|
||||
|
@ -43,7 +37,6 @@ export class RemainingRequestsComponent implements OnInit {
|
|||
this.calculateTime();
|
||||
}
|
||||
});
|
||||
|
||||
if (this.movie) {
|
||||
this.requestService.getRemainingMovieRequests().subscribe(callback);
|
||||
}
|
||||
|
|
|
@ -5,31 +5,43 @@
|
|||
|
||||
<div class="col-md-2">
|
||||
<span [translate]="'Requests.RequestedBy'"></span>
|
||||
|
||||
<span *ngIf="!isAdmin">{{child.requestedUser.userName}}</span>
|
||||
<span *ngIf="isAdmin && child.requestedUser.alias">{{child.requestedUser.alias}}</span>
|
||||
<span *ngIf="isAdmin && !child.requestedUser.alias">{{child.requestedUser.userName}}</span>
|
||||
<span *ngIf="child.requestedByAlias">{{child.requestedByAlias}}</span>
|
||||
<span *ngIf="!child.requestedByAlias">
|
||||
<span *ngIf="!isAdmin">{{child.requestedUser.userName}}</span>
|
||||
<span *ngIf="isAdmin && child.requestedUser.alias">{{child.requestedUser.alias}}</span>
|
||||
<span *ngIf="isAdmin && !child.requestedUser.alias">{{child.requestedUser.userName}}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col-md-1 col-md-push-9">
|
||||
<button id="subscribeBtn" *ngIf="child.showSubscribe && !child.subscribed" (click)="subscribe(child)" class="btn btn-sm btn-primary-outline" pTooltip="Subscribe for notifications" type="submit"><i class="fa fa-rss"></i> Subscribe</button>
|
||||
<button id="subscribeBtn" *ngIf="child.showSubscribe && child.subscribed" (click)="unSubscribe(child)" class="btn btn-sm btn-danger-outline" pTooltip="UnSubscribe for notifications" type="submit"><i class="fa fa-rss"></i> UnSubscribe</button>
|
||||
|
||||
|
||||
<div *ngIf="isAdmin">
|
||||
<button id="approveBtn" *ngIf="child.canApprove && !child.approved" (click)="approve(child)" class="btn btn-sm btn-success-outline" type="submit"><i class="fa fa-plus"></i> {{ 'Common.Approve' | translate }}</button>
|
||||
<button id="unavailableBtn" *ngIf="child.available" (click)="changeAvailability(child, false)" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"><i class="fa fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}</button>
|
||||
<button id="availableBtn" *ngIf="!child.available" (click)="changeAvailability(child, true)" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change"><i class="fa fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}</button>
|
||||
<button id="subscribeBtn" *ngIf="child.showSubscribe && !child.subscribed" (click)="subscribe(child)"
|
||||
class="btn btn-sm btn-primary-outline" pTooltip="Subscribe for notifications" type="submit"><i
|
||||
class="fa fa-rss"></i> Subscribe</button>
|
||||
<button id="subscribeBtn" *ngIf="child.showSubscribe && child.subscribed" (click)="unSubscribe(child)"
|
||||
class="btn btn-sm btn-danger-outline" pTooltip="UnSubscribe for notifications" type="submit"><i
|
||||
class="fa fa-rss"></i> UnSubscribe</button>
|
||||
|
||||
|
||||
<div *ngIf="isAdmin">
|
||||
<button id="approveBtn" *ngIf="child.canApprove && !child.approved" (click)="approve(child)" class="btn btn-sm btn-success-outline"
|
||||
type="submit"><i class="fa fa-plus"></i> {{ 'Common.Approve' | translate }}</button>
|
||||
<button id="unavailableBtn" *ngIf="child.available" (click)="changeAvailability(child, false)"
|
||||
style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"><i class="fa fa-minus"></i>
|
||||
{{ 'Requests.MarkUnavailable' | translate }}</button>
|
||||
<button id="availableBtn" *ngIf="!child.available" (click)="changeAvailability(child, true)" style="text-align: right"
|
||||
value="true" class="btn btn-sm btn-success-outline change"><i class="fa fa-plus"></i> {{
|
||||
'Requests.MarkAvailable' | translate }}</button>
|
||||
|
||||
<button id="denyBtn" *ngIf="!child.denied" type="button" (click)="deny(child)" class="btn btn-sm btn-danger-outline deny">
|
||||
<i class="fa fa-times"></i> {{ 'Requests.Deny' | translate }}</button>
|
||||
|
||||
|
||||
</div>
|
||||
<div *ngIf="isAdmin || isRequestUser(child)">
|
||||
<button id="removeBtn" type="button" (click)="removeRequest(child)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> {{ 'Requests.Remove' | translate }}</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div *ngIf="isAdmin || isRequestUser(child)">
|
||||
<button id="removeBtn" type="button" (click)="removeRequest(child)" class="btn btn-sm btn-danger-outline deny"><i
|
||||
class="fa fa-times"></i> {{ 'Requests.Remove' | translate }}</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
|
@ -77,15 +89,19 @@
|
|||
{{ep.airDate | amLocal | amDateFormat: 'L' }}
|
||||
</td>
|
||||
<td>
|
||||
<span *ngIf="child.denied" class="label label-danger" id="deniedLabel" [translate]="'Common.Denied'">
|
||||
<i style="color:red;" class="fa fa-check" pTooltip="{{child.deniedReason}}"></i>
|
||||
<span *ngIf="child.denied" class="label label-danger" id="deniedLabel"
|
||||
[translate]="'Common.Denied'">
|
||||
<i style="color:red;" class="fa fa-check" pTooltip="{{child.deniedReason}}"></i>
|
||||
</span>
|
||||
<span *ngIf="!child.denied && ep.available" class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span>
|
||||
<span *ngIf="!child.denied &&ep.approved && !ep.available" class="label label-info" id="processingRequestLabel" [translate]="'Common.ProcessingRequest'"></span>
|
||||
<span *ngIf="!child.denied && ep.available" class="label label-success" id="availableLabel"
|
||||
[translate]="'Common.Available'"></span>
|
||||
<span *ngIf="!child.denied &&ep.approved && !ep.available" class="label label-info"
|
||||
id="processingRequestLabel" [translate]="'Common.ProcessingRequest'"></span>
|
||||
<div *ngIf="!child.denied && !ep.approved">
|
||||
<div *ngIf="!ep.available"><span class="label label-warning" id="pendingApprovalLabel" [translate]="'Common.PendingApproval'"></span></div>
|
||||
<div *ngIf="!ep.available"><span class="label label-warning" id="pendingApprovalLabel"
|
||||
[translate]="'Common.PendingApproval'"></span></div>
|
||||
</div>
|
||||
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -105,8 +121,8 @@
|
|||
<p-dialog *ngIf="requestToDeny" header="Deny Request '{{requestToDeny.title}}''" [(visible)]="denyDisplay" [draggable]="false">
|
||||
<span>Please enter a rejection reason, the user will be notified of this:</span>
|
||||
<textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea>
|
||||
<p-footer>
|
||||
<button type="button" (click)="denyRequest();" label="Reject" class="btn btn-success">Deny</button>
|
||||
<button type="button"(click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button>
|
||||
</p-footer>
|
||||
</p-dialog>
|
||||
<p-footer>
|
||||
<button type="button" (click)="denyRequest();" label="Reject" class="btn btn-success">Deny</button>
|
||||
<button type="button" (click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button>
|
||||
</p-footer>
|
||||
</p-dialog>
|
|
@ -27,28 +27,35 @@
|
|||
</div>
|
||||
<!-- Refine search options -->
|
||||
<div class="row top-spacing form-group vcenter" *ngIf="refineSearchEnabled">
|
||||
<div class="col-md-1">
|
||||
<div class="form-group">
|
||||
<label class="control-label">Year</label>
|
||||
|
||||
<input [(ngModel)]="searchYear" class="form-control form-control-custom refine-option">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<div class="form-group">
|
||||
<label class="control-label">Year</label>
|
||||
|
||||
<!-- <label for="name" class="col-xs-2 col-md-1">Language:</label> -->
|
||||
<div class="col-md-2">
|
||||
<div class="form-group">
|
||||
<label for="select" class="control-label">Language</label>
|
||||
<div id="profiles">
|
||||
<select [(ngModel)]="selectedLanguage" class="form-control form-control-custom refine-option"
|
||||
id="select">
|
||||
<option *ngFor="let lang of langauges" value="{{lang.code}}">{{lang.nativeName}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input [(ngModel)]="searchYear" class="form-control form-control-custom refine-option">
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
</div>
|
||||
|
||||
<!-- <label for="name" class="col-xs-2 col-md-1">Language:</label> -->
|
||||
<div class="col-md-2">
|
||||
<div class="form-group">
|
||||
<label for="select" class="control-label">Language</label>
|
||||
<div id="profiles">
|
||||
<select [(ngModel)]="selectedLanguage" class="form-control form-control-custom refine-option" id="select">
|
||||
<option *ngFor="let lang of langauges" value="{{lang.code}}">{{lang.nativeName}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="actorSearch" name="actorSearch" [(ngModel)]="actorSearch">
|
||||
<label for="actorSearch" tooltipPosition="top" pTooltip="Search for movies by actor">Actor Search</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-7">
|
||||
<button class="btn pull-right btn-success-outline" (click)="applyRefinedSearch()">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -70,7 +77,8 @@
|
|||
<div class="myBg backdrop" [style.background-image]="result.background"></div>
|
||||
<div class="tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
|
||||
<div class="col-sm-2 small-padding">
|
||||
<img *ngIf="result.posterPath" class="img-responsive poster" src="{{result.posterPath}}" alt="poster">
|
||||
<img *ngIf="result.posterPath" class="img-responsive poster movie-poster" src="{{result.posterPath}}"
|
||||
alt="poster">
|
||||
|
||||
</div>
|
||||
<div class="col-sm-8 small-padding">
|
||||
|
|
|
@ -27,6 +27,7 @@ export class MovieSearchComponent implements OnInit {
|
|||
public searchApplied = false;
|
||||
public refineSearchEnabled = false;
|
||||
public searchYear?: number;
|
||||
public actorSearch: boolean;
|
||||
public selectedLanguage: string;
|
||||
public langauges: ILanguageRefine[];
|
||||
|
||||
|
@ -204,7 +205,7 @@ export class MovieSearchComponent implements OnInit {
|
|||
}
|
||||
val.background = this.sanitizer.bypassSecurityTrustStyle
|
||||
("url(" + "https://image.tmdb.org/t/p/w1280" + val.backdropPath + ")");
|
||||
|
||||
|
||||
if (this.applyRefinedSearch) {
|
||||
this.searchService.getMovieInformationWithRefined(val.id, this.selectedLanguage)
|
||||
.subscribe(m => {
|
||||
|
@ -212,9 +213,9 @@ export class MovieSearchComponent implements OnInit {
|
|||
});
|
||||
} else {
|
||||
this.searchService.getMovieInformation(val.id)
|
||||
.subscribe(m => {
|
||||
this.updateItem(val, m);
|
||||
});
|
||||
.subscribe(m => {
|
||||
this.updateItem(val, m);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -239,14 +240,25 @@ export class MovieSearchComponent implements OnInit {
|
|||
return;
|
||||
}
|
||||
if (this.refineOpen) {
|
||||
this.searchService.searchMovieWithRefined(this.searchText, this.searchYear, this.selectedLanguage)
|
||||
.subscribe(x => {
|
||||
this.movieResults = x;
|
||||
this.searchApplied = true;
|
||||
// Now let's load some extra info including IMDB Id
|
||||
// This way the search is fast at displaying results.
|
||||
this.getExtraInfo();
|
||||
});
|
||||
if (!this.actorSearch) {
|
||||
this.searchService.searchMovieWithRefined(this.searchText, this.searchYear, this.selectedLanguage)
|
||||
.subscribe(x => {
|
||||
this.movieResults = x;
|
||||
this.searchApplied = true;
|
||||
// Now let's load some extra info including IMDB Id
|
||||
// This way the search is fast at displaying results.
|
||||
this.getExtraInfo();
|
||||
});
|
||||
} else {
|
||||
this.searchService.searchMovieByActor(this.searchText, this.selectedLanguage)
|
||||
.subscribe(x => {
|
||||
this.movieResults = x;
|
||||
this.searchApplied = true;
|
||||
// Now let's load some extra info including IMDB Id
|
||||
// This way the search is fast at displaying results.
|
||||
this.getExtraInfo();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.searchService.searchMovie(this.searchText)
|
||||
.subscribe(x => {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="myBg backdrop" [style.background-image]="result.background"></div>
|
||||
<div class="tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
|
||||
<div class="col-sm-3 small-padding">
|
||||
<img *ngIf="result.poster" class="img-responsive poster" src="{{result.poster}}" alt="poster">
|
||||
<img *ngIf="result.poster" class="img-responsive poster artist-cover" src="{{result.poster}}" alt="poster">
|
||||
|
||||
</div>
|
||||
<div class="col-sm-7 small-padding">
|
||||
|
|
|
@ -42,8 +42,11 @@
|
|||
<td>
|
||||
{{ep.title}}
|
||||
</td>
|
||||
<td>
|
||||
<td *ngIf="ep.airDateDisplay != 'Unknown'">
|
||||
{{ep.airDate | amLocal | amDateFormat: 'L' }}
|
||||
</td>
|
||||
<td *ngIf="ep.airDateDisplay == 'Unknown'">
|
||||
{{ep.airDateDisplay }}
|
||||
</td>
|
||||
<td>
|
||||
<ng-template [ngIf]="ep.available"><span class="label label-success" id="availableLabel">Available</span></ng-template>
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
<div class="tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
|
||||
<div class="col-sm-2 small-padding">
|
||||
|
||||
<img *ngIf="node.banner" class="img-responsive poster" width="150" [src]="node.banner" alt="poster">
|
||||
<img *ngIf="node.banner" class="img-responsive poster tv-poster" width="150" [src]="node.banner" alt="poster">
|
||||
|
||||
</div>
|
||||
<div class="col-sm-8 small-padding">
|
||||
|
|
25
src/Ombi/ClientApp/app/services/custompage.service.ts
Normal file
25
src/Ombi/ClientApp/app/services/custompage.service.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { PlatformLocation } from "@angular/common";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import {
|
||||
ICustomPage,
|
||||
} from "../interfaces";
|
||||
|
||||
import { ServiceHelpers } from "./service.helpers";
|
||||
|
||||
@Injectable()
|
||||
export class CustomPageService extends ServiceHelpers {
|
||||
constructor(public http: HttpClient, public platformLocation: PlatformLocation) {
|
||||
super(http, "/api/v1/CustomPage", platformLocation);
|
||||
}
|
||||
|
||||
public getCustomPage(): Observable<ICustomPage> {
|
||||
return this.http.get<ICustomPage>(this.url, {headers: this.headers});
|
||||
}
|
||||
|
||||
public saveCustomPage(model: ICustomPage): Observable<boolean> {
|
||||
return this.http.post<boolean>(this.url, model, {headers: this.headers});
|
||||
}
|
||||
}
|
|
@ -16,3 +16,4 @@ export * from "./notificationMessage.service";
|
|||
export * from "./recentlyAdded.service";
|
||||
export * from "./vote.service";
|
||||
export * from "./requestretry.service";
|
||||
export * from "./custompage.service";
|
||||
|
|
|
@ -15,4 +15,8 @@ export class MobileService extends ServiceHelpers {
|
|||
public getUserDeviceList(): Observable<IMobileUsersViewModel[]> {
|
||||
return this.http.get<IMobileUsersViewModel[]>(`${this.url}notification/`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public deleteUser(userId: string): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}remove/`, userId, {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,10 @@ export class SearchService extends ServiceHelpers {
|
|||
return this.http.post<ISearchMovieResult>(`${this.url}/Movie/info`, { theMovieDbId, languageCode: langCode });
|
||||
}
|
||||
|
||||
public searchMovieByActor(searchTerm: string, langCode: string): Observable<ISearchMovieResult[]> {
|
||||
return this.http.post<ISearchMovieResult[]>(`${this.url}/Movie/Actor`, { searchTerm, languageCode: langCode });
|
||||
}
|
||||
|
||||
// TV
|
||||
public searchTv(searchTerm: string): Observable<ISearchTvResult[]> {
|
||||
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/${searchTerm}`, { headers: this.headers });
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
ICronTestModel,
|
||||
ICronViewModelBody,
|
||||
ICustomizationSettings,
|
||||
ICustomPage,
|
||||
IDiscordNotifcationSettings,
|
||||
IDogNzbSettings,
|
||||
IEmailNotificationSettings,
|
||||
|
@ -113,14 +112,6 @@ export class SettingsService extends ServiceHelpers {
|
|||
return this.http.get<IAuthenticationSettings>(`${this.url}/Authentication`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getCustomPage(): Observable<ICustomPage> {
|
||||
return this.http.get<ICustomPage>(`${this.url}/CustomPage`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public saveCustomPage(model: ICustomPage): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}/CustomPage`, model, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getClientId(): Observable<string> {
|
||||
return this.http.get<string>(`${this.url}/clientid`, {headers: this.headers});
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="useCustomPage" name="useCustomPage" [(ngModel)]="settings.useCustomPage">
|
||||
<label for="useCustomPage" tooltipPosition="top" pTooltip="Enabled a custom page where you can fully edit">Use
|
||||
<label for="useCustomPage" tooltipPosition="top" pTooltip="Enabled a custom page where you can fully edit. You will need the Edit Custom Page role.">Use
|
||||
Custom Page</label>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -63,6 +63,18 @@
|
|||
<input type="text" class="form-control-custom form-control" id="authToken" [(ngModel)]="server.apiKey" placeholder="Emby Api Key" value="{{server.apiKey}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="authToken" class="control-label">Externally Facing Hostname
|
||||
<i class="fa fa-question-circle"
|
||||
pTooltip="This will be the external address that users will naviagte to when they press the 'View On Emby' button"></i>
|
||||
</label>
|
||||
<div>
|
||||
<input type="text" class="form-control-custom form-control" id="authToken" [(ngModel)]="server.serverHostname" placeholder="e.g. https://jellyfin.server.com/" value="{{server.serverHostname}}">
|
||||
<small><span *ngIf="server.serverHostname">Current URL: "{{server.serverHostname}}/#!/itemdetails.html?id=1"</span>
|
||||
<span *ngIf="!server.serverHostname">Current URL: "https://app.emby.media/#!/itemdetails.html?id=1</span></small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button id="testEmby" type="button" (click)="test(server)" class="btn btn-primary-outline">Test Connectivity <div id="spinner"></div></button>
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label for="select" class="control-label">User to send test notification to</label>
|
||||
<label for="select" class="control-label">Users</label>
|
||||
<div>
|
||||
<select class="form-control form-control-custom" id="select" [(ngModel)]="testUserId" [ngModelOptions]="{standalone: true}">
|
||||
<option value="">Please select</option>
|
||||
|
@ -46,7 +46,12 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="button" (click)="test(form)" class="btn btn-danger-outline">Test</button>
|
||||
<button [disabled]="form.invalid" type="button" (click)="test(form)" class="btn btn-danger-outline">Send Test Notification</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="button" (click)="remove(form)" class="btn btn-danger-outline">Remove User</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -79,4 +79,24 @@ export class MobileComponent implements OnInit {
|
|||
});
|
||||
|
||||
}
|
||||
|
||||
public remove() {
|
||||
if (!this.testUserId) {
|
||||
this.notificationService.warning("Warning", "Please select a user to remove");
|
||||
return;
|
||||
}
|
||||
|
||||
this.mobileService.deleteUser(this.testUserId).subscribe(x => {
|
||||
if (x) {
|
||||
this.notificationService.success("Removed users notification");
|
||||
const userToRemove = this.userList.filter(u => {
|
||||
return u.userId === this.testUserId;
|
||||
})[1];
|
||||
this.userList.splice(this.userList.indexOf(userToRemove),1);
|
||||
} else {
|
||||
this.notificationService.error("There was an error when removing the notification. Please check your logs");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
<div class="modal-header">
|
||||
<h3>Add A Friend!</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>You can invite a user to share your Plex Library here. The invited user will be asked to confirm friendship.</p>
|
||||
<p>Please note that this user will not appear in your Ombi Users since they have not accepted the Plex Invite, as soon as they accept
|
||||
the Plex invite then the User Importer job will run (if enabled) and add the user into Ombi.
|
||||
</p>
|
||||
|
||||
|
||||
<div *ngIf="plexServers">
|
||||
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)" style="padding-top:5%;">
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="username" class="control-label">Username/Email</label>
|
||||
|
||||
<input type="text" class="form-control form-control-custom " id="username" name="username" p formControlName="username" [ngClass]="{'form-error': form.get('username').hasError('required')}">
|
||||
<small *ngIf="form.get('username').hasError('required')" class="error-text">The Username/Email is required</small>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="select" class="control-label">Select a Server</label>
|
||||
<div id="profiles">
|
||||
<select formControlName="selectedServer" (change)="selected()" class="form-control form-control-custom" id="select" [ngClass]="{'form-error': form.get('selectedServer').hasError('required')}">
|
||||
<option *ngFor="let server of plexServers" value="{{server.machineId}}">{{server.serverName}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<small *ngIf="form.get('selectedServer').hasError('required')" class="error-text">You need to select a server!</small>
|
||||
</div>
|
||||
|
||||
<div *ngIf="plexLibs" class="form-group">
|
||||
<label for="select" class="control-label">Libraries to share</label>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="selectAll" formControlName="allLibsSelected">
|
||||
<label for="selectAll">All</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div *ngIf="!form.value.allLibsSelected">
|
||||
<div *ngFor="let lib of plexLibs">
|
||||
<div class="col-md-4">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="{{lib.id}}" value={{lib.id}} (change)="checkedLib($event.target.checked, $event.target.value)">
|
||||
<label for="{{lib.id}}">{{lib.title}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary-outline" (click)="onSubmit(form)" [disabled]="form.invalid">Add</button>
|
||||
<button type="button" class="btn btn-danger-outline" (click)="activeModal.close('Close click')">Close</button>
|
||||
</div>
|
|
@ -1,84 +0,0 @@
|
|||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||
import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap";
|
||||
|
||||
import { NotificationService, PlexService } from "../services";
|
||||
|
||||
import { IPlexSection, IPlexServersAdd } from "../interfaces";
|
||||
|
||||
@Component({
|
||||
selector: "ngbd-modal-content",
|
||||
templateUrl: "./addplexuser.component.html",
|
||||
})
|
||||
export class AddPlexUserComponent implements OnInit {
|
||||
|
||||
@Input() public name: string;
|
||||
|
||||
public plexServers: IPlexServersAdd[];
|
||||
public plexLibs: IPlexSection[];
|
||||
|
||||
public libsSelected: number[] = [];
|
||||
|
||||
public form: FormGroup;
|
||||
|
||||
constructor(public activeModal: NgbActiveModal,
|
||||
private plexService: PlexService,
|
||||
private notificationService: NotificationService,
|
||||
private fb: FormBuilder) {
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.form = this.fb.group({
|
||||
selectedServer: [null, Validators.required],
|
||||
allLibsSelected: [true],
|
||||
username:[null, Validators.required],
|
||||
});
|
||||
this.getServers();
|
||||
}
|
||||
|
||||
public getServers() {
|
||||
this.plexService.getServersFromSettings().subscribe(x => {
|
||||
if (x.success) {
|
||||
this.plexServers = x.servers;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getPlexLibs(machineId: string) {
|
||||
this.plexService.getLibrariesFromSettings(machineId).subscribe(x => {
|
||||
if (x.successful) {
|
||||
this.plexLibs = x.data;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public selected() {
|
||||
this.getPlexLibs(this.form.value.selectedServer);
|
||||
}
|
||||
|
||||
public checkedLib(checked: boolean, value: number) {
|
||||
if(checked) {
|
||||
this.libsSelected.push(value);
|
||||
} else {
|
||||
this.libsSelected = this.libsSelected.filter(v => v !== value);
|
||||
}
|
||||
}
|
||||
|
||||
public onSubmit(form: FormGroup) {
|
||||
if (form.invalid) {
|
||||
this.notificationService.error("Please check your entered values");
|
||||
return;
|
||||
}
|
||||
const libs = form.value.allLibsSelected ? [] : this.libsSelected;
|
||||
|
||||
this.plexService.addUserToServer({ username: form.value.username, machineIdentifier: form.value.selectedServer, libsSelected: libs }).subscribe(x => {
|
||||
if (x.success) {
|
||||
this.notificationService.success("User added to Plex");
|
||||
} else {
|
||||
this.notificationService.error(x.error);
|
||||
}
|
||||
this.activeModal.close();
|
||||
});
|
||||
|
||||
}
|
||||
}
|
|
@ -5,9 +5,7 @@
|
|||
<button type="button" class="btn btn-success-outline" data-test="adduserbtn" [routerLink]="['/usermanagement/user']">Add User To Ombi</button>
|
||||
|
||||
<button type="button" style="float:right;" class="btn btn-primary-outline"(click)="showBulkEdit = !showBulkEdit" [disabled]="!hasChecked()">Bulk Edit</button>
|
||||
<div *ngIf="plexEnabled">
|
||||
<button type="button" style="float:right;" class="btn btn-success-outline" (click)="open()">Add Plex Friend</button>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<table class="table table-striped table-hover table-responsive table-condensed table-usermanagement">
|
||||
<thead>
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { NgbModal } from "@ng-bootstrap/ng-bootstrap";
|
||||
import { ICheckbox, ICustomizationSettings, IEmailNotificationSettings, IUser } from "../interfaces";
|
||||
import { IdentityService, NotificationService, SettingsService } from "../services";
|
||||
import { AddPlexUserComponent } from "./addplexuser.component";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./usermanagement.component.html",
|
||||
|
@ -27,8 +25,7 @@ export class UserManagementComponent implements OnInit {
|
|||
constructor(private identityService: IdentityService,
|
||||
private settingsService: SettingsService,
|
||||
private notificationService: NotificationService,
|
||||
private plexSettings: SettingsService,
|
||||
private modalService: NgbModal) { }
|
||||
private plexSettings: SettingsService) { }
|
||||
|
||||
public ngOnInit() {
|
||||
this.users = [];
|
||||
|
@ -43,11 +40,6 @@ export class UserManagementComponent implements OnInit {
|
|||
this.settingsService.getEmailNotificationSettings().subscribe(x => this.emailSettings = x);
|
||||
}
|
||||
|
||||
public open() {
|
||||
const modalRef = this.modalService.open(AddPlexUserComponent, {container:"ombi", backdropClass:"custom-modal-backdrop", windowClass:"window"});
|
||||
modalRef.componentInstance.name = "World";
|
||||
}
|
||||
|
||||
public welcomeEmail(user: IUser) {
|
||||
if (!user.emailAddress) {
|
||||
this.notificationService.error("The user needs an email address.");
|
||||
|
|
|
@ -16,7 +16,6 @@ import { IdentityService, PlexService, RadarrService, SonarrService } from "../s
|
|||
import { AuthGuard } from "../auth/auth.guard";
|
||||
|
||||
import { OrderModule } from "ngx-order-pipe";
|
||||
import { AddPlexUserComponent } from "./addplexuser.component";
|
||||
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
|
||||
|
@ -45,12 +44,8 @@ const routes: Routes = [
|
|||
declarations: [
|
||||
UserManagementComponent,
|
||||
UpdateDetailsComponent,
|
||||
AddPlexUserComponent,
|
||||
UserManagementUserComponent,
|
||||
],
|
||||
entryComponents:[
|
||||
AddPlexUserComponent,
|
||||
],
|
||||
exports: [
|
||||
RouterModule,
|
||||
],
|
||||
|
|
|
@ -41,6 +41,7 @@ export class EmbyComponent implements OnInit {
|
|||
port: 8096,
|
||||
ssl: false,
|
||||
subDir: "",
|
||||
serverHostname: "",
|
||||
|
||||
});
|
||||
}
|
||||
|
|
53
src/Ombi/Controllers/CustomPageController.cs
Normal file
53
src/Ombi/Controllers/CustomPageController.cs
Normal file
|
@ -0,0 +1,53 @@
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
|
||||
namespace Ombi.Controllers
|
||||
{
|
||||
[ApiV1]
|
||||
[Produces("application/json")]
|
||||
[ApiController]
|
||||
public class CustomPageController : ControllerBase
|
||||
{
|
||||
public CustomPageController(ISettingsService<CustomPageSettings> settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
private readonly ISettingsService<CustomPageSettings> _settings;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Custom Page Settings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<CustomPageSettings> CustomPageSettings()
|
||||
{
|
||||
return await Get();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the Custom Page Settings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize(Roles = OmbiRoles.EditCustomPage)]
|
||||
public async Task<bool> CustomPageSettings([FromBody] CustomPageSettings page)
|
||||
{
|
||||
return await Save(page);
|
||||
}
|
||||
private async Task<CustomPageSettings> Get()
|
||||
{
|
||||
return await _settings.GetSettingsAsync();
|
||||
}
|
||||
|
||||
private async Task<bool> Save(CustomPageSettings settingsModel)
|
||||
{
|
||||
return await _settings.SaveSettingsAsync(settingsModel);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -239,6 +239,7 @@ namespace Ombi.Controllers
|
|||
await CreateRole(OmbiRoles.Disabled);
|
||||
await CreateRole(OmbiRoles.ReceivesNewsletter);
|
||||
await CreateRole(OmbiRoles.ManageOwnRequests);
|
||||
await CreateRole(OmbiRoles.EditCustomPage);
|
||||
}
|
||||
|
||||
private async Task CreateRole(string role)
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Attributes;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Helpers;
|
||||
|
@ -20,14 +21,16 @@ namespace Ombi.Controllers
|
|||
[ApiController]
|
||||
public class MobileController : ControllerBase
|
||||
{
|
||||
public MobileController(IRepository<NotificationUserId> notification, OmbiUserManager user)
|
||||
public MobileController(IRepository<NotificationUserId> notification, OmbiUserManager user, ILogger<MobileController> log)
|
||||
{
|
||||
_notification = notification;
|
||||
_userManager = user;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
private readonly IRepository<NotificationUserId> _notification;
|
||||
private readonly OmbiUserManager _userManager;
|
||||
private readonly ILogger _log;
|
||||
|
||||
[HttpPost("Notification")]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
|
@ -78,5 +81,24 @@ namespace Ombi.Controllers
|
|||
}
|
||||
return vm;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
[Admin]
|
||||
public async Task<bool> RemoveUser([FromBody] string userId)
|
||||
{
|
||||
var user = await _userManager.Users.Include(x => x.NotificationUserIds).FirstOrDefaultAsync(x => x.Id.Equals(userId, StringComparison.InvariantCultureIgnoreCase));
|
||||
try
|
||||
{
|
||||
await _notification.DeleteRange(user.NotificationUserIds);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.LogError(e, "Could not delete user notification");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Ombi.Core.Engine;
|
||||
using Ombi.Core.Models.Requests;
|
||||
|
@ -11,6 +12,7 @@ using Ombi.Core.Models;
|
|||
using Ombi.Core.Models.UI;
|
||||
using Ombi.Store.Entities;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ombi.Controllers
|
||||
{
|
||||
|
@ -76,6 +78,7 @@ namespace Ombi.Controllers
|
|||
[HttpPost]
|
||||
public async Task<RequestEngineResult> RequestAlbum([FromBody] MusicAlbumRequestViewModel album)
|
||||
{
|
||||
album.RequestedByAlias = GetApiAlias();
|
||||
var result = await _engine.RequestAlbum(album);
|
||||
if (result.Result)
|
||||
{
|
||||
|
@ -168,5 +171,17 @@ namespace Ombi.Controllers
|
|||
{
|
||||
return await _engine.GetRemainingRequests();
|
||||
}
|
||||
private string GetApiAlias()
|
||||
{
|
||||
// Make sure this only applies when using the API KEY
|
||||
if (HttpContext.Request.Headers.Keys.Contains("ApiKey", StringComparer.InvariantCultureIgnoreCase))
|
||||
{
|
||||
if (HttpContext.Request.Headers.TryGetValue("ApiAlias", out var apiAlias))
|
||||
{
|
||||
return apiAlias;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Ombi.Core.Engine;
|
||||
using Ombi.Core.Engine.Interfaces;
|
||||
|
@ -8,12 +9,14 @@ using System.Collections.Generic;
|
|||
using System.Threading.Tasks;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Attributes;
|
||||
using Ombi.Core.Models.UI;
|
||||
using Ombi.Models;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Core.Models;
|
||||
using Ombi.Helpers;
|
||||
|
||||
namespace Ombi.Controllers
|
||||
{
|
||||
|
@ -82,6 +85,7 @@ namespace Ombi.Controllers
|
|||
[HttpPost("movie")]
|
||||
public async Task<RequestEngineResult> RequestMovie([FromBody] MovieRequestViewModel movie)
|
||||
{
|
||||
movie.RequestedByAlias = GetApiAlias();
|
||||
var result = await MovieRequestEngine.RequestMovie(movie);
|
||||
if (result.Result)
|
||||
{
|
||||
|
@ -277,6 +281,7 @@ namespace Ombi.Controllers
|
|||
[HttpPost("tv")]
|
||||
public async Task<RequestEngineResult> RequestTv([FromBody] TvRequestViewModel tv)
|
||||
{
|
||||
tv.RequestedByAlias = GetApiAlias();
|
||||
var result = await TvRequestEngine.RequestTvShow(tv);
|
||||
if (result.Result)
|
||||
{
|
||||
|
@ -521,5 +526,19 @@ namespace Ombi.Controllers
|
|||
{
|
||||
return await TvRequestEngine.GetRemainingRequests();
|
||||
}
|
||||
|
||||
private string GetApiAlias()
|
||||
{
|
||||
// Make sure this only applies when using the API KEY
|
||||
if (HttpContext.Request.Headers.Keys.Contains("ApiKey", StringComparer.InvariantCultureIgnoreCase))
|
||||
{
|
||||
if (HttpContext.Request.Headers.TryGetValue("ApiAlias", out var apiAlias))
|
||||
{
|
||||
return apiAlias;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,8 @@ using Ombi.Core.Models.Search;
|
|||
using Ombi.Models;
|
||||
using StackExchange.Profiling;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Ombi.Core.Engine.Demo;
|
||||
using Ombi.Helpers;
|
||||
|
||||
namespace Ombi.Controllers
|
||||
{
|
||||
|
@ -19,18 +21,26 @@ namespace Ombi.Controllers
|
|||
[ApiController]
|
||||
public class SearchController : Controller
|
||||
{
|
||||
public SearchController(IMovieEngine movie, ITvSearchEngine tvEngine, ILogger<SearchController> logger, IMusicSearchEngine music)
|
||||
public SearchController(IMovieEngine movie, ITvSearchEngine tvEngine, ILogger<SearchController> logger, IMusicSearchEngine music,
|
||||
IDemoMovieSearchEngine demoMovieSearch, IDemoTvSearchEngine demoTvSearchEngine)
|
||||
{
|
||||
MovieEngine = movie;
|
||||
TvEngine = tvEngine;
|
||||
Logger = logger;
|
||||
MusicEngine = music;
|
||||
DemoMovieSearch = demoMovieSearch;
|
||||
DemoTvSearch = demoTvSearchEngine;
|
||||
IsDemo = DemoSingleton.Instance.Demo;
|
||||
}
|
||||
|
||||
private ILogger<SearchController> Logger { get; }
|
||||
|
||||
private IMovieEngine MovieEngine { get; }
|
||||
private ITvSearchEngine TvEngine { get; }
|
||||
private IMusicSearchEngine MusicEngine { get; }
|
||||
private IDemoMovieSearchEngine DemoMovieSearch { get; }
|
||||
private IDemoTvSearchEngine DemoTvSearch { get; }
|
||||
private readonly bool IsDemo;
|
||||
|
||||
/// <summary>
|
||||
/// Searches for a movie.
|
||||
|
@ -47,10 +57,33 @@ namespace Ombi.Controllers
|
|||
{
|
||||
Logger.LogDebug("Searching : {searchTerm}", searchTerm);
|
||||
|
||||
if (IsDemo)
|
||||
{
|
||||
return await DemoMovieSearch.Search(searchTerm);
|
||||
}
|
||||
return await MovieEngine.Search(searchTerm, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for movies by a certain actor.
|
||||
/// </summary>
|
||||
/// <param name="model">language code is optional, by default it will be en. Language code uses ISO 639-1</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("movie/actor")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> SearchActor([FromBody] SearchActorModel model)
|
||||
{
|
||||
if (model == null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
return Json(await MovieEngine.SearchActor(model.SearchTerm, model.LanguageCode));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for a movie.
|
||||
/// </summary>
|
||||
|
@ -154,6 +187,10 @@ namespace Ombi.Controllers
|
|||
[ProducesDefaultResponseType]
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> Popular()
|
||||
{
|
||||
if (IsDemo)
|
||||
{
|
||||
return await DemoMovieSearch.PopularMovies();
|
||||
}
|
||||
return await MovieEngine.PopularMovies();
|
||||
}
|
||||
/// <summary>
|
||||
|
@ -166,6 +203,10 @@ namespace Ombi.Controllers
|
|||
[ProducesDefaultResponseType]
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies()
|
||||
{
|
||||
if (IsDemo)
|
||||
{
|
||||
return await DemoMovieSearch.NowPlayingMovies();
|
||||
}
|
||||
return await MovieEngine.NowPlayingMovies();
|
||||
}
|
||||
/// <summary>
|
||||
|
@ -178,6 +219,10 @@ namespace Ombi.Controllers
|
|||
[ProducesDefaultResponseType]
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies()
|
||||
{
|
||||
if (IsDemo)
|
||||
{
|
||||
return await DemoMovieSearch.TopRatedMovies();
|
||||
}
|
||||
return await MovieEngine.TopRatedMovies();
|
||||
}
|
||||
/// <summary>
|
||||
|
@ -190,6 +235,10 @@ namespace Ombi.Controllers
|
|||
[ProducesDefaultResponseType]
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies()
|
||||
{
|
||||
if (IsDemo)
|
||||
{
|
||||
return await DemoMovieSearch.UpcomingMovies();
|
||||
}
|
||||
return await MovieEngine.UpcomingMovies();
|
||||
}
|
||||
|
||||
|
@ -204,6 +253,10 @@ namespace Ombi.Controllers
|
|||
[ProducesDefaultResponseType]
|
||||
public async Task<IEnumerable<SearchTvShowViewModel>> SearchTv(string searchTerm)
|
||||
{
|
||||
if (IsDemo)
|
||||
{
|
||||
return await DemoTvSearch.Search(searchTerm);
|
||||
}
|
||||
return await TvEngine.Search(searchTerm);
|
||||
}
|
||||
|
||||
|
@ -231,6 +284,10 @@ namespace Ombi.Controllers
|
|||
[ProducesDefaultResponseType]
|
||||
public async Task<IEnumerable<SearchTvShowViewModel>> PopularTv()
|
||||
{
|
||||
if (IsDemo)
|
||||
{
|
||||
return await DemoTvSearch.NowPlayingMovies();
|
||||
}
|
||||
return await TvEngine.Popular();
|
||||
}
|
||||
|
||||
|
@ -244,6 +301,10 @@ namespace Ombi.Controllers
|
|||
[ProducesDefaultResponseType]
|
||||
public async Task<IEnumerable<SearchTvShowViewModel>> AnticipatedTv()
|
||||
{
|
||||
if (IsDemo)
|
||||
{
|
||||
return await DemoTvSearch.NowPlayingMovies();
|
||||
}
|
||||
return await TvEngine.Anticipated();
|
||||
}
|
||||
|
||||
|
@ -258,6 +319,10 @@ namespace Ombi.Controllers
|
|||
[ProducesDefaultResponseType]
|
||||
public async Task<IEnumerable<SearchTvShowViewModel>> MostWatched()
|
||||
{
|
||||
if (IsDemo)
|
||||
{
|
||||
return await DemoTvSearch.NowPlayingMovies();
|
||||
}
|
||||
return await TvEngine.MostWatches();
|
||||
}
|
||||
|
||||
|
@ -271,6 +336,10 @@ namespace Ombi.Controllers
|
|||
[ProducesDefaultResponseType]
|
||||
public async Task<IEnumerable<SearchTvShowViewModel>> Trending()
|
||||
{
|
||||
if (IsDemo)
|
||||
{
|
||||
return await DemoTvSearch.NowPlayingMovies();
|
||||
}
|
||||
return await TvEngine.Trending();
|
||||
}
|
||||
|
||||
|
|
|
@ -707,27 +707,6 @@ namespace Ombi.Controllers
|
|||
return emailSettings.Enabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Custom Page Settings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("CustomPage")]
|
||||
[AllowAnonymous]
|
||||
public async Task<CustomPageSettings> CustomPageSettings()
|
||||
{
|
||||
return await Get<CustomPageSettings>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the Custom Page Settings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost("CustomPage")]
|
||||
public async Task<bool> CustomPageSettings([FromBody] CustomPageSettings page)
|
||||
{
|
||||
return await Save(page);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the discord notification settings.
|
||||
/// </summary>
|
||||
|
|
8
src/Ombi/Models/SearchActorModel.cs
Normal file
8
src/Ombi/Models/SearchActorModel.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ombi.Models
|
||||
{
|
||||
public class SearchActorModel
|
||||
{
|
||||
public string SearchTerm { get; set; }
|
||||
public string LanguageCode { get; set; } = "en";
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Ombi.Store.Context;
|
||||
using Ombi.Store.Entities;
|
||||
using CommandLine;
|
||||
|
@ -24,12 +23,14 @@ namespace Ombi
|
|||
var host = string.Empty;
|
||||
var storagePath = string.Empty;
|
||||
var baseUrl = string.Empty;
|
||||
var demo = false;
|
||||
var result = Parser.Default.ParseArguments<Options>(args)
|
||||
.WithParsed(o =>
|
||||
{
|
||||
host = o.Host;
|
||||
storagePath = o.StoragePath;
|
||||
baseUrl = o.BaseUrl;
|
||||
demo = o.Demo;
|
||||
}).WithNotParsed(err =>
|
||||
{
|
||||
foreach (var e in err)
|
||||
|
@ -44,52 +45,52 @@ namespace Ombi
|
|||
|
||||
var urlValue = string.Empty;
|
||||
var instance = StoragePathSingleton.Instance;
|
||||
var demoInstance = DemoSingleton.Instance;
|
||||
demoInstance.Demo = demo;
|
||||
instance.StoragePath = storagePath ?? string.Empty;
|
||||
// Check if we need to migrate the settings
|
||||
CheckAndMigrate();
|
||||
using (var ctx = new SettingsContext())
|
||||
var ctx = new SettingsContext();
|
||||
var config = ctx.ApplicationConfigurations.ToList();
|
||||
var url = config.FirstOrDefault(x => x.Type == ConfigurationTypes.Url);
|
||||
var dbBaseUrl = config.FirstOrDefault(x => x.Type == ConfigurationTypes.BaseUrl);
|
||||
if (url == null)
|
||||
{
|
||||
var config = ctx.ApplicationConfigurations.ToList();
|
||||
var url = config.FirstOrDefault(x => x.Type == ConfigurationTypes.Url);
|
||||
var dbBaseUrl = config.FirstOrDefault(x => x.Type == ConfigurationTypes.BaseUrl);
|
||||
if (url == null)
|
||||
url = new ApplicationConfiguration
|
||||
{
|
||||
url = new ApplicationConfiguration
|
||||
Type = ConfigurationTypes.Url,
|
||||
Value = "http://*:5000"
|
||||
};
|
||||
|
||||
ctx.ApplicationConfigurations.Add(url);
|
||||
ctx.SaveChanges();
|
||||
urlValue = url.Value;
|
||||
}
|
||||
if (!url.Value.Equals(host))
|
||||
{
|
||||
url.Value = UrlArgs;
|
||||
ctx.SaveChanges();
|
||||
urlValue = url.Value;
|
||||
}
|
||||
|
||||
if (dbBaseUrl == null)
|
||||
{
|
||||
if (baseUrl.HasValue() && baseUrl.StartsWith("/"))
|
||||
{
|
||||
dbBaseUrl = new ApplicationConfiguration
|
||||
{
|
||||
Type = ConfigurationTypes.Url,
|
||||
Value = "http://*:5000"
|
||||
Type = ConfigurationTypes.BaseUrl,
|
||||
Value = baseUrl
|
||||
};
|
||||
|
||||
ctx.ApplicationConfigurations.Add(url);
|
||||
ctx.SaveChanges();
|
||||
urlValue = url.Value;
|
||||
}
|
||||
if (!url.Value.Equals(host))
|
||||
{
|
||||
url.Value = UrlArgs;
|
||||
ctx.SaveChanges();
|
||||
urlValue = url.Value;
|
||||
}
|
||||
|
||||
if (dbBaseUrl == null)
|
||||
{
|
||||
if (baseUrl.HasValue() && baseUrl.StartsWith("/"))
|
||||
{
|
||||
dbBaseUrl = new ApplicationConfiguration
|
||||
{
|
||||
Type = ConfigurationTypes.BaseUrl,
|
||||
Value = baseUrl
|
||||
};
|
||||
ctx.ApplicationConfigurations.Add(dbBaseUrl);
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
}
|
||||
else if (baseUrl.HasValue() && !baseUrl.Equals(dbBaseUrl.Value))
|
||||
{
|
||||
dbBaseUrl.Value = baseUrl;
|
||||
ctx.ApplicationConfigurations.Add(dbBaseUrl);
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
}
|
||||
else if (baseUrl.HasValue() && !baseUrl.Equals(dbBaseUrl.Value))
|
||||
{
|
||||
dbBaseUrl.Value = baseUrl;
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
|
||||
DeleteSchedulesDb();
|
||||
|
||||
|
@ -111,124 +112,118 @@ namespace Ombi
|
|||
{
|
||||
var doneGlobal = false;
|
||||
var doneConfig = false;
|
||||
using (var ombi = new OmbiContext())
|
||||
using (var settings = new SettingsContext())
|
||||
var ombi = new OmbiContext();
|
||||
var settings = new SettingsContext();
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
if (ombi.Settings.Any())
|
||||
{
|
||||
if (ombi.Settings.Any())
|
||||
{
|
||||
// OK migrate it!
|
||||
var allSettings = ombi.Settings.ToList();
|
||||
settings.Settings.AddRange(allSettings);
|
||||
doneGlobal = true;
|
||||
}
|
||||
|
||||
// Check for any application settings
|
||||
|
||||
if (ombi.ApplicationConfigurations.Any())
|
||||
{
|
||||
// OK migrate it!
|
||||
var allSettings = ombi.ApplicationConfigurations.ToList();
|
||||
settings.ApplicationConfigurations.AddRange(allSettings);
|
||||
doneConfig = true;
|
||||
}
|
||||
|
||||
settings.SaveChanges();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
// OK migrate it!
|
||||
var allSettings = ombi.Settings.ToList();
|
||||
settings.Settings.AddRange(allSettings);
|
||||
doneGlobal = true;
|
||||
}
|
||||
|
||||
// Now delete the old stuff
|
||||
if (doneGlobal)
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM GlobalSettings");
|
||||
if (doneConfig)
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM ApplicationConfiguration");
|
||||
// Check for any application settings
|
||||
|
||||
if (ombi.ApplicationConfigurations.Any())
|
||||
{
|
||||
// OK migrate it!
|
||||
var allSettings = ombi.ApplicationConfigurations.ToList();
|
||||
settings.ApplicationConfigurations.AddRange(allSettings);
|
||||
doneConfig = true;
|
||||
}
|
||||
|
||||
settings.SaveChanges();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
|
||||
// Now delete the old stuff
|
||||
if (doneGlobal)
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM GlobalSettings");
|
||||
if (doneConfig)
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM ApplicationConfiguration");
|
||||
|
||||
// Now migrate all the external stuff
|
||||
using (var ombi = new OmbiContext())
|
||||
using (var external = new ExternalContext())
|
||||
var external = new ExternalContext();
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
if (ombi.PlexEpisode.Any())
|
||||
{
|
||||
|
||||
if (ombi.PlexEpisode.Any())
|
||||
{
|
||||
external.PlexEpisode.AddRange(ombi.PlexEpisode.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM PlexEpisode");
|
||||
}
|
||||
|
||||
if (ombi.PlexSeasonsContent.Any())
|
||||
{
|
||||
external.PlexSeasonsContent.AddRange(ombi.PlexSeasonsContent.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM PlexSeasonsContent");
|
||||
}
|
||||
if (ombi.PlexServerContent.Any())
|
||||
{
|
||||
external.PlexServerContent.AddRange(ombi.PlexServerContent.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM PlexServerContent");
|
||||
}
|
||||
if (ombi.EmbyEpisode.Any())
|
||||
{
|
||||
external.EmbyEpisode.AddRange(ombi.EmbyEpisode.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM EmbyEpisode");
|
||||
}
|
||||
|
||||
if (ombi.EmbyContent.Any())
|
||||
{
|
||||
external.EmbyContent.AddRange(ombi.EmbyContent.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM EmbyContent");
|
||||
}
|
||||
if (ombi.RadarrCache.Any())
|
||||
{
|
||||
external.RadarrCache.AddRange(ombi.RadarrCache.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM RadarrCache");
|
||||
}
|
||||
if (ombi.SonarrCache.Any())
|
||||
{
|
||||
external.SonarrCache.AddRange(ombi.SonarrCache.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM SonarrCache");
|
||||
}
|
||||
if (ombi.LidarrAlbumCache.Any())
|
||||
{
|
||||
external.LidarrAlbumCache.AddRange(ombi.LidarrAlbumCache.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM LidarrAlbumCache");
|
||||
}
|
||||
if (ombi.LidarrArtistCache.Any())
|
||||
{
|
||||
external.LidarrArtistCache.AddRange(ombi.LidarrArtistCache.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM LidarrArtistCache");
|
||||
}
|
||||
if (ombi.SickRageEpisodeCache.Any())
|
||||
{
|
||||
external.SickRageEpisodeCache.AddRange(ombi.SickRageEpisodeCache.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM SickRageEpisodeCache");
|
||||
}
|
||||
if (ombi.SickRageCache.Any())
|
||||
{
|
||||
external.SickRageCache.AddRange(ombi.SickRageCache.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM SickRageCache");
|
||||
}
|
||||
if (ombi.CouchPotatoCache.Any())
|
||||
{
|
||||
external.CouchPotatoCache.AddRange(ombi.CouchPotatoCache.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM CouchPotatoCache");
|
||||
}
|
||||
|
||||
external.SaveChanges();
|
||||
external.PlexEpisode.AddRange(ombi.PlexEpisode.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM PlexEpisode");
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
if (ombi.PlexSeasonsContent.Any())
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
external.PlexSeasonsContent.AddRange(ombi.PlexSeasonsContent.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM PlexSeasonsContent");
|
||||
}
|
||||
if (ombi.PlexServerContent.Any())
|
||||
{
|
||||
external.PlexServerContent.AddRange(ombi.PlexServerContent.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM PlexServerContent");
|
||||
}
|
||||
if (ombi.EmbyEpisode.Any())
|
||||
{
|
||||
external.EmbyEpisode.AddRange(ombi.EmbyEpisode.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM EmbyEpisode");
|
||||
}
|
||||
|
||||
if (ombi.EmbyContent.Any())
|
||||
{
|
||||
external.EmbyContent.AddRange(ombi.EmbyContent.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM EmbyContent");
|
||||
}
|
||||
if (ombi.RadarrCache.Any())
|
||||
{
|
||||
external.RadarrCache.AddRange(ombi.RadarrCache.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM RadarrCache");
|
||||
}
|
||||
if (ombi.SonarrCache.Any())
|
||||
{
|
||||
external.SonarrCache.AddRange(ombi.SonarrCache.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM SonarrCache");
|
||||
}
|
||||
if (ombi.LidarrAlbumCache.Any())
|
||||
{
|
||||
external.LidarrAlbumCache.AddRange(ombi.LidarrAlbumCache.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM LidarrAlbumCache");
|
||||
}
|
||||
if (ombi.LidarrArtistCache.Any())
|
||||
{
|
||||
external.LidarrArtistCache.AddRange(ombi.LidarrArtistCache.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM LidarrArtistCache");
|
||||
}
|
||||
if (ombi.SickRageEpisodeCache.Any())
|
||||
{
|
||||
external.SickRageEpisodeCache.AddRange(ombi.SickRageEpisodeCache.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM SickRageEpisodeCache");
|
||||
}
|
||||
if (ombi.SickRageCache.Any())
|
||||
{
|
||||
external.SickRageCache.AddRange(ombi.SickRageCache.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM SickRageCache");
|
||||
}
|
||||
if (ombi.CouchPotatoCache.Any())
|
||||
{
|
||||
external.CouchPotatoCache.AddRange(ombi.CouchPotatoCache.ToList());
|
||||
ombi.Database.ExecuteSqlCommand("DELETE FROM CouchPotatoCache");
|
||||
}
|
||||
|
||||
external.SaveChanges();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void DeleteSchedulesDb()
|
||||
|
@ -277,5 +272,8 @@ namespace Ombi
|
|||
[Option("baseurl", Required = false, HelpText = "The base URL for reverse proxy scenarios")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[Option("demo", Required = false, HelpText = "Demo mode, you will never need to use this, fuck that fruit company...")]
|
||||
public bool Demo { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ using Ombi.Store.Context;
|
|||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using Serilog;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace Ombi
|
||||
{
|
||||
|
@ -42,35 +43,12 @@ namespace Ombi
|
|||
.AddEnvironmentVariables();
|
||||
Configuration = builder.Build();
|
||||
|
||||
//if (env.IsDevelopment())
|
||||
//{
|
||||
Serilog.ILogger config;
|
||||
if (string.IsNullOrEmpty(StoragePath.StoragePath))
|
||||
{
|
||||
config = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.RollingFile(Path.Combine(env.ContentRootPath, "Logs", "log-{Date}.txt"))
|
||||
.CreateLogger();
|
||||
}
|
||||
else
|
||||
{
|
||||
config = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.RollingFile(Path.Combine(StoragePath.StoragePath, "Logs", "log-{Date}.txt"))
|
||||
.CreateLogger();
|
||||
}
|
||||
ILogger config = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.RollingFile(Path.Combine(StoragePath.StoragePath.IsNullOrEmpty() ? env.ContentRootPath : StoragePath.StoragePath, "Logs", "log-{Date}.txt"))
|
||||
.CreateLogger();
|
||||
|
||||
Log.Logger = config;
|
||||
|
||||
|
||||
//}
|
||||
//if (env.IsProduction())
|
||||
//{
|
||||
// Log.Logger = new LoggerConfiguration()
|
||||
// .MinimumLevel.Debug()
|
||||
// .WriteTo.RollingFile(Path.Combine(env.ContentRootPath, "Logs", "log-{Date}.txt"))
|
||||
// .WriteTo.SQLite("Ombi.db", "Logs", LogEventLevel.Debug)
|
||||
// .CreateLogger();
|
||||
//}
|
||||
}
|
||||
|
||||
public IConfigurationRoot Configuration { get; }
|
||||
|
@ -126,7 +104,6 @@ namespace Ombi
|
|||
{
|
||||
x.UseSQLiteStorage(sqliteStorage);
|
||||
x.UseActivator(new IoCJobActivator(services.BuildServiceProvider()));
|
||||
//x.UseConsole();
|
||||
});
|
||||
|
||||
services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
|
||||
|
@ -209,7 +186,7 @@ namespace Ombi
|
|||
app.UseHangfireDashboard(settings.BaseUrl.HasValue() ? $"{settings.BaseUrl}/hangfire" : "/hangfire",
|
||||
new DashboardOptions
|
||||
{
|
||||
Authorization = new[] {new HangfireAuthorizationFilter()}
|
||||
Authorization = new[] { new HangfireAuthorizationFilter() }
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -237,15 +214,12 @@ namespace Ombi
|
|||
app.UseMiddleware<ApiKeyMiddlewear>();
|
||||
|
||||
app.UseCors("MyPolicy");
|
||||
//app.ApiKeyMiddlewear(app.ApplicationServices);
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
|
||||
});
|
||||
|
||||
|
||||
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
|
@ -256,8 +230,6 @@ namespace Ombi
|
|||
name: "spa-fallback",
|
||||
defaults: new { controller = "Home", action = "Index" });
|
||||
});
|
||||
|
||||
ombiService.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -109,6 +109,9 @@ namespace Ombi
|
|||
services.Configure<UserSettings>(configuration.GetSection("UserSettings"));
|
||||
services.Configure<TokenAuthentication>(configuration.GetSection("TokenAuthentication"));
|
||||
services.Configure<LandingPageBackground>(configuration.GetSection("LandingPageBackground"));
|
||||
services.Configure<DemoLists>(configuration.GetSection("Demo"));
|
||||
var enabledDemo = Convert.ToBoolean(configuration.GetSection("Demo:Enabled").Value);
|
||||
DemoSingleton.Instance.Demo = enabledDemo;
|
||||
}
|
||||
|
||||
public static void AddJwtAuthentication(this IServiceCollection services, IConfigurationRoot configuration)
|
||||
|
|
|
@ -47,5 +47,68 @@
|
|||
296762,
|
||||
280619
|
||||
]
|
||||
}
|
||||
},
|
||||
// Please ignore the below
|
||||
"Demo": {
|
||||
"Enabled": false,
|
||||
"Movies": [
|
||||
//https://en.wikipedia.org/wiki/List_of_films_in_the_public_domain_in_the_United_States
|
||||
130816,
|
||||
20278,
|
||||
22657,
|
||||
29998,
|
||||
22356,
|
||||
120862,
|
||||
23325,
|
||||
22718,
|
||||
10378,
|
||||
22733,
|
||||
144613,
|
||||
156397,
|
||||
43888,
|
||||
262743,
|
||||
92341,
|
||||
75888,
|
||||
53828,
|
||||
38346,
|
||||
33468,
|
||||
72012,
|
||||
22642,
|
||||
15401,
|
||||
16093,
|
||||
4808,
|
||||
111370,
|
||||
22948,
|
||||
165009,
|
||||
43386,
|
||||
105852,
|
||||
166316,
|
||||
18449,
|
||||
28503,
|
||||
20367,
|
||||
41021 //The Devil Bat
|
||||
],
|
||||
"TvShows": [
|
||||
//https://infogalactic.com/info/List_of_TV_series_with_episodes_in_the_public_domain
|
||||
26741,
|
||||
9475,
|
||||
4379,
|
||||
17434,
|
||||
12751,
|
||||
17436,
|
||||
4378,
|
||||
7792,
|
||||
10643,
|
||||
23503,
|
||||
19339,
|
||||
10632,
|
||||
12740,
|
||||
23466,
|
||||
6910,
|
||||
3327,
|
||||
2122,
|
||||
22148,
|
||||
25941 // Front Row Center
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
11740
src/Ombi/package-lock.json
generated
Normal file
11740
src/Ombi/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -126,7 +126,7 @@
|
|||
"GridTitle": "Título",
|
||||
"AirDate": "Fecha de estreno",
|
||||
"GridStatus": "Estado",
|
||||
"ReportIssue": "Informar de un problema/error",
|
||||
"ReportIssue": "Informar de Problema",
|
||||
"Filter": "Filtrar",
|
||||
"Sort": "Ordenar",
|
||||
"SeasonNumberHeading": "Temporada: {seasonNumber}",
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
"ContinueButton": "Doorgaan",
|
||||
"Available": "Beschikbaar",
|
||||
"PartiallyAvailable": "Deels Beschikbaar",
|
||||
"Monitored": "Gecontroleerd",
|
||||
"Monitored": "Onder toezicht",
|
||||
"NotAvailable": "Niet Beschikbaar",
|
||||
"ProcessingRequest": "Verzoek wordt verwerkt",
|
||||
"PendingApproval": "Wacht op goedkeuring",
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
"Requests": "Solicitações",
|
||||
"UserManagement": "Gerenciador de Usuário",
|
||||
"Issues": "Problemas",
|
||||
"Vote": "Vote",
|
||||
"Vote": "Votar",
|
||||
"Donate": "Fazer uma doação!",
|
||||
"DonateLibraryMaintainer": "Doar para o Dono da Biblioteca",
|
||||
"DonateTooltip": "É assim que eu convenço a minha mulher a deixar-me passar o meu tempo livre desenvolvendo Ombi;)",
|
||||
|
@ -74,7 +74,7 @@
|
|||
"ViewOnEmby": "Assistir no Emby",
|
||||
"RequestAdded": "Pedido de {{title}} foi adicionado com sucesso",
|
||||
"Similar": "Semelhante",
|
||||
"Refine": "Refine",
|
||||
"Refine": "Filtro",
|
||||
"Movies": {
|
||||
"PopularMovies": "Filmes populares",
|
||||
"UpcomingMovies": "Próximos filmes",
|
||||
|
@ -137,11 +137,11 @@
|
|||
"SortStatusAsc": "Status ▲",
|
||||
"SortStatusDesc": "Status ▼",
|
||||
"Remaining": {
|
||||
"Quota": "{{remaining}}/{{total}} requests remaining",
|
||||
"NextDays": "Another request will be added in {{time}} days",
|
||||
"NextHours": "Another request will be added in {{time}} hours",
|
||||
"NextMinutes": "Another request will be added in {{time}} minutes",
|
||||
"NextMinute": "Another request will be added in {{time}} minute"
|
||||
"Quota": "{{remaining}}/{{total}} solicitações restantes",
|
||||
"NextDays": "Outro pedido será adicionado em {{time}} dias",
|
||||
"NextHours": "Outro pedido será adicionado em {{time}} horas",
|
||||
"NextMinutes": "Outro pedido será adicionado em {{time}} minutos",
|
||||
"NextMinute": "Outro pedido será adicionado em {{time}} minuto"
|
||||
}
|
||||
},
|
||||
"Issues": {
|
||||
|
@ -171,15 +171,15 @@
|
|||
"PendingApproval": "Aprovação Pendente"
|
||||
},
|
||||
"UserManagment": {
|
||||
"TvRemaining": "TV: {{remaining}}/{{total}} remaining",
|
||||
"MovieRemaining": "Movies: {{remaining}}/{{total}} remaining",
|
||||
"MusicRemaining": "Music: {{remaining}}/{{total}} remaining",
|
||||
"TvRemaining": "Tv: {{remaining}}/{{total}} restantes",
|
||||
"MovieRemaining": "Filmes: {{remaining}}/{{total}} restantes",
|
||||
"MusicRemaining": "Música: {{remaining}}/{{total}} restantes",
|
||||
"TvDue": "TV: {{date}}",
|
||||
"MovieDue": "Movie: {{date}}",
|
||||
"MusicDue": "Music: {{date}}"
|
||||
"MovieDue": "Filme: {{date}}",
|
||||
"MusicDue": "Música: {{date}}"
|
||||
},
|
||||
"Votes": {
|
||||
"CompletedVotesTab": "Voted",
|
||||
"VotesTab": "Votes Needed"
|
||||
"CompletedVotesTab": "Votado",
|
||||
"VotesTab": "Votos necessários"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,17 +12,17 @@
|
|||
"Common": {
|
||||
"ContinueButton": "Fortsätt",
|
||||
"Available": "Tillgänglig",
|
||||
"PartiallyAvailable": "Delvis tillgänliga",
|
||||
"PartiallyAvailable": "Delvis tillgänglig",
|
||||
"Monitored": "Övervakad",
|
||||
"NotAvailable": "Finns ej",
|
||||
"NotAvailable": "Inte tillgänglig",
|
||||
"ProcessingRequest": "Bearbetar förfrågan",
|
||||
"PendingApproval": "Väntar på godkännande",
|
||||
"RequestDenied": "Efterfrågan nekas",
|
||||
"NotRequested": "Inte önskad",
|
||||
"Requested": "Begärd önskan",
|
||||
"NotRequested": "Inte begärd",
|
||||
"Requested": "Begärd",
|
||||
"Request": "Begär",
|
||||
"Denied": "Nekad",
|
||||
"Approve": "Godkän",
|
||||
"Approve": "Godkänn",
|
||||
"PartlyAvailable": "Delvis tillgänglig",
|
||||
"Errors": {
|
||||
"Validation": "Vänligen kontrollera din angivna värden"
|
||||
|
@ -34,24 +34,24 @@
|
|||
},
|
||||
"LandingPage": {
|
||||
"OnlineHeading": "Online just nu",
|
||||
"OnlineParagraph": "Medieservern är online för tillfället",
|
||||
"OnlineParagraph": "Medieservern är för närvarande online",
|
||||
"PartiallyOnlineHeading": "Delvis online",
|
||||
"PartiallyOnlineParagraph": "Medieservern är delvis online.",
|
||||
"MultipleServersUnavailable": "Det är {{serversUnavailable}} servrar offline just nu utav {{totalServers}} servrar totalt.",
|
||||
"SingleServerUnavailable": "Det finns {{serversUnavailable}} servrar offline utav {{totalServers}}.",
|
||||
"MultipleServersUnavailable": "Servrar offline: {{serversUnavailable}}, av totalt {{totalServers}}.",
|
||||
"SingleServerUnavailable": "Servrar offline: {{serversUnavailable}}, av totalt {{totalServers}}.",
|
||||
"OfflineHeading": "För närvarande Offline",
|
||||
"OfflineParagraph": "Medieservern är för närvarande offline.",
|
||||
"CheckPageForUpdates": "Kontrollera sidan för kontinuerlig platsuppdateringar."
|
||||
"CheckPageForUpdates": "Håll utkik här för uppdateringar på denna sida."
|
||||
},
|
||||
"NavigationBar": {
|
||||
"Search": "Sök",
|
||||
"Requests": "Förfrågningar",
|
||||
"UserManagement": "Användarhantering",
|
||||
"Issues": "Problem",
|
||||
"Vote": "Vote",
|
||||
"Vote": "Rösta",
|
||||
"Donate": "Donera!",
|
||||
"DonateLibraryMaintainer": "Donera till bibliotekets utvecklare",
|
||||
"DonateTooltip": "Det är så här jag övertygar min fru att jag vill spendera min fritid att utveckla Ombi ;)",
|
||||
"DonateTooltip": "Det är så här jag övertygar min fru att låta mig spendera min fritid att utveckla Ombi ;)",
|
||||
"UpdateAvailableTooltip": "Uppdatering tillgänglig!",
|
||||
"Settings": "Inställningar",
|
||||
"Welcome": "Välkommen {{username}}",
|
||||
|
@ -67,14 +67,14 @@
|
|||
"TvTab": "TV-serier",
|
||||
"MusicTab": "Musik",
|
||||
"Suggestions": "Förslag",
|
||||
"NoResults": "Tyvärr, hittade vi inte några resultat!",
|
||||
"DigitalDate": "Digitalt släpp: {{date}}",
|
||||
"NoResults": "Tyvärr hittade vi inte några resultat!",
|
||||
"DigitalDate": "Digitalt releasedatum: {{date}}",
|
||||
"TheatricalRelease": "Biopremiär: {{date}}",
|
||||
"ViewOnPlex": "Visa på Plex",
|
||||
"ViewOnEmby": "Visa på Emby",
|
||||
"RequestAdded": "Efterfrågan om {{title}} har lagts till",
|
||||
"RequestAdded": "Begäran av {{title}} har lagts till",
|
||||
"Similar": "Liknande",
|
||||
"Refine": "Refine",
|
||||
"Refine": "Förfina",
|
||||
"Movies": {
|
||||
"PopularMovies": "Populära filmer",
|
||||
"UpcomingMovies": "Kommande filmer",
|
||||
|
@ -85,7 +85,7 @@
|
|||
},
|
||||
"TvShows": {
|
||||
"Popular": "Populära",
|
||||
"Trending": "Trendar",
|
||||
"Trending": "Hetast just nu",
|
||||
"MostWatched": "Mest sedda",
|
||||
"MostAnticipated": "Mest efterlängtade",
|
||||
"Results": "Resultat",
|
||||
|
@ -94,59 +94,59 @@
|
|||
"FirstSeason": "Första säsongen",
|
||||
"LatestSeason": "Senaste säsongen",
|
||||
"Select": "Välj...",
|
||||
"SubmitRequest": "Skicka förfrågan",
|
||||
"SubmitRequest": "Skicka begäran",
|
||||
"Season": "Säsong: {{seasonNumber}}",
|
||||
"SelectAllInSeason": "Välj alla i denna säsong {{seasonNumber}}"
|
||||
"SelectAllInSeason": "Välj alla avsnitt i säsong {{seasonNumber}}"
|
||||
}
|
||||
},
|
||||
"Requests": {
|
||||
"Title": "Efterfrågningar",
|
||||
"Paragraph": "Nedan kan du se din och andras efterfrågningar, samt nedladdnings och godkännande status.",
|
||||
"Paragraph": "Nedan kan du se dina och alla andras förfrågningar, samt deras nedladdnings-och godkännandestatus.",
|
||||
"MoviesTab": "Filmer",
|
||||
"TvTab": "TV-serier",
|
||||
"MusicTab": "Musik",
|
||||
"RequestedBy": "Efterfrågats av:",
|
||||
"Status": "Status:",
|
||||
"RequestStatus": "Status för efterfrågan:",
|
||||
"RequestStatus": "Status för begäran:",
|
||||
"Denied": " Nekad:",
|
||||
"TheatricalRelease": "Biopremiär: {{date}}",
|
||||
"ReleaseDate": "Släppt: {{date}}",
|
||||
"ReleaseDate": "Releasedatum: {{date}}",
|
||||
"TheatricalReleaseSort": "Biopremiär",
|
||||
"DigitalRelease": "Digitalt Releasedatum: {{date}}",
|
||||
"RequestDate": "Datum för efterfrågan:",
|
||||
"QualityOverride": "Kvalité överskridande:",
|
||||
"RootFolderOverride": "Root mapp överskridande:",
|
||||
"ChangeRootFolder": "Byt Root mapp",
|
||||
"ChangeQualityProfile": "Byt kvalité profil",
|
||||
"RequestDate": "Datum för begäran:",
|
||||
"QualityOverride": "Kvalitétsöverskridande:",
|
||||
"RootFolderOverride": "Rotmappsöverskridande:",
|
||||
"ChangeRootFolder": "Byt rotmapp",
|
||||
"ChangeQualityProfile": "Byt kvalitétsprofil",
|
||||
"MarkUnavailable": "Markera Otillgänglig",
|
||||
"MarkAvailable": "Markera Tillgänglig",
|
||||
"Remove": "Ta bort",
|
||||
"Deny": "Neka",
|
||||
"Season": "Säsong:",
|
||||
"GridTitle": "Titel",
|
||||
"AirDate": "Sändningsdatum",
|
||||
"AirDate": "Releasedatum",
|
||||
"GridStatus": "Status",
|
||||
"ReportIssue": "Rapportera Problem",
|
||||
"ReportIssue": "Rapportera problem",
|
||||
"Filter": "Filtrera",
|
||||
"Sort": "Sortera",
|
||||
"SeasonNumberHeading": "Säsong: {seasonNumber}",
|
||||
"SortTitleAsc": "Titel ▲",
|
||||
"SortTitleDesc": "Titel ▼",
|
||||
"SortRequestDateAsc": "Efterfrågades ▲",
|
||||
"SortRequestDateDesc": "Efterfrågades ▼",
|
||||
"SortRequestDateAsc": "Datum för begäran ▲",
|
||||
"SortRequestDateDesc": "Datum för begäran ▼",
|
||||
"SortStatusAsc": "Status ▲",
|
||||
"SortStatusDesc": "Status ▼",
|
||||
"Remaining": {
|
||||
"Quota": "{{remaining}}/{{total}} återstående förfrågningar",
|
||||
"NextDays": "En annan begäran kommer att läggas till om {{time}} Dagar",
|
||||
"NextHours": "En annan begäran kommer att läggas till om {{time}} Timmar",
|
||||
"NextMinutes": "En annan begäran kommer att läggas till om {{time}} Minuter",
|
||||
"NextMinute": "En annan begäran kommer att läggas till om {{time}} Minut"
|
||||
"NextDays": "En ny begäran kommer att läggas till om {{time}} Dagar",
|
||||
"NextHours": "En ny begäran kommer att läggas till om {{time}} Timmar",
|
||||
"NextMinutes": "En ny begäran kommer att läggas till om {{time}} Minuter",
|
||||
"NextMinute": "En ny begäran kommer att läggas till om {{time}} Minut"
|
||||
}
|
||||
},
|
||||
"Issues": {
|
||||
"Title": "Problem",
|
||||
"PendingTitle": "Väntande Problem",
|
||||
"PendingTitle": "Väntande problem",
|
||||
"InProgressTitle": "Pågående problem",
|
||||
"ResolvedTitle": "Lösta problem",
|
||||
"ColumnTitle": "Titel",
|
||||
|
@ -171,15 +171,15 @@
|
|||
"PendingApproval": "Väntar på godkännande"
|
||||
},
|
||||
"UserManagment": {
|
||||
"TvRemaining": "TV: {{remaining}}/{{total}} remaining",
|
||||
"MovieRemaining": "Movies: {{remaining}}/{{total}} remaining",
|
||||
"MusicRemaining": "Music: {{remaining}}/{{total}} remaining",
|
||||
"TvRemaining": "TV: {{remaining}}/{{total}} återstående",
|
||||
"MovieRemaining": "Movies: {{remaining}}/{{total}} återstående",
|
||||
"MusicRemaining": "Music: {{remaining}}/{{total}} återstående",
|
||||
"TvDue": "TV: {{date}}",
|
||||
"MovieDue": "Movie: {{date}}",
|
||||
"MusicDue": "Music: {{date}}"
|
||||
},
|
||||
"Votes": {
|
||||
"CompletedVotesTab": "Voted",
|
||||
"VotesTab": "Votes Needed"
|
||||
"CompletedVotesTab": "Röstat",
|
||||
"VotesTab": "Röster krävs"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue