Merge pull request #4199 from Ashton-Sidhu/feature/genre-filter

Filter out Genres from Discover
pull/4210/head v4.0.1387
Jamie 3 years ago committed by GitHub
commit 105d05515d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,5 +7,9 @@ namespace Ombi.Core.Settings.Models.External
public bool ShowAdultMovies { get; set; } public bool ShowAdultMovies { get; set; }
public List<int> ExcludedKeywordIds { get; set; } public List<int> ExcludedKeywordIds { get; set; }
public List<int> ExcludedMovieGenreIds { get; set; }
public List<int> ExcludedTvGenreIds { get; set; }
} }
} }

@ -4,6 +4,10 @@ using System.Threading.Tasks;
using Ombi.Api.TheMovieDb.Models; using Ombi.Api.TheMovieDb.Models;
using Ombi.TheMovieDbApi.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 namespace Ombi.Api.TheMovieDb
{ {
public interface IMovieDbApi public interface IMovieDbApi
@ -34,5 +38,6 @@ namespace Ombi.Api.TheMovieDb
Task<Keyword> GetKeyword(int keywordId); Task<Keyword> GetKeyword(int keywordId);
Task<WatchProviders> GetMovieWatchProviders(int theMoviedbId, CancellationToken token); Task<WatchProviders> GetMovieWatchProviders(int theMoviedbId, CancellationToken token);
Task<WatchProviders> GetTvWatchProviders(int theMoviedbId, CancellationToken token); Task<WatchProviders> GetTvWatchProviders(int theMoviedbId, CancellationToken token);
Task<List<Genre>> GetGenres(string media);
} }
} }

@ -36,4 +36,9 @@ namespace Ombi.TheMovieDbApi.Models
public int total_results { get; set; } public int total_results { get; set; }
public int total_pages { get; set; } public int total_pages { get; set; }
} }
}
public class GenreContainer<T>
{
public List<T> genres { get; set; }
}
}

@ -13,6 +13,10 @@ using Ombi.Core.Settings.Models.External;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.TheMovieDbApi.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 namespace Ombi.Api.TheMovieDb
{ {
public class TheMovieDbApi : IMovieDbApi public class TheMovieDbApi : IMovieDbApi
@ -198,6 +202,7 @@ namespace Ombi.Api.TheMovieDb
request.AddQueryString("page", page.ToString()); request.AddQueryString("page", page.ToString());
} }
await AddDiscoverSettings(request); await AddDiscoverSettings(request);
await AddGenreFilter(request, type);
AddRetry(request); AddRetry(request);
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request, cancellationToken); var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request, cancellationToken);
return Mapper.Map<List<MovieDbSearchResult>>(result.results); return Mapper.Map<List<MovieDbSearchResult>>(result.results);
@ -233,6 +238,7 @@ namespace Ombi.Api.TheMovieDb
request.AddQueryString("vote_count.gte", "250"); request.AddQueryString("vote_count.gte", "250");
await AddDiscoverSettings(request); await AddDiscoverSettings(request);
await AddGenreFilter(request, type);
AddRetry(request); AddRetry(request);
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request); var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
return Mapper.Map<List<MovieDbSearchResult>>(result.results); return Mapper.Map<List<MovieDbSearchResult>>(result.results);
@ -269,6 +275,7 @@ namespace Ombi.Api.TheMovieDb
request.AddQueryString("page", page.ToString()); request.AddQueryString("page", page.ToString());
} }
await AddDiscoverSettings(request); await AddDiscoverSettings(request);
await AddGenreFilter(request, type);
AddRetry(request); AddRetry(request);
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request); var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
return Mapper.Map<List<MovieDbSearchResult>>(result.results); return Mapper.Map<List<MovieDbSearchResult>>(result.results);
@ -297,6 +304,7 @@ namespace Ombi.Api.TheMovieDb
} }
await AddDiscoverSettings(request); await AddDiscoverSettings(request);
await AddGenreFilter(request, "movie");
AddRetry(request); AddRetry(request);
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request); var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
return Mapper.Map<List<MovieDbSearchResult>>(result.results); return Mapper.Map<List<MovieDbSearchResult>>(result.results);
@ -344,6 +352,16 @@ namespace Ombi.Api.TheMovieDb
return keyword == null || keyword.Id == 0 ? null : keyword; return keyword == null || keyword.Id == 0 ? null : keyword;
} }
public async Task<List<Genre>> 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<GenreContainer<Genre>>(request);
return result.genres ?? new List<Genre>();
}
public Task<TheMovieDbContainer<MultiSearch>> MultiSearch(string searchTerm, string languageCode, CancellationToken cancellationToken) public Task<TheMovieDbContainer<MultiSearch>> MultiSearch(string searchTerm, string languageCode, CancellationToken cancellationToken)
{ {
var request = new Request("search/multi", BaseUri, HttpMethod.Get); 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<int> 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) private static void AddRetry(Request request)
{ {
request.Retry = true; request.Retry = true;

@ -284,6 +284,8 @@ export interface IVoteSettings extends ISettings {
export interface ITheMovieDbSettings extends ISettings { export interface ITheMovieDbSettings extends ISettings {
showAdultMovies: boolean; showAdultMovies: boolean;
excludedKeywordIds: number[]; excludedKeywordIds: number[];
excludedMovieGenreIds: number[];
excludedTvGenreIds: number[]
} }
export interface IUpdateModel export interface IUpdateModel

@ -22,4 +22,8 @@ export class TheMovieDbService extends ServiceHelpers {
return this.http.get<IMovieDbKeyword>(`${this.url}/Keywords/${keywordId}`, { headers: this.headers }) return this.http.get<IMovieDbKeyword>(`${this.url}/Keywords/${keywordId}`, { headers: this.headers })
.pipe(catchError((error: HttpErrorResponse) => error.status === 404 ? empty() : throwError(error))); .pipe(catchError((error: HttpErrorResponse) => error.status === 404 ? empty() : throwError(error)));
} }
public getGenres(media: string): Observable<IMovieDbKeyword[]> {
return this.http.get<IMovieDbKeyword[]>(`${this.url}/Genres/${media}`, { headers: this.headers })
}
} }

@ -15,7 +15,7 @@
<form [formGroup]='tagForm'> <form [formGroup]='tagForm'>
<mat-form-field class="example-full-width"> <mat-form-field class="example-full-width">
<input type="text" placeholder="Excluded Keyword IDs for Movie Suggestions" matInput <input type="text" placeholder="Excluded Keyword IDs for Movie & TV Suggestions" matInput
formControlName="input" [matAutocomplete]="auto" formControlName="input" [matAutocomplete]="auto"
matTooltip="Prevent movies with certain keywords from being suggested. May require a restart to take effect."> matTooltip="Prevent movies with certain keywords from being suggested. May require a restart to take effect.">
<mat-autocomplete (optionSelected)="optionSelected($event.option.value)" autoActiveFirstOption <mat-autocomplete (optionSelected)="optionSelected($event.option.value)" autoActiveFirstOption
@ -28,7 +28,45 @@
<mat-chip-list #chipList> <mat-chip-list #chipList>
<mat-chip *ngFor="let key of excludedKeywords" [selectable]="false" [removable]="true" <mat-chip *ngFor="let key of excludedKeywords" [selectable]="false" [removable]="true"
(removed)="remove(key)"> (removed)="remove(key, 'keyword')">
{{key.name}}
<i matChipRemove class="fas fa-times fa-lg"></i>
</mat-chip>
</mat-chip-list>
<div class="md-form-field" style="margin-top:1em;">
<mat-form-field appearance="outline" >
<mat-label>Movie Genres</mat-label>
<mat-select formControlName="excludedMovieGenres" multiple>
<mat-option *ngFor="let genre of filteredMovieGenres" [value]="genre.id">
{{genre.name}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<mat-chip-list #chipList>
<mat-chip *ngFor="let key of excludedMovieGenres" [selectable]="false" [removable]="true"
(removed)="remove(key, 'movieGenre')">
{{key.name}}
<i matChipRemove class="fas fa-times fa-lg"></i>
</mat-chip>
</mat-chip-list>
<div class="md-form-field" style="margin-top:1em;">
<mat-form-field appearance="outline" >
<mat-label>Tv Genres</mat-label>
<mat-select formControlName="excludedTvGenres" multiple>
<mat-option *ngFor="let genre of filteredTvGenres" [value]="genre.id">
{{genre.name}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<mat-chip-list #chipList>
<mat-chip *ngFor="let key of excludedTvGenres" [selectable]="false" [removable]="true"
(removed)="remove(key, 'tvGenre')">
{{key.name}} {{key.name}}
<i matChipRemove class="fas fa-times fa-lg"></i> <i matChipRemove class="fas fa-times fa-lg"></i>
</mat-chip> </mat-chip>
@ -52,4 +90,4 @@
</div> </div>
</div> </div>
</fieldset> </fieldset>
</div> </div>

@ -23,8 +23,13 @@ export class TheMovieDbComponent implements OnInit {
public settings: ITheMovieDbSettings; public settings: ITheMovieDbSettings;
public excludedKeywords: IKeywordTag[]; public excludedKeywords: IKeywordTag[];
public excludedMovieGenres: IKeywordTag[];
public excludedTvGenres: IKeywordTag[];
public tagForm: FormGroup; public tagForm: FormGroup;
public filteredTags: IMovieDbKeyword[]; public filteredTags: IMovieDbKeyword[];
public filteredMovieGenres: IMovieDbKeyword[];
public filteredTvGenres: IMovieDbKeyword[];
@ViewChild('fruitInput') public fruitInput: ElementRef<HTMLInputElement>; @ViewChild('fruitInput') public fruitInput: ElementRef<HTMLInputElement>;
constructor(private settingsService: SettingsService, constructor(private settingsService: SettingsService,
@ -35,9 +40,13 @@ export class TheMovieDbComponent implements OnInit {
public ngOnInit() { public ngOnInit() {
this.tagForm = this.fb.group({ this.tagForm = this.fb.group({
input: null, input: null,
excludedMovieGenres: null,
excludedTvGenres: null,
}); });
this.settingsService.getTheMovieDbSettings().subscribe(settings => { this.settingsService.getTheMovieDbSettings().subscribe(settings => {
this.settings = settings; this.settings = settings;
// Map Keyword ids -> keyword name
this.excludedKeywords = settings.excludedKeywordIds this.excludedKeywords = settings.excludedKeywordIds
? settings.excludedKeywordIds.map(id => ({ ? settings.excludedKeywordIds.map(id => ({
id, id,
@ -45,13 +54,56 @@ export class TheMovieDbComponent implements OnInit {
initial: true, initial: true,
})) }))
: []; : [];
this.excludedKeywords.forEach(key => {
this.tmdbService.getKeyword(key.id).subscribe(keyResult => { this.excludedKeywords.forEach(key => {
this.excludedKeywords.filter((val, idx) => { this.tmdbService.getKeyword(key.id).subscribe(keyResult => {
val.name = keyResult.name; 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 this.tagForm
@ -65,19 +117,48 @@ export class TheMovieDbComponent implements OnInit {
}) })
) )
.subscribe((r) => (this.filteredTags = r)); .subscribe((r) => (this.filteredTags = r));
} }
public remove(tag: IKeywordTag): void { public remove(tag: IKeywordTag, tag_type: string): void {
const index = this.excludedKeywords.indexOf(tag); 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) { if (index >= 0) {
this.excludedKeywords.splice(index, 1); exclusion_list.splice(index, 1);
} }
} }
public save() { 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.excludedKeywordIds = this.excludedKeywords.map(k => k.id);
this.settings.excludedMovieGenreIds = selectedMovieGenres;
this.settings.excludedTvGenreIds = selectedTvGenres;
this.settingsService.saveTheMovieDbSettings(this.settings).subscribe(x => { this.settingsService.saveTheMovieDbSettings(this.settings).subscribe(x => {
if (x) { if (x) {
this.notificationService.success("Successfully saved The Movie Database settings"); this.notificationService.success("Successfully saved The Movie Database settings");

@ -5,6 +5,10 @@ using Ombi.Attributes;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; 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 namespace Ombi.Controllers.External
{ {
[Admin] [Admin]
@ -34,5 +38,13 @@ namespace Ombi.Controllers.External
var keyword = await TmdbApi.GetKeyword(keywordId); var keyword = await TmdbApi.GetKeyword(keywordId);
return keyword == null ? NotFound() : (IActionResult)Ok(keyword); return keyword == null ? NotFound() : (IActionResult)Ok(keyword);
} }
/// <summary>
/// Gets the genres for either Tv or Movies depending on media type
/// </summary>
/// <param name="media">Either `tv` or `movie`.</param>
[HttpGet("Genres/{media}")]
public async Task<IEnumerable<Genre>> GetGenres(string media) =>
await TmdbApi.GetGenres(media);
} }
} }

Loading…
Cancel
Save