Unverified Commit 71b850e7 authored by Jamie's avatar Jamie Committed by GitHub

Merge pull request #3215 from gtbuchanan/adult-filter

Add adult movie filtering
parents 291af1f0 721ef47b
......@@ -93,24 +93,7 @@ namespace Ombi.Api
public void AddQueryString(string key, string value)
{
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) return;
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;
_modified = FullUri.AddQueryParameter(key, value);
}
public void AddJsonBody(object obj)
......
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<TheMovieDbContainer<ActorResult>> SearchByActor(string searchTerm, string langCode);
Task<ActorCredits> GetActorMovieCredits(int actorId, string langCode);
Task<List<Keyword>> SearchKeyword(string searchTerm);
Task<Keyword> GetKeyword(int keywordId);
}
}
\ No newline at end of file
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; }
}
}
This diff is collapsed.
export interface IMovieDbKeyword {
id: number;
name: string;
}
......@@ -245,3 +245,8 @@ export interface IVoteSettings extends ISettings {
musicVoteMax: number;
tvShowVoteMax: number;
}
export interface ITheMovieDbSettings extends ISettings {
showAdultMovies: boolean;
excludedKeywordIds: number[];
}
......@@ -3,6 +3,7 @@ export * from "./ICouchPotato";
export * from "./IImages";
export * from "./IMediaServerStatus";
export * from "./INotificationSettings";
export * from "./IMovieDb";
export * from "./IPlex";
export * from "./IRadarr";
export * from "./IRequestEngineResult";
......
......@@ -7,3 +7,4 @@ export * from "./tester.service";
export * from "./plexoauth.service";
export * from "./plextv.service";
export * from "./lidarr.service";
export * from "./themoviedb.service";
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,
ISonarrSettings,
ITelegramNotifcationSettings,
ITheMovieDbSettings,
IUpdateSettings,
IUserManagementSettings,
IVoteSettings,
......@@ -301,6 +302,14 @@ export class SettingsService extends ServiceHelpers {
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> {
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 { RouterModule, Routes } from "@angular/router";
import { NgbAccordionModule, NgbModule } from "@ng-bootstrap/ng-bootstrap";
import { TagInputModule } from "ngx-chips";
import { ClipboardModule } from "ngx-clipboard";
import { AuthGuard } from "../auth/auth.guard";
import { AuthService } from "../auth/auth.service";
import {
CouchPotatoService, EmbyService, IssuesService, JobService, LidarrService, MobileService, NotificationMessageService, PlexService, RadarrService,
RequestRetryService, SonarrService, TesterService, ValidationService,
RequestRetryService, SonarrService, TesterService, TheMovieDbService, ValidationService,
} from "../services";
import { PipeModule } from "../pipes/pipe.module";
......@@ -41,6 +42,7 @@ import { PlexComponent } from "./plex/plex.component";
import { RadarrComponent } from "./radarr/radarr.component";
import { SickRageComponent } from "./sickrage/sickrage.component";
import { SonarrComponent } from "./sonarr/sonarr.component";
import { TheMovieDbComponent } from "./themoviedb/themoviedb.component";
import { UpdateComponent } from "./update/update.component";
import { UserManagementComponent } from "./usermanagement/usermanagement.component";
import { VoteComponent } from "./vote/vote.component";
......@@ -80,6 +82,7 @@ const routes: Routes = [
{ path: "Newsletter", component: NewsletterComponent, canActivate: [AuthGuard] },
{ path: "Lidarr", component: LidarrComponent, canActivate: [AuthGuard] },
{ path: "Vote", component: VoteComponent, canActivate: [AuthGuard] },
{ path: "TheMovieDb", component: TheMovieDbComponent, canActivate: [AuthGuard] },
{ path: "FailedRequests", component: FailedRequestsComponent, canActivate: [AuthGuard] },
];
......@@ -97,6 +100,7 @@ const routes: Routes = [
NgbAccordionModule,
AutoCompleteModule,
CalendarModule,
TagInputModule,
ClipboardModule,
PipeModule,
RadioButtonModule,
......@@ -135,6 +139,7 @@ const routes: Routes = [
NewsletterComponent,
LidarrComponent,
VoteComponent,
TheMovieDbComponent,
FailedRequestsComponent,
],
exports: [
......@@ -156,6 +161,7 @@ const routes: Routes = [
NotificationMessageService,
LidarrService,
RequestRetryService,
TheMovieDbService,
],
})
......
......@@ -13,6 +13,7 @@
<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/Vote']">Vote</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/TheMovieDb']">The Movie Database</a></li>
</ul>
</li>
......
<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">&nbsp;({{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>
\ No newline at end of file
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;
}
.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 {
padding:0px;
......
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;
}
/// <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>
/// Saves the email notification settings.
/// </summary>
......
......@@ -63,6 +63,7 @@
"natives": "1.1.6",
"ng2-cookies": "^1.0.12",
"ngx-bootstrap": "^3.1.4",
"ngx-chips": "^2.1.0",
"ngx-clipboard": "^11.1.1",
"ngx-editor": "^4.1.0",
"ngx-infinite-scroll": "^6.0.1",
......
......@@ -4183,10 +4183,25 @@ ng2-cookies@^1.0.12:
version "1.0.12"
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:
version "3.1.4"
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:
version "11.1.9"
resolved "https://registry.yarnpkg.com/ngx-clipboard/-/ngx-clipboard-11.1.9.tgz#a391853dc49e436de407260863a2c814d73a9332"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment