mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-16 02:02:55 -07:00
Merge pull request #4199 from Ashton-Sidhu/feature/genre-filter
Filter out Genres from Discover
This commit is contained in:
commit
105d05515d
9 changed files with 205 additions and 14 deletions
|
@ -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>
|
||||||
|
|
|
@ -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,6 +54,7 @@ export class TheMovieDbComponent implements OnInit {
|
||||||
initial: true,
|
initial: true,
|
||||||
}))
|
}))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
this.excludedKeywords.forEach(key => {
|
this.excludedKeywords.forEach(key => {
|
||||||
this.tmdbService.getKeyword(key.id).subscribe(keyResult => {
|
this.tmdbService.getKeyword(key.id).subscribe(keyResult => {
|
||||||
this.excludedKeywords.filter((val, idx) => {
|
this.excludedKeywords.filter((val, idx) => {
|
||||||
|
@ -52,6 +62,48 @@ export class TheMovieDbComponent implements OnInit {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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…
Add table
Add a link
Reference in a new issue