Skip to content

Commit

Permalink
feat: Radarr tags (#4815)
Browse files Browse the repository at this point in the history
  • Loading branch information
tidusjar authored Jan 4, 2023
1 parent c127157 commit 6fa5064
Show file tree
Hide file tree
Showing 28 changed files with 493 additions and 183 deletions.
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 @@ -162,7 +164,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 @@ -211,6 +213,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

0 comments on commit 6fa5064

Please sign in to comment.