Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Radarr tags #4815

Merged
merged 2 commits into from
Jan 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/Ombi.Api.Radarr/IRadarrV3Api.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.Radarr.Models;
using Ombi.Api.Radarr.Models.V3;
Expand All @@ -14,7 +15,8 @@ public interface IRadarrV3Api
Task<MovieResponse> GetMovie(int id, string apiKey, string baseUrl);
Task<MovieResponse> UpdateMovie(MovieResponse movie, string apiKey, string baseUrl);
Task<bool> MovieSearch(int[] movieIds, string apiKey, string baseUrl);
Task<RadarrAddMovie> AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath,string apiKey, string baseUrl, bool searchNow, string minimumAvailability);
Task<RadarrAddMovie> AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath,string apiKey, string baseUrl, bool searchNow, string minimumAvailability, List<int> tags);
Task<List<Tag>> GetTags(string apiKey, string baseUrl);
Task<Tag> CreateTag(string apiKey, string baseUrl, string tagName);
}
}
1 change: 1 addition & 0 deletions src/Ombi.Api.Radarr/Models/V2/RadarrAddMovie.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ public RadarrAddMovie()
public int year { get; set; }
public string minimumAvailability { get; set; }
public long sizeOnDisk { get; set; }
public int[] tags { get; set; }
}
}
14 changes: 12 additions & 2 deletions src/Ombi.Api.Radarr/RadarrV3Api.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public async Task<MovieResponse> UpdateMovie(MovieResponse movie, string apiKey,
return await Api.Request<MovieResponse>(request);
}

public async Task<RadarrAddMovie> AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath, string apiKey, string baseUrl, bool searchNow, string minimumAvailability)
public async Task<RadarrAddMovie> AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath, string apiKey, string baseUrl, bool searchNow, string minimumAvailability, List<int> tags)
{
var request = new Request("/api/v3/movie", baseUrl, HttpMethod.Post);

Expand All @@ -86,7 +86,8 @@ public async Task<RadarrAddMovie> AddMovie(int tmdbId, string title, int year, i
monitored = true,
year = year,
minimumAvailability = minimumAvailability,
sizeOnDisk = 0
sizeOnDisk = 0,
tags = tags.Any() ? tags.ToArray() : Enumerable.Empty<int>().ToArray()
};

if (searchNow)
Expand Down Expand Up @@ -156,5 +157,14 @@ private void AddHeaders(Request request, string key)
{
request.AddHeader("X-Api-Key", key);
}

public Task<Tag> CreateTag(string apiKey, string baseUrl, string tagName)
{
var request = new Request($"/api/v3/tag", baseUrl, HttpMethod.Post);
request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(new { Label = tagName });

return Api.Request<Tag>(request);
}
}
}
31 changes: 28 additions & 3 deletions src/Ombi.Core/Senders/MovieSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
using Ombi.Store.Repository;
using System.Collections.Generic;
using Ombi.Api.Radarr.Models;
using Microsoft.Extensions.Options;
using Ombi.Api.Sonarr;

namespace Ombi.Core.Senders
{
Expand Down Expand Up @@ -67,7 +69,7 @@ public async Task<SenderResult> Send(MovieRequests model, bool is4K)
}
if (radarrSettings.Enabled)
{
return await SendToRadarr(model, is4K, radarrSettings);
return await SendToRadarr(model, radarrSettings);
}

var dogSettings = await _dogNzbSettings.GetSettingsAsync();
Expand Down Expand Up @@ -131,7 +133,7 @@ private async Task<DogNzbMovieAddResult> SendToDogNzb(FullBaseRequest model, Dog
return await _dogNzbApi.AddMovie(settings.ApiKey, id);
}

private async Task<SenderResult> SendToRadarr(MovieRequests model, bool is4K, RadarrSettings settings)
private async Task<SenderResult> SendToRadarr(MovieRequests model, RadarrSettings settings)
{
var qualityToUse = int.Parse(settings.DefaultQualityProfile);

Expand All @@ -154,6 +156,17 @@ private async Task<SenderResult> SendToRadarr(MovieRequests model, bool is4K, Ra
}
}

var tags = new List<int>();
if (settings.Tag.HasValue)
{
tags.Add(settings.Tag.Value);
}
if (settings.SendUserTags)
{
var userTag = await GetOrCreateTag(model, settings);
tags.Add(userTag.id);
}

// Overrides on the request take priority
if (model.QualityOverride > 0)
{
Expand All @@ -174,7 +187,7 @@ private async Task<SenderResult> SendToRadarr(MovieRequests model, bool is4K, Ra
{
var result = await _radarrV3Api.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year,
qualityToUse, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly,
settings.MinimumAvailability);
settings.MinimumAvailability, tags);

if (!string.IsNullOrEmpty(result.Error?.message))
{
Expand Down Expand Up @@ -212,5 +225,17 @@ private async Task<string> RadarrRootPath(int overrideId, RadarrSettings setting
var selectedPath = paths.FirstOrDefault(x => x.id == overrideId);
return selectedPath?.path ?? string.Empty;
}

private async Task<Tag> GetOrCreateTag(MovieRequests model, RadarrSettings s)
{
var tagName = model.RequestedUser.UserName;
// Does tag exist?

var allTags = await _radarrV3Api.GetTags(s.ApiKey, s.FullUri);
var existingTag = allTags.FirstOrDefault(x => x.label.Equals(tagName, StringComparison.InvariantCultureIgnoreCase));
existingTag ??= await _radarrV3Api.CreateTag(s.ApiKey, s.FullUri, tagName);

return existingTag;
}
}
}
2 changes: 2 additions & 0 deletions src/Ombi.Settings/Settings/Models/External/RadarrSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public class RadarrSettings : ExternalSettings
public bool AddOnly { get; set; }
public string MinimumAvailability { get; set; }
public bool ScanForAvailability { get; set; }
public int? Tag { get; set; }
public bool SendUserTags { get; set; }
}

public class Radarr4KSettings : RadarrSettings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ public class SonarrSettings : ExternalSettings
public string RootPathAnime { get; set; }
public int? AnimeTag { get; set; }
public int? Tag { get; set; }
public bool SendUserTags { get; set; }
public bool AddOnly { get; set; }
public int LanguageProfile { get; set; }
public int LanguageProfileAnime { get; set; }
public bool ScanForAvailability { get; set; }

public bool SendUserTags { get; set; }
}
}
5 changes: 4 additions & 1 deletion src/Ombi/ClientApp/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { BrowserModule } from "@angular/platform-browser";
import { ButtonModule } from "primeng/button";
import { CUSTOMIZATION_INITIALIZER } from "./state/customization/customization-initializer";
import { SONARR_INITIALIZER } from "./state/sonarr/sonarr-initializer";
import { RADARR_INITIALIZER } from "./state/radarr/radarr-initializer";
import { ConfirmDialogModule } from "primeng/confirmdialog";
import { CookieComponent } from "./auth/cookie.component";
import { CookieService } from "ng2-cookies";
Expand All @@ -24,6 +25,7 @@ import { DialogModule } from "primeng/dialog";
import { FEATURES_INITIALIZER } from "./state/features/features-initializer";
import { FeatureState } from "./state/features";
import { SonarrSettingsState } from "./state/sonarr";
import { RadarrSettingsState } from "./state/radarr";
import { JwtModule } from "@auth0/angular-jwt";
import { LandingPageComponent } from "./landingpage/landingpage.component";
import { LandingPageService } from "./services";
Expand Down Expand Up @@ -163,7 +165,7 @@ export function JwtTokenGetter() {
}),
SidebarModule,
MatNativeDateModule, MatIconModule, MatSidenavModule, MatListModule, MatToolbarModule, LayoutModule, MatSlideToggleModule,
NgxsModule.forRoot([CustomizationState, FeatureState, SonarrSettingsState], {
NgxsModule.forRoot([CustomizationState, FeatureState, SonarrSettingsState, RadarrSettingsState], {
developmentMode: !environment.production,
}),
...environment.production ? [] :
Expand Down Expand Up @@ -212,6 +214,7 @@ export function JwtTokenGetter() {
FEATURES_INITIALIZER,
SONARR_INITIALIZER,
CUSTOMIZATION_INITIALIZER,
RADARR_INITIALIZER,
{
provide: APP_BASE_HREF,
useValue: window["baseHref"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export class DiscoverCardComponent implements OnInit {
AdminRequestDialogComponent,
{
width: "700px",
data: { type: RequestType.movie, id: this.result.id },
data: { type: RequestType.movie, id: this.result.id, is4k: is4k },
panelClass: "modal-panel",
}
);
Expand Down
2 changes: 2 additions & 0 deletions src/Ombi/ClientApp/src/app/interfaces/ISettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ export interface IRadarrSettings extends IExternalSettings {
addOnly: boolean;
minimumAvailability: string;
scanForAvailability: boolean;
tag: number | null;
sendUserTags: boolean;
}

export interface IRadarrCombined {
Expand Down
19 changes: 12 additions & 7 deletions src/Ombi/ClientApp/src/app/login/login.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ import { TranslateService } from "@ngx-translate/core";
import { APP_BASE_HREF } from "@angular/common";
import { AuthService } from "../auth/auth.service";
import { IAuthenticationSettings, ICustomizationSettings } from "../interfaces";
import { PlexTvService } from "../services";
import { SettingsService } from "../services";
import { StatusService } from "../services";
import { PlexTvService, StatusService, SettingsService } from "../services";

import { StorageService } from "../shared/storage/storage-service";
import { MatSnackBar } from "@angular/material/snack-bar";
import { CustomizationFacade } from "../state/customization";
import { SonarrFacade } from "app/state/sonarr";
import { RadarrFacade } from "app/state/radarr";

@Component({
templateUrl: "./login.component.html",
Expand Down Expand Up @@ -62,6 +61,7 @@ export class LoginComponent implements OnDestroy, OnInit {
private plexTv: PlexTvService,
private store: StorageService,
private sonarrFacade: SonarrFacade,
private radarrFacade: RadarrFacade,
private readonly notify: MatSnackBar
) {
this.href = href;
Expand Down Expand Up @@ -89,7 +89,7 @@ export class LoginComponent implements OnDestroy, OnInit {
});

if (authService.loggedIn()) {
this.router.navigate(["/"]);
this.loadStores();
}
}

Expand Down Expand Up @@ -144,7 +144,7 @@ export class LoginComponent implements OnDestroy, OnInit {

if (this.authService.loggedIn()) {
this.ngOnDestroy();
this.sonarrFacade.load().subscribe();
this.loadStores();
this.router.navigate(["/"]);
} else {
this.notify.open(this.errorBody, "OK", {
Expand Down Expand Up @@ -221,7 +221,7 @@ export class LoginComponent implements OnDestroy, OnInit {
this.oAuthWindow.close();
}
this.oauthLoading = false;
this.sonarrFacade.load().subscribe();
this.loadStores();
this.router.navigate(["search"]);
return;
}
Expand Down Expand Up @@ -252,7 +252,7 @@ export class LoginComponent implements OnDestroy, OnInit {

if (this.authService.loggedIn()) {
this.ngOnDestroy();
this.sonarrFacade.load().subscribe();
this.loadStores();
this.router.navigate(["/"]);
} else {
this.notify.open(this.errorBody, "OK", {
Expand All @@ -274,4 +274,9 @@ export class LoginComponent implements OnDestroy, OnInit {
public ngOnDestroy() {
clearInterval(this.pinTimer);
}

private loadStores() {
this.sonarrFacade.load().subscribe();
this.radarrFacade.load().subscribe();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class MovieDetailsComponent implements OnInit{
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");

if (this.isAdmin) {
this.showAdvanced = await this.radarrService.isRadarrEnabled();
this.showAdvanced = await firstValueFrom(this.radarrService.isRadarrEnabled());
}

if (this.imdbId) {
Expand Down Expand Up @@ -111,7 +111,7 @@ export class MovieDetailsComponent implements OnInit{
is4K = false;
}
if (this.isAdmin) {
const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.movie, id: this.movie.id }, panelClass: 'modal-panel' });
const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.movie, id: this.movie.id, is4K: is4K }, panelClass: 'modal-panel' });
dialog.afterClosed().subscribe(async (result) => {
if (result) {
const requestResult = await firstValueFrom(this.requestService.requestMovie({ theMovieDbId: this.theMovidDbId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class TvRequestGridComponent {
});

if (this.isAdmin) {
const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.tvShow, id: this.tv.id }, panelClass: 'modal-panel' });
const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.tvShow, id: this.tv.id, is4k: null }, panelClass: 'modal-panel' });
dialog.afterClosed().subscribe(async (result) => {
if (result) {
viewModel.requestOnBehalf = result.username?.id;
Expand Down
24 changes: 21 additions & 3 deletions src/Ombi/ClientApp/src/app/services/applications/radarr.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { HttpClient } from "@angular/common/http";
import { Injectable, Inject } from "@angular/core";
import { Observable } from "rxjs";

import { IRadarrProfile, IRadarrRootFolder } from "../../interfaces";
import { IRadarrProfile, IRadarrRootFolder, ITag } from "../../interfaces";
import { IRadarrSettings } from "../../interfaces";
import { ServiceHelpers } from "../service.helpers";

Expand All @@ -23,10 +23,28 @@ export class RadarrService extends ServiceHelpers {
public getRootFoldersFromSettings(): Observable<IRadarrRootFolder[]> {
return this.http.get<IRadarrRootFolder[]>(`${this.url}/RootFolders/`, { headers: this.headers });
}

public getQualityProfilesFromSettings(): Observable<IRadarrProfile[]> {
return this.http.get<IRadarrProfile[]>(`${this.url}/Profiles/`, { headers: this.headers });
}
public isRadarrEnabled(): Promise<boolean> {
return this.http.get<boolean>(`${this.url}/enabled/`, { headers: this.headers }).toPromise();

public getRootFolders4kFromSettings(): Observable<IRadarrRootFolder[]> {
return this.http.get<IRadarrRootFolder[]>(`${this.url}/RootFolders/4k`, { headers: this.headers });
}

public getQualityProfiles4kFromSettings(): Observable<IRadarrProfile[]> {
return this.http.get<IRadarrProfile[]>(`${this.url}/Profiles/4k`, { headers: this.headers });
}

public isRadarrEnabled(): Observable<boolean> {
return this.http.get<boolean>(`${this.url}/enabled/`, { headers: this.headers });
}

public getTagsWithSettings(settings: IRadarrSettings): Observable<ITag[]> {
return this.http.post<ITag[]>(`${this.url}/tags/`, JSON.stringify(settings), { headers: this.headers });
}

public getTags(): Observable<ITag[]> {
return this.http.get<ITag[]>(`${this.url}/tags/`, { headers: this.headers })
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
<div class="md-form-field">
<mat-slide-toggle formControlName="scanForAvailability">Scan for Availability</mat-slide-toggle>
</div>
<div class="md-form-field">
<mat-slide-toggle formControlName="sendUserTags" id="sendUserTags">Add the user as a tag</mat-slide-toggle>
<small><br>This will add the username of the requesting user as a tag in Sonarr. If the tag doesn't exist, Ombi will create it.</small>
</div>
<div class="md-form-field" >
<mat-slide-toggle formControlName="addOnly">
Do not search for Movies
Expand Down Expand Up @@ -79,6 +83,22 @@
</mat-form-field>

</div>
<div class="md-form-field">
<div class="md-form-field" style="display:inline;">
<button mat-raised-button (click)="getTags(form)" type="button" color="primary">Load Tags <span *ngIf="tagsRunning" class="fas fa-spinner fa-spin"></span></button>
</div>
<div class="md-form-field" style="margin-top:1em;"></div>
<mat-form-field appearance="outline" >
<mat-label>Tag</mat-label>
<mat-select formControlName="tag">
<mat-option *ngFor="let tag of tags" [value]="tag.id">
{{tag.label}}
</mat-option>
</mat-select>
</mat-form-field>

</div>

<div class="md-form-field">
<mat-form-field appearance="outline" >
<mat-label>Default Minimum Availability</mat-label>
Expand Down
Loading