mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-21 05:43:19 -07:00
Merge pull request #3215 from gtbuchanan/adult-filter
Add adult movie filtering
This commit is contained in:
commit
71b850e7bd
20 changed files with 412 additions and 62 deletions
|
@ -93,24 +93,7 @@ namespace Ombi.Api
|
||||||
public void AddQueryString(string key, string value)
|
public void AddQueryString(string key, string value)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) return;
|
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) return;
|
||||||
|
_modified = FullUri.AddQueryParameter(key, value);
|
||||||
var builder = new UriBuilder(FullUri);
|
|
||||||
var startingTag = string.Empty;
|
|
||||||
var hasQuery = false;
|
|
||||||
if (string.IsNullOrEmpty(builder.Query))
|
|
||||||
{
|
|
||||||
startingTag = "?";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
hasQuery = true;
|
|
||||||
startingTag = builder.Query.Contains("?") ? "&" : "?";
|
|
||||||
}
|
|
||||||
builder.Query = hasQuery
|
|
||||||
? $"{builder.Query}{startingTag}{key}={value}"
|
|
||||||
: $"{startingTag}{key}={value}";
|
|
||||||
|
|
||||||
_modified = builder.Uri;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddJsonBody(object obj)
|
public void AddJsonBody(object obj)
|
||||||
|
|
11
src/Ombi.Settings/Settings/Models/External/TheMovieDbSettings.cs
vendored
Normal file
11
src/Ombi.Settings/Settings/Models/External/TheMovieDbSettings.cs
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Settings.Models.External
|
||||||
|
{
|
||||||
|
public sealed class TheMovieDbSettings : Ombi.Settings.Settings.Models.Settings
|
||||||
|
{
|
||||||
|
public bool ShowAdultMovies { get; set; }
|
||||||
|
|
||||||
|
public List<int> ExcludedKeywordIds { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,5 +21,7 @@ namespace Ombi.Api.TheMovieDb
|
||||||
Task<TvInfo> GetTVInfo(string themoviedbid);
|
Task<TvInfo> GetTVInfo(string themoviedbid);
|
||||||
Task<TheMovieDbContainer<ActorResult>> SearchByActor(string searchTerm, string langCode);
|
Task<TheMovieDbContainer<ActorResult>> SearchByActor(string searchTerm, string langCode);
|
||||||
Task<ActorCredits> GetActorMovieCredits(int actorId, string langCode);
|
Task<ActorCredits> GetActorMovieCredits(int actorId, string langCode);
|
||||||
|
Task<List<Keyword>> SearchKeyword(string searchTerm);
|
||||||
|
Task<Keyword> GetKeyword(int keywordId);
|
||||||
}
|
}
|
||||||
}
|
}
|
13
src/Ombi.TheMovieDbApi/Models/Keyword.cs
Normal file
13
src/Ombi.TheMovieDbApi/Models/Keyword.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace Ombi.Api.TheMovieDb.Models
|
||||||
|
{
|
||||||
|
public sealed class Keyword
|
||||||
|
{
|
||||||
|
[DataMember(Name = "id")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Name = "name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,31 +1,37 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
|
using Nito.AsyncEx;
|
||||||
using Ombi.Api.TheMovieDb.Models;
|
using Ombi.Api.TheMovieDb.Models;
|
||||||
using Ombi.Helpers;
|
using Ombi.Core.Settings;
|
||||||
|
using Ombi.Core.Settings.Models.External;
|
||||||
using Ombi.TheMovieDbApi.Models;
|
using Ombi.TheMovieDbApi.Models;
|
||||||
|
|
||||||
namespace Ombi.Api.TheMovieDb
|
namespace Ombi.Api.TheMovieDb
|
||||||
{
|
{
|
||||||
public class TheMovieDbApi : IMovieDbApi
|
public class TheMovieDbApi : IMovieDbApi
|
||||||
{
|
{
|
||||||
public TheMovieDbApi(IMapper mapper, IApi api)
|
public TheMovieDbApi(IMapper mapper, IApi api, ISettingsService<TheMovieDbSettings> settingsService)
|
||||||
{
|
{
|
||||||
Api = api;
|
Api = api;
|
||||||
Mapper = mapper;
|
Mapper = mapper;
|
||||||
|
Settings = new AsyncLazy<TheMovieDbSettings>(() => settingsService.GetSettingsAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const string ApiToken = "b8eabaf5608b88d0298aa189dd90bf00";
|
||||||
|
private const string BaseUri ="http://api.themoviedb.org/3/";
|
||||||
private IMapper Mapper { get; }
|
private IMapper Mapper { get; }
|
||||||
private readonly string ApiToken = "b8eabaf5608b88d0298aa189dd90bf00";
|
|
||||||
private static readonly string BaseUri ="http://api.themoviedb.org/3/";
|
|
||||||
private IApi Api { get; }
|
private IApi Api { get; }
|
||||||
|
private AsyncLazy<TheMovieDbSettings> Settings { get; }
|
||||||
|
|
||||||
public async Task<MovieResponseDto> GetMovieInformation(int movieId)
|
public async Task<MovieResponseDto> GetMovieInformation(int movieId)
|
||||||
{
|
{
|
||||||
var request = new Request($"movie/{movieId}", BaseUri, HttpMethod.Get);
|
var request = new Request($"movie/{movieId}", BaseUri, HttpMethod.Get);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
request.AddQueryString("api_key", ApiToken);
|
||||||
AddRetry(request);
|
AddRetry(request);
|
||||||
|
|
||||||
var result = await Api.Request<MovieResponse>(request);
|
var result = await Api.Request<MovieResponse>(request);
|
||||||
|
@ -35,7 +41,7 @@ namespace Ombi.Api.TheMovieDb
|
||||||
public async Task<FindResult> Find(string externalId, ExternalSource source)
|
public async Task<FindResult> Find(string externalId, ExternalSource source)
|
||||||
{
|
{
|
||||||
var request = new Request($"find/{externalId}", BaseUri, HttpMethod.Get);
|
var request = new Request($"find/{externalId}", BaseUri, HttpMethod.Get);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
request.AddQueryString("api_key", ApiToken);
|
||||||
AddRetry(request);
|
AddRetry(request);
|
||||||
|
|
||||||
request.AddQueryString("external_source", source.ToString());
|
request.AddQueryString("external_source", source.ToString());
|
||||||
|
@ -46,9 +52,11 @@ namespace Ombi.Api.TheMovieDb
|
||||||
public async Task<TheMovieDbContainer<ActorResult>> SearchByActor(string searchTerm, string langCode)
|
public async Task<TheMovieDbContainer<ActorResult>> SearchByActor(string searchTerm, string langCode)
|
||||||
{
|
{
|
||||||
var request = new Request($"search/person", BaseUri, HttpMethod.Get);
|
var request = new Request($"search/person", BaseUri, HttpMethod.Get);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
request.AddQueryString("api_key", ApiToken);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("query", searchTerm);
|
request.AddQueryString("query", searchTerm);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("language", langCode);
|
request.AddQueryString("language", langCode);
|
||||||
|
var settings = await Settings;
|
||||||
|
request.AddQueryString("include_adult", settings.ShowAdultMovies.ToString().ToLower());
|
||||||
|
|
||||||
var result = await Api.Request<TheMovieDbContainer<ActorResult>>(request);
|
var result = await Api.Request<TheMovieDbContainer<ActorResult>>(request);
|
||||||
return result;
|
return result;
|
||||||
|
@ -57,8 +65,8 @@ namespace Ombi.Api.TheMovieDb
|
||||||
public async Task<ActorCredits> GetActorMovieCredits(int actorId, string langCode)
|
public async Task<ActorCredits> GetActorMovieCredits(int actorId, string langCode)
|
||||||
{
|
{
|
||||||
var request = new Request($"person/{actorId}/movie_credits", BaseUri, HttpMethod.Get);
|
var request = new Request($"person/{actorId}/movie_credits", BaseUri, HttpMethod.Get);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
request.AddQueryString("api_key", ApiToken);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("language", langCode);
|
request.AddQueryString("language", langCode);
|
||||||
|
|
||||||
var result = await Api.Request<ActorCredits>(request);
|
var result = await Api.Request<ActorCredits>(request);
|
||||||
return result;
|
return result;
|
||||||
|
@ -67,8 +75,8 @@ namespace Ombi.Api.TheMovieDb
|
||||||
public async Task<List<TvSearchResult>> SearchTv(string searchTerm)
|
public async Task<List<TvSearchResult>> SearchTv(string searchTerm)
|
||||||
{
|
{
|
||||||
var request = new Request($"search/tv", BaseUri, HttpMethod.Get);
|
var request = new Request($"search/tv", BaseUri, HttpMethod.Get);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
request.AddQueryString("api_key", ApiToken);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("query", searchTerm);
|
request.AddQueryString("query", searchTerm);
|
||||||
AddRetry(request);
|
AddRetry(request);
|
||||||
|
|
||||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||||
|
@ -78,7 +86,7 @@ namespace Ombi.Api.TheMovieDb
|
||||||
public async Task<TvExternals> GetTvExternals(int theMovieDbId)
|
public async Task<TvExternals> GetTvExternals(int theMovieDbId)
|
||||||
{
|
{
|
||||||
var request = new Request($"/tv/{theMovieDbId}/external_ids", BaseUri, HttpMethod.Get);
|
var request = new Request($"/tv/{theMovieDbId}/external_ids", BaseUri, HttpMethod.Get);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
request.AddQueryString("api_key", ApiToken);
|
||||||
AddRetry(request);
|
AddRetry(request);
|
||||||
|
|
||||||
return await Api.Request<TvExternals>(request);
|
return await Api.Request<TvExternals>(request);
|
||||||
|
@ -87,9 +95,8 @@ namespace Ombi.Api.TheMovieDb
|
||||||
public async Task<List<MovieSearchResult>> SimilarMovies(int movieId, string langCode)
|
public async Task<List<MovieSearchResult>> SimilarMovies(int movieId, string langCode)
|
||||||
{
|
{
|
||||||
var request = new Request($"movie/{movieId}/similar", BaseUri, HttpMethod.Get);
|
var request = new Request($"movie/{movieId}/similar", BaseUri, HttpMethod.Get);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
request.AddQueryString("api_key", ApiToken);
|
||||||
|
request.AddQueryString("language", langCode);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("language", langCode);
|
|
||||||
AddRetry(request);
|
AddRetry(request);
|
||||||
|
|
||||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||||
|
@ -99,65 +106,113 @@ namespace Ombi.Api.TheMovieDb
|
||||||
public async Task<MovieResponseDto> GetMovieInformationWithExtraInfo(int movieId, string langCode = "en")
|
public async Task<MovieResponseDto> GetMovieInformationWithExtraInfo(int movieId, string langCode = "en")
|
||||||
{
|
{
|
||||||
var request = new Request($"movie/{movieId}", BaseUri, HttpMethod.Get);
|
var request = new Request($"movie/{movieId}", BaseUri, HttpMethod.Get);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
request.AddQueryString("api_key", ApiToken);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("append_to_response", "videos,release_dates");
|
request.AddQueryString("append_to_response", "videos,release_dates");
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("language", langCode);
|
request.AddQueryString("language", langCode);
|
||||||
AddRetry(request);
|
AddRetry(request);
|
||||||
var result = await Api.Request<MovieResponse>(request);
|
var result = await Api.Request<MovieResponse>(request);
|
||||||
return Mapper.Map<MovieResponseDto>(result);
|
return Mapper.Map<MovieResponseDto>(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<MovieSearchResult>> SearchMovie(string searchTerm, int? year, string langageCode)
|
public async Task<List<MovieSearchResult>> SearchMovie(string searchTerm, int? year, string langCode)
|
||||||
{
|
{
|
||||||
var request = new Request($"search/movie", BaseUri, HttpMethod.Get);
|
var request = new Request($"search/movie", BaseUri, HttpMethod.Get);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
request.AddQueryString("api_key", ApiToken);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("query", searchTerm);
|
request.AddQueryString("query", searchTerm);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("language", langageCode);
|
request.AddQueryString("language", langCode);
|
||||||
if (year.HasValue && year.Value > 0)
|
if (year.HasValue && year.Value > 0)
|
||||||
{
|
{
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("year", year.Value.ToString());
|
request.AddQueryString("year", year.Value.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var settings = await Settings;
|
||||||
|
request.AddQueryString("include_adult", settings.ShowAdultMovies.ToString().ToLower());
|
||||||
|
|
||||||
AddRetry(request);
|
AddRetry(request);
|
||||||
|
|
||||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<MovieSearchResult>> PopularMovies(string langageCode)
|
/// <remarks>
|
||||||
|
/// Maintains filter parity with <a href="https://developers.themoviedb.org/3/movies/get-popular-movies">/movie/popular</a>.
|
||||||
|
/// </remarks>
|
||||||
|
public async Task<List<MovieSearchResult>> PopularMovies(string langCode)
|
||||||
{
|
{
|
||||||
var request = new Request($"movie/popular", BaseUri, HttpMethod.Get);
|
var request = new Request($"discover/movie", BaseUri, HttpMethod.Get);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
request.AddQueryString("api_key", ApiToken);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("language", langageCode);
|
request.AddQueryString("language", langCode);
|
||||||
|
request.AddQueryString("sort_by", "popularity.desc");
|
||||||
|
await AddDiscoverMovieSettings(request);
|
||||||
AddRetry(request);
|
AddRetry(request);
|
||||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<MovieSearchResult>> TopRated(string langageCode)
|
/// <remarks>
|
||||||
|
/// Maintains filter parity with <a href="https://developers.themoviedb.org/3/movies/get-top-rated-movies">/movie/top_rated</a>.
|
||||||
|
/// </remarks>
|
||||||
|
public async Task<List<MovieSearchResult>> TopRated(string langCode)
|
||||||
{
|
{
|
||||||
var request = new Request($"movie/top_rated", BaseUri, HttpMethod.Get);
|
var request = new Request($"discover/movie", BaseUri, HttpMethod.Get);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
request.AddQueryString("api_key", ApiToken);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("language", langageCode);
|
request.AddQueryString("language", langCode);
|
||||||
|
request.AddQueryString("sort_by", "vote_average.desc");
|
||||||
|
|
||||||
|
// `vote_count` consideration isn't explicitly documented, but using only the `sort_by` filter
|
||||||
|
// does not provide the same results as `/movie/top_rated`. This appears to be adequate enough
|
||||||
|
// to filter out extremely high-rated movies due to very little votes
|
||||||
|
request.AddQueryString("vote_count.gte", "250");
|
||||||
|
|
||||||
|
await AddDiscoverMovieSettings(request);
|
||||||
AddRetry(request);
|
AddRetry(request);
|
||||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<MovieSearchResult>> Upcoming(string langageCode)
|
/// <remarks>
|
||||||
|
/// Maintains filter parity with <a href="https://developers.themoviedb.org/3/movies/get-upcoming">/movie/upcoming</a>.
|
||||||
|
/// </remarks>
|
||||||
|
public async Task<List<MovieSearchResult>> Upcoming(string langCode)
|
||||||
{
|
{
|
||||||
var request = new Request($"movie/upcoming", BaseUri, HttpMethod.Get);
|
var request = new Request($"discover/movie", BaseUri, HttpMethod.Get);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
request.AddQueryString("api_key", ApiToken);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("language", langageCode);
|
request.AddQueryString("language", langCode);
|
||||||
|
|
||||||
|
// Release types "2 or 3" explicitly stated as used in docs
|
||||||
|
request.AddQueryString("with_release_type", "2|3");
|
||||||
|
|
||||||
|
// The date range being used in `/movie/upcoming` isn't documented, but we infer it is
|
||||||
|
// an offset from today based on the minimum and maximum date they provide in the output
|
||||||
|
var startDate = DateTime.Today.AddDays(7);
|
||||||
|
request.AddQueryString("release_date.gte", startDate.ToString("yyyy-MM-dd"));
|
||||||
|
request.AddQueryString("release_date.lte", startDate.AddDays(17).ToString("yyyy-MM-dd"));
|
||||||
|
|
||||||
|
await AddDiscoverMovieSettings(request);
|
||||||
AddRetry(request);
|
AddRetry(request);
|
||||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<MovieSearchResult>> NowPlaying(string langageCode)
|
/// <remarks>
|
||||||
|
/// Maintains filter parity with <a href="https://developers.themoviedb.org/3/movies/get-now-playing">/movie/now_playing</a>.
|
||||||
|
/// </remarks>
|
||||||
|
public async Task<List<MovieSearchResult>> NowPlaying(string langCode)
|
||||||
{
|
{
|
||||||
var request = new Request($"movie/now_playing", BaseUri, HttpMethod.Get);
|
var request = new Request($"discover/movie", BaseUri, HttpMethod.Get);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
request.AddQueryString("api_key", ApiToken);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("language", langageCode);
|
request.AddQueryString("language", langCode);
|
||||||
|
|
||||||
|
// Release types "2 or 3" explicitly stated as used in docs
|
||||||
|
request.AddQueryString("with_release_type", "2|3");
|
||||||
|
|
||||||
|
// The date range being used in `/movie/now_playing` isn't documented, but we infer it is
|
||||||
|
// an offset from today based on the minimum and maximum date they provide in the output
|
||||||
|
var today = DateTime.Today;
|
||||||
|
request.AddQueryString("release_date.gte", today.AddDays(-42).ToString("yyyy-MM-dd"));
|
||||||
|
request.AddQueryString("release_date.lte", today.AddDays(6).ToString("yyyy-MM-dd"));
|
||||||
|
|
||||||
|
await AddDiscoverMovieSettings(request);
|
||||||
AddRetry(request);
|
AddRetry(request);
|
||||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||||
|
@ -166,11 +221,43 @@ namespace Ombi.Api.TheMovieDb
|
||||||
public async Task<TvInfo> GetTVInfo(string themoviedbid)
|
public async Task<TvInfo> GetTVInfo(string themoviedbid)
|
||||||
{
|
{
|
||||||
var request = new Request($"/tv/{themoviedbid}", BaseUri, HttpMethod.Get);
|
var request = new Request($"/tv/{themoviedbid}", BaseUri, HttpMethod.Get);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
request.AddQueryString("api_key", ApiToken);
|
||||||
AddRetry(request);
|
AddRetry(request);
|
||||||
|
|
||||||
return await Api.Request<TvInfo>(request);
|
return await Api.Request<TvInfo>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<Keyword>> SearchKeyword(string searchTerm)
|
||||||
|
{
|
||||||
|
var request = new Request("search/keyword", BaseUri, HttpMethod.Get);
|
||||||
|
request.AddQueryString("api_key", ApiToken);
|
||||||
|
request.AddQueryString("query", searchTerm);
|
||||||
|
AddRetry(request);
|
||||||
|
|
||||||
|
var result = await Api.Request<TheMovieDbContainer<Keyword>>(request);
|
||||||
|
return result.results ?? new List<Keyword>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Keyword> GetKeyword(int keywordId)
|
||||||
|
{
|
||||||
|
var request = new Request($"keyword/{keywordId}", BaseUri, HttpMethod.Get);
|
||||||
|
request.AddQueryString("api_key", ApiToken);
|
||||||
|
AddRetry(request);
|
||||||
|
|
||||||
|
var keyword = await Api.Request<Keyword>(request);
|
||||||
|
return keyword == null || keyword.Id == 0 ? null : keyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddDiscoverMovieSettings(Request request)
|
||||||
|
{
|
||||||
|
var settings = await Settings;
|
||||||
|
request.AddQueryString("include_adult", settings.ShowAdultMovies.ToString().ToLower());
|
||||||
|
if (settings.ExcludedKeywordIds?.Any() == true)
|
||||||
|
{
|
||||||
|
request.AddQueryString("without_keywords", string.Join(",", settings.ExcludedKeywordIds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void AddRetry(Request request)
|
private static void AddRetry(Request request)
|
||||||
{
|
{
|
||||||
request.Retry = true;
|
request.Retry = true;
|
||||||
|
|
4
src/Ombi/ClientApp/app/interfaces/IMovieDb.ts
Normal file
4
src/Ombi/ClientApp/app/interfaces/IMovieDb.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export interface IMovieDbKeyword {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
|
@ -245,3 +245,8 @@ export interface IVoteSettings extends ISettings {
|
||||||
musicVoteMax: number;
|
musicVoteMax: number;
|
||||||
tvShowVoteMax: number;
|
tvShowVoteMax: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ITheMovieDbSettings extends ISettings {
|
||||||
|
showAdultMovies: boolean;
|
||||||
|
excludedKeywordIds: number[];
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ export * from "./ICouchPotato";
|
||||||
export * from "./IImages";
|
export * from "./IImages";
|
||||||
export * from "./IMediaServerStatus";
|
export * from "./IMediaServerStatus";
|
||||||
export * from "./INotificationSettings";
|
export * from "./INotificationSettings";
|
||||||
|
export * from "./IMovieDb";
|
||||||
export * from "./IPlex";
|
export * from "./IPlex";
|
||||||
export * from "./IRadarr";
|
export * from "./IRadarr";
|
||||||
export * from "./IRequestEngineResult";
|
export * from "./IRequestEngineResult";
|
||||||
|
|
|
@ -7,3 +7,4 @@ export * from "./tester.service";
|
||||||
export * from "./plexoauth.service";
|
export * from "./plexoauth.service";
|
||||||
export * from "./plextv.service";
|
export * from "./plextv.service";
|
||||||
export * from "./lidarr.service";
|
export * from "./lidarr.service";
|
||||||
|
export * from "./themoviedb.service";
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { PlatformLocation } from "@angular/common";
|
||||||
|
import { HttpClient, HttpErrorResponse, HttpParams } from "@angular/common/http";
|
||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { empty, Observable, throwError } from "rxjs";
|
||||||
|
import { catchError } from "rxjs/operators";
|
||||||
|
|
||||||
|
import { IMovieDbKeyword } from "../../interfaces";
|
||||||
|
import { ServiceHelpers } from "../service.helpers";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TheMovieDbService extends ServiceHelpers {
|
||||||
|
constructor(http: HttpClient, public platformLocation: PlatformLocation) {
|
||||||
|
super(http, "/api/v1/TheMovieDb", platformLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getKeywords(searchTerm: string): Observable<IMovieDbKeyword[]> {
|
||||||
|
const params = new HttpParams().set("searchTerm", searchTerm);
|
||||||
|
return this.http.get<IMovieDbKeyword[]>(`${this.url}/Keywords`, {headers: this.headers, params});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getKeyword(keywordId: number): Observable<IMovieDbKeyword> {
|
||||||
|
return this.http.get<IMovieDbKeyword>(`${this.url}/Keywords/${keywordId}`, { headers: this.headers })
|
||||||
|
.pipe(catchError((error: HttpErrorResponse) => error.status === 404 ? empty() : throwError(error)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ import {
|
||||||
ISlackNotificationSettings,
|
ISlackNotificationSettings,
|
||||||
ISonarrSettings,
|
ISonarrSettings,
|
||||||
ITelegramNotifcationSettings,
|
ITelegramNotifcationSettings,
|
||||||
|
ITheMovieDbSettings,
|
||||||
IUpdateSettings,
|
IUpdateSettings,
|
||||||
IUserManagementSettings,
|
IUserManagementSettings,
|
||||||
IVoteSettings,
|
IVoteSettings,
|
||||||
|
@ -301,6 +302,14 @@ export class SettingsService extends ServiceHelpers {
|
||||||
return this.http.post<boolean>(`${this.url}/vote`, JSON.stringify(settings), {headers: this.headers});
|
return this.http.post<boolean>(`${this.url}/vote`, JSON.stringify(settings), {headers: this.headers});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getTheMovieDbSettings(): Observable<ITheMovieDbSettings> {
|
||||||
|
return this.http.get<ITheMovieDbSettings>(`${this.url}/themoviedb`, {headers: this.headers});
|
||||||
|
}
|
||||||
|
|
||||||
|
public saveTheMovieDbSettings(settings: ITheMovieDbSettings) {
|
||||||
|
return this.http.post<boolean>(`${this.url}/themoviedb`, JSON.stringify(settings), {headers: this.headers});
|
||||||
|
}
|
||||||
|
|
||||||
public getNewsletterSettings(): Observable<INewsletterNotificationSettings> {
|
public getNewsletterSettings(): Observable<INewsletterNotificationSettings> {
|
||||||
return this.http.get<INewsletterNotificationSettings>(`${this.url}/notifications/newsletter`, {headers: this.headers});
|
return this.http.get<INewsletterNotificationSettings>(`${this.url}/notifications/newsletter`, {headers: this.headers});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,14 @@ import { NgModule } from "@angular/core";
|
||||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||||
import { RouterModule, Routes } from "@angular/router";
|
import { RouterModule, Routes } from "@angular/router";
|
||||||
import { NgbAccordionModule, NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
import { NgbAccordionModule, NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
||||||
|
import { TagInputModule } from "ngx-chips";
|
||||||
import { ClipboardModule } from "ngx-clipboard";
|
import { ClipboardModule } from "ngx-clipboard";
|
||||||
|
|
||||||
import { AuthGuard } from "../auth/auth.guard";
|
import { AuthGuard } from "../auth/auth.guard";
|
||||||
import { AuthService } from "../auth/auth.service";
|
import { AuthService } from "../auth/auth.service";
|
||||||
import {
|
import {
|
||||||
CouchPotatoService, EmbyService, IssuesService, JobService, LidarrService, MobileService, NotificationMessageService, PlexService, RadarrService,
|
CouchPotatoService, EmbyService, IssuesService, JobService, LidarrService, MobileService, NotificationMessageService, PlexService, RadarrService,
|
||||||
RequestRetryService, SonarrService, TesterService, ValidationService,
|
RequestRetryService, SonarrService, TesterService, TheMovieDbService, ValidationService,
|
||||||
} from "../services";
|
} from "../services";
|
||||||
|
|
||||||
import { PipeModule } from "../pipes/pipe.module";
|
import { PipeModule } from "../pipes/pipe.module";
|
||||||
|
@ -41,6 +42,7 @@ import { PlexComponent } from "./plex/plex.component";
|
||||||
import { RadarrComponent } from "./radarr/radarr.component";
|
import { RadarrComponent } from "./radarr/radarr.component";
|
||||||
import { SickRageComponent } from "./sickrage/sickrage.component";
|
import { SickRageComponent } from "./sickrage/sickrage.component";
|
||||||
import { SonarrComponent } from "./sonarr/sonarr.component";
|
import { SonarrComponent } from "./sonarr/sonarr.component";
|
||||||
|
import { TheMovieDbComponent } from "./themoviedb/themoviedb.component";
|
||||||
import { UpdateComponent } from "./update/update.component";
|
import { UpdateComponent } from "./update/update.component";
|
||||||
import { UserManagementComponent } from "./usermanagement/usermanagement.component";
|
import { UserManagementComponent } from "./usermanagement/usermanagement.component";
|
||||||
import { VoteComponent } from "./vote/vote.component";
|
import { VoteComponent } from "./vote/vote.component";
|
||||||
|
@ -80,6 +82,7 @@ const routes: Routes = [
|
||||||
{ path: "Newsletter", component: NewsletterComponent, canActivate: [AuthGuard] },
|
{ path: "Newsletter", component: NewsletterComponent, canActivate: [AuthGuard] },
|
||||||
{ path: "Lidarr", component: LidarrComponent, canActivate: [AuthGuard] },
|
{ path: "Lidarr", component: LidarrComponent, canActivate: [AuthGuard] },
|
||||||
{ path: "Vote", component: VoteComponent, canActivate: [AuthGuard] },
|
{ path: "Vote", component: VoteComponent, canActivate: [AuthGuard] },
|
||||||
|
{ path: "TheMovieDb", component: TheMovieDbComponent, canActivate: [AuthGuard] },
|
||||||
{ path: "FailedRequests", component: FailedRequestsComponent, canActivate: [AuthGuard] },
|
{ path: "FailedRequests", component: FailedRequestsComponent, canActivate: [AuthGuard] },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -97,6 +100,7 @@ const routes: Routes = [
|
||||||
NgbAccordionModule,
|
NgbAccordionModule,
|
||||||
AutoCompleteModule,
|
AutoCompleteModule,
|
||||||
CalendarModule,
|
CalendarModule,
|
||||||
|
TagInputModule,
|
||||||
ClipboardModule,
|
ClipboardModule,
|
||||||
PipeModule,
|
PipeModule,
|
||||||
RadioButtonModule,
|
RadioButtonModule,
|
||||||
|
@ -135,6 +139,7 @@ const routes: Routes = [
|
||||||
NewsletterComponent,
|
NewsletterComponent,
|
||||||
LidarrComponent,
|
LidarrComponent,
|
||||||
VoteComponent,
|
VoteComponent,
|
||||||
|
TheMovieDbComponent,
|
||||||
FailedRequestsComponent,
|
FailedRequestsComponent,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
|
@ -156,6 +161,7 @@ const routes: Routes = [
|
||||||
NotificationMessageService,
|
NotificationMessageService,
|
||||||
LidarrService,
|
LidarrService,
|
||||||
RequestRetryService,
|
RequestRetryService,
|
||||||
|
TheMovieDbService,
|
||||||
],
|
],
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/UserManagement']">User Importer</a></li>
|
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/UserManagement']">User Importer</a></li>
|
||||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Authentication']">Authentication</a></li>
|
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Authentication']">Authentication</a></li>
|
||||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Vote']">Vote</a></li>
|
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Vote']">Vote</a></li>
|
||||||
|
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/TheMovieDb']">The Movie Database</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
<settings-menu></settings-menu>
|
||||||
|
|
||||||
|
<fieldset *ngIf="settings">
|
||||||
|
<legend>The Movie Database</legend>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="checkbox">
|
||||||
|
<input type="checkbox" id="showAdultMovies" name="showAdultMovies" [(ngModel)]="settings.showAdultMovies">
|
||||||
|
<label for="showAdultMovies" tooltipPosition="top" pTooltip="Include adult movies (pornography) in results">Show Adult Movies</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label" pTooltip="Prevent movies with certain keywords from being suggested. May require a restart to take effect.">
|
||||||
|
Excluded Keyword IDs for Movie Suggestions
|
||||||
|
</label>
|
||||||
|
<tag-input #input
|
||||||
|
[(ngModel)]="excludedKeywords"
|
||||||
|
[identifyBy]="'id'" [displayBy]="'name'"
|
||||||
|
[placeholder]="'Search by keyword'"
|
||||||
|
[secondaryPlaceholder]="'Search by keyword'"
|
||||||
|
[theme]="'dark'"
|
||||||
|
[onTextChangeDebounce]="500"
|
||||||
|
[onAdding]="onAddingKeyword"
|
||||||
|
(onSelect)="onKeywordSelect($event)">
|
||||||
|
<ng-template item-template let-item="item" let-index="index">
|
||||||
|
<span class="fa fa-cloud-download" *ngIf="item.initial"></span>
|
||||||
|
<span>{{item.id}}</span>
|
||||||
|
<span *ngIf="!item.initial"> ({{item.name}})</span>
|
||||||
|
<delete-icon aria-label="Remove tag" role="button"
|
||||||
|
(click)="input.removeItem(item, index)">
|
||||||
|
</delete-icon>
|
||||||
|
</ng-template>
|
||||||
|
<tag-input-dropdown [autocompleteObservable]="autocompleteKeyword"
|
||||||
|
[identifyBy]="'id'" [displayBy]="'name'"
|
||||||
|
[limitItemsTo]="6"
|
||||||
|
[minimumTextLength]="1"
|
||||||
|
[showDropdownIfEmpty]="false"
|
||||||
|
[keepOpen]="false">
|
||||||
|
</tag-input-dropdown>
|
||||||
|
</tag-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div>
|
||||||
|
<button (click)="save()" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
import { empty, of } from "rxjs";
|
||||||
|
|
||||||
|
import { ITheMovieDbSettings } from "../../interfaces";
|
||||||
|
import { NotificationService } from "../../services";
|
||||||
|
import { SettingsService } from "../../services";
|
||||||
|
import { TheMovieDbService } from "../../services";
|
||||||
|
|
||||||
|
interface IKeywordTag {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
initial: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: "./themoviedb.component.html",
|
||||||
|
})
|
||||||
|
export class TheMovieDbComponent implements OnInit {
|
||||||
|
|
||||||
|
public settings: ITheMovieDbSettings;
|
||||||
|
public excludedKeywords: IKeywordTag[];
|
||||||
|
|
||||||
|
constructor(private settingsService: SettingsService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
|
private tmdbService: TheMovieDbService) { }
|
||||||
|
|
||||||
|
public ngOnInit() {
|
||||||
|
this.settingsService.getTheMovieDbSettings().subscribe(settings => {
|
||||||
|
this.settings = settings;
|
||||||
|
this.excludedKeywords = settings.excludedKeywordIds
|
||||||
|
? settings.excludedKeywordIds.map(id => ({
|
||||||
|
id,
|
||||||
|
name: "",
|
||||||
|
initial: true,
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public autocompleteKeyword = (text: string) => this.tmdbService.getKeywords(text);
|
||||||
|
|
||||||
|
public onAddingKeyword = (tag: string | IKeywordTag) => {
|
||||||
|
if (typeof tag === "string") {
|
||||||
|
const id = Number(tag);
|
||||||
|
return isNaN(id) ? empty() : this.tmdbService.getKeyword(id);
|
||||||
|
} else {
|
||||||
|
return of(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public onKeywordSelect = (keyword: IKeywordTag) => {
|
||||||
|
if (keyword.initial) {
|
||||||
|
this.tmdbService.getKeyword(keyword.id)
|
||||||
|
.subscribe(k => {
|
||||||
|
keyword.name = k.name;
|
||||||
|
keyword.initial = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public save() {
|
||||||
|
this.settings.excludedKeywordIds = this.excludedKeywords.map(k => k.id);
|
||||||
|
this.settingsService.saveTheMovieDbSettings(this.settings).subscribe(x => {
|
||||||
|
if (x) {
|
||||||
|
this.notificationService.success("Successfully saved The Movie Database settings");
|
||||||
|
} else {
|
||||||
|
this.notificationService.success("There was an error when saving The Movie Database settings");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1031,6 +1031,14 @@ a > h4:hover {
|
||||||
width:300px;
|
width:300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ng2-tag-input.dark input {
|
||||||
|
background: transparent;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ng2-tag-input .fa-cloud-download {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
::ng-deep ngb-accordion > div.card > div.card-header {
|
::ng-deep ngb-accordion > div.card > div.card-header {
|
||||||
padding:0px;
|
padding:0px;
|
||||||
|
|
38
src/Ombi/Controllers/External/TheMovieDbController.cs
vendored
Normal file
38
src/Ombi/Controllers/External/TheMovieDbController.cs
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Ombi.Api.TheMovieDb;
|
||||||
|
using Ombi.Api.TheMovieDb.Models;
|
||||||
|
using Ombi.Attributes;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ombi.Controllers.External
|
||||||
|
{
|
||||||
|
[Admin]
|
||||||
|
[ApiV1]
|
||||||
|
[Produces("application/json")]
|
||||||
|
public sealed class TheMovieDbController : Controller
|
||||||
|
{
|
||||||
|
public TheMovieDbController(IMovieDbApi tmdbApi) => TmdbApi = tmdbApi;
|
||||||
|
|
||||||
|
private IMovieDbApi TmdbApi { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Searches for keywords matching the specified term.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="searchTerm">The search term.</param>
|
||||||
|
[HttpGet("Keywords")]
|
||||||
|
public async Task<IEnumerable<Keyword>> GetKeywords([FromQuery]string searchTerm) =>
|
||||||
|
await TmdbApi.SearchKeyword(searchTerm);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the keyword matching the specified ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="keywordId">The keyword ID.</param>
|
||||||
|
[HttpGet("Keywords/{keywordId}")]
|
||||||
|
public async Task<IActionResult> GetKeywords(int keywordId)
|
||||||
|
{
|
||||||
|
var keyword = await TmdbApi.GetKeyword(keywordId);
|
||||||
|
return keyword == null ? NotFound() : (IActionResult)Ok(keyword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -659,6 +659,25 @@ namespace Ombi.Controllers
|
||||||
return vote.Enabled;
|
return vote.Enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save The Movie DB settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="settings">The settings.</param>
|
||||||
|
[HttpPost("themoviedb")]
|
||||||
|
public async Task<bool> TheMovieDbSettings([FromBody]TheMovieDbSettings settings)
|
||||||
|
{
|
||||||
|
return await Save(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get The Movie DB settings.
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("themoviedb")]
|
||||||
|
public async Task<TheMovieDbSettings> TheMovieDbSettings()
|
||||||
|
{
|
||||||
|
return await Get<TheMovieDbSettings>();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Saves the email notification settings.
|
/// Saves the email notification settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
"natives": "1.1.6",
|
"natives": "1.1.6",
|
||||||
"ng2-cookies": "^1.0.12",
|
"ng2-cookies": "^1.0.12",
|
||||||
"ngx-bootstrap": "^3.1.4",
|
"ngx-bootstrap": "^3.1.4",
|
||||||
|
"ngx-chips": "^2.1.0",
|
||||||
"ngx-clipboard": "^11.1.1",
|
"ngx-clipboard": "^11.1.1",
|
||||||
"ngx-editor": "^4.1.0",
|
"ngx-editor": "^4.1.0",
|
||||||
"ngx-infinite-scroll": "^6.0.1",
|
"ngx-infinite-scroll": "^6.0.1",
|
||||||
|
|
|
@ -4183,10 +4183,25 @@ ng2-cookies@^1.0.12:
|
||||||
version "1.0.12"
|
version "1.0.12"
|
||||||
resolved "https://registry.yarnpkg.com/ng2-cookies/-/ng2-cookies-1.0.12.tgz#3f3e613e0137b0649b705c678074b4bd08149ccc"
|
resolved "https://registry.yarnpkg.com/ng2-cookies/-/ng2-cookies-1.0.12.tgz#3f3e613e0137b0649b705c678074b4bd08149ccc"
|
||||||
|
|
||||||
|
ng2-material-dropdown@0.11.0:
|
||||||
|
version "0.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ng2-material-dropdown/-/ng2-material-dropdown-0.11.0.tgz#27a402ef3cbdcaf6791ef4cfd4b257e31db7546f"
|
||||||
|
integrity sha512-wptBo09qKecY0QPTProAThrc4A3ajJTcHE9LTpCG5XZZUhXLBzhnGK8OW33TN8A+K/jqcs7OB74ppYJiqs3nhQ==
|
||||||
|
dependencies:
|
||||||
|
tslib "^1.9.0"
|
||||||
|
|
||||||
ngx-bootstrap@^3.1.4:
|
ngx-bootstrap@^3.1.4:
|
||||||
version "3.1.4"
|
version "3.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/ngx-bootstrap/-/ngx-bootstrap-3.1.4.tgz#5105c0227da3b51a1972d04efa1504a79474fd57"
|
resolved "https://registry.yarnpkg.com/ngx-bootstrap/-/ngx-bootstrap-3.1.4.tgz#5105c0227da3b51a1972d04efa1504a79474fd57"
|
||||||
|
|
||||||
|
ngx-chips@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ngx-chips/-/ngx-chips-2.1.0.tgz#aa299bcf40dc3e1f6288bf1d29e2fdfe9a132ed3"
|
||||||
|
integrity sha512-OQV4dTfD3nXm5d2mGKUSgwOtJOaMnZ4F+lwXOtd7DWRSUne0JQWwoZNHdOpuS6saBGhqCPDAwq6KxdR5XSgZUQ==
|
||||||
|
dependencies:
|
||||||
|
ng2-material-dropdown "0.11.0"
|
||||||
|
tslib "^1.9.0"
|
||||||
|
|
||||||
ngx-clipboard@^11.1.1:
|
ngx-clipboard@^11.1.1:
|
||||||
version "11.1.9"
|
version "11.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/ngx-clipboard/-/ngx-clipboard-11.1.9.tgz#a391853dc49e436de407260863a2c814d73a9332"
|
resolved "https://registry.yarnpkg.com/ngx-clipboard/-/ngx-clipboard-11.1.9.tgz#a391853dc49e436de407260863a2c814d73a9332"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue