mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-16 02:02:55 -07:00
merge develop into v4
This commit is contained in:
commit
a5139dd25c
59 changed files with 577 additions and 321 deletions
8
.github/FUNDING.yml
vendored
Normal file
8
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [tidusjar]
|
||||||
|
patreon: tidusjar
|
||||||
|
#open_collective: # Replace with a single Open Collective username
|
||||||
|
#ko_fi: # Replace with a single Ko-fi username
|
||||||
|
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
custom: https://paypal.me/PlexRequestsNet
|
18
.github/workflows/aspnetcore.yml
vendored
Normal file
18
.github/workflows/aspnetcore.yml
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
name: ASP.NET Core CI
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Setup .NET Core
|
||||||
|
uses: actions/setup-dotnet@v1
|
||||||
|
with:
|
||||||
|
dotnet-version: 2.2.108
|
||||||
|
|
||||||
|
- name: Build Backend
|
||||||
|
run: ./build.sh --settings_skipverification=true
|
9
.github/workflows/test.workflow
vendored
Normal file
9
.github/workflows/test.workflow
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
workflow "New workflow" {
|
||||||
|
on = "push"
|
||||||
|
resolves = [".NET Core CLI"]
|
||||||
|
}
|
||||||
|
|
||||||
|
action ".NET Core CLI" {
|
||||||
|
uses = "baruchiro/github-actions@0.0.1"
|
||||||
|
args = "build src/Ombi.sln"
|
||||||
|
}
|
149
CHANGELOG.md
149
CHANGELOG.md
|
@ -4,24 +4,129 @@
|
||||||
|
|
||||||
### **New Features**
|
### **New Features**
|
||||||
|
|
||||||
|
- Added better support for Jellyfin, we will now auto detect if it's a jellyfin server after pressing the discover button. [tidusjar]
|
||||||
|
|
||||||
|
- Update aspnetcore.yml. [Jamie]
|
||||||
|
|
||||||
|
- Update aspnetcore.yml. [Jamie]
|
||||||
|
|
||||||
|
- Update aspnetcore.yml. [Jamie]
|
||||||
|
|
||||||
|
- Update aspnetcore.yml. [Jamie]
|
||||||
|
|
||||||
|
- Update aspnetcore.yml. [Jamie]
|
||||||
|
|
||||||
|
- Update and rename .github/workflows to .github/.github/workflows/test.workflow. [Jamie]
|
||||||
|
|
||||||
|
- Update aspnetcore.yml. [Jamie]
|
||||||
|
|
||||||
|
- Update aspnetcore.yml. [Jamie]
|
||||||
|
|
||||||
|
- Update aspnetcore.yml. [Jamie]
|
||||||
|
|
||||||
|
- Added a bit more logging into the recently added scan. [tidusjar]
|
||||||
|
|
||||||
|
- Update emby.component.html. [sorano]
|
||||||
|
|
||||||
|
- Update EmbyHelper.cs. [sorano]
|
||||||
|
|
||||||
|
- Update CHANGELOG.md. [Jamie]
|
||||||
|
|
||||||
|
### **Fixes**
|
||||||
|
|
||||||
|
- Fixed #3078. [tidusjar]
|
||||||
|
|
||||||
|
- Fixes issue #3195 The new string extension method ToHttpsUrl ensures that URLs starting with "https" are no longer turned into "httpss" The commit also replaces all occurances of the error prone .Replace("http", "https") in the whole solution. [msdeibel]
|
||||||
|
|
||||||
|
- Create test.workflow. [Jamie]
|
||||||
|
|
||||||
|
- Delete test.workflow. [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Norwegian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Norwegian) [Jamie]
|
||||||
|
|
||||||
|
- Fix for #3183. [tidusjar]
|
||||||
|
|
||||||
|
- Fixed an issue where running the recently added sync via the UI was running the full sync. [tidusjar]
|
||||||
|
|
||||||
|
- Fixed #3143. [Jamie Rees]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Russian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Russian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Russian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Russian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Russian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Russian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Russian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Russian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Polish) [Jamie]
|
||||||
|
|
||||||
|
- Fixed the issue when we are logging errors in the logs incorrectly. [Jamie]
|
||||||
|
|
||||||
|
- Removed the lanuage profile from the Lidarr integration. [tidusjar]
|
||||||
|
|
||||||
|
- Try and clear up the issue #2998. [tidusjar]
|
||||||
|
|
||||||
|
- Fixed an issue where shows that have no aired, episodes are not marked as monitored in Sonarr. [tidusjar]
|
||||||
|
|
||||||
|
- Fixed an error when finishing the content sync. [tidusjar]
|
||||||
|
|
||||||
|
- Fixed issue where using the API to request a movie/tv show would throw an exception when only using the API Key #3091. [tidusjar]
|
||||||
|
|
||||||
|
- Put "Ombi" back as the product name for Plex oAuth. [tidusjar]
|
||||||
|
|
||||||
|
|
||||||
|
## v3.0.4680 (2019-07-17)
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
|
- Update CHANGELOG.md. [Jamie]
|
||||||
|
|
||||||
|
### **Fixes**
|
||||||
|
|
||||||
|
- Fix Plex's (intentional) mistake #3073. [Jamie Rees]
|
||||||
|
|
||||||
|
- #2994 Fixed the startup issue. [tidusjar]
|
||||||
|
|
||||||
|
- #2994 - enable multithreading in the sql config. [Jamie Rees]
|
||||||
|
|
||||||
|
|
||||||
|
## v3.0.4659 (2019-07-02)
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
|
- Update appsettings.json. [Jamie]
|
||||||
|
|
||||||
|
- Update CHANGELOG.md. [Jamie]
|
||||||
|
|
||||||
|
|
||||||
|
## v3.0.4654 (2019-07-02)
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
- Added further logging into the API's (debug logging) [tidusjar]
|
- Added further logging into the API's (debug logging) [tidusjar]
|
||||||
|
|
||||||
- Added transactions around all of the CUD operations. [Jamie Rees]
|
- Added transactions around all of the CUD operations. [Jamie Rees]
|
||||||
|
|
||||||
- Update stale.yml. [Jamie]
|
|
||||||
|
|
||||||
- Update README.md. [Dyson Parkes]
|
|
||||||
|
|
||||||
- Added stalebot. [tidusjar]
|
|
||||||
|
|
||||||
- Added some validation around the new crons. [Jamie Rees]
|
- Added some validation around the new crons. [Jamie Rees]
|
||||||
|
|
||||||
- Added some defensive coding around when we create an artist for #2915. [tidusjar]
|
- Added some defensive coding around when we create an artist for #2915. [tidusjar]
|
||||||
|
|
||||||
- Update README.md. [Jamie]
|
|
||||||
|
|
||||||
- Update README.md. [Jamie]
|
|
||||||
|
|
||||||
- Update JobSetup.cs. [Jamie]
|
- Update JobSetup.cs. [Jamie]
|
||||||
|
|
||||||
- Update JobSetup.cs. [Jamie]
|
- Update JobSetup.cs. [Jamie]
|
||||||
|
@ -32,10 +137,28 @@
|
||||||
|
|
||||||
- Update dependancies. [TidusJar]
|
- Update dependancies. [TidusJar]
|
||||||
|
|
||||||
|
- Update stale.yml. [Jamie]
|
||||||
|
|
||||||
|
- Update README.md. [Dyson Parkes]
|
||||||
|
|
||||||
|
- Update README.md. [Jamie]
|
||||||
|
|
||||||
|
- Update README.md. [Jamie]
|
||||||
|
|
||||||
- Update CHANGELOG.md. [Jamie]
|
- Update CHANGELOG.md. [Jamie]
|
||||||
|
|
||||||
|
- Added stalebot. [tidusjar]
|
||||||
|
|
||||||
### **Fixes**
|
### **Fixes**
|
||||||
|
|
||||||
|
- Add back in the login time. [tidusjar]
|
||||||
|
|
||||||
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
- New translations en.json (Spanish) [Jamie]
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
- New translations en.json (Swedish) [Jamie]
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
@ -314,6 +437,12 @@
|
||||||
|
|
||||||
- Converted the Plex Jobs to use Quartz. [Jamie]
|
- Converted the Plex Jobs to use Quartz. [Jamie]
|
||||||
|
|
||||||
|
- Remove the need for the schedules.db #2994. [tidusjar]
|
||||||
|
|
||||||
|
- Create FUNDING.yml. [Jamie]
|
||||||
|
|
||||||
|
- Logging and slight change to the string matching now not dependant on Thread Culture #2866. [tidusjar]
|
||||||
|
|
||||||
|
|
||||||
## v3.0.4256 (2019-02-19)
|
## v3.0.4256 (2019-02-19)
|
||||||
|
|
||||||
|
|
|
@ -138,9 +138,9 @@ Task("TSLint")
|
||||||
});
|
});
|
||||||
|
|
||||||
Task("PrePublish")
|
Task("PrePublish")
|
||||||
.IsDependentOn("SetVersionInfo")
|
.IsDependentOn("SetVersionInfo");
|
||||||
.IsDependentOn("Gulp Publish")
|
//.IsDependentOn("Gulp Publish") // these are done in the main csproj
|
||||||
.IsDependentOn("TSLint");
|
//.IsDependentOn("TSLint");
|
||||||
|
|
||||||
|
|
||||||
Task("Package")
|
Task("Package")
|
||||||
|
|
|
@ -46,6 +46,17 @@ namespace Ombi.Api.Emby
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<PublicInfo> GetPublicInformation(string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request("emby/System/Info/public", baseUrl, HttpMethod.Get);
|
||||||
|
|
||||||
|
AddHeaders(request, string.Empty);
|
||||||
|
|
||||||
|
var obj = await Api.Request<PublicInfo>(request);
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri)
|
public async Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri)
|
||||||
{
|
{
|
||||||
var request = new Request("emby/users/authenticatebyname", baseUri, HttpMethod.Post);
|
var request = new Request("emby/users/authenticatebyname", baseUri, HttpMethod.Post);
|
||||||
|
@ -124,6 +135,7 @@ namespace Ombi.Api.Emby
|
||||||
{
|
{
|
||||||
return await GetInformation<MovieInformation>(mediaId, apiKey, userId, baseUrl);
|
return await GetInformation<MovieInformation>(mediaId, apiKey, userId, baseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EpisodeInformation> GetEpisodeInformation(string mediaId, string apiKey, string userId, string baseUrl)
|
public async Task<EpisodeInformation> GetEpisodeInformation(string mediaId, string apiKey, string userId, string baseUrl)
|
||||||
{
|
{
|
||||||
return await GetInformation<EpisodeInformation>(mediaId, apiKey, userId, baseUrl);
|
return await GetInformation<EpisodeInformation>(mediaId, apiKey, userId, baseUrl);
|
||||||
|
|
|
@ -29,5 +29,6 @@ namespace Ombi.Api.Emby
|
||||||
Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl);
|
Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl);
|
||||||
Task<MovieInformation> GetMovieInformation(string mediaId, string apiKey, string userId, string baseUrl);
|
Task<MovieInformation> GetMovieInformation(string mediaId, string apiKey, string userId, string baseUrl);
|
||||||
Task<EpisodeInformation> GetEpisodeInformation(string mediaId, string apiKey, string userId, string baseUrl);
|
Task<EpisodeInformation> GetEpisodeInformation(string mediaId, string apiKey, string userId, string baseUrl);
|
||||||
|
Task<PublicInfo> GetPublicInformation(string baseUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
19
src/Ombi.Api.Emby/Models/PublicInfo.cs
Normal file
19
src/Ombi.Api.Emby/Models/PublicInfo.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
namespace Ombi.Api.Emby.Models
|
||||||
|
{
|
||||||
|
public class PublicInfo
|
||||||
|
{
|
||||||
|
public string LocalAddress { get; set; }
|
||||||
|
public string ServerName { get; set; }
|
||||||
|
public string Version { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Only populated for Jellyfin
|
||||||
|
/// </summary>
|
||||||
|
public string ProductName { get; set; }
|
||||||
|
|
||||||
|
public bool IsJellyfin => !string.IsNullOrEmpty(ProductName) && ProductName.Contains("Jellyfin");
|
||||||
|
|
||||||
|
public string OperatingSystem { get; set; }
|
||||||
|
public string Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -20,7 +20,6 @@ namespace Ombi.Api.Lidarr
|
||||||
Task<AlbumResponse> MontiorAlbum(int albumId, string apiKey, string baseUrl);
|
Task<AlbumResponse> MontiorAlbum(int albumId, string apiKey, string baseUrl);
|
||||||
Task<List<AlbumResponse>> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl);
|
Task<List<AlbumResponse>> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl);
|
||||||
Task<List<MetadataProfile>> GetMetadataProfile(string apiKey, string baseUrl);
|
Task<List<MetadataProfile>> GetMetadataProfile(string apiKey, string baseUrl);
|
||||||
Task<List<LanguageProfiles>> GetLanguageProfile(string apiKey, string baseUrl);
|
|
||||||
Task<LidarrStatus> Status(string apiKey, string baseUrl);
|
Task<LidarrStatus> Status(string apiKey, string baseUrl);
|
||||||
Task<CommandResult> AlbumSearch(int[] albumIds, string apiKey, string baseUrl);
|
Task<CommandResult> AlbumSearch(int[] albumIds, string apiKey, string baseUrl);
|
||||||
Task<AlbumByForeignId> AlbumInformation(string albumId, string apiKey, string baseUrl);
|
Task<AlbumByForeignId> AlbumInformation(string albumId, string apiKey, string baseUrl);
|
||||||
|
|
|
@ -158,13 +158,6 @@ namespace Ombi.Api.Lidarr
|
||||||
return Api.Request<List<AlbumResponse>>(request);
|
return Api.Request<List<AlbumResponse>>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<List<LanguageProfiles>> GetLanguageProfile(string apiKey, string baseUrl)
|
|
||||||
{
|
|
||||||
var request = new Request($"{ApiVersion}/languageprofile", baseUrl, HttpMethod.Get);
|
|
||||||
AddHeaders(request, apiKey);
|
|
||||||
return Api.Request<List<LanguageProfiles>>(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<MetadataProfile>> GetMetadataProfile(string apiKey, string baseUrl)
|
public Task<List<MetadataProfile>> GetMetadataProfile(string apiKey, string baseUrl)
|
||||||
{
|
{
|
||||||
var request = new Request($"{ApiVersion}/metadataprofile", baseUrl, HttpMethod.Get);
|
var request = new Request($"{ApiVersion}/metadataprofile", baseUrl, HttpMethod.Get);
|
||||||
|
|
|
@ -17,7 +17,6 @@ namespace Ombi.Api.Lidarr.Models
|
||||||
public Image[] images { get; set; }
|
public Image[] images { get; set; }
|
||||||
public string remotePoster { get; set; }
|
public string remotePoster { get; set; }
|
||||||
public int qualityProfileId { get; set; }
|
public int qualityProfileId { get; set; }
|
||||||
public int languageProfileId { get; set; }
|
|
||||||
public int metadataProfileId { get; set; }
|
public int metadataProfileId { get; set; }
|
||||||
public bool albumFolder { get; set; }
|
public bool albumFolder { get; set; }
|
||||||
public bool monitored { get; set; }
|
public bool monitored { get; set; }
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Ombi.Api.Lidarr.Models
|
|
||||||
{
|
|
||||||
public class LanguageProfiles
|
|
||||||
{
|
|
||||||
public string name { get; set; }
|
|
||||||
public int id { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -227,7 +227,7 @@ namespace Ombi.Api.Plex
|
||||||
request.AddQueryString("context[device][environment]", "bundled");
|
request.AddQueryString("context[device][environment]", "bundled");
|
||||||
request.AddQueryString("context[device][layout]", "desktop");
|
request.AddQueryString("context[device][layout]", "desktop");
|
||||||
request.AddQueryString("context[device][platform]", "Web");
|
request.AddQueryString("context[device][platform]", "Web");
|
||||||
request.AddQueryString("context[device][device]", "Ombi (Web)");
|
request.AddQueryString("context[device][device]", "Ombi");
|
||||||
|
|
||||||
var s = await GetSettings();
|
var s = await GetSettings();
|
||||||
await CheckInstallId(s);
|
await CheckInstallId(s);
|
||||||
|
@ -295,7 +295,7 @@ namespace Ombi.Api.Plex
|
||||||
request.AddHeader("X-Plex-Client-Identifier", s.InstallId.ToString("N"));
|
request.AddHeader("X-Plex-Client-Identifier", s.InstallId.ToString("N"));
|
||||||
request.AddHeader("X-Plex-Product", ApplicationName);
|
request.AddHeader("X-Plex-Product", ApplicationName);
|
||||||
request.AddHeader("X-Plex-Version", "3");
|
request.AddHeader("X-Plex-Version", "3");
|
||||||
request.AddHeader("X-Plex-Device", "Ombi (Web)");
|
request.AddHeader("X-Plex-Device", "Ombi");
|
||||||
request.AddHeader("X-Plex-Platform", "Web");
|
request.AddHeader("X-Plex-Platform", "Web");
|
||||||
request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml");
|
request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml");
|
||||||
request.AddHeader("Accept", "application/json");
|
request.AddHeader("Accept", "application/json");
|
||||||
|
|
|
@ -17,6 +17,7 @@ using Ombi.Api.Lidarr.Models;
|
||||||
using Ombi.Core.Authentication;
|
using Ombi.Core.Authentication;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
|
using Ombi.Core.Helpers;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Settings.Settings.Models.External;
|
using Ombi.Settings.Settings.Models.External;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
|
@ -166,7 +167,7 @@ namespace Ombi.Core.Engine
|
||||||
Rating = a.ratings?.value ?? 0m,
|
Rating = a.ratings?.value ?? 0m,
|
||||||
ReleaseDate = a.releaseDate,
|
ReleaseDate = a.releaseDate,
|
||||||
Title = a.title,
|
Title = a.title,
|
||||||
Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url?.Replace("http", "https"),
|
Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url?.ToHttpsUrl(),
|
||||||
Genres = a.genres,
|
Genres = a.genres,
|
||||||
AlbumType = a.albumType,
|
AlbumType = a.albumType,
|
||||||
ArtistName = a.artist.artistName,
|
ArtistName = a.artist.artistName,
|
||||||
|
@ -187,7 +188,7 @@ namespace Ombi.Core.Engine
|
||||||
//vm.ArtistName = a.artist?.artistName;
|
//vm.ArtistName = a.artist?.artistName;
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url?.Replace("http", "https");
|
vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url?.ToHttpsUrl();
|
||||||
|
|
||||||
await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum);
|
await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum);
|
||||||
|
|
||||||
|
@ -205,7 +206,7 @@ namespace Ombi.Core.Engine
|
||||||
Rating = a.ratings?.value ?? 0m,
|
Rating = a.ratings?.value ?? 0m,
|
||||||
ReleaseDate = a.releaseDate,
|
ReleaseDate = a.releaseDate,
|
||||||
Title = a.title,
|
Title = a.title,
|
||||||
Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url?.Replace("http", "https"),
|
Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url?.ToHttpsUrl(),
|
||||||
Genres = a.genres
|
Genres = a.genres
|
||||||
};
|
};
|
||||||
if (a.artistId > 0)
|
if (a.artistId > 0)
|
||||||
|
@ -223,7 +224,7 @@ namespace Ombi.Core.Engine
|
||||||
vm.ArtistName = a.artist?.artistName;
|
vm.ArtistName = a.artist?.artistName;
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url?.Replace("http", "https");
|
vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url?.ToHttpsUrl();
|
||||||
if (vm.Cover.IsNullOrEmpty())
|
if (vm.Cover.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
vm.Cover = a.remoteCover;
|
vm.Cover = a.remoteCover;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Security.Principal;
|
using System;
|
||||||
|
using System.Security.Principal;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Ombi.Core.Authentication;
|
using Ombi.Core.Authentication;
|
||||||
|
@ -23,8 +24,8 @@ namespace Ombi.Core.Rule.Rules.Request
|
||||||
|
|
||||||
public async Task<RuleResult> Execute(BaseRequest obj)
|
public async Task<RuleResult> Execute(BaseRequest obj)
|
||||||
{
|
{
|
||||||
var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
|
var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName.Equals(User.Identity.Name, StringComparison.InvariantCultureIgnoreCase));
|
||||||
if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin))
|
if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin) || user.IsSystemUser)
|
||||||
{
|
{
|
||||||
obj.Approved = true;
|
obj.Approved = true;
|
||||||
return Success();
|
return Success();
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
@ -25,8 +26,8 @@ namespace Ombi.Core.Rule.Rules.Request
|
||||||
|
|
||||||
public async Task<RuleResult> Execute(BaseRequest obj)
|
public async Task<RuleResult> Execute(BaseRequest obj)
|
||||||
{
|
{
|
||||||
var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
|
var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName.Equals(User.Identity.Name, StringComparison.InvariantCultureIgnoreCase));
|
||||||
if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin))
|
if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin) || user.IsSystemUser)
|
||||||
return Success();
|
return Success();
|
||||||
|
|
||||||
if (obj.RequestType == RequestType.Movie)
|
if (obj.RequestType == RequestType.Movie)
|
||||||
|
|
|
@ -70,11 +70,11 @@ namespace Ombi.Core.Rule.Rules.Search
|
||||||
var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null);
|
var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null);
|
||||||
if ((server?.ServerHostname ?? string.Empty).HasValue())
|
if ((server?.ServerHostname ?? string.Empty).HasValue())
|
||||||
{
|
{
|
||||||
obj.EmbyUrl = $"{server.ServerHostname}#!/itemdetails.html?id={item.EmbyId}";
|
obj.EmbyUrl = EmbyHelper.GetEmbyMediaUrl(item.EmbyId, server?.ServerHostname, s.IsJellyfin);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
obj.EmbyUrl = $"https://app.emby.media/#!/itemdetails.html?id={item.EmbyId}";
|
obj.EmbyUrl = EmbyHelper.GetEmbyMediaUrl(item.EmbyId, null, s.IsJellyfin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ namespace Ombi.Core.Rule.Rules.Specific
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await UserManager.IsInRoleAsync(requestedUser, OmbiRoles.Admin))
|
if (await UserManager.IsInRoleAsync(requestedUser, OmbiRoles.Admin) || requestedUser.IsSystemUser)
|
||||||
{
|
{
|
||||||
sendNotification = false; // Don't bother sending a notification if the user is an admin
|
sendNotification = false; // Don't bother sending a notification if the user is an admin
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,7 +110,6 @@ namespace Ombi.Core.Senders
|
||||||
artistName = model.ArtistName,
|
artistName = model.ArtistName,
|
||||||
cleanName = model.ArtistName.ToLowerInvariant().RemoveSpaces(),
|
cleanName = model.ArtistName.ToLowerInvariant().RemoveSpaces(),
|
||||||
images = new Image[] { },
|
images = new Image[] { },
|
||||||
languageProfileId = settings.LanguageProfileId,
|
|
||||||
links = new Link[] {},
|
links = new Link[] {},
|
||||||
metadataProfileId = settings.MetadataProfileId,
|
metadataProfileId = settings.MetadataProfileId,
|
||||||
qualityProfileId = qualityToUse,
|
qualityProfileId = qualityToUse,
|
||||||
|
|
|
@ -346,6 +346,11 @@ namespace Ombi.Core.Senders
|
||||||
existingSeason.monitored = true;
|
existingSeason.monitored = true;
|
||||||
seriesChanges = true;
|
seriesChanges = true;
|
||||||
}
|
}
|
||||||
|
// Now update the episodes that need updating
|
||||||
|
foreach (var epToUpdate in episodesToUpdate.Where(x => x.seasonNumber == season.SeasonNumber))
|
||||||
|
{
|
||||||
|
await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
44
src/Ombi.Helpers.Tests/StringHelperTests.cs
Normal file
44
src/Ombi.Helpers.Tests/StringHelperTests.cs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace Ombi.Helpers.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class StringHelperTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void ToHttpsUrl_ShouldReturnsHttpsUrl_HttpUrl()
|
||||||
|
{
|
||||||
|
var sourceUrl = "http://www.test.url";
|
||||||
|
var expectedUrl = "https://www.test.url";
|
||||||
|
|
||||||
|
Assert.AreEqual(expectedUrl, sourceUrl.ToHttpsUrl(), "Should return the source URL as https");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ToHttpsUrl_ShouldReturnsUnchangedUrl_HttpsUrl()
|
||||||
|
{
|
||||||
|
var sourceUrl = "https://www.test.url";
|
||||||
|
var expectedUrl = "https://www.test.url";
|
||||||
|
|
||||||
|
Assert.AreEqual(expectedUrl, sourceUrl.ToHttpsUrl(), "Should return the unchanged https URL");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ToHttpsUrl_ShouldReturnsUnchangedUrl_NonHttpUrl()
|
||||||
|
{
|
||||||
|
var sourceUrl = "ftp://www.test.url";
|
||||||
|
var expectedUrl = "ftp://www.test.url";
|
||||||
|
|
||||||
|
Assert.AreEqual(expectedUrl, sourceUrl.ToHttpsUrl(), "Should return the unchanged non-http URL");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ToHttpsUrl_ShouldReturnsUnchangedUrl_InvalidUrl()
|
||||||
|
{
|
||||||
|
var sourceUrl = "http:/www.test.url";
|
||||||
|
var expectedUrl = "http:/www.test.url";
|
||||||
|
|
||||||
|
Assert.AreEqual(expectedUrl, sourceUrl.ToHttpsUrl(), "Should return the unchanged invalid URL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,19 +2,24 @@
|
||||||
{
|
{
|
||||||
public class EmbyHelper
|
public class EmbyHelper
|
||||||
{
|
{
|
||||||
public static string GetEmbyMediaUrl(string mediaId, string customerServerUrl = null)
|
public static string GetEmbyMediaUrl(string mediaId, string customerServerUrl = null, bool isJellyfin = false)
|
||||||
{
|
{
|
||||||
|
string path = "item/item";
|
||||||
|
if (isJellyfin)
|
||||||
|
{
|
||||||
|
path = "itemdetails";
|
||||||
|
}
|
||||||
if (customerServerUrl.HasValue())
|
if (customerServerUrl.HasValue())
|
||||||
{
|
{
|
||||||
if (!customerServerUrl.EndsWith("/"))
|
if (!customerServerUrl.EndsWith("/"))
|
||||||
{
|
{
|
||||||
return $"{customerServerUrl}/#!/itemdetails.html?id={mediaId}";
|
return $"{customerServerUrl}/#!/{path}.html?id={mediaId}";
|
||||||
}
|
}
|
||||||
return $"{customerServerUrl}#!/itemdetails.html?id={mediaId}";
|
return $"{customerServerUrl}#!/{path}.html?id={mediaId}";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return $"https://app.emby.media/#!/itemdetails.html?id={mediaId}";
|
return $"https://app.emby.media/#!/{path}.html?id={mediaId}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,5 +128,10 @@ namespace Ombi.Helpers
|
||||||
{
|
{
|
||||||
return string.Concat(str.Where(c => !chars.Contains(c)));
|
return string.Concat(str.Where(c => !chars.Contains(c)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ToHttpsUrl(this string currentUrl)
|
||||||
|
{
|
||||||
|
return currentUrl.Replace("http://", "https://");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -24,7 +24,7 @@ namespace Ombi.Mapping.Profiles
|
||||||
.ForMember(dest => dest.Runtime, opts => opts.MapFrom(src => src.show.runtime.ToString()))
|
.ForMember(dest => dest.Runtime, opts => opts.MapFrom(src => src.show.runtime.ToString()))
|
||||||
.ForMember(dest => dest.SeriesId, opts => opts.MapFrom(src => src.show.id))
|
.ForMember(dest => dest.SeriesId, opts => opts.MapFrom(src => src.show.id))
|
||||||
.ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.show.name))
|
.ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.show.name))
|
||||||
.ForMember(dest => dest.Banner, opts => opts.MapFrom(src => !string.IsNullOrEmpty(src.show.image.medium) ? src.show.image.medium.Replace("http", "https") : string.Empty))
|
.ForMember(dest => dest.Banner, opts => opts.MapFrom(src => !string.IsNullOrEmpty(src.show.image.medium) ? src.show.image.medium.ToHttpsUrl() : string.Empty))
|
||||||
.ForMember(dest => dest.Status, opts => opts.MapFrom(src => src.show.status));
|
.ForMember(dest => dest.Status, opts => opts.MapFrom(src => src.show.status));
|
||||||
|
|
||||||
CreateMap<TvMazeShow, SearchTvShowViewModel>()
|
CreateMap<TvMazeShow, SearchTvShowViewModel>()
|
||||||
|
@ -41,7 +41,7 @@ namespace Ombi.Mapping.Profiles
|
||||||
.ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.name))
|
.ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.name))
|
||||||
.ForMember(dest => dest.Banner,
|
.ForMember(dest => dest.Banner,
|
||||||
opts => opts.MapFrom(src => !string.IsNullOrEmpty(src.image.medium)
|
opts => opts.MapFrom(src => !string.IsNullOrEmpty(src.image.medium)
|
||||||
? src.image.medium.Replace("http", "https")
|
? src.image.medium.ToHttpsUrl()
|
||||||
: string.Empty))
|
: string.Empty))
|
||||||
.ForMember(dest => dest.Status, opts => opts.MapFrom(src => src.status));
|
.ForMember(dest => dest.Status, opts => opts.MapFrom(src => src.status));
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ using Ombi.Notifications.Models;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Settings.Settings.Models.Notifications;
|
using Ombi.Settings.Settings.Models.Notifications;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Entities.Requests;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
using Ombi.Store.Repository.Requests;
|
using Ombi.Store.Repository.Requests;
|
||||||
|
|
||||||
|
@ -21,13 +22,14 @@ namespace Ombi.Notifications.Agents
|
||||||
{
|
{
|
||||||
public MobileNotification(IOneSignalApi api, ISettingsService<MobileNotificationSettings> sn, ILogger<MobileNotification> log, INotificationTemplatesRepository r,
|
public MobileNotification(IOneSignalApi api, ISettingsService<MobileNotificationSettings> sn, ILogger<MobileNotification> log, INotificationTemplatesRepository r,
|
||||||
IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<NotificationUserId> notification,
|
IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<NotificationUserId> notification,
|
||||||
UserManager<OmbiUser> um, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
|
UserManager<OmbiUser> um, IRepository<RequestSubscription> sub, IMusicRequestRepository music, IRepository<Issues> issueRepository,
|
||||||
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref)
|
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref)
|
||||||
{
|
{
|
||||||
_api = api;
|
_api = api;
|
||||||
_logger = log;
|
_logger = log;
|
||||||
_notifications = notification;
|
_notifications = notification;
|
||||||
_userManager = um;
|
_userManager = um;
|
||||||
|
_issueRepository = issueRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string NotificationName => "MobileNotification";
|
public override string NotificationName => "MobileNotification";
|
||||||
|
@ -36,6 +38,7 @@ namespace Ombi.Notifications.Agents
|
||||||
private readonly ILogger<MobileNotification> _logger;
|
private readonly ILogger<MobileNotification> _logger;
|
||||||
private readonly IRepository<NotificationUserId> _notifications;
|
private readonly IRepository<NotificationUserId> _notifications;
|
||||||
private readonly UserManager<OmbiUser> _userManager;
|
private readonly UserManager<OmbiUser> _userManager;
|
||||||
|
private readonly IRepository<Issues> _issueRepository;
|
||||||
|
|
||||||
protected override bool ValidateConfiguration(MobileNotificationSettings settings)
|
protected override bool ValidateConfiguration(MobileNotificationSettings settings)
|
||||||
{
|
{
|
||||||
|
@ -95,8 +98,9 @@ namespace Ombi.Notifications.Agents
|
||||||
var isAdmin = bool.Parse(isAdminString);
|
var isAdmin = bool.Parse(isAdminString);
|
||||||
if (isAdmin)
|
if (isAdmin)
|
||||||
{
|
{
|
||||||
|
model.Substitutes.TryGetValue("IssueId", out var issueId);
|
||||||
// Send to user
|
// Send to user
|
||||||
var playerIds = GetUsers(model, NotificationType.IssueComment);
|
var playerIds = await GetUsersForIssue(model, int.Parse(issueId), NotificationType.IssueComment);
|
||||||
await Send(playerIds, notification, settings, model);
|
await Send(playerIds, notification, settings, model);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -250,6 +254,7 @@ namespace Ombi.Notifications.Agents
|
||||||
}
|
}
|
||||||
return playerIds;
|
return playerIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> GetUsers(NotificationOptions model, NotificationType type)
|
private List<string> GetUsers(NotificationOptions model, NotificationType type)
|
||||||
{
|
{
|
||||||
var notificationIds = new List<NotificationUserId>();
|
var notificationIds = new List<NotificationUserId>();
|
||||||
|
@ -268,7 +273,29 @@ namespace Ombi.Notifications.Agents
|
||||||
if (!notificationIds?.Any() ?? true)
|
if (!notificationIds?.Any() ?? true)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
$"there are no admins to send a notification for {type}, for agent {NotificationAgent.Mobile}");
|
$"there are no users to send a notification for {type}, for agent {NotificationAgent.Mobile}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var playerIds = notificationIds.Select(x => x.PlayerId).ToList();
|
||||||
|
return playerIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<string>> GetUsersForIssue(NotificationOptions model, int issueId, NotificationType type)
|
||||||
|
{
|
||||||
|
var notificationIds = new List<NotificationUserId>();
|
||||||
|
|
||||||
|
var issue = await _issueRepository.GetAll()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == issueId);
|
||||||
|
|
||||||
|
// Get the user that raised the issue to send the notification to
|
||||||
|
var userRaised = await _userManager.Users.Include(x => x.NotificationUserIds).FirstOrDefaultAsync(x => x.Id == issue.UserReportedId);
|
||||||
|
|
||||||
|
notificationIds = userRaised.NotificationUserIds;
|
||||||
|
|
||||||
|
if (!notificationIds?.Any() ?? true)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(
|
||||||
|
$"there are no users to send a notification for {type}, for agent {NotificationAgent.Mobile}");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
var playerIds = notificationIds.Select(x => x.PlayerId).ToList();
|
var playerIds = notificationIds.Select(x => x.PlayerId).ToList();
|
||||||
|
|
|
@ -79,7 +79,7 @@ namespace Ombi.Notifications.Agents
|
||||||
|
|
||||||
protected override async Task RequestDeclined(NotificationOptions model, SlackNotificationSettings settings)
|
protected override async Task RequestDeclined(NotificationOptions model, SlackNotificationSettings settings)
|
||||||
{
|
{
|
||||||
await Run(model, settings, NotificationType.RequestAvailable);
|
await Run(model, settings, NotificationType.RequestDeclined);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task RequestApproved(NotificationOptions model, SlackNotificationSettings settings)
|
protected override async Task RequestApproved(NotificationOptions model, SlackNotificationSettings settings)
|
||||||
|
|
|
@ -52,13 +52,13 @@ namespace Ombi.Schedule.Jobs.Emby
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await StartServerCache(server);
|
await StartServerCache(server, embySettings);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||||
.SendAsync(NotificationHub.NotificationEvent, "Emby Content Sync Failed");
|
.SendAsync(NotificationHub.NotificationEvent, "Emby Content Sync Failed");
|
||||||
_logger.LogError(e, "Exception when caching Emby for server {0}", server.Name);
|
_logger.LogError(e, "Exception when caching {1} for server {0}", server.Name, embySettings.IsJellyfin ? "Jellyfin" : "Emby");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task StartServerCache(EmbyServers server)
|
private async Task StartServerCache(EmbyServers server, EmbySettings settings)
|
||||||
{
|
{
|
||||||
if (!ValidateSettings(server))
|
if (!ValidateSettings(server))
|
||||||
return;
|
return;
|
||||||
|
@ -146,7 +146,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
||||||
Title = tvShow.Name,
|
Title = tvShow.Name,
|
||||||
Type = EmbyMediaType.Series,
|
Type = EmbyMediaType.Series,
|
||||||
EmbyId = tvShow.Id,
|
EmbyId = tvShow.Id,
|
||||||
Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id, server.ServerHostname),
|
Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id, server.ServerHostname, settings.IsJellyfin),
|
||||||
AddedAt = DateTime.UtcNow
|
AddedAt = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -696,7 +696,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
var banner = info.image?.original;
|
var banner = info.image?.original;
|
||||||
if (!string.IsNullOrEmpty(banner))
|
if (!string.IsNullOrEmpty(banner))
|
||||||
{
|
{
|
||||||
banner = banner.Replace("http", "https"); // Always use the Https banners
|
banner = banner.ToHttpsUrl(); // Always use the Https banners
|
||||||
}
|
}
|
||||||
|
|
||||||
var tvInfo = await _movieApi.GetTVInfo(t.TheMovieDbId);
|
var tvInfo = await _movieApi.GetTVInfo(t.TheMovieDbId);
|
||||||
|
@ -818,7 +818,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
var banner = info.image?.original;
|
var banner = info.image?.original;
|
||||||
if (!string.IsNullOrEmpty(banner))
|
if (!string.IsNullOrEmpty(banner))
|
||||||
{
|
{
|
||||||
banner = banner.Replace("http", "https"); // Always use the Https banners
|
banner = banner.ToHttpsUrl(); // Always use the Https banners
|
||||||
}
|
}
|
||||||
|
|
||||||
var tvInfo = await _movieApi.GetTVInfo(t.TheMovieDbId);
|
var tvInfo = await _movieApi.GetTVInfo(t.TheMovieDbId);
|
||||||
|
|
|
@ -21,6 +21,7 @@ using Ombi.Store.Entities;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
using Ombi.Updater;
|
using Ombi.Updater;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
|
using SharpCompress.Common;
|
||||||
using SharpCompress.Readers;
|
using SharpCompress.Readers;
|
||||||
using SharpCompress.Readers.Tar;
|
using SharpCompress.Readers.Tar;
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,6 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
{
|
{
|
||||||
_log.LogInformation("Starting the Metadata refresh from RecentlyAddedSync");
|
_log.LogInformation("Starting the Metadata refresh from RecentlyAddedSync");
|
||||||
var plexSettings = await _plexSettings.GetSettingsAsync();
|
var plexSettings = await _plexSettings.GetSettingsAsync();
|
||||||
var embySettings = await _embySettings.GetSettingsAsync();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (plexSettings.Enable)
|
if (plexSettings.Enable)
|
||||||
|
@ -98,19 +97,6 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
_log.LogError(e, "Exception when refreshing the Plex Metadata");
|
_log.LogError(e, "Exception when refreshing the Plex Metadata");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (plexSettings.Enable)
|
|
||||||
{
|
|
||||||
await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (embySettings.Enable)
|
|
||||||
{
|
|
||||||
await OmbiQuartz.TriggerJob(nameof(IEmbyAvaliabilityChecker), "Emby");
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StartPlexWithKnownContent(IEnumerable<int> contentids)
|
private async Task StartPlexWithKnownContent(IEnumerable<int> contentids)
|
||||||
|
|
|
@ -89,7 +89,7 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var processedContent = new ProcessedContent();
|
var processedContent = new ProcessedContent();
|
||||||
Logger.LogInformation("Starting Plex Content Cacher");
|
Logger.LogInformation($"Starting Plex Content Cacher {(recentlyAddedSearch ? "Recently Added Scan" : "")}");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (recentlyAddedSearch)
|
if (recentlyAddedSearch)
|
||||||
|
@ -117,6 +117,7 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
|
|
||||||
if ((processedContent?.HasProcessedContent ?? false) && recentlyAddedSearch)
|
if ((processedContent?.HasProcessedContent ?? false) && recentlyAddedSearch)
|
||||||
{
|
{
|
||||||
|
Logger.LogInformation("Starting Metadata refresh");
|
||||||
// Just check what we send it
|
// Just check what we send it
|
||||||
await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System");
|
await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System");
|
||||||
}
|
}
|
||||||
|
@ -126,8 +127,7 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
|
|
||||||
await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex");
|
await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex");
|
||||||
}
|
}
|
||||||
|
Logger.LogInformation("Finished Plex Content Cacher, with processed content: {0}, episodes: {1}. Recently Added Scan: {2}", processedContent?.Content?.Count() ?? 0, processedContent?.Episodes?.Count() ?? 0, recentlyAddedSearch);
|
||||||
Logger.LogInformation("Finished Plex Content Cacher, with processed content: {0}, episodes: {0}", processedContent.Content.Count(), processedContent.Episodes.Count());
|
|
||||||
|
|
||||||
await Notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
await Notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||||
.SendAsync(NotificationHub.NotificationEvent, recentlyAddedSearch ? "Plex Recently Added Sync Finished" : "Plex Content Sync Finished");
|
.SendAsync(NotificationHub.NotificationEvent, recentlyAddedSearch ? "Plex Recently Added Sync Finished" : "Plex Content Sync Finished");
|
||||||
|
@ -179,7 +179,8 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
|
|
||||||
foreach (var content in allContent)
|
foreach (var content in allContent)
|
||||||
{
|
{
|
||||||
if (content.viewGroup.Equals(PlexMediaType.Episode.ToString(), StringComparison.CurrentCultureIgnoreCase))
|
Logger.LogDebug($"Got type '{content.viewGroup}' to process");
|
||||||
|
if (content.viewGroup.Equals(PlexMediaType.Episode.ToString(), StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Found some episodes, this must be a recently added sync");
|
Logger.LogDebug("Found some episodes, this must be a recently added sync");
|
||||||
var count = 0;
|
var count = 0;
|
||||||
|
@ -223,7 +224,7 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
episodesProcessed.AddRange(episodesAdded.Select(x => x.Id));
|
episodesProcessed.AddRange(episodesAdded.Select(x => x.Id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase))
|
if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
// Process Shows
|
// Process Shows
|
||||||
Logger.LogDebug("Processing TV Shows");
|
Logger.LogDebug("Processing TV Shows");
|
||||||
|
@ -253,7 +254,7 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
|
|
||||||
await Repo.SaveChangesAsync();
|
await Repo.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
if (content.viewGroup.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase))
|
if (content.viewGroup.Equals(PlexMediaType.Movie.ToString(), StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Processing Movies");
|
Logger.LogDebug("Processing Movies");
|
||||||
foreach (var movie in content?.Metadata ?? new Metadata[] { })
|
foreach (var movie in content?.Metadata ?? new Metadata[] { })
|
||||||
|
|
|
@ -56,7 +56,9 @@ namespace Ombi.Schedule.Jobs.Radarr
|
||||||
var movieIds = new List<RadarrCache>();
|
var movieIds = new List<RadarrCache>();
|
||||||
foreach (var m in movies)
|
foreach (var m in movies)
|
||||||
{
|
{
|
||||||
if (m.tmdbId > 0 && m.monitored)
|
if(m.monitored)
|
||||||
|
{
|
||||||
|
if (m.tmdbId > 0)
|
||||||
{
|
{
|
||||||
movieIds.Add(new RadarrCache
|
movieIds.Add(new RadarrCache
|
||||||
{
|
{
|
||||||
|
@ -69,6 +71,7 @@ namespace Ombi.Schedule.Jobs.Radarr
|
||||||
Logger.LogError("TMDBId is not > 0 for movie {0}", m.title);
|
Logger.LogError("TMDBId is not > 0 for movie {0}", m.title);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
using (var tran = await _ctx.Database.BeginTransactionAsync())
|
using (var tran = await _ctx.Database.BeginTransactionAsync())
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<PackageReference Include="Microsoft.AspNet.SignalR" Version="2.4.1" />
|
<PackageReference Include="Microsoft.AspNet.SignalR" Version="2.4.1" />
|
||||||
<PackageReference Include="Quartz" Version="3.0.7" />
|
<PackageReference Include="Quartz" Version="3.0.7" />
|
||||||
<PackageReference Include="Serilog" Version="2.8.0" />
|
<PackageReference Include="Serilog" Version="2.8.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.18.2" />
|
<PackageReference Include="SharpCompress" Version="0.24.0" />
|
||||||
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
|
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.6.13" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.6.13" />
|
||||||
<PackageReference Include="Markdig" Version="0.14.8" />
|
<PackageReference Include="Markdig" Version="0.14.8" />
|
||||||
|
|
|
@ -6,6 +6,7 @@ namespace Ombi.Core.Settings.Models.External
|
||||||
public sealed class EmbySettings : Ombi.Settings.Settings.Models.Settings
|
public sealed class EmbySettings : Ombi.Settings.Settings.Models.Settings
|
||||||
{
|
{
|
||||||
public bool Enable { get; set; }
|
public bool Enable { get; set; }
|
||||||
|
public bool IsJellyfin { get; set; }
|
||||||
public List<EmbyServers> Servers { get; set; } = new List<EmbyServers>();
|
public List<EmbyServers> Servers { get; set; } = new List<EmbyServers>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ namespace Ombi.Settings.Settings.Models.External
|
||||||
public string DefaultQualityProfile { get; set; }
|
public string DefaultQualityProfile { get; set; }
|
||||||
public string DefaultRootPath { get; set; }
|
public string DefaultRootPath { get; set; }
|
||||||
public bool AlbumFolder { get; set; }
|
public bool AlbumFolder { get; set; }
|
||||||
public int LanguageProfileId { get; set; }
|
|
||||||
public int MetadataProfileId { get; set; }
|
public int MetadataProfileId { get; set; }
|
||||||
public bool AddOnly { get; set; }
|
public bool AddOnly { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,7 +101,6 @@ namespace Ombi.Store.Context
|
||||||
UserName = "Api",
|
UserName = "Api",
|
||||||
UserType = UserType.SystemUser,
|
UserType = UserType.SystemUser,
|
||||||
NormalizedUserName = "API",
|
NormalizedUserName = "API",
|
||||||
|
|
||||||
});
|
});
|
||||||
SaveChanges();
|
SaveChanges();
|
||||||
tran.Commit();
|
tran.Commit();
|
||||||
|
|
|
@ -33,6 +33,7 @@ export interface IUpdateSettings extends ISettings {
|
||||||
|
|
||||||
export interface IEmbySettings extends ISettings {
|
export interface IEmbySettings extends ISettings {
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
|
isJellyfin: boolean;
|
||||||
servers: IEmbyServer[];
|
servers: IEmbyServer[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +45,11 @@ export interface IEmbyServer extends IExternalSettings {
|
||||||
serverHostname: string;
|
serverHostname: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IPublicInfo {
|
||||||
|
serverName: string;
|
||||||
|
isJellyfin: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IPlexSettings extends ISettings {
|
export interface IPlexSettings extends ISettings {
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
servers: IPlexServer[];
|
servers: IPlexServer[];
|
||||||
|
|
|
@ -24,6 +24,14 @@ import { MatSnackBar } from "@angular/material";
|
||||||
})
|
})
|
||||||
export class LoginComponent implements OnDestroy, OnInit {
|
export class LoginComponent implements OnDestroy, OnInit {
|
||||||
|
|
||||||
|
public get appName(): string {
|
||||||
|
if (this.customizationSettings.applicationName) {
|
||||||
|
return this.customizationSettings.applicationName;
|
||||||
|
} else {
|
||||||
|
return "Ombi";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public form: FormGroup;
|
public form: FormGroup;
|
||||||
public customizationSettings: ICustomizationSettings;
|
public customizationSettings: ICustomizationSettings;
|
||||||
public authenticationSettings: IAuthenticationSettings;
|
public authenticationSettings: IAuthenticationSettings;
|
||||||
|
@ -52,6 +60,8 @@ export class LoginComponent implements OnDestroy, OnInit {
|
||||||
private errorValidation: string;
|
private errorValidation: string;
|
||||||
private href: string;
|
private href: string;
|
||||||
|
|
||||||
|
private oAuthWindow: Window|null;
|
||||||
|
|
||||||
constructor(private authService: AuthService, private router: Router, private status: StatusService,
|
constructor(private authService: AuthService, private router: Router, private status: StatusService,
|
||||||
private fb: FormBuilder, private settingsService: SettingsService, private images: ImageService, private sanitizer: DomSanitizer,
|
private fb: FormBuilder, private settingsService: SettingsService, private images: ImageService, private sanitizer: DomSanitizer,
|
||||||
private route: ActivatedRoute, @Inject(APP_BASE_HREF) href:string, private translate: TranslateService, private plexTv: PlexTvService,
|
private route: ActivatedRoute, @Inject(APP_BASE_HREF) href:string, private translate: TranslateService, private plexTv: PlexTvService,
|
||||||
|
@ -142,8 +152,6 @@ export class LoginComponent implements OnDestroy, OnInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private oAuthWindow: Window;
|
|
||||||
|
|
||||||
public oauth() {
|
public oauth() {
|
||||||
this.oAuthWindow = window.open(window.location.toString(), "_blank", `toolbar=0,
|
this.oAuthWindow = window.open(window.location.toString(), "_blank", `toolbar=0,
|
||||||
location=0,
|
location=0,
|
||||||
|
@ -176,7 +184,10 @@ export class LoginComponent implements OnDestroy, OnInit {
|
||||||
|
|
||||||
if (this.authService.loggedIn()) {
|
if (this.authService.loggedIn()) {
|
||||||
this.ngOnDestroy();
|
this.ngOnDestroy();
|
||||||
|
|
||||||
|
if(this.oAuthWindow) {
|
||||||
this.oAuthWindow.close();
|
this.oAuthWindow.close();
|
||||||
|
}
|
||||||
this.router.navigate(["search"]);
|
this.router.navigate(["search"]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Observable } from "rxjs";
|
||||||
|
|
||||||
import { ServiceHelpers } from "../service.helpers";
|
import { ServiceHelpers } from "../service.helpers";
|
||||||
|
|
||||||
import { IEmbySettings, IUsersModel } from "../../interfaces";
|
import { IEmbyServer, IEmbySettings, IPublicInfo, IUsersModel } from "../../interfaces";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EmbyService extends ServiceHelpers {
|
export class EmbyService extends ServiceHelpers {
|
||||||
|
@ -16,8 +16,13 @@ export class EmbyService extends ServiceHelpers {
|
||||||
public logIn(settings: IEmbySettings): Observable<IEmbySettings> {
|
public logIn(settings: IEmbySettings): Observable<IEmbySettings> {
|
||||||
return this.http.post<IEmbySettings>(`${this.url}`, JSON.stringify(settings), {headers: this.headers});
|
return this.http.post<IEmbySettings>(`${this.url}`, JSON.stringify(settings), {headers: this.headers});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getUsers(): Observable<IUsersModel[]> {
|
public getUsers(): Observable<IUsersModel[]> {
|
||||||
return this.http.get<IUsersModel[]>(`${this.url}users`, {headers: this.headers});
|
return this.http.get<IUsersModel[]>(`${this.url}users`, {headers: this.headers});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getPublicInfo(server: IEmbyServer): Observable<IPublicInfo> {
|
||||||
|
return this.http.post<IPublicInfo>(`${this.url}info`, JSON.stringify(server), {headers: this.headers});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,4 @@ export class LidarrService extends ServiceHelpers {
|
||||||
public getMetadataProfiles(settings: ILidarrSettings): Observable<IProfiles[]> {
|
public getMetadataProfiles(settings: ILidarrSettings): Observable<IProfiles[]> {
|
||||||
return this.http.post<IProfiles[]>(`${this.url}/Metadata/`, JSON.stringify(settings), {headers: this.headers});
|
return this.http.post<IProfiles[]>(`${this.url}/Metadata/`, JSON.stringify(settings), {headers: this.headers});
|
||||||
}
|
}
|
||||||
public getLanguages(settings: ILidarrSettings): Observable<IProfiles[]> {
|
|
||||||
return this.http.post<IProfiles[]>(`${this.url}/Langauges/`,JSON.stringify(settings), {headers: this.headers});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div *ngIf="settings">
|
<div *ngIf="settings">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>
|
<legend>
|
||||||
Emby Configuration
|
Emby/Jellyfin Configuration
|
||||||
</legend>
|
</legend>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -71,8 +71,8 @@
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
<input type="text" class="form-control-custom form-control" id="authToken" [(ngModel)]="server.serverHostname" placeholder="e.g. https://jellyfin.server.com/" value="{{server.serverHostname}}">
|
<input type="text" class="form-control-custom form-control" id="authToken" [(ngModel)]="server.serverHostname" placeholder="e.g. https://jellyfin.server.com/" value="{{server.serverHostname}}">
|
||||||
<small><span *ngIf="server.serverHostname">Current URL: "{{server.serverHostname}}/#!/itemdetails.html?id=1"</span>
|
<small><span *ngIf="server.serverHostname">Current URL: "{{server.serverHostname}}/#!/{{settings.isJellyfin ? ("itemdetails"): ("item/item")}}.html?id=1"</span>
|
||||||
<span *ngIf="!server.serverHostname">Current URL: "https://app.emby.media/#!/itemdetails.html?id=1</span></small>
|
<span *ngIf="!server.serverHostname">Current URL: "https://app.emby.media/#!/{{settings.isJellyfin ? ("itemdetails"): ("item/item")}}.html?id=1</span></small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -80,6 +80,11 @@
|
||||||
<button id="testEmby" type="button" (click)="test(server)" class="btn btn-primary-outline">Test Connectivity <div id="spinner"></div></button>
|
<button id="testEmby" type="button" (click)="test(server)" class="btn btn-primary-outline">Test Connectivity <div id="spinner"></div></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div>
|
||||||
|
<button id="discover" type="button" (click)="discoverServerInfo(server)" class="btn btn-primary-outline">Discover Server Information <div id="spinner"></div></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ngb-tab>
|
</ngb-tab>
|
||||||
|
@ -88,7 +93,7 @@
|
||||||
<div class="col-md-1">
|
<div class="col-md-1">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div>
|
<div>
|
||||||
<button (click)="save()" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
<button [disabled]="!hasDiscovered" (click)="save()" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
|
||||||
import { IEmbyServer, IEmbySettings } from "../../interfaces";
|
import { IEmbyServer, IEmbySettings } from "../../interfaces";
|
||||||
import { JobService, NotificationService, SettingsService, TesterService } from "../../services";
|
import { EmbyService, JobService, NotificationService, SettingsService, TesterService } from "../../services";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "./emby.component.html",
|
templateUrl: "./emby.component.html",
|
||||||
|
@ -9,16 +9,25 @@ import { JobService, NotificationService, SettingsService, TesterService } from
|
||||||
export class EmbyComponent implements OnInit {
|
export class EmbyComponent implements OnInit {
|
||||||
|
|
||||||
public settings: IEmbySettings;
|
public settings: IEmbySettings;
|
||||||
|
public hasDiscovered: boolean;
|
||||||
|
|
||||||
constructor(private settingsService: SettingsService,
|
constructor(private settingsService: SettingsService,
|
||||||
private notificationService: NotificationService,
|
private notificationService: NotificationService,
|
||||||
private testerService: TesterService,
|
private testerService: TesterService,
|
||||||
private jobService: JobService) { }
|
private jobService: JobService,
|
||||||
|
private embyService: EmbyService) { }
|
||||||
|
|
||||||
public ngOnInit() {
|
public ngOnInit() {
|
||||||
this.settingsService.getEmby().subscribe(x => this.settings = x);
|
this.settingsService.getEmby().subscribe(x => this.settings = x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async discoverServerInfo(server: IEmbyServer) {
|
||||||
|
const result = await this.embyService.getPublicInfo(server).toPromise();
|
||||||
|
this.settings.isJellyfin = result.isJellyfin;
|
||||||
|
server.name = result.serverName;
|
||||||
|
this.hasDiscovered = true;
|
||||||
|
}
|
||||||
|
|
||||||
public addTab() {
|
public addTab() {
|
||||||
if (this.settings.servers == null) {
|
if (this.settings.servers == null) {
|
||||||
this.settings.servers = [];
|
this.settings.servers = [];
|
||||||
|
|
|
@ -72,19 +72,6 @@
|
||||||
*ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button></div>
|
*ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label>Language Profile</mat-label>
|
|
||||||
<mat-select formControlName="languageProfileId" required>
|
|
||||||
<mat-option *ngFor="let folder of languageProfiles" [value]="folder.id">
|
|
||||||
{{folder.name}}
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
<div><button mat-raised-button (click)="getLanguageProfiles(form)" color="primary">Load Languages <span
|
|
||||||
*ngIf="languageRunning" class="fa fa-spinner fa-spin"></span></button></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label>Metadata Profile</mat-label>
|
<mat-label>Metadata Profile</mat-label>
|
||||||
|
|
|
@ -12,14 +12,12 @@ import { SettingsService } from "../../services";
|
||||||
export class LidarrComponent implements OnInit {
|
export class LidarrComponent implements OnInit {
|
||||||
|
|
||||||
public qualities: IRadarrProfile[];
|
public qualities: IRadarrProfile[];
|
||||||
public languageProfiles: IProfiles[];
|
|
||||||
public metadataProfiles: IProfiles[];
|
public metadataProfiles: IProfiles[];
|
||||||
public rootFolders: IRadarrRootFolder[];
|
public rootFolders: IRadarrRootFolder[];
|
||||||
public minimumAvailabilityOptions: IMinimumAvailability[];
|
public minimumAvailabilityOptions: IMinimumAvailability[];
|
||||||
public profilesRunning: boolean;
|
public profilesRunning: boolean;
|
||||||
public rootFoldersRunning: boolean;
|
public rootFoldersRunning: boolean;
|
||||||
public metadataRunning: boolean;
|
public metadataRunning: boolean;
|
||||||
public languageRunning: boolean;
|
|
||||||
public advanced = false;
|
public advanced = false;
|
||||||
public form: FormGroup;
|
public form: FormGroup;
|
||||||
|
|
||||||
|
@ -43,7 +41,6 @@ export class LidarrComponent implements OnInit {
|
||||||
ip: [x.ip, [Validators.required]],
|
ip: [x.ip, [Validators.required]],
|
||||||
port: [x.port, [Validators.required]],
|
port: [x.port, [Validators.required]],
|
||||||
albumFolder: [x.albumFolder],
|
albumFolder: [x.albumFolder],
|
||||||
languageProfileId: [x.languageProfileId, [Validators.required]],
|
|
||||||
metadataProfileId: [x.metadataProfileId, [Validators.required]],
|
metadataProfileId: [x.metadataProfileId, [Validators.required]],
|
||||||
addOnly: [x.addOnly],
|
addOnly: [x.addOnly],
|
||||||
});
|
});
|
||||||
|
@ -54,9 +51,6 @@ export class LidarrComponent implements OnInit {
|
||||||
if (x.defaultRootPath) {
|
if (x.defaultRootPath) {
|
||||||
this.getRootFolders(this.form);
|
this.getRootFolders(this.form);
|
||||||
}
|
}
|
||||||
if (x.languageProfileId) {
|
|
||||||
this.getLanguageProfiles(this.form);
|
|
||||||
}
|
|
||||||
if (x.metadataProfileId) {
|
if (x.metadataProfileId) {
|
||||||
this.getMetadataProfiles(this.form);
|
this.getMetadataProfiles(this.form);
|
||||||
}
|
}
|
||||||
|
@ -68,9 +62,6 @@ export class LidarrComponent implements OnInit {
|
||||||
this.rootFolders = [];
|
this.rootFolders = [];
|
||||||
this.rootFolders.push({ path: "Please Select", id: -1 });
|
this.rootFolders.push({ path: "Please Select", id: -1 });
|
||||||
|
|
||||||
this.languageProfiles = [];
|
|
||||||
this.languageProfiles.push({ name: "Please Select", id: -1 });
|
|
||||||
|
|
||||||
this.metadataProfiles = [];
|
this.metadataProfiles = [];
|
||||||
this.metadataProfiles.push({ name: "Please Select", id: -1 });
|
this.metadataProfiles.push({ name: "Please Select", id: -1 });
|
||||||
}
|
}
|
||||||
|
@ -108,17 +99,6 @@ export class LidarrComponent implements OnInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLanguageProfiles(form: FormGroup) {
|
|
||||||
this.languageRunning = true;
|
|
||||||
this.lidarrService.getLanguages(form.value).subscribe(x => {
|
|
||||||
this.languageProfiles = x;
|
|
||||||
this.languageProfiles.unshift({ name: "Please Select", id: -1 });
|
|
||||||
|
|
||||||
this.languageRunning = false;
|
|
||||||
this.notificationService.success("Successfully retrieved the Language profiles");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public test(form: FormGroup) {
|
public test(form: FormGroup) {
|
||||||
if (form.invalid) {
|
if (form.invalid) {
|
||||||
this.notificationService.error("Please check your entered values");
|
this.notificationService.error("Please check your entered values");
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<button mat-button [matMenuTriggerFor]="mediaservermenu"><i class="fa fa-server" aria-hidden="true"></i> Media Server</button>
|
<button mat-button [matMenuTriggerFor]="mediaservermenu"><i class="fa fa-server" aria-hidden="true"></i> Media Server</button>
|
||||||
<mat-menu #mediaservermenu="matMenu">
|
<mat-menu #mediaservermenu="matMenu">
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Plex']">Plex</button>
|
<button mat-menu-item [routerLink]="['/Settings/Plex']">Plex</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Emby']">Emby</button>
|
<button mat-menu-item [routerLink]="['/Settings/Emby']">Emby/Jellyfin</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<button mat-button [matMenuTriggerFor]="tvmenu"><i class="fa fa-television" aria-hidden="true"></i> TV</button>
|
<button mat-button [matMenuTriggerFor]="tvmenu"><i class="fa fa-television" aria-hidden="true"></i> TV</button>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div *ngFor="let server of embySettings.servers">
|
<div *ngFor="let server of embySettings.servers">
|
||||||
<div>
|
<div>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input type="text" matInput [(ngModel)]="server.ip" id="Ip" name="Ip" placeholder="Emby Hostname or IP Address">
|
<input type="text" matInput [(ngModel)]="server.ip" id="Ip" name="Ip" placeholder="Emby/Jellyfin Hostname or IP Address">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ export class EmbyComponent implements OnInit {
|
||||||
}
|
}
|
||||||
this.embySettings = {
|
this.embySettings = {
|
||||||
servers: [],
|
servers: [],
|
||||||
|
isJellyfin: false,
|
||||||
id: 0,
|
id: 0,
|
||||||
enable: true,
|
enable: true,
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,6 +4,8 @@ using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Ombi.Api.Emby;
|
using Ombi.Api.Emby;
|
||||||
|
using Ombi.Api.Emby.Models;
|
||||||
|
using Ombi.Api.Plex;
|
||||||
using Ombi.Attributes;
|
using Ombi.Attributes;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Core.Settings.Models.External;
|
using Ombi.Core.Settings.Models.External;
|
||||||
|
@ -59,6 +61,13 @@ namespace Ombi.Controllers.V1.External
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("info")]
|
||||||
|
public async Task<PublicInfo> GetServerInfo([FromBody] EmbyServers server)
|
||||||
|
{
|
||||||
|
var result = await EmbyApi.GetPublicInformation(server.FullUri);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the emby users.
|
/// Gets the emby users.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -60,16 +60,6 @@ namespace Ombi.Controllers.V1.External
|
||||||
{
|
{
|
||||||
return await _lidarrApi.GetMetadataProfile(settings.ApiKey, settings.FullUri);
|
return await _lidarrApi.GetMetadataProfile(settings.ApiKey, settings.FullUri);
|
||||||
}
|
}
|
||||||
/// <summary>
|
|
||||||
/// Gets the Lidarr Langauge profiles.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="settings">The settings.</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[HttpPost("Langauges")]
|
|
||||||
public async Task<IEnumerable<LanguageProfiles>> GetLanguageProfiles([FromBody] LidarrSettings settings)
|
|
||||||
{
|
|
||||||
return await _lidarrApi.GetLanguageProfile(settings.ApiKey, settings.FullUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the Lidarr profiles using the saved settings
|
/// Gets the Lidarr profiles using the saved settings
|
||||||
|
|
|
@ -233,6 +233,8 @@ namespace Ombi.Controllers.V1
|
||||||
await CreateRole(OmbiRoles.AutoApproveMovie);
|
await CreateRole(OmbiRoles.AutoApproveMovie);
|
||||||
await CreateRole(OmbiRoles.Admin);
|
await CreateRole(OmbiRoles.Admin);
|
||||||
await CreateRole(OmbiRoles.AutoApproveTv);
|
await CreateRole(OmbiRoles.AutoApproveTv);
|
||||||
|
await CreateRole(OmbiRoles.AutoApproveMusic);
|
||||||
|
await CreateRole(OmbiRoles.RequestMusic);
|
||||||
await CreateRole(OmbiRoles.PowerUser);
|
await CreateRole(OmbiRoles.PowerUser);
|
||||||
await CreateRole(OmbiRoles.RequestMovie);
|
await CreateRole(OmbiRoles.RequestMovie);
|
||||||
await CreateRole(OmbiRoles.RequestTv);
|
await CreateRole(OmbiRoles.RequestTv);
|
||||||
|
@ -279,7 +281,7 @@ namespace Ombi.Controllers.V1
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<UserViewModel> GetCurrentUser()
|
public async Task<UserViewModel> GetCurrentUser()
|
||||||
{
|
{
|
||||||
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
|
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName.Equals(User.Identity.Name, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
|
||||||
return await GetUserWithRoles(user);
|
return await GetUserWithRoles(user);
|
||||||
}
|
}
|
||||||
|
@ -873,7 +875,7 @@ namespace Ombi.Controllers.V1
|
||||||
[ApiExplorerSettings(IgnoreApi = true)]
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
public async Task<string> GetUserAccessToken()
|
public async Task<string> GetUserAccessToken()
|
||||||
{
|
{
|
||||||
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
|
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName.Equals(User.Identity.Name, StringComparison.InvariantCultureIgnoreCase));
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
return Guid.Empty.ToString("N");
|
return Guid.Empty.ToString("N");
|
||||||
|
@ -895,7 +897,7 @@ namespace Ombi.Controllers.V1
|
||||||
[HttpGet("notificationpreferences")]
|
[HttpGet("notificationpreferences")]
|
||||||
public async Task<List<UserNotificationPreferences>> GetUserPreferences()
|
public async Task<List<UserNotificationPreferences>> GetUserPreferences()
|
||||||
{
|
{
|
||||||
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
|
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName.Equals(User.Identity.Name, StringComparison.InvariantCultureIgnoreCase));
|
||||||
return await GetPreferences(user);
|
return await GetPreferences(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -948,7 +950,7 @@ namespace Ombi.Controllers.V1
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
// Check if we are editing a different user than ourself, if we are then we need to power user role
|
// Check if we are editing a different user than ourself, if we are then we need to power user role
|
||||||
var me = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
|
var me = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName.Equals(User.Identity.Name, StringComparison.InvariantCultureIgnoreCase));
|
||||||
if (!me.Id.Equals(user.Id, StringComparison.InvariantCultureIgnoreCase))
|
if (!me.Id.Equals(user.Id, StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
var isPowerUser = await UserManager.IsInRoleAsync(me, OmbiRoles.PowerUser);
|
var isPowerUser = await UserManager.IsInRoleAsync(me, OmbiRoles.PowerUser);
|
||||||
|
|
|
@ -185,7 +185,7 @@ namespace Ombi.Controllers.V1
|
||||||
Comment = c.Comment,
|
Comment = c.Comment,
|
||||||
Date = c.Date,
|
Date = c.Date,
|
||||||
Username = c.User.UserAlias,
|
Username = c.User.UserAlias,
|
||||||
AdminComment = roles.Contains(OmbiRoles.PowerUser) || roles.Contains(OmbiRoles.Admin)
|
AdminComment = roles.Contains(OmbiRoles.PowerUser) || roles.Contains(OmbiRoles.Admin) || c.User.IsSystemUser
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return vm;
|
return vm;
|
||||||
|
@ -221,9 +221,10 @@ namespace Ombi.Controllers.V1
|
||||||
UserId = user.Id
|
UserId = user.Id
|
||||||
};
|
};
|
||||||
|
|
||||||
var isAdmin = await _userManager.IsInRoleAsync(user, OmbiRoles.Admin);
|
var isAdmin = await _userManager.IsInRoleAsync(user, OmbiRoles.Admin) || user.IsSystemUser;
|
||||||
AddIssueNotificationSubstitutes(notificationModel, issue, issue.UserReported.UserAlias);
|
AddIssueNotificationSubstitutes(notificationModel, issue, issue.UserReported.UserAlias);
|
||||||
notificationModel.Substitutes.Add("NewIssueComment", comment.Comment);
|
notificationModel.Substitutes.Add("NewIssueComment", comment.Comment);
|
||||||
|
notificationModel.Substitutes.Add("IssueId", comment.IssueId.ToString());
|
||||||
notificationModel.Substitutes.Add("AdminComment", isAdmin.ToString());
|
notificationModel.Substitutes.Add("AdminComment", isAdmin.ToString());
|
||||||
|
|
||||||
if (isAdmin)
|
if (isAdmin)
|
||||||
|
|
|
@ -118,7 +118,7 @@ namespace Ombi.Controllers.V1
|
||||||
[HttpPost("plexrecentlyadded")]
|
[HttpPost("plexrecentlyadded")]
|
||||||
public bool StartRecentlyAdded()
|
public bool StartRecentlyAdded()
|
||||||
{
|
{
|
||||||
OmbiQuartz.Scheduler.TriggerJob(new JobKey(nameof(IPlexContentSync), "Plex"), new JobDataMap(new Dictionary<string, string> { { "recentlyAddedSearch", "true" } }));
|
OmbiQuartz.Scheduler.TriggerJob(new JobKey(nameof(IPlexContentSync) + "RecentlyAdded", "Plex"), new JobDataMap(new Dictionary<string, string> { { "recentlyAddedSearch", "true" } }));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace Ombi.Controllers.V1
|
||||||
{
|
{
|
||||||
if (body?.PlayerId.HasValue() ?? false)
|
if (body?.PlayerId.HasValue() ?? false)
|
||||||
{
|
{
|
||||||
var user = await _userManager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
|
var user = await _userManager.Users.FirstOrDefaultAsync(x => x.UserName.Equals(User.Identity.Name, StringComparison.InvariantCultureIgnoreCase));
|
||||||
// Check if we already have this notification id
|
// Check if we already have this notification id
|
||||||
var alreadyExists = await _notification.GetAll().AnyAsync(x => x.PlayerId == body.PlayerId && x.UserId == user.Id);
|
var alreadyExists = await _notification.GetAll().AnyAsync(x => x.PlayerId == body.PlayerId && x.UserId == user.Id);
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using AutoMapper.EquivalencyExpression;
|
using AutoMapper.EquivalencyExpression;
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
using Hangfire.Dashboard;
|
using Hangfire.Dashboard;
|
||||||
|
using Hangfire.MemoryStorage;
|
||||||
using Hangfire.SQLite;
|
using Hangfire.SQLite;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
@ -94,20 +95,15 @@ namespace Ombi
|
||||||
services.AddSwagger();
|
services.AddSwagger();
|
||||||
services.AddAppSettingsValues(Configuration);
|
services.AddAppSettingsValues(Configuration);
|
||||||
|
|
||||||
var i = StoragePathSingleton.Instance;
|
|
||||||
if (string.IsNullOrEmpty(i.StoragePath))
|
|
||||||
{
|
|
||||||
i.StoragePath = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sqliteStorage = $"Data Source={Path.Combine(i.StoragePath, "Schedules.db")};";
|
|
||||||
|
|
||||||
services.AddHangfire(x =>
|
services.AddHangfire(x =>
|
||||||
{
|
{
|
||||||
x.UseSQLiteStorage(sqliteStorage);
|
x.UseMemoryStorage();
|
||||||
x.UseActivator(new IoCJobActivator(services.BuildServiceProvider()));
|
x.UseActivator(new IoCJobActivator(services.BuildServiceProvider()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
SQLitePCL.raw.sqlite3_config(2);
|
||||||
|
|
||||||
services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
|
services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
|
||||||
{
|
{
|
||||||
builder.AllowAnyHeader()
|
builder.AllowAnyHeader()
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"IncludeScopes": false,
|
"IncludeScopes": false,
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Debug",
|
"Default": "Information",
|
||||||
"System": "Information",
|
"System": "Information",
|
||||||
"Microsoft": "None",
|
"Microsoft": "None",
|
||||||
"Hangfire": "None"
|
"Hangfire": "None"
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
},
|
},
|
||||||
"NavigationBar": {
|
"NavigationBar": {
|
||||||
"Search": "Rechercher",
|
"Search": "Rechercher",
|
||||||
"Requests": "Demandes",
|
"Requests": "En attente",
|
||||||
"UserManagement": "Gestion des utilisateurs",
|
"UserManagement": "Gestion des utilisateurs",
|
||||||
"Issues": "Problèmes",
|
"Issues": "Problèmes",
|
||||||
"Vote": "Vote",
|
"Vote": "Vote",
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
"Title": "Rechercher",
|
"Title": "Rechercher",
|
||||||
"Paragraph": "Vous voulez regarder quelque chose qui n'est pas disponible actuellement ? Pas de problème, recherchez-le ci-dessous et demandez-le !",
|
"Paragraph": "Vous voulez regarder quelque chose qui n'est pas disponible actuellement ? Pas de problème, recherchez-le ci-dessous et demandez-le !",
|
||||||
"MoviesTab": "Films",
|
"MoviesTab": "Films",
|
||||||
"TvTab": "TV",
|
"TvTab": "Séries",
|
||||||
"MusicTab": "Musique",
|
"MusicTab": "Musique",
|
||||||
"Suggestions": "Suggestions",
|
"Suggestions": "Suggestions",
|
||||||
"NoResults": "Désolé, nous n'avons trouvé aucun résultat !",
|
"NoResults": "Désolé, nous n'avons trouvé aucun résultat !",
|
||||||
|
@ -104,7 +104,7 @@
|
||||||
"Title": "Demandes",
|
"Title": "Demandes",
|
||||||
"Paragraph": "Vous pouvez voir ci-dessous vos demandes et celles des autres, ainsi que leur statut de téléchargement et d'approbation.",
|
"Paragraph": "Vous pouvez voir ci-dessous vos demandes et celles des autres, ainsi que leur statut de téléchargement et d'approbation.",
|
||||||
"MoviesTab": "Films",
|
"MoviesTab": "Films",
|
||||||
"TvTab": "Émissions",
|
"TvTab": "Séries",
|
||||||
"MusicTab": "Musique",
|
"MusicTab": "Musique",
|
||||||
"RequestedBy": "Demandé par :",
|
"RequestedBy": "Demandé par :",
|
||||||
"Status": "Statut :",
|
"Status": "Statut :",
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"Common": {
|
"Common": {
|
||||||
"ContinueButton": "Gå videre",
|
"ContinueButton": "Gå videre",
|
||||||
"Available": "Tilgjengelig",
|
"Available": "Tilgjengelig",
|
||||||
"PartiallyAvailable": "Partially Available",
|
"PartiallyAvailable": "Delvis tilgjengelig",
|
||||||
"Monitored": "Overvåket",
|
"Monitored": "Overvåket",
|
||||||
"NotAvailable": "Ikke tilgjengelig",
|
"NotAvailable": "Ikke tilgjengelig",
|
||||||
"ProcessingRequest": "Behandler forespørsel",
|
"ProcessingRequest": "Behandler forespørsel",
|
||||||
|
@ -74,8 +74,8 @@
|
||||||
"ViewOnEmby": "Spill av på Emby",
|
"ViewOnEmby": "Spill av på Emby",
|
||||||
"RequestAdded": "Forespørsel om {{title}} er lagt til",
|
"RequestAdded": "Forespørsel om {{title}} er lagt til",
|
||||||
"Similar": "Lignende",
|
"Similar": "Lignende",
|
||||||
"Refine": "Refine",
|
"Refine": "Spesifiser",
|
||||||
"SearchBarPlaceholder": "Type Here to Search",
|
"SearchBarPlaceholder": "Angi nøkkelord for søk",
|
||||||
"Movies": {
|
"Movies": {
|
||||||
"PopularMovies": "Populære filmer",
|
"PopularMovies": "Populære filmer",
|
||||||
"UpcomingMovies": "Kommende filmer",
|
"UpcomingMovies": "Kommende filmer",
|
||||||
|
@ -86,7 +86,7 @@
|
||||||
},
|
},
|
||||||
"TvShows": {
|
"TvShows": {
|
||||||
"Popular": "Populært",
|
"Popular": "Populært",
|
||||||
"Trending": "Trending",
|
"Trending": "På vei opp",
|
||||||
"MostWatched": "Mest sett",
|
"MostWatched": "Mest sett",
|
||||||
"MostAnticipated": "Mest etterlengtede",
|
"MostAnticipated": "Mest etterlengtede",
|
||||||
"Results": "Resultater",
|
"Results": "Resultater",
|
||||||
|
@ -111,8 +111,8 @@
|
||||||
"RequestStatus": "Status for forespørsel:",
|
"RequestStatus": "Status for forespørsel:",
|
||||||
"Denied": " Avslått:",
|
"Denied": " Avslått:",
|
||||||
"TheatricalRelease": "Kinopremiere: {{date}}",
|
"TheatricalRelease": "Kinopremiere: {{date}}",
|
||||||
"ReleaseDate": "Released: {{date}}",
|
"ReleaseDate": "Utgitt: {{date}}",
|
||||||
"TheatricalReleaseSort": "Theatrical Release",
|
"TheatricalReleaseSort": "Kinopremiere",
|
||||||
"DigitalRelease": "Digital utgivelse: {{date}}",
|
"DigitalRelease": "Digital utgivelse: {{date}}",
|
||||||
"RequestDate": "Dato for forespørsel:",
|
"RequestDate": "Dato for forespørsel:",
|
||||||
"QualityOverride": "Overstyr kvalitet:",
|
"QualityOverride": "Overstyr kvalitet:",
|
||||||
|
@ -133,16 +133,16 @@
|
||||||
"SeasonNumberHeading": "Sesong: {seasonNumber}",
|
"SeasonNumberHeading": "Sesong: {seasonNumber}",
|
||||||
"SortTitleAsc": "Tittel ▲",
|
"SortTitleAsc": "Tittel ▲",
|
||||||
"SortTitleDesc": "Tittel ▼",
|
"SortTitleDesc": "Tittel ▼",
|
||||||
"SortRequestDateAsc": "Request Date ▲",
|
"SortRequestDateAsc": "Dato for forespørsel ▲",
|
||||||
"SortRequestDateDesc": "Request Date ▼",
|
"SortRequestDateDesc": "Dato for forespørsel ▼",
|
||||||
"SortStatusAsc": "Status ▲",
|
"SortStatusAsc": "Status ▲",
|
||||||
"SortStatusDesc": "Status ▼",
|
"SortStatusDesc": "Status ▼",
|
||||||
"Remaining": {
|
"Remaining": {
|
||||||
"Quota": "{{remaining}}/{{total}} requests remaining",
|
"Quota": "{{remaining}}/{{total}} forespørsler igjen",
|
||||||
"NextDays": "Another request will be added in {{time}} days",
|
"NextDays": "En ny foresøprel vil bli lagt til om {{time}} dager",
|
||||||
"NextHours": "Another request will be added in {{time}} hours",
|
"NextHours": "En ny foresøprel vil bli lagt til om {{time}} timer",
|
||||||
"NextMinutes": "Another request will be added in {{time}} minutes",
|
"NextMinutes": "En ny foresøprel vil bli lagt til om {{time}} minutter",
|
||||||
"NextMinute": "Another request will be added in {{time}} minute"
|
"NextMinute": "En ny foresøprel vil bli lagt til om {{time}} minutt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Issues": {
|
"Issues": {
|
||||||
|
@ -181,6 +181,6 @@
|
||||||
},
|
},
|
||||||
"Votes": {
|
"Votes": {
|
||||||
"CompletedVotesTab": "Stemt",
|
"CompletedVotesTab": "Stemt",
|
||||||
"VotesTab": "Votes Needed"
|
"VotesTab": "Stemmer som trengs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
},
|
},
|
||||||
"Search": {
|
"Search": {
|
||||||
"Title": "Szukaj",
|
"Title": "Szukaj",
|
||||||
"Paragraph": "Chcesz obejrzeć coś, co nie jest obecnie dostępne? Żaden problem, po prostu wyszukaj poniżej i dodaj zgłoszenie!",
|
"Paragraph": "Chcesz obejrzeć coś, co nie jest obecnie dostępne? Żaden problem! Po prostu wyszukaj poniżej i dodaj zgłoszenie!",
|
||||||
"MoviesTab": "Filmy",
|
"MoviesTab": "Filmy",
|
||||||
"TvTab": "Seriale",
|
"TvTab": "Seriale",
|
||||||
"MusicTab": "Muzyka",
|
"MusicTab": "Muzyka",
|
||||||
|
|
|
@ -16,171 +16,171 @@
|
||||||
"Monitored": "Мониторинг",
|
"Monitored": "Мониторинг",
|
||||||
"NotAvailable": "Недоступно",
|
"NotAvailable": "Недоступно",
|
||||||
"ProcessingRequest": "Обработка запроса",
|
"ProcessingRequest": "Обработка запроса",
|
||||||
"PendingApproval": "Ожидание утверждения",
|
"PendingApproval": "В ожидании одобрения",
|
||||||
"RequestDenied": "Запрос отклонен",
|
"RequestDenied": "Запрос отклонен",
|
||||||
"NotRequested": "Не запрошено",
|
"NotRequested": "Не запрошено",
|
||||||
"Requested": "Запрос отправлен",
|
"Requested": "Запрошено",
|
||||||
"Request": "Запрос",
|
"Request": "Запросить",
|
||||||
"Denied": "Запрещено",
|
"Denied": "Отказано",
|
||||||
"Approve": "Утвердить",
|
"Approve": "Одобрить",
|
||||||
"PartlyAvailable": "Partly Available",
|
"PartlyAvailable": "Частично доступно",
|
||||||
"Errors": {
|
"Errors": {
|
||||||
"Validation": "Please check your entered values"
|
"Validation": "Пожалуйста, проверьте введенные значения"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"PasswordReset": {
|
"PasswordReset": {
|
||||||
"EmailAddressPlaceholder": "Email Address",
|
"EmailAddressPlaceholder": "Адрес эл. почты",
|
||||||
"ResetPasswordButton": "Reset Password"
|
"ResetPasswordButton": "Сбросить пароль"
|
||||||
},
|
},
|
||||||
"LandingPage": {
|
"LandingPage": {
|
||||||
"OnlineHeading": "Currently Online",
|
"OnlineHeading": "Сейчас в сети",
|
||||||
"OnlineParagraph": "The media server is currently online",
|
"OnlineParagraph": "Медиа-сервер в настоящее время в сети",
|
||||||
"PartiallyOnlineHeading": "Partially Online",
|
"PartiallyOnlineHeading": "Частично в сети",
|
||||||
"PartiallyOnlineParagraph": "The media server is partially online.",
|
"PartiallyOnlineParagraph": "Медиа-сервер частично в сети.",
|
||||||
"MultipleServersUnavailable": "There are {{serversUnavailable}} servers offline out of {{totalServers}}.",
|
"MultipleServersUnavailable": "В сети нет {{serversUnavailable}} серверов из {{totalServers}}.",
|
||||||
"SingleServerUnavailable": "There is {{serversUnavailable}} server offline out of {{totalServers}}.",
|
"SingleServerUnavailable": "В сети нет {{serversUnavailable}} серверов из {{totalServers}}.",
|
||||||
"OfflineHeading": "Currently Offline",
|
"OfflineHeading": "В настоящее время в offline",
|
||||||
"OfflineParagraph": "The media server is currently offline.",
|
"OfflineParagraph": "Медиа-сервер в настоящее время не в сети.",
|
||||||
"CheckPageForUpdates": "Check this page for continuous site updates."
|
"CheckPageForUpdates": "Проверьте эту страницу для получения последних новостей сайта."
|
||||||
},
|
},
|
||||||
"NavigationBar": {
|
"NavigationBar": {
|
||||||
"Search": "Search",
|
"Search": "Поиск",
|
||||||
"Requests": "Requests",
|
"Requests": "Запросы",
|
||||||
"UserManagement": "User Management",
|
"UserManagement": "Управление пользователями",
|
||||||
"Issues": "Issues",
|
"Issues": "Проблемы",
|
||||||
"Vote": "Vote",
|
"Vote": "Голосование",
|
||||||
"Donate": "Donate!",
|
"Donate": "Поддержать!",
|
||||||
"DonateLibraryMaintainer": "Donate to Library Maintainer",
|
"DonateLibraryMaintainer": "Поддержать библиотекаря",
|
||||||
"DonateTooltip": "This is how I convince my wife to let me spend my spare time developing Ombi ;)",
|
"DonateTooltip": "Так я убедил свою жену позволить мне тратить своё свободное время на разработку Ombi ;)",
|
||||||
"UpdateAvailableTooltip": "Update Available!",
|
"UpdateAvailableTooltip": "Доступно обновление!",
|
||||||
"Settings": "Settings",
|
"Settings": "Настройки",
|
||||||
"Welcome": "Welcome {{username}}",
|
"Welcome": "Добро пожаловать, {{username}}",
|
||||||
"UpdateDetails": "Update Details",
|
"UpdateDetails": "Обновить детали",
|
||||||
"Logout": "Logout",
|
"Logout": "Выйти",
|
||||||
"OpenMobileApp": "Open Mobile App",
|
"OpenMobileApp": "Открыть моб. приложение",
|
||||||
"RecentlyAdded": "Recently Added"
|
"RecentlyAdded": "Недавно добавленные"
|
||||||
},
|
},
|
||||||
"Search": {
|
"Search": {
|
||||||
"Title": "Search",
|
"Title": "Поиск",
|
||||||
"Paragraph": "Want to watch something that is not currently available? No problem, just search for it below and request it!",
|
"Paragraph": "Хотите посмотреть что-то, чего нет в доступе? Нет проблем, просто вбейте название и запросите!",
|
||||||
"MoviesTab": "Movies",
|
"MoviesTab": "Фильмы",
|
||||||
"TvTab": "TV Shows",
|
"TvTab": "Сериалы",
|
||||||
"MusicTab": "Music",
|
"MusicTab": "Музыка",
|
||||||
"Suggestions": "Suggestions",
|
"Suggestions": "Рекомендации",
|
||||||
"NoResults": "Sorry, we didn't find any results!",
|
"NoResults": "Извините, мы ничего не нашли!",
|
||||||
"DigitalDate": "Digital Release: {{date}}",
|
"DigitalDate": "Дигитальный релиз: {{date}}",
|
||||||
"TheatricalRelease": "Theatrical Release: {{date}}",
|
"TheatricalRelease": "Релиз в кинотеатрах: {{date}}",
|
||||||
"ViewOnPlex": "View On Plex",
|
"ViewOnPlex": "Смотреть в Plex",
|
||||||
"ViewOnEmby": "View On Emby",
|
"ViewOnEmby": "Смотреть в Emby",
|
||||||
"RequestAdded": "Request for {{title}} has been added successfully",
|
"RequestAdded": "Запрос на {{title}} успешно добавлен",
|
||||||
"Similar": "Similar",
|
"Similar": "Похожие",
|
||||||
"Refine": "Refine",
|
"Refine": "Уточнить",
|
||||||
"SearchBarPlaceholder": "Type Here to Search",
|
"SearchBarPlaceholder": "Поиск...",
|
||||||
"Movies": {
|
"Movies": {
|
||||||
"PopularMovies": "Popular Movies",
|
"PopularMovies": "Популярные фильмы",
|
||||||
"UpcomingMovies": "Upcoming Movies",
|
"UpcomingMovies": "В скором времени",
|
||||||
"TopRatedMovies": "Top Rated Movies",
|
"TopRatedMovies": "Фильмы с высоким рейтингом",
|
||||||
"NowPlayingMovies": "Now Playing Movies",
|
"NowPlayingMovies": "Сейчас в кинотеатрах",
|
||||||
"HomePage": "Home Page",
|
"HomePage": "Главная страница",
|
||||||
"Trailer": "Trailer"
|
"Trailer": "Трейлер"
|
||||||
},
|
},
|
||||||
"TvShows": {
|
"TvShows": {
|
||||||
"Popular": "Popular",
|
"Popular": "Популярное",
|
||||||
"Trending": "Trending",
|
"Trending": "Сейчас смотрят",
|
||||||
"MostWatched": "Most Watched",
|
"MostWatched": "Самые просматриваемые",
|
||||||
"MostAnticipated": "Most Anticipated",
|
"MostAnticipated": "Самые ожидаемые",
|
||||||
"Results": "Results",
|
"Results": "Результаты",
|
||||||
"AirDate": "Air Date:",
|
"AirDate": "Дата выхода:",
|
||||||
"AllSeasons": "All Seasons",
|
"AllSeasons": "Все сезоны",
|
||||||
"FirstSeason": "First Season",
|
"FirstSeason": "Первый сезон",
|
||||||
"LatestSeason": "Latest Season",
|
"LatestSeason": "Последний сезон",
|
||||||
"Select": "Select ...",
|
"Select": "Выбрать...",
|
||||||
"SubmitRequest": "Submit Request",
|
"SubmitRequest": "Подать запрос",
|
||||||
"Season": "Season: {{seasonNumber}}",
|
"Season": "Сезон: {{seasonNumber}}",
|
||||||
"SelectAllInSeason": "Select All in Season {{seasonNumber}}"
|
"SelectAllInSeason": "Выбрать все в сезоне {{seasonNumber}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Requests": {
|
"Requests": {
|
||||||
"Title": "Requests",
|
"Title": "Запросы",
|
||||||
"Paragraph": "Below you can see yours and all other requests, as well as their download and approval status.",
|
"Paragraph": "Ниже вы можете увидеть ваши и все другие запросы, а также их статус загрузки и одобрения.",
|
||||||
"MoviesTab": "Movies",
|
"MoviesTab": "Фильмы",
|
||||||
"TvTab": "TV Shows",
|
"TvTab": "Сериалы",
|
||||||
"MusicTab": "Music",
|
"MusicTab": "Музыка",
|
||||||
"RequestedBy": "Requested By:",
|
"RequestedBy": "Автор запроса:",
|
||||||
"Status": "Status:",
|
"Status": "Статус:",
|
||||||
"RequestStatus": "Request status:",
|
"RequestStatus": "Статус запроса:",
|
||||||
"Denied": " Denied:",
|
"Denied": " Отказано:",
|
||||||
"TheatricalRelease": "Theatrical Release: {{date}}",
|
"TheatricalRelease": "Релиз в кинотеатрах: {{date}}",
|
||||||
"ReleaseDate": "Released: {{date}}",
|
"ReleaseDate": "Дата выхода: {{date}}",
|
||||||
"TheatricalReleaseSort": "Theatrical Release",
|
"TheatricalReleaseSort": "Релиз в кинотеатрах",
|
||||||
"DigitalRelease": "Digital Release: {{date}}",
|
"DigitalRelease": "Дигитальный релиз: {{date}}",
|
||||||
"RequestDate": "Request Date:",
|
"RequestDate": "Дата запроса:",
|
||||||
"QualityOverride": "Quality Override:",
|
"QualityOverride": "Переопределение качества:",
|
||||||
"RootFolderOverride": "Root Folder Override:",
|
"RootFolderOverride": "Переопределение корневой папки:",
|
||||||
"ChangeRootFolder": "Root Folder",
|
"ChangeRootFolder": "Корневая папка",
|
||||||
"ChangeQualityProfile": "Quality Profile",
|
"ChangeQualityProfile": "Профиль качества",
|
||||||
"MarkUnavailable": "Mark Unavailable",
|
"MarkUnavailable": "Отметить недоступным",
|
||||||
"MarkAvailable": "Mark Available",
|
"MarkAvailable": "Отметить доступным",
|
||||||
"Remove": "Remove",
|
"Remove": "Удалить",
|
||||||
"Deny": "Deny",
|
"Deny": "Отклонить",
|
||||||
"Season": "Season:",
|
"Season": "Сезон:",
|
||||||
"GridTitle": "Title",
|
"GridTitle": "Название",
|
||||||
"AirDate": "AirDate",
|
"AirDate": "Дата",
|
||||||
"GridStatus": "Status",
|
"GridStatus": "Статус",
|
||||||
"ReportIssue": "Report Issue",
|
"ReportIssue": "Сообщить о проблеме",
|
||||||
"Filter": "Filter",
|
"Filter": "Фильтр",
|
||||||
"Sort": "Sort",
|
"Sort": "Сортировать",
|
||||||
"SeasonNumberHeading": "Season: {seasonNumber}",
|
"SeasonNumberHeading": "Сезон: {seasonNumber}",
|
||||||
"SortTitleAsc": "Title ▲",
|
"SortTitleAsc": "Название ▲",
|
||||||
"SortTitleDesc": "Title ▼",
|
"SortTitleDesc": "Название ▼",
|
||||||
"SortRequestDateAsc": "Request Date ▲",
|
"SortRequestDateAsc": "Дата запроса ▲",
|
||||||
"SortRequestDateDesc": "Request Date ▼",
|
"SortRequestDateDesc": "Дата запроса ▼",
|
||||||
"SortStatusAsc": "Status ▲",
|
"SortStatusAsc": "Статус ▲",
|
||||||
"SortStatusDesc": "Status ▼",
|
"SortStatusDesc": "Статус ▼",
|
||||||
"Remaining": {
|
"Remaining": {
|
||||||
"Quota": "{{remaining}}/{{total}} requests remaining",
|
"Quota": "Осталось запросов: {{remaining}}/{{total}}",
|
||||||
"NextDays": "Another request will be added in {{time}} days",
|
"NextDays": "Следующий запрос будет добавлен через {{time}} дней",
|
||||||
"NextHours": "Another request will be added in {{time}} hours",
|
"NextHours": "Следующий запрос будет добавлен через {{time}} часов",
|
||||||
"NextMinutes": "Another request will be added in {{time}} minutes",
|
"NextMinutes": "Следующий запрос будет добавлен через {{time}} минут",
|
||||||
"NextMinute": "Another request will be added in {{time}} minute"
|
"NextMinute": "Следующий запрос будет добавлен через {{time}} минуту"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Issues": {
|
"Issues": {
|
||||||
"Title": "Issues",
|
"Title": "Проблемы",
|
||||||
"PendingTitle": "Pending Issues",
|
"PendingTitle": "Проблемы в ожидании",
|
||||||
"InProgressTitle": "In Progress Issues",
|
"InProgressTitle": "Проблемы в процессе",
|
||||||
"ResolvedTitle": "Resolved Issues",
|
"ResolvedTitle": "Решенные проблемы",
|
||||||
"ColumnTitle": "Title",
|
"ColumnTitle": "Название",
|
||||||
"Category": "Category",
|
"Category": "Категория",
|
||||||
"Status": "Status",
|
"Status": "Статус",
|
||||||
"Details": "Details",
|
"Details": "Подробная информация",
|
||||||
"Description": "Description",
|
"Description": "Описание",
|
||||||
"NoComments": "No Comments!",
|
"NoComments": "Нет комментариев!",
|
||||||
"MarkInProgress": "Mark In Progress",
|
"MarkInProgress": "Отметить в процессе",
|
||||||
"MarkResolved": "Mark Resolved",
|
"MarkResolved": "Отметить как решенное",
|
||||||
"SendMessageButton": "Send",
|
"SendMessageButton": "Отправить",
|
||||||
"Subject": "Subject",
|
"Subject": "Тема",
|
||||||
"Comments": "Comments",
|
"Comments": "Комментарии",
|
||||||
"WriteMessagePlaceholder": "Write your message here...",
|
"WriteMessagePlaceholder": "Введите текст сообщения здесь...",
|
||||||
"ReportedBy": "Reported By"
|
"ReportedBy": "Жалоба поступила от"
|
||||||
},
|
},
|
||||||
"Filter": {
|
"Filter": {
|
||||||
"ClearFilter": "Clear Filter",
|
"ClearFilter": "Сбросить фильтр",
|
||||||
"FilterHeaderAvailability": "Availability",
|
"FilterHeaderAvailability": "Доступность",
|
||||||
"FilterHeaderRequestStatus": "Status",
|
"FilterHeaderRequestStatus": "Статус",
|
||||||
"Approved": "Approved",
|
"Approved": "Одобрено",
|
||||||
"PendingApproval": "Pending Approval"
|
"PendingApproval": "В ожидании одобрения"
|
||||||
},
|
},
|
||||||
"UserManagment": {
|
"UserManagment": {
|
||||||
"TvRemaining": "TV: {{remaining}}/{{total}} remaining",
|
"TvRemaining": "Сериалы: {{remaining}}/{{total}} осталось",
|
||||||
"MovieRemaining": "Movies: {{remaining}}/{{total}} remaining",
|
"MovieRemaining": "Фильмы: {{remaining}}/{{total}} осталось",
|
||||||
"MusicRemaining": "Music: {{remaining}}/{{total}} remaining",
|
"MusicRemaining": "Музыка: {{remaining}}/{{total}} осталось",
|
||||||
"TvDue": "TV: {{date}}",
|
"TvDue": "Сериалы: {{date}}",
|
||||||
"MovieDue": "Movie: {{date}}",
|
"MovieDue": "Фильм: {{date}}",
|
||||||
"MusicDue": "Music: {{date}}"
|
"MusicDue": "Музыка: {{date}}"
|
||||||
},
|
},
|
||||||
"Votes": {
|
"Votes": {
|
||||||
"CompletedVotesTab": "Voted",
|
"CompletedVotesTab": "Проголосовано",
|
||||||
"VotesTab": "Votes Needed"
|
"VotesTab": "Необходимы голоса"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue