diff --git a/src/Ombi.Settings/Settings/Models/External/TheMovieDbSettings.cs b/src/Ombi.Settings/Settings/Models/External/TheMovieDbSettings.cs index fe0c238d8..c2df1b9b3 100644 --- a/src/Ombi.Settings/Settings/Models/External/TheMovieDbSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/TheMovieDbSettings.cs @@ -7,5 +7,9 @@ namespace Ombi.Core.Settings.Models.External public bool ShowAdultMovies { get; set; } public List ExcludedKeywordIds { get; set; } + + public List ExcludedMovieGenreIds { get; set; } + + public List ExcludedTvGenreIds { get; set; } } } diff --git a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs index ea2465c1d..fb15aab5f 100644 --- a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs @@ -4,6 +4,10 @@ using System.Threading.Tasks; using Ombi.Api.TheMovieDb.Models; using Ombi.TheMovieDbApi.Models; +// Due to conflicting Genre models in +// Ombi.TheMovieDbApi.Models and Ombi.Api.TheMovieDb.Models +using Genre = Ombi.TheMovieDbApi.Models.Genre; + namespace Ombi.Api.TheMovieDb { public interface IMovieDbApi @@ -34,5 +38,6 @@ namespace Ombi.Api.TheMovieDb Task GetKeyword(int keywordId); Task GetMovieWatchProviders(int theMoviedbId, CancellationToken token); Task GetTvWatchProviders(int theMoviedbId, CancellationToken token); + Task> GetGenres(string media); } -} \ No newline at end of file +} diff --git a/src/Ombi.TheMovieDbApi/Models/TheMovieDbContainer.cs b/src/Ombi.TheMovieDbApi/Models/TheMovieDbContainer.cs index 72f58dd02..5eb5dbeac 100644 --- a/src/Ombi.TheMovieDbApi/Models/TheMovieDbContainer.cs +++ b/src/Ombi.TheMovieDbApi/Models/TheMovieDbContainer.cs @@ -36,4 +36,9 @@ namespace Ombi.TheMovieDbApi.Models public int total_results { get; set; } public int total_pages { get; set; } } -} \ No newline at end of file + + public class GenreContainer + { + public List genres { get; set; } + } +} diff --git a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs index 74c849d59..c5a290e04 100644 --- a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs @@ -13,6 +13,10 @@ using Ombi.Core.Settings.Models.External; using Ombi.Helpers; using Ombi.TheMovieDbApi.Models; +// Due to conflicting Genre models in +// Ombi.TheMovieDbApi.Models and Ombi.Api.TheMovieDb.Models +using Genre = Ombi.TheMovieDbApi.Models.Genre; + namespace Ombi.Api.TheMovieDb { public class TheMovieDbApi : IMovieDbApi @@ -198,6 +202,7 @@ namespace Ombi.Api.TheMovieDb request.AddQueryString("page", page.ToString()); } await AddDiscoverSettings(request); + await AddGenreFilter(request, type); AddRetry(request); var result = await Api.Request>(request, cancellationToken); return Mapper.Map>(result.results); @@ -233,6 +238,7 @@ namespace Ombi.Api.TheMovieDb request.AddQueryString("vote_count.gte", "250"); await AddDiscoverSettings(request); + await AddGenreFilter(request, type); AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); @@ -269,6 +275,7 @@ namespace Ombi.Api.TheMovieDb request.AddQueryString("page", page.ToString()); } await AddDiscoverSettings(request); + await AddGenreFilter(request, type); AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); @@ -297,6 +304,7 @@ namespace Ombi.Api.TheMovieDb } await AddDiscoverSettings(request); + await AddGenreFilter(request, "movie"); AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); @@ -344,6 +352,16 @@ namespace Ombi.Api.TheMovieDb return keyword == null || keyword.Id == 0 ? null : keyword; } + public async Task> GetGenres(string media) + { + var request = new Request($"genre/{media}/list", BaseUri, HttpMethod.Get); + request.AddQueryString("api_key", ApiToken); + AddRetry(request); + + var result = await Api.Request>(request); + return result.genres ?? new List(); + } + public Task> MultiSearch(string searchTerm, string languageCode, CancellationToken cancellationToken) { var request = new Request("search/multi", BaseUri, HttpMethod.Get); @@ -380,6 +398,28 @@ namespace Ombi.Api.TheMovieDb } } + private async Task AddGenreFilter(Request request, string media_type) + { + var settings = await Settings; + List excludedGenres; + + switch (media_type) { + case "tv": + excludedGenres = settings.ExcludedTvGenreIds; + break; + case "movie": + excludedGenres = settings.ExcludedMovieGenreIds; + break; + default: + return; + } + + if (excludedGenres?.Any() == true) + { + request.AddQueryString("without_genres", string.Join(",", excludedGenres)); + } + } + private static void AddRetry(Request request) { request.Retry = true; diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts index d73fdddc8..5dfaa3e15 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts @@ -284,6 +284,8 @@ export interface IVoteSettings extends ISettings { export interface ITheMovieDbSettings extends ISettings { showAdultMovies: boolean; excludedKeywordIds: number[]; + excludedMovieGenreIds: number[]; + excludedTvGenreIds: number[] } export interface IUpdateModel diff --git a/src/Ombi/ClientApp/src/app/services/applications/themoviedb.service.ts b/src/Ombi/ClientApp/src/app/services/applications/themoviedb.service.ts index 677034143..ff76d591b 100644 --- a/src/Ombi/ClientApp/src/app/services/applications/themoviedb.service.ts +++ b/src/Ombi/ClientApp/src/app/services/applications/themoviedb.service.ts @@ -22,4 +22,8 @@ export class TheMovieDbService extends ServiceHelpers { return this.http.get(`${this.url}/Keywords/${keywordId}`, { headers: this.headers }) .pipe(catchError((error: HttpErrorResponse) => error.status === 404 ? empty() : throwError(error))); } + + public getGenres(media: string): Observable { + return this.http.get(`${this.url}/Genres/${media}`, { headers: this.headers }) + } } diff --git a/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.html b/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.html index 4d47aa8a9..fb10cc142 100644 --- a/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.html +++ b/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.html @@ -15,7 +15,7 @@
- + (removed)="remove(key, 'keyword')"> + {{key.name}} + + + + +
+ + Movie Genres + + + {{genre.name}} + + + +
+ + + + {{key.name}} + + + + +
+ + Tv Genres + + + {{genre.name}} + + + +
+ + + {{key.name}} @@ -52,4 +90,4 @@ - \ No newline at end of file + diff --git a/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.ts b/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.ts index 228d4fc42..49ec3e51f 100644 --- a/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.ts @@ -23,8 +23,13 @@ export class TheMovieDbComponent implements OnInit { public settings: ITheMovieDbSettings; public excludedKeywords: IKeywordTag[]; + public excludedMovieGenres: IKeywordTag[]; + public excludedTvGenres: IKeywordTag[]; public tagForm: FormGroup; public filteredTags: IMovieDbKeyword[]; + public filteredMovieGenres: IMovieDbKeyword[]; + public filteredTvGenres: IMovieDbKeyword[]; + @ViewChild('fruitInput') public fruitInput: ElementRef; constructor(private settingsService: SettingsService, @@ -35,9 +40,13 @@ export class TheMovieDbComponent implements OnInit { public ngOnInit() { this.tagForm = this.fb.group({ input: null, + excludedMovieGenres: null, + excludedTvGenres: null, }); this.settingsService.getTheMovieDbSettings().subscribe(settings => { this.settings = settings; + + // Map Keyword ids -> keyword name this.excludedKeywords = settings.excludedKeywordIds ? settings.excludedKeywordIds.map(id => ({ id, @@ -45,13 +54,56 @@ export class TheMovieDbComponent implements OnInit { initial: true, })) : []; - this.excludedKeywords.forEach(key => { - this.tmdbService.getKeyword(key.id).subscribe(keyResult => { - this.excludedKeywords.filter((val, idx) => { - val.name = keyResult.name; - }) + + this.excludedKeywords.forEach(key => { + this.tmdbService.getKeyword(key.id).subscribe(keyResult => { + this.excludedKeywords.filter((val, idx) => { + val.name = keyResult.name; + }) + }); + }); + + // Map Movie Genre ids -> genre name + this.excludedMovieGenres = settings.excludedMovieGenreIds + ? settings.excludedMovieGenreIds.map(id => ({ + id, + name: "", + initial: true, + })) + : []; + + this.tmdbService.getGenres("movie").subscribe(results => { + this.filteredMovieGenres = results; + + this.excludedMovieGenres.forEach(genre => { + results.forEach(result => { + if (genre.id == result.id) { + genre.name = result.name; + } }); }); + }); + + // Map Tv Genre ids -> genre name + this.excludedTvGenres = settings.excludedTvGenreIds + ? settings.excludedTvGenreIds.map(id => ({ + id, + name: "", + initial: true, + })) + : []; + + this.tmdbService.getGenres("tv").subscribe(results => { + this.filteredTvGenres = results; + + this.excludedTvGenres.forEach(genre => { + results.forEach(result => { + if (genre.id == result.id) { + genre.name = result.name; + } + }); + }); + }); }); this.tagForm @@ -65,19 +117,48 @@ export class TheMovieDbComponent implements OnInit { }) ) .subscribe((r) => (this.filteredTags = r)); - } - public remove(tag: IKeywordTag): void { - const index = this.excludedKeywords.indexOf(tag); + public remove(tag: IKeywordTag, tag_type: string): void { + var exclusion_list; + + switch (tag_type) { + case "keyword": + exclusion_list = this.excludedKeywords; + break; + case "movieGenre": + exclusion_list = this.excludedMovieGenres; + break; + case "tvGenre": + exclusion_list = this.excludedTvGenres; + break; + default: + return; + } + + const index = exclusion_list.indexOf(tag); if (index >= 0) { - this.excludedKeywords.splice(index, 1); + exclusion_list.splice(index, 1); } } public save() { + + var selectedMovieGenres: number[] = this.tagForm.controls.excludedMovieGenres.value ?? []; + var selectedTvGenres: number[] = this.tagForm.controls.excludedTvGenres.value ?? []; + + var movieIds: number[] = this.excludedMovieGenres.map(k => k.id); + var tvIds: number[] = this.excludedTvGenres.map(k => k.id) + + // Concat and dedup already excluded genres + newly selected ones + selectedMovieGenres = movieIds.concat(selectedMovieGenres.filter(item => movieIds.indexOf(item) < 0)); + selectedTvGenres = tvIds.concat(selectedTvGenres.filter(item => tvIds.indexOf(item) < 0)); + this.settings.excludedKeywordIds = this.excludedKeywords.map(k => k.id); + this.settings.excludedMovieGenreIds = selectedMovieGenres; + this.settings.excludedTvGenreIds = selectedTvGenres; + this.settingsService.saveTheMovieDbSettings(this.settings).subscribe(x => { if (x) { this.notificationService.success("Successfully saved The Movie Database settings"); diff --git a/src/Ombi/Controllers/V1/External/TheMovieDbController.cs b/src/Ombi/Controllers/V1/External/TheMovieDbController.cs index 714831633..ac4bb4eea 100644 --- a/src/Ombi/Controllers/V1/External/TheMovieDbController.cs +++ b/src/Ombi/Controllers/V1/External/TheMovieDbController.cs @@ -5,6 +5,10 @@ using Ombi.Attributes; using System.Collections.Generic; using System.Threading.Tasks; +// Due to conflicting Genre models in +// Ombi.TheMovieDbApi.Models and Ombi.Api.TheMovieDb.Models +using Genre = Ombi.TheMovieDbApi.Models.Genre; + namespace Ombi.Controllers.External { [Admin] @@ -34,5 +38,13 @@ namespace Ombi.Controllers.External var keyword = await TmdbApi.GetKeyword(keywordId); return keyword == null ? NotFound() : (IActionResult)Ok(keyword); } + + /// + /// Gets the genres for either Tv or Movies depending on media type + /// + /// Either `tv` or `movie`. + [HttpGet("Genres/{media}")] + public async Task> GetGenres(string media) => + await TmdbApi.GetGenres(media); } }