mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-31 03:50:08 -07:00
merge
This commit is contained in:
commit
eedd67b205
269 changed files with 10168 additions and 1028 deletions
|
@ -25,10 +25,6 @@ namespace Ombi.Api.Emby
|
|||
|
||||
public IEmbyApi CreateClient(EmbySettings settings)
|
||||
{
|
||||
if (settings.IsJellyfin)
|
||||
{
|
||||
return new JellyfinApi(_api);
|
||||
}
|
||||
return new EmbyApi(_api);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,8 @@
|
|||
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; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
33
src/Ombi.Api.Jellyfin/IBaseJellyfinApi.cs
Normal file
33
src/Ombi.Api.Jellyfin/IBaseJellyfinApi.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Api.Jellyfin.Models;
|
||||
using Ombi.Api.Jellyfin.Models.Media.Tv;
|
||||
using Ombi.Api.Jellyfin.Models.Movie;
|
||||
|
||||
namespace Ombi.Api.Jellyfin
|
||||
{
|
||||
public interface IBaseJellyfinApi
|
||||
{
|
||||
Task<JellyfinSystemInfo> GetSystemInformation(string apiKey, string baseUrl);
|
||||
Task<List<JellyfinUser>> GetUsers(string baseUri, string apiKey);
|
||||
Task<JellyfinUser> LogIn(string username, string password, string apiKey, string baseUri);
|
||||
|
||||
Task<JellyfinItemContainer<JellyfinMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId,
|
||||
string baseUri);
|
||||
|
||||
Task<JellyfinItemContainer<JellyfinEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId,
|
||||
string baseUri);
|
||||
|
||||
Task<JellyfinItemContainer<JellyfinSeries>> GetAllShows(string apiKey, int startIndex, int count, string userId,
|
||||
string baseUri);
|
||||
|
||||
Task<JellyfinItemContainer<JellyfinMovie>> GetCollection(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<EpisodeInformation> GetEpisodeInformation(string mediaId, string apiKey, string userId, string baseUrl);
|
||||
Task<PublicInfo> GetPublicInformation(string baseUrl);
|
||||
}
|
||||
}
|
10
src/Ombi.Api.Jellyfin/IJellyfinApi.cs
Normal file
10
src/Ombi.Api.Jellyfin/IJellyfinApi.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.Threading.Tasks;
|
||||
using Ombi.Api.Jellyfin.Models;
|
||||
|
||||
namespace Ombi.Api.Jellyfin
|
||||
{
|
||||
public interface IJellyfinApi : IBaseJellyfinApi
|
||||
{
|
||||
Task<JellyfinConnectUser> LoginConnectUser(string username, string password);
|
||||
}
|
||||
}
|
|
@ -3,14 +3,14 @@ using System.Net.Http;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore.Internal;
|
||||
using Newtonsoft.Json;
|
||||
using Ombi.Api.Emby.Models;
|
||||
using Ombi.Api.Emby.Models.Media.Tv;
|
||||
using Ombi.Api.Emby.Models.Movie;
|
||||
using Ombi.Api.Jellyfin.Models;
|
||||
using Ombi.Api.Jellyfin.Models.Media.Tv;
|
||||
using Ombi.Api.Jellyfin.Models.Movie;
|
||||
using Ombi.Helpers;
|
||||
|
||||
namespace Ombi.Api.Emby
|
||||
namespace Ombi.Api.Jellyfin
|
||||
{
|
||||
public class JellyfinApi : IEmbyApi
|
||||
public class JellyfinApi : IJellyfinApi
|
||||
{
|
||||
public JellyfinApi(IApi api)
|
||||
{
|
||||
|
@ -20,27 +20,27 @@ namespace Ombi.Api.Emby
|
|||
private IApi Api { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns all users from the Emby Instance
|
||||
/// Returns all users from the Jellyfin Instance
|
||||
/// </summary>
|
||||
/// <param name="baseUri"></param>
|
||||
/// <param name="apiKey"></param>
|
||||
public async Task<List<EmbyUser>> GetUsers(string baseUri, string apiKey)
|
||||
public async Task<List<JellyfinUser>> GetUsers(string baseUri, string apiKey)
|
||||
{
|
||||
var request = new Request("users", baseUri, HttpMethod.Get);
|
||||
|
||||
AddHeaders(request, apiKey);
|
||||
var obj = await Api.Request<List<EmbyUser>>(request);
|
||||
var obj = await Api.Request<List<JellyfinUser>>(request);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public async Task<EmbySystemInfo> GetSystemInformation(string apiKey, string baseUrl)
|
||||
public async Task<JellyfinSystemInfo> GetSystemInformation(string apiKey, string baseUrl)
|
||||
{
|
||||
var request = new Request("System/Info", baseUrl, HttpMethod.Get);
|
||||
|
||||
AddHeaders(request, apiKey);
|
||||
|
||||
var obj = await Api.Request<EmbySystemInfo>(request);
|
||||
var obj = await Api.Request<JellyfinSystemInfo>(request);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ namespace Ombi.Api.Emby
|
|||
return obj;
|
||||
}
|
||||
|
||||
public async Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri)
|
||||
public async Task<JellyfinUser> LogIn(string username, string password, string apiKey, string baseUri)
|
||||
{
|
||||
var request = new Request("users/authenticatebyname", baseUri, HttpMethod.Post);
|
||||
var body = new
|
||||
|
@ -71,11 +71,11 @@ namespace Ombi.Api.Emby
|
|||
$"MediaBrowser Client=\"Ombi\", Device=\"Ombi\", DeviceId=\"v3\", Version=\"v3\"");
|
||||
AddHeaders(request, apiKey);
|
||||
|
||||
var obj = await Api.Request<EmbyUser>(request);
|
||||
var obj = await Api.Request<JellyfinUser>(request);
|
||||
return obj;
|
||||
}
|
||||
|
||||
public async Task<EmbyItemContainer<EmbyMovie>> GetCollection(string mediaId, string apiKey, string userId, string baseUrl)
|
||||
public async Task<JellyfinItemContainer<JellyfinMovie>> GetCollection(string mediaId, string apiKey, string userId, string baseUrl)
|
||||
{
|
||||
var request = new Request($"users/{userId}/items?parentId={mediaId}", baseUrl, HttpMethod.Get);
|
||||
AddHeaders(request, apiKey);
|
||||
|
@ -84,22 +84,22 @@ namespace Ombi.Api.Emby
|
|||
|
||||
request.AddQueryString("IsVirtualItem", "False");
|
||||
|
||||
return await Api.Request<EmbyItemContainer<EmbyMovie>>(request);
|
||||
return await Api.Request<JellyfinItemContainer<JellyfinMovie>>(request);
|
||||
}
|
||||
|
||||
public async Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri)
|
||||
public async Task<JellyfinItemContainer<JellyfinMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri)
|
||||
{
|
||||
return await GetAll<EmbyMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count);
|
||||
return await GetAll<JellyfinMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count);
|
||||
}
|
||||
|
||||
public async Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, string baseUri)
|
||||
public async Task<JellyfinItemContainer<JellyfinEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, string baseUri)
|
||||
{
|
||||
return await GetAll<EmbyEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count);
|
||||
return await GetAll<JellyfinEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count);
|
||||
}
|
||||
|
||||
public async Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, int startIndex, int count, string userId, string baseUri)
|
||||
public async Task<JellyfinItemContainer<JellyfinSeries>> GetAllShows(string apiKey, int startIndex, int count, string userId, string baseUri)
|
||||
{
|
||||
return await GetAll<EmbySeries>("Series", apiKey, userId, baseUri, false, startIndex, count);
|
||||
return await GetAll<JellyfinSeries>("Series", apiKey, userId, baseUri, false, startIndex, count);
|
||||
}
|
||||
|
||||
public async Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl)
|
||||
|
@ -126,7 +126,7 @@ namespace Ombi.Api.Emby
|
|||
return JsonConvert.DeserializeObject<T>(response);
|
||||
}
|
||||
|
||||
private async Task<EmbyItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview = false)
|
||||
private async Task<JellyfinItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview = false)
|
||||
{
|
||||
var request = new Request($"users/{userId}/items", baseUri, HttpMethod.Get);
|
||||
|
||||
|
@ -139,10 +139,10 @@ namespace Ombi.Api.Emby
|
|||
AddHeaders(request, apiKey);
|
||||
|
||||
|
||||
var obj = await Api.Request<EmbyItemContainer<T>>(request);
|
||||
var obj = await Api.Request<JellyfinItemContainer<T>>(request);
|
||||
return obj;
|
||||
}
|
||||
private async Task<EmbyItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count)
|
||||
private async Task<JellyfinItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count)
|
||||
{
|
||||
var request = new Request($"users/{userId}/items", baseUri, HttpMethod.Get);
|
||||
|
||||
|
@ -157,7 +157,7 @@ namespace Ombi.Api.Emby
|
|||
AddHeaders(request, apiKey);
|
||||
|
||||
|
||||
var obj = await Api.Request<EmbyItemContainer<T>>(request);
|
||||
var obj = await Api.Request<JellyfinItemContainer<T>>(request);
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -172,7 +172,7 @@ namespace Ombi.Api.Emby
|
|||
req.AddHeader("Device", "Ombi");
|
||||
}
|
||||
|
||||
public Task<EmbyConnectUser> LoginConnectUser(string username, string password)
|
||||
public Task<JellyfinConnectUser> LoginConnectUser(string username, string password)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
37
src/Ombi.Api.Jellyfin/JellyfinApiFactory.cs
Normal file
37
src/Ombi.Api.Jellyfin/JellyfinApiFactory.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using Ombi.Api;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Api.Jellyfin
|
||||
{
|
||||
public class JellyfinApiFactory : IJellyfinApiFactory
|
||||
{
|
||||
private readonly ISettingsService<JellyfinSettings> _jellyfinSettings;
|
||||
private readonly IApi _api;
|
||||
|
||||
// TODO, if we need to derive futher, need to rework
|
||||
public JellyfinApiFactory(ISettingsService<JellyfinSettings> jellyfinSettings, IApi api)
|
||||
{
|
||||
_jellyfinSettings = jellyfinSettings;
|
||||
_api = api;
|
||||
}
|
||||
|
||||
public async Task<IJellyfinApi> CreateClient()
|
||||
{
|
||||
var settings = await _jellyfinSettings.GetSettingsAsync();
|
||||
return CreateClient(settings);
|
||||
}
|
||||
|
||||
public IJellyfinApi CreateClient(JellyfinSettings settings)
|
||||
{
|
||||
return new JellyfinApi(_api);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IJellyfinApiFactory
|
||||
{
|
||||
Task<IJellyfinApi> CreateClient();
|
||||
IJellyfinApi CreateClient(JellyfinSettings settings);
|
||||
}
|
||||
}
|
45
src/Ombi.Api.Jellyfin/Models/JellyfinConfiguration.cs
Normal file
45
src/Ombi.Api.Jellyfin/Models/JellyfinConfiguration.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2017 Jamie Rees
|
||||
// File: JellyfinConfiguration.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
namespace Ombi.Api.Jellyfin.Models
|
||||
{
|
||||
public class JellyfinConfiguration
|
||||
{
|
||||
public bool PlayDefaultAudioTrack { get; set; }
|
||||
public bool DisplayMissingEpisodes { get; set; }
|
||||
public bool DisplayUnairedEpisodes { get; set; }
|
||||
public object[] GroupedFolders { get; set; }
|
||||
public string SubtitleMode { get; set; }
|
||||
public bool DisplayCollectionsView { get; set; }
|
||||
public bool EnableLocalPassword { get; set; }
|
||||
public object[] OrderedViews { get; set; }
|
||||
public object[] LatestItemsExcludes { get; set; }
|
||||
public bool HidePlayedInLatest { get; set; }
|
||||
public bool RememberAudioSelections { get; set; }
|
||||
public bool RememberSubtitleSelections { get; set; }
|
||||
public bool EnableNextEpisodeAutoPlay { get; set; }
|
||||
}
|
||||
}
|
47
src/Ombi.Api.Jellyfin/Models/JellyfinConnectUser.cs
Normal file
47
src/Ombi.Api.Jellyfin/Models/JellyfinConnectUser.cs
Normal file
|
@ -0,0 +1,47 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2017 Jamie Rees
|
||||
// File: JellyfinConnectUser.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
namespace Ombi.Api.Jellyfin.Models
|
||||
{
|
||||
public class JellyfinConnectUser
|
||||
{
|
||||
public string AccessToken { get; set; }
|
||||
public User User { get; set; }
|
||||
}
|
||||
|
||||
public class User
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string IsActive { get; set; }
|
||||
public string ImageUrl { get; set; }
|
||||
public object IsSupporter { get; set; }
|
||||
public object ExpDate { get; set; }
|
||||
}
|
||||
|
||||
}
|
10
src/Ombi.Api.Jellyfin/Models/JellyfinItemContainer.cs
Normal file
10
src/Ombi.Api.Jellyfin/Models/JellyfinItemContainer.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Ombi.Api.Jellyfin.Models
|
||||
{
|
||||
public class JellyfinItemContainer<T>
|
||||
{
|
||||
public List<T> Items { get; set; }
|
||||
public int TotalRecordCount { get; set; }
|
||||
}
|
||||
}
|
10
src/Ombi.Api.Jellyfin/Models/JellyfinMediaType.cs
Normal file
10
src/Ombi.Api.Jellyfin/Models/JellyfinMediaType.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Ombi.Api.Jellyfin.Models
|
||||
{
|
||||
public enum JellyfinMediaType
|
||||
{
|
||||
Movie = 0,
|
||||
Series = 1,
|
||||
Music = 2,
|
||||
Episode = 3
|
||||
}
|
||||
}
|
59
src/Ombi.Api.Jellyfin/Models/JellyfinPolicy.cs
Normal file
59
src/Ombi.Api.Jellyfin/Models/JellyfinPolicy.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2017 Jamie Rees
|
||||
// File: JellyfinPolicy.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
namespace Ombi.Api.Jellyfin.Models
|
||||
{
|
||||
public class JellyfinPolicy
|
||||
{
|
||||
public bool IsAdministrator { get; set; }
|
||||
public bool IsHidden { get; set; }
|
||||
public bool IsDisabled { get; set; }
|
||||
public object[] BlockedTags { get; set; }
|
||||
public bool EnableUserPreferenceAccess { get; set; }
|
||||
public object[] AccessSchedules { get; set; }
|
||||
public object[] BlockUnratedItems { get; set; }
|
||||
public bool EnableRemoteControlOfOtherUsers { get; set; }
|
||||
public bool EnableSharedDeviceControl { get; set; }
|
||||
public bool EnableLiveTvManagement { get; set; }
|
||||
public bool EnableLiveTvAccess { get; set; }
|
||||
public bool EnableMediaPlayback { get; set; }
|
||||
public bool EnableAudioPlaybackTranscoding { get; set; }
|
||||
public bool EnableVideoPlaybackTranscoding { get; set; }
|
||||
public bool EnablePlaybackRemuxing { get; set; }
|
||||
public bool EnableContentDeletion { get; set; }
|
||||
public bool EnableContentDownloading { get; set; }
|
||||
public bool EnableSync { get; set; }
|
||||
public bool EnableSyncTranscoding { get; set; }
|
||||
public object[] EnabledDevices { get; set; }
|
||||
public bool EnableAllDevices { get; set; }
|
||||
public object[] EnabledChannels { get; set; }
|
||||
public bool EnableAllChannels { get; set; }
|
||||
public object[] EnabledFolders { get; set; }
|
||||
public bool EnableAllFolders { get; set; }
|
||||
public int InvalidLoginAttemptCount { get; set; }
|
||||
public bool EnablePublicSharing { get; set; }
|
||||
}
|
||||
}
|
63
src/Ombi.Api.Jellyfin/Models/JellyfinSystemInfo.cs
Normal file
63
src/Ombi.Api.Jellyfin/Models/JellyfinSystemInfo.cs
Normal file
|
@ -0,0 +1,63 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2017 Jamie Rees
|
||||
// File: JellyfinSystemInfo.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
namespace Ombi.Api.Jellyfin.Models
|
||||
{
|
||||
public class JellyfinSystemInfo
|
||||
{
|
||||
public string SystemUpdateLevel { get; set; }
|
||||
public string OperatingSystemDisplayName { get; set; }
|
||||
public bool SupportsRunningAsService { get; set; }
|
||||
public string MacAddress { get; set; }
|
||||
public bool HasPendingRestart { get; set; }
|
||||
public bool SupportsLibraryMonitor { get; set; }
|
||||
public object[] InProgressInstallations { get; set; }
|
||||
public int WebSocketPortNumber { get; set; }
|
||||
public object[] CompletedInstallations { get; set; }
|
||||
public bool CanSelfRestart { get; set; }
|
||||
public bool CanSelfUpdate { get; set; }
|
||||
public object[] FailedPluginAssemblies { get; set; }
|
||||
public string ProgramDataPath { get; set; }
|
||||
public string ItemsByNamePath { get; set; }
|
||||
public string CachePath { get; set; }
|
||||
public string LogPath { get; set; }
|
||||
public string InternalMetadataPath { get; set; }
|
||||
public string TranscodingTempPath { get; set; }
|
||||
public int HttpServerPortNumber { get; set; }
|
||||
public bool SupportsHttps { get; set; }
|
||||
public int HttpsPortNumber { get; set; }
|
||||
public bool HasUpdateAvailable { get; set; }
|
||||
public bool SupportsAutoRunAtStartup { get; set; }
|
||||
public string EncoderLocationType { get; set; }
|
||||
public string SystemArchitecture { get; set; }
|
||||
public string LocalAddress { get; set; }
|
||||
public string WanAddress { get; set; }
|
||||
public string ServerName { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string OperatingSystem { get; set; }
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
47
src/Ombi.Api.Jellyfin/Models/JellyfinUser.cs
Normal file
47
src/Ombi.Api.Jellyfin/Models/JellyfinUser.cs
Normal file
|
@ -0,0 +1,47 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2017 Jamie Rees
|
||||
// File: JellyfinUser.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace Ombi.Api.Jellyfin.Models
|
||||
{
|
||||
public class JellyfinUser
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string ServerId { get; set; }
|
||||
public string ConnectUserName { get; set; }
|
||||
public string ConnectLinkType { get; set; }
|
||||
public string Id { get; set; }
|
||||
public bool HasPassword { get; set; }
|
||||
public bool HasConfiguredPassword { get; set; }
|
||||
public bool HasConfiguredEasyPassword { get; set; }
|
||||
public DateTime LastLoginDate { get; set; }
|
||||
public DateTime LastActivityDate { get; set; }
|
||||
public JellyfinConfiguration Configuration { get; set; }
|
||||
public JellyfinPolicy Policy { get; set; }
|
||||
}
|
||||
}
|
7
src/Ombi.Api.Jellyfin/Models/JellyfinUserLogin.cs
Normal file
7
src/Ombi.Api.Jellyfin/Models/JellyfinUserLogin.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Ombi.Api.Jellyfin.Models
|
||||
{
|
||||
public class JellyfinUserLogin
|
||||
{
|
||||
public JellyfinUser User { get; set; }
|
||||
}
|
||||
}
|
8
src/Ombi.Api.Jellyfin/Models/Media/JellyfinChapter.cs
Normal file
8
src/Ombi.Api.Jellyfin/Models/Media/JellyfinChapter.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ombi.Api.Jellyfin.Models.Movie
|
||||
{
|
||||
public class JellyfinChapter
|
||||
{
|
||||
public long StartPositionTicks { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace Ombi.Api.Jellyfin.Models.Movie
|
||||
{
|
||||
public class JellyfinExternalurl
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
10
src/Ombi.Api.Jellyfin/Models/Media/JellyfinImagetags.cs
Normal file
10
src/Ombi.Api.Jellyfin/Models/Media/JellyfinImagetags.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Ombi.Api.Jellyfin.Models.Movie
|
||||
{
|
||||
public class JellyfinImagetags
|
||||
{
|
||||
public string Primary { get; set; }
|
||||
public string Logo { get; set; }
|
||||
public string Thumb { get; set; }
|
||||
public string Banner { get; set; }
|
||||
}
|
||||
}
|
30
src/Ombi.Api.Jellyfin/Models/Media/JellyfinMediasource.cs
Normal file
30
src/Ombi.Api.Jellyfin/Models/Media/JellyfinMediasource.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
namespace Ombi.Api.Jellyfin.Models.Movie
|
||||
{
|
||||
public class JellyfinMediasource
|
||||
{
|
||||
public string Protocol { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Path { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Container { get; set; }
|
||||
public string Name { get; set; }
|
||||
public bool IsRemote { get; set; }
|
||||
public string ETag { get; set; }
|
||||
public long RunTimeTicks { get; set; }
|
||||
public bool ReadAtNativeFramerate { get; set; }
|
||||
public bool SupportsTranscoding { get; set; }
|
||||
public bool SupportsDirectStream { get; set; }
|
||||
public bool SupportsDirectPlay { get; set; }
|
||||
public bool IsInfiniteStream { get; set; }
|
||||
public bool RequiresOpening { get; set; }
|
||||
public bool RequiresClosing { get; set; }
|
||||
public bool SupportsProbing { get; set; }
|
||||
public string VideoType { get; set; }
|
||||
public JellyfinMediastream[] MediaStreams { get; set; }
|
||||
public object[] PlayableStreamFileNames { get; set; }
|
||||
public object[] Formats { get; set; }
|
||||
public int Bitrate { get; set; }
|
||||
public int DefaultAudioStreamIndex { get; set; }
|
||||
|
||||
}
|
||||
}
|
36
src/Ombi.Api.Jellyfin/Models/Media/JellyfinMediastream.cs
Normal file
36
src/Ombi.Api.Jellyfin/Models/Media/JellyfinMediastream.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
namespace Ombi.Api.Jellyfin.Models.Movie
|
||||
{
|
||||
public class JellyfinMediastream
|
||||
{
|
||||
public string Codec { get; set; }
|
||||
public string Language { get; set; }
|
||||
public string TimeBase { get; set; }
|
||||
public string CodecTimeBase { get; set; }
|
||||
public string NalLengthSize { get; set; }
|
||||
public bool IsInterlaced { get; set; }
|
||||
public bool IsAVC { get; set; }
|
||||
public int BitRate { get; set; }
|
||||
public int BitDepth { get; set; }
|
||||
public int RefFrames { get; set; }
|
||||
public bool IsDefault { get; set; }
|
||||
public bool IsForced { get; set; }
|
||||
public int Height { get; set; }
|
||||
public int Width { get; set; }
|
||||
public float AverageFrameRate { get; set; }
|
||||
public float RealFrameRate { get; set; }
|
||||
public string Profile { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string AspectRatio { get; set; }
|
||||
public int Index { get; set; }
|
||||
public bool IsExternal { get; set; }
|
||||
public bool IsTextSubtitleStream { get; set; }
|
||||
public bool SupportsExternalStream { get; set; }
|
||||
public string PixelFormat { get; set; }
|
||||
public int Level { get; set; }
|
||||
public bool IsAnamorphic { get; set; }
|
||||
public string DisplayTitle { get; set; }
|
||||
public string ChannelLayout { get; set; }
|
||||
public int Channels { get; set; }
|
||||
public int SampleRate { get; set; }
|
||||
}
|
||||
}
|
11
src/Ombi.Api.Jellyfin/Models/Media/JellyfinPerson.cs
Normal file
11
src/Ombi.Api.Jellyfin/Models/Media/JellyfinPerson.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace Ombi.Api.Jellyfin.Models.Movie
|
||||
{
|
||||
public class JellyfinPerson
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Role { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string PrimaryImageTag { get; set; }
|
||||
}
|
||||
}
|
13
src/Ombi.Api.Jellyfin/Models/Media/JellyfinProviderids.cs
Normal file
13
src/Ombi.Api.Jellyfin/Models/Media/JellyfinProviderids.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace Ombi.Api.Jellyfin.Models.Movie
|
||||
{
|
||||
public class JellyfinProviderids
|
||||
{
|
||||
public string Tmdb { get; set; }
|
||||
public string Imdb { get; set; }
|
||||
public string TmdbCollection { get; set; }
|
||||
|
||||
public string Tvdb { get; set; }
|
||||
public string Zap2It { get; set; }
|
||||
public string TvRage { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace Ombi.Api.Jellyfin.Models.Movie
|
||||
{
|
||||
public class JellyfinRemotetrailer
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
8
src/Ombi.Api.Jellyfin/Models/Media/JellyfinStudio.cs
Normal file
8
src/Ombi.Api.Jellyfin/Models/Media/JellyfinStudio.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ombi.Api.Jellyfin.Models.Movie
|
||||
{
|
||||
public class JellyfinStudio
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
15
src/Ombi.Api.Jellyfin/Models/Media/JellyfinUserdata.cs
Normal file
15
src/Ombi.Api.Jellyfin/Models/Media/JellyfinUserdata.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace Ombi.Api.Jellyfin.Models.Movie
|
||||
{
|
||||
public class JellyfinUserdata
|
||||
{
|
||||
public double PlaybackPositionTicks { get; set; }
|
||||
public int PlayCount { get; set; }
|
||||
public bool IsFavorite { get; set; }
|
||||
public bool Played { get; set; }
|
||||
public string Key { get; set; }
|
||||
public DateTime LastPlayedDate { get; set; }
|
||||
public int UnplayedItemCount { get; set; }
|
||||
}
|
||||
}
|
34
src/Ombi.Api.Jellyfin/Models/Media/Movie/JellyfinMovie.cs
Normal file
34
src/Ombi.Api.Jellyfin/Models/Media/Movie/JellyfinMovie.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
|
||||
namespace Ombi.Api.Jellyfin.Models.Movie
|
||||
{
|
||||
public class JellyfinMovie
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string ServerId { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Container { get; set; }
|
||||
public DateTime PremiereDate { get; set; }
|
||||
public object[] ProductionLocations { get; set; }
|
||||
public string OfficialRating { get; set; }
|
||||
public float CommunityRating { get; set; }
|
||||
public long RunTimeTicks { get; set; }
|
||||
public string PlayAccess { get; set; }
|
||||
public int ProductionYear { get; set; }
|
||||
public bool IsPlaceHolder { get; set; }
|
||||
public bool IsHD { get; set; }
|
||||
public bool IsFolder { get; set; }
|
||||
public string Type { get; set; }
|
||||
public int LocalTrailerCount { get; set; }
|
||||
public JellyfinUserdata UserData { get; set; }
|
||||
public string VideoType { get; set; }
|
||||
public JellyfinImagetags ImageTags { get; set; }
|
||||
public string[] BackdropImageTags { get; set; }
|
||||
public string LocationType { get; set; }
|
||||
public string MediaType { get; set; }
|
||||
public bool HasSubtitles { get; set; }
|
||||
public int CriticRating { get; set; }
|
||||
public string Overview { get; set; }
|
||||
public JellyfinProviderids ProviderIds { get; set; }
|
||||
}
|
||||
}
|
60
src/Ombi.Api.Jellyfin/Models/Media/Movie/MovieInformation.cs
Normal file
60
src/Ombi.Api.Jellyfin/Models/Media/Movie/MovieInformation.cs
Normal file
|
@ -0,0 +1,60 @@
|
|||
using System;
|
||||
|
||||
namespace Ombi.Api.Jellyfin.Models.Movie
|
||||
{
|
||||
public class MovieInformation
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string OriginalTitle { get; set; }
|
||||
public string ServerId { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Etag { get; set; }
|
||||
public DateTime DateCreated { get; set; }
|
||||
public bool CanDelete { get; set; }
|
||||
public bool CanDownload { get; set; }
|
||||
public bool SupportsSync { get; set; }
|
||||
public string Container { get; set; }
|
||||
public string SortName { get; set; }
|
||||
public DateTime PremiereDate { get; set; }
|
||||
public JellyfinExternalurl[] ExternalUrls { get; set; }
|
||||
public JellyfinMediasource[] MediaSources { get; set; }
|
||||
public string[] ProductionLocations { get; set; }
|
||||
public string Path { get; set; }
|
||||
public string OfficialRating { get; set; }
|
||||
public string Overview { get; set; }
|
||||
public string[] Taglines { get; set; }
|
||||
public string[] Genres { get; set; }
|
||||
public float CommunityRating { get; set; }
|
||||
public int VoteCount { get; set; }
|
||||
public long RunTimeTicks { get; set; }
|
||||
public string PlayAccess { get; set; }
|
||||
public int ProductionYear { get; set; }
|
||||
public bool IsPlaceHolder { get; set; }
|
||||
public JellyfinRemotetrailer[] RemoteTrailers { get; set; }
|
||||
public JellyfinProviderids ProviderIds { get; set; }
|
||||
public bool IsHD { get; set; }
|
||||
public bool IsFolder { get; set; }
|
||||
public string ParentId { get; set; }
|
||||
public string Type { get; set; }
|
||||
public JellyfinPerson[] People { get; set; }
|
||||
public JellyfinStudio[] Studios { get; set; }
|
||||
public int LocalTrailerCount { get; set; }
|
||||
public JellyfinUserdata UserData { get; set; }
|
||||
public string DisplayPreferencesId { get; set; }
|
||||
public object[] Tags { get; set; }
|
||||
public string[] Keywords { get; set; }
|
||||
public JellyfinMediastream[] MediaStreams { get; set; }
|
||||
public string VideoType { get; set; }
|
||||
public JellyfinImagetags ImageTags { get; set; }
|
||||
public string[] BackdropImageTags { get; set; }
|
||||
public object[] ScreenshotImageTags { get; set; }
|
||||
public JellyfinChapter[] Chapters { get; set; }
|
||||
public string LocationType { get; set; }
|
||||
public string MediaType { get; set; }
|
||||
public string HomePageUrl { get; set; }
|
||||
public int Budget { get; set; }
|
||||
public float Revenue { get; set; }
|
||||
public object[] LockedFields { get; set; }
|
||||
public bool LockData { get; set; }
|
||||
}
|
||||
}
|
71
src/Ombi.Api.Jellyfin/Models/Media/Tv/EpisodeInformation.cs
Normal file
71
src/Ombi.Api.Jellyfin/Models/Media/Tv/EpisodeInformation.cs
Normal file
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using Ombi.Api.Jellyfin.Models.Movie;
|
||||
|
||||
namespace Ombi.Api.Jellyfin.Models.Media.Tv
|
||||
{
|
||||
public class EpisodeInformation
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string ServerId { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Etag { get; set; }
|
||||
public DateTime DateCreated { get; set; }
|
||||
public bool CanDelete { get; set; }
|
||||
public bool CanDownload { get; set; }
|
||||
public bool SupportsSync { get; set; }
|
||||
public string Container { get; set; }
|
||||
public string SortName { get; set; }
|
||||
public DateTime PremiereDate { get; set; }
|
||||
public JellyfinExternalurl[] ExternalUrls { get; set; }
|
||||
public JellyfinMediasource[] MediaSources { get; set; }
|
||||
public string Path { get; set; }
|
||||
public string Overview { get; set; }
|
||||
public object[] Taglines { get; set; }
|
||||
public object[] Genres { get; set; }
|
||||
public string[] SeriesGenres { get; set; }
|
||||
public float CommunityRating { get; set; }
|
||||
public int VoteCount { get; set; }
|
||||
public long RunTimeTicks { get; set; }
|
||||
public string PlayAccess { get; set; }
|
||||
public int ProductionYear { get; set; }
|
||||
public bool IsPlaceHolder { get; set; }
|
||||
public int IndexNumber { get; set; }
|
||||
public int ParentIndexNumber { get; set; }
|
||||
public object[] RemoteTrailers { get; set; }
|
||||
public JellyfinProviderids ProviderIds { get; set; }
|
||||
public bool IsHD { get; set; }
|
||||
public bool IsFolder { get; set; }
|
||||
public string ParentId { get; set; }
|
||||
public string Type { get; set; }
|
||||
public object[] People { get; set; }
|
||||
public object[] Studios { get; set; }
|
||||
public string ParentLogoItemId { get; set; }
|
||||
public string ParentBackdropItemId { get; set; }
|
||||
public string[] ParentBackdropImageTags { get; set; }
|
||||
public int LocalTrailerCount { get; set; }
|
||||
public JellyfinUserdata UserData { get; set; }
|
||||
public string SeriesName { get; set; }
|
||||
public string SeriesId { get; set; }
|
||||
public string SeasonId { get; set; }
|
||||
public string DisplayPreferencesId { get; set; }
|
||||
public object[] Tags { get; set; }
|
||||
public object[] Keywords { get; set; }
|
||||
public string SeriesPrimaryImageTag { get; set; }
|
||||
public string SeasonName { get; set; }
|
||||
public JellyfinMediastream[] MediaStreams { get; set; }
|
||||
public string VideoType { get; set; }
|
||||
public JellyfinImagetags ImageTags { get; set; }
|
||||
public object[] BackdropImageTags { get; set; }
|
||||
public object[] ScreenshotImageTags { get; set; }
|
||||
public string ParentLogoImageTag { get; set; }
|
||||
public string SeriesStudio { get; set; }
|
||||
public JellyfinSeriesstudioinfo SeriesStudioInfo { get; set; }
|
||||
public string ParentThumbItemId { get; set; }
|
||||
public string ParentThumbImageTag { get; set; }
|
||||
public JellyfinChapter[] Chapters { get; set; }
|
||||
public string LocationType { get; set; }
|
||||
public string MediaType { get; set; }
|
||||
public object[] LockedFields { get; set; }
|
||||
public bool LockData { get; set; }
|
||||
}
|
||||
}
|
45
src/Ombi.Api.Jellyfin/Models/Media/Tv/JellyfinEpisodes.cs
Normal file
45
src/Ombi.Api.Jellyfin/Models/Media/Tv/JellyfinEpisodes.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using Ombi.Api.Jellyfin.Models.Movie;
|
||||
using System;
|
||||
|
||||
namespace Ombi.Api.Jellyfin.Models.Media.Tv
|
||||
{
|
||||
public class JellyfinEpisodes
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string ServerId { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Container { get; set; }
|
||||
public DateTime PremiereDate { get; set; }
|
||||
public float CommunityRating { get; set; }
|
||||
public long RunTimeTicks { get; set; }
|
||||
public string PlayAccess { get; set; }
|
||||
public int ProductionYear { get; set; }
|
||||
public bool IsPlaceHolder { get; set; }
|
||||
public int IndexNumber { get; set; }
|
||||
public int? IndexNumberEnd { get; set; }
|
||||
public int ParentIndexNumber { get; set; }
|
||||
public bool IsHD { get; set; }
|
||||
public bool IsFolder { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string ParentLogoItemId { get; set; }
|
||||
public string ParentBackdropItemId { get; set; }
|
||||
public string[] ParentBackdropImageTags { get; set; }
|
||||
public int LocalTrailerCount { get; set; }
|
||||
public JellyfinUserdata UserData { get; set; }
|
||||
public string SeriesName { get; set; }
|
||||
public string SeriesId { get; set; }
|
||||
public string SeasonId { get; set; }
|
||||
public string SeriesPrimaryImageTag { get; set; }
|
||||
public string SeasonName { get; set; }
|
||||
public string VideoType { get; set; }
|
||||
public JellyfinImagetags ImageTags { get; set; }
|
||||
public object[] BackdropImageTags { get; set; }
|
||||
public string ParentLogoImageTag { get; set; }
|
||||
public string ParentThumbItemId { get; set; }
|
||||
public string ParentThumbImageTag { get; set; }
|
||||
public string LocationType { get; set; }
|
||||
public string MediaType { get; set; }
|
||||
public bool HasSubtitles { get; set; }
|
||||
public JellyfinProviderids ProviderIds { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace Ombi.Api.Jellyfin.Models.Media.Tv
|
||||
{
|
||||
public class JellyfinRemotetrailer
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
32
src/Ombi.Api.Jellyfin/Models/Media/Tv/JellyfinSeries.cs
Normal file
32
src/Ombi.Api.Jellyfin/Models/Media/Tv/JellyfinSeries.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using Ombi.Api.Jellyfin.Models.Movie;
|
||||
using System;
|
||||
|
||||
namespace Ombi.Api.Jellyfin.Models.Media.Tv
|
||||
{
|
||||
public class JellyfinSeries
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string ServerId { get; set; }
|
||||
public string Id { get; set; }
|
||||
public DateTime PremiereDate { get; set; }
|
||||
public string OfficialRating { get; set; }
|
||||
public float CommunityRating { get; set; }
|
||||
public long RunTimeTicks { get; set; }
|
||||
public string PlayAccess { get; set; }
|
||||
public int ProductionYear { get; set; }
|
||||
public bool IsFolder { get; set; }
|
||||
public string Type { get; set; }
|
||||
public int LocalTrailerCount { get; set; }
|
||||
public JellyfinUserdata UserData { get; set; }
|
||||
public int ChildCount { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string AirTime { get; set; }
|
||||
public string[] AirDays { get; set; }
|
||||
public JellyfinImagetags ImageTags { get; set; }
|
||||
public string[] BackdropImageTags { get; set; }
|
||||
public string LocationType { get; set; }
|
||||
public DateTime EndDate { get; set; }
|
||||
|
||||
public JellyfinProviderids ProviderIds { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace Ombi.Api.Jellyfin.Models.Media.Tv
|
||||
{
|
||||
public class JellyfinSeriesstudioinfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
59
src/Ombi.Api.Jellyfin/Models/Media/Tv/SeriesInformation.cs
Normal file
59
src/Ombi.Api.Jellyfin/Models/Media/Tv/SeriesInformation.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using Ombi.Api.Jellyfin.Models.Movie;
|
||||
|
||||
namespace Ombi.Api.Jellyfin.Models.Media.Tv
|
||||
{
|
||||
public class SeriesInformation
|
||||
{
|
||||
|
||||
public string Name { get; set; }
|
||||
public string ServerId { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Etag { get; set; }
|
||||
public DateTime DateCreated { get; set; }
|
||||
public DateTime DateLastMediaAdded { get; set; }
|
||||
public bool CanDelete { get; set; }
|
||||
public bool CanDownload { get; set; }
|
||||
public bool SupportsSync { get; set; }
|
||||
public string SortName { get; set; }
|
||||
public DateTime PremiereDate { get; set; }
|
||||
public JellyfinExternalurl[] ExternalUrls { get; set; }
|
||||
public string Path { get; set; }
|
||||
public string OfficialRating { get; set; }
|
||||
public string Overview { get; set; }
|
||||
public string ShortOverview { get; set; }
|
||||
public object[] Taglines { get; set; }
|
||||
public string[] Genres { get; set; }
|
||||
public float CommunityRating { get; set; }
|
||||
public int VoteCount { get; set; }
|
||||
public long CumulativeRunTimeTicks { get; set; }
|
||||
public long RunTimeTicks { get; set; }
|
||||
public string PlayAccess { get; set; }
|
||||
public int ProductionYear { get; set; }
|
||||
public JellyfinRemotetrailer[] RemoteTrailers { get; set; }
|
||||
public JellyfinProviderids ProviderIds { get; set; }
|
||||
public bool IsFolder { get; set; }
|
||||
public string ParentId { get; set; }
|
||||
public string Type { get; set; }
|
||||
public JellyfinPerson[] People { get; set; }
|
||||
public JellyfinStudio[] Studios { get; set; }
|
||||
public int LocalTrailerCount { get; set; }
|
||||
public JellyfinUserdata UserData { get; set; }
|
||||
public int RecursiveItemCount { get; set; }
|
||||
public int ChildCount { get; set; }
|
||||
public string DisplayPreferencesId { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string AirTime { get; set; }
|
||||
public string[] AirDays { get; set; }
|
||||
public object[] Tags { get; set; }
|
||||
public object[] Keywords { get; set; }
|
||||
public JellyfinImagetags ImageTags { get; set; }
|
||||
public string[] BackdropImageTags { get; set; }
|
||||
public object[] ScreenshotImageTags { get; set; }
|
||||
public string LocationType { get; set; }
|
||||
public string HomePageUrl { get; set; }
|
||||
public object[] LockedFields { get; set; }
|
||||
public bool LockData { get; set; }
|
||||
|
||||
}
|
||||
}
|
12
src/Ombi.Api.Jellyfin/Models/PublicInfo.cs
Normal file
12
src/Ombi.Api.Jellyfin/Models/PublicInfo.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace Ombi.Api.Jellyfin.Models
|
||||
{
|
||||
public class PublicInfo
|
||||
{
|
||||
public string LocalAddress { get; set; }
|
||||
public string ServerName { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string OperatingSystem { get; set; }
|
||||
public string Id { get; set; }
|
||||
}
|
||||
|
||||
}
|
17
src/Ombi.Api.Jellyfin/Ombi.Api.Jellyfin.csproj
Normal file
17
src/Ombi.Api.Jellyfin/Ombi.Api.Jellyfin.csproj
Normal file
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<AssemblyVersion>3.0.0.0</AssemblyVersion>
|
||||
<FileVersion>3.0.0.0</FileVersion>
|
||||
<Version></Version>
|
||||
<PackageVersion></PackageVersion>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -2,6 +2,7 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Hqub.MusicBrainz.API.Entities;
|
||||
using Hqub.MusicBrainz.API.Entities.Collections;
|
||||
using Ombi.Api.MusicBrainz.Models;
|
||||
|
||||
namespace Ombi.Api.MusicBrainz
|
||||
|
@ -11,6 +12,7 @@ namespace Ombi.Api.MusicBrainz
|
|||
Task<IEnumerable<Artist>> SearchArtist(string artistQuery);
|
||||
Task<IEnumerable<Release>> GetReleaseForArtist(string artistId);
|
||||
Task<Artist> GetArtistInformation(string artistId);
|
||||
Task<Release> GetAlbumInformation(string albumId);
|
||||
Task<ReleaseGroupArt> GetCoverArtForReleaseGroup(string musicBrainzId, CancellationToken token);
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Hqub.MusicBrainz.API;
|
||||
using Hqub.MusicBrainz.API.Entities;
|
||||
using Hqub.MusicBrainz.API.Entities.Collections;
|
||||
using Newtonsoft.Json;
|
||||
using Ombi.Api.MusicBrainz.Models;
|
||||
|
||||
|
@ -20,6 +21,12 @@ namespace Ombi.Api.MusicBrainz
|
|||
_api = api;
|
||||
}
|
||||
|
||||
public Task<Release> GetAlbumInformation(string albumId)
|
||||
{
|
||||
var album = Release.GetAsync(albumId);
|
||||
return album;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Artist>> SearchArtist(string artistQuery)
|
||||
{
|
||||
var artist = await Artist.SearchAsync(artistQuery, 10);
|
||||
|
|
14
src/Ombi.Api.RottenTomatoes/IRottenTomatoesApi.cs
Normal file
14
src/Ombi.Api.RottenTomatoes/IRottenTomatoesApi.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using Ombi.Api.RottenTomatoes.Models;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Api.RottenTomatoes
|
||||
{
|
||||
public interface IRottenTomatoesApi
|
||||
{
|
||||
Task<MovieRatings> GetMovieRatings(string movieName, int movieYear);
|
||||
Task<TvRatings> GetTvRatings(string showName, int showYear);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Ombi.Api.RottenTomatoes.Models
|
||||
{
|
||||
public class RottenTomatoesMovieResponse
|
||||
{
|
||||
public int total { get; set; }
|
||||
public List<Movie> movies { get; set; }
|
||||
}
|
||||
|
||||
public class Movie
|
||||
{
|
||||
public string id { get; set; }
|
||||
public string title { get; set; }
|
||||
public int year { get; set; }
|
||||
public string mpaa_rating { get; set; }
|
||||
public object runtime { get; set; }
|
||||
public string critics_consensus { get; set; }
|
||||
public MovieRatings ratings { get; set; }
|
||||
public Links links { get; set; }
|
||||
}
|
||||
|
||||
public class MovieRatings
|
||||
{
|
||||
public string critics_rating { get; set; }
|
||||
public int critics_score { get; set; }
|
||||
public string audience_rating { get; set; }
|
||||
public int audience_score { get; set; }
|
||||
}
|
||||
|
||||
public class Links
|
||||
{
|
||||
public string alternate { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
namespace Ombi.Api.RottenTomatoes.Models
|
||||
{
|
||||
public class RottenTomatoesTvResponse
|
||||
{
|
||||
public int tvCount { get; set; }
|
||||
public TvSeries[] tvSeries { get; set; }
|
||||
}
|
||||
|
||||
public class TvSeries
|
||||
{
|
||||
public string title { get; set; }
|
||||
public int startYear { get; set; }
|
||||
public int endYear { get; set; }
|
||||
public string url { get; set; }
|
||||
public string meterClass { get; set; }
|
||||
public int meterScore { get; set; }
|
||||
public string image { get; set; }
|
||||
}
|
||||
|
||||
}
|
8
src/Ombi.Api.RottenTomatoes/Models/TvRatings.cs
Normal file
8
src/Ombi.Api.RottenTomatoes/Models/TvRatings.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ombi.Api.RottenTomatoes.Models
|
||||
{
|
||||
public class TvRatings
|
||||
{
|
||||
public string Class { get; set; }
|
||||
public int Score { get; set; }
|
||||
}
|
||||
}
|
12
src/Ombi.Api.RottenTomatoes/Ombi.Api.RottenTomatoes.csproj
Normal file
12
src/Ombi.Api.RottenTomatoes/Ombi.Api.RottenTomatoes.csproj
Normal file
|
@ -0,0 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
56
src/Ombi.Api.RottenTomatoes/RottenTomatoesApi.cs
Normal file
56
src/Ombi.Api.RottenTomatoes/RottenTomatoesApi.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using Ombi.Api.RottenTomatoes.Models;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Api.RottenTomatoes
|
||||
{
|
||||
public class RottenTomatoesApi : IRottenTomatoesApi
|
||||
{
|
||||
public RottenTomatoesApi(IApi api)
|
||||
{
|
||||
_api = api;
|
||||
}
|
||||
|
||||
private string Endpoint => "https://www.rottentomatoes.com/api/private";
|
||||
private IApi _api { get; }
|
||||
|
||||
public async Task<MovieRatings> GetMovieRatings(string movieName, int movieYear)
|
||||
{
|
||||
var request = new Request("/v1.0/movies", Endpoint, HttpMethod.Get);
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddQueryString("q", movieName);
|
||||
var result = await _api.Request<RottenTomatoesMovieResponse>(request);
|
||||
|
||||
var movieFound = result.movies.FirstOrDefault(x => x.year == movieYear);
|
||||
if (movieFound == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return movieFound.ratings;
|
||||
}
|
||||
|
||||
public async Task<TvRatings> GetTvRatings(string showName, int showYear)
|
||||
{
|
||||
var request = new Request("/v2.0/search/", Endpoint, HttpMethod.Get);
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddQueryString("q", showName);
|
||||
request.AddQueryString("limit", 10.ToString());
|
||||
var result = await _api.Request<RottenTomatoesTvResponse>(request);
|
||||
|
||||
var showFound = result.tvSeries.FirstOrDefault(x => x.startYear == showYear);
|
||||
if (showFound == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TvRatings
|
||||
{
|
||||
Class = showFound.meterClass,
|
||||
Score = showFound.meterScore
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,7 +30,7 @@ namespace Ombi.Core.Tests.Authentication
|
|||
AuthenticationSettings.Setup(x => x.GetSettingsAsync())
|
||||
.ReturnsAsync(new AuthenticationSettings());
|
||||
_um = new OmbiUserManager(UserStore.Object, null, null, null, null, null, null, null, null,
|
||||
PlexApi.Object, null, null, AuthenticationSettings.Object);
|
||||
PlexApi.Object, null, null, null, null, AuthenticationSettings.Object);
|
||||
}
|
||||
|
||||
public OmbiUserManager _um { get; set; }
|
||||
|
|
|
@ -115,4 +115,4 @@ namespace Ombi.Core.Tests.Rule.Search
|
|||
Assert.False(search.Available);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
119
src/Ombi.Core.Tests/Rule/Search/JellyfinAvailabilityRuleTests.cs
Normal file
119
src/Ombi.Core.Tests/Rule/Search/JellyfinAvailabilityRuleTests.cs
Normal file
|
@ -0,0 +1,119 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Ombi.Core.Models.Search;
|
||||
using Ombi.Core.Rule.Rules.Search;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using Ombi.Store.Repository.Requests;
|
||||
|
||||
namespace Ombi.Core.Tests.Rule.Search
|
||||
{
|
||||
public class JellyfinAvailabilityRuleTests
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
ContextMock = new Mock<IJellyfinContentRepository>();
|
||||
SettingsMock = new Mock<ISettingsService<JellyfinSettings>>();
|
||||
Rule = new JellyfinAvailabilityRule(ContextMock.Object, SettingsMock.Object);
|
||||
}
|
||||
|
||||
private JellyfinAvailabilityRule Rule { get; set; }
|
||||
private Mock<IJellyfinContentRepository> ContextMock { get; set; }
|
||||
private Mock<ISettingsService<JellyfinSettings>> SettingsMock { get; set; }
|
||||
|
||||
[Test]
|
||||
public async Task Movie_ShouldBe_Available_WhenFoundInJellyfin()
|
||||
{
|
||||
SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new JellyfinSettings());
|
||||
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new JellyfinContent
|
||||
{
|
||||
ProviderId = "123"
|
||||
});
|
||||
var search = new SearchMovieViewModel()
|
||||
{
|
||||
TheMovieDbId = "123",
|
||||
};
|
||||
var result = await Rule.Execute(search);
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.True(search.Available);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Movie_Has_Custom_Url_When_Specified_In_Settings()
|
||||
{
|
||||
SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new JellyfinSettings
|
||||
{
|
||||
Enable = true,
|
||||
Servers = new List<JellyfinServers>
|
||||
{
|
||||
new JellyfinServers
|
||||
{
|
||||
ServerHostname = "http://test.com/",
|
||||
ServerId = "8"
|
||||
}
|
||||
}
|
||||
});
|
||||
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new JellyfinContent
|
||||
{
|
||||
ProviderId = "123",
|
||||
JellyfinId = 1.ToString(),
|
||||
});
|
||||
var search = new SearchMovieViewModel()
|
||||
{
|
||||
TheMovieDbId = "123",
|
||||
};
|
||||
var result = await Rule.Execute(search);
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.That(search.JellyfinUrl, Is.EqualTo("http://test.com/web/index.html#!/details?id=1&serverId=8"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Movie_Uses_Default_Url_When()
|
||||
{
|
||||
SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new JellyfinSettings
|
||||
{
|
||||
Enable = true,
|
||||
Servers = new List<JellyfinServers>
|
||||
{
|
||||
new JellyfinServers
|
||||
{
|
||||
Ip = "8080",
|
||||
Port = 9090,
|
||||
ServerHostname = string.Empty,
|
||||
ServerId = "8"
|
||||
}
|
||||
}
|
||||
});
|
||||
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new JellyfinContent
|
||||
{
|
||||
ProviderId = "123",
|
||||
JellyfinId = 1.ToString()
|
||||
});
|
||||
var search = new SearchMovieViewModel()
|
||||
{
|
||||
TheMovieDbId = "123",
|
||||
};
|
||||
var result = await Rule.Execute(search);
|
||||
|
||||
Assert.True(result.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Movie_ShouldBe_NotAvailable_WhenNotFoundInJellyfin()
|
||||
{
|
||||
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).Returns(Task.FromResult(default(JellyfinContent)));
|
||||
var search = new SearchMovieViewModel();
|
||||
var result = await Rule.Execute(search);
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.False(search.Available);
|
||||
}
|
||||
}
|
||||
}
|
94
src/Ombi.Core.Tests/WatchProviderParserTests.cs
Normal file
94
src/Ombi.Core.Tests/WatchProviderParserTests.cs
Normal file
|
@ -0,0 +1,94 @@
|
|||
using NUnit.Framework;
|
||||
using Ombi.Api.TheMovieDb.Models;
|
||||
using Ombi.Core.Helpers;
|
||||
using Ombi.Store.Entities;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ombi.Core.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class WatchProviderParserTests
|
||||
{
|
||||
[TestCase("GB", TestName = "UpperCase")]
|
||||
[TestCase("gb", TestName = "LowerCase")]
|
||||
[TestCase("gB", TestName = "MixedCase")]
|
||||
public void GetValidStreamData(string streamingCountry)
|
||||
{
|
||||
var result = WatchProviderParser.GetUserWatchProviders(new WatchProviders
|
||||
{
|
||||
Results = new Results
|
||||
{
|
||||
GB = new WatchProviderData()
|
||||
{
|
||||
StreamInformation = new List<StreamData>
|
||||
{
|
||||
new StreamData
|
||||
{
|
||||
provider_name = "Netflix",
|
||||
display_priority = 0,
|
||||
logo_path = "logo",
|
||||
provider_id = 8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, new OmbiUser { StreamingCountry = streamingCountry });
|
||||
|
||||
Assert.That(result[0].provider_name, Is.EqualTo("Netflix"));
|
||||
}
|
||||
|
||||
[TestCase("GB", TestName = "Missing_UpperCase")]
|
||||
[TestCase("gb", TestName = "Missing_LowerCase")]
|
||||
[TestCase("gB", TestName = "Missing_MixedCase")]
|
||||
public void GetMissingStreamData(string streamingCountry)
|
||||
{
|
||||
var result = WatchProviderParser.GetUserWatchProviders(new WatchProviders
|
||||
{
|
||||
Results = new Results
|
||||
{
|
||||
AR = new WatchProviderData()
|
||||
{
|
||||
StreamInformation = new List<StreamData>
|
||||
{
|
||||
new StreamData
|
||||
{
|
||||
provider_name = "Netflix",
|
||||
display_priority = 0,
|
||||
logo_path = "logo",
|
||||
provider_id = 8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, new OmbiUser { StreamingCountry = streamingCountry });
|
||||
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetInvalidStreamData()
|
||||
{
|
||||
var result = WatchProviderParser.GetUserWatchProviders(new WatchProviders
|
||||
{
|
||||
Results = new Results
|
||||
{
|
||||
AR = new WatchProviderData()
|
||||
{
|
||||
StreamInformation = new List<StreamData>
|
||||
{
|
||||
new StreamData
|
||||
{
|
||||
provider_name = "Netflix",
|
||||
display_priority = 0,
|
||||
logo_path = "logo",
|
||||
provider_id = 8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, new OmbiUser { StreamingCountry = "BLAH" });
|
||||
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,6 +33,7 @@ using Microsoft.EntityFrameworkCore;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Ombi.Api.Emby;
|
||||
using Ombi.Api.Jellyfin;
|
||||
using Ombi.Api.Plex;
|
||||
using Ombi.Api.Plex.Models;
|
||||
using Ombi.Core.Settings;
|
||||
|
@ -49,18 +50,24 @@ namespace Ombi.Core.Authentication
|
|||
IPasswordHasher<OmbiUser> passwordHasher, IEnumerable<IUserValidator<OmbiUser>> userValidators,
|
||||
IEnumerable<IPasswordValidator<OmbiUser>> passwordValidators, ILookupNormalizer keyNormalizer,
|
||||
IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<OmbiUser>> logger, IPlexApi plexApi,
|
||||
IEmbyApiFactory embyApi, ISettingsService<EmbySettings> embySettings, ISettingsService<AuthenticationSettings> auth)
|
||||
IEmbyApiFactory embyApi, ISettingsService<EmbySettings> embySettings,
|
||||
IJellyfinApiFactory jellyfinApi, ISettingsService<JellyfinSettings> jellyfinSettings,
|
||||
ISettingsService<AuthenticationSettings> auth)
|
||||
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
|
||||
{
|
||||
_plexApi = plexApi;
|
||||
_embyApi = embyApi;
|
||||
_jellyfinApi = jellyfinApi;
|
||||
_embySettings = embySettings;
|
||||
_jellyfinSettings = jellyfinSettings;
|
||||
_authSettings = auth;
|
||||
}
|
||||
|
||||
private readonly IPlexApi _plexApi;
|
||||
private readonly IEmbyApiFactory _embyApi;
|
||||
private readonly IJellyfinApiFactory _jellyfinApi;
|
||||
private readonly ISettingsService<EmbySettings> _embySettings;
|
||||
private readonly ISettingsService<JellyfinSettings> _jellyfinSettings;
|
||||
private readonly ISettingsService<AuthenticationSettings> _authSettings;
|
||||
|
||||
public override async Task<bool> CheckPasswordAsync(OmbiUser user, string password)
|
||||
|
@ -83,6 +90,10 @@ namespace Ombi.Core.Authentication
|
|||
{
|
||||
return await CheckEmbyPasswordAsync(user, password);
|
||||
}
|
||||
if (user.UserType == UserType.JellyfinUser)
|
||||
{
|
||||
return await CheckJellyfinPasswordAsync(user, password);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -185,5 +196,36 @@ namespace Ombi.Core.Authentication
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sign the user into Jellyfin
|
||||
/// <remarks>We do not check if the user is in the owners "friends" since they must have a local user account to get this far.
|
||||
/// We also have to try and authenticate them with every server, the first server that work we just say it was a success</remarks>
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> CheckJellyfinPasswordAsync(OmbiUser user, string password)
|
||||
{
|
||||
var jellyfinSettings = await _jellyfinSettings.GetSettingsAsync();
|
||||
var client = _jellyfinApi.CreateClient(jellyfinSettings);
|
||||
|
||||
foreach (var server in jellyfinSettings.Servers)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await client.LogIn(user.UserName, password, server.ApiKey, server.FullUri);
|
||||
if (result != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError(e, "Jellyfin Login Failed");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ using Ombi.Core.Settings;
|
|||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using Ombi.Api.TheMovieDb.Models;
|
||||
using Ombi.Core.Helpers;
|
||||
|
||||
namespace Ombi.Core.Engine
|
||||
{
|
||||
|
@ -179,6 +181,12 @@ namespace Ombi.Core.Engine
|
|||
return user.Language;
|
||||
}
|
||||
|
||||
protected async Task<List<StreamData>> GetUserWatchProvider(WatchProviders providers)
|
||||
{
|
||||
var user = await GetUser();
|
||||
return WatchProviderParser.GetUserWatchProviders(providers, user);
|
||||
}
|
||||
|
||||
private OmbiSettings ombiSettings;
|
||||
protected async Task<OmbiSettings> GetOmbiSettings()
|
||||
{
|
||||
|
|
|
@ -26,5 +26,6 @@ namespace Ombi.Core.Engine.Interfaces
|
|||
int ResultLimit { get; set; }
|
||||
|
||||
Task<MovieFullInfoViewModel> GetMovieInfoByImdbId(string imdbId, CancellationToken requestAborted);
|
||||
Task<IEnumerable<StreamingData>> GetStreamInformation(int movieDbId, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -9,5 +9,6 @@ namespace Ombi.Core.Engine.Interfaces
|
|||
Task<ArtistInformation> GetArtistInformation(string artistId);
|
||||
Task<ArtistInformation> GetArtistInformationByRequestId(int requestId);
|
||||
Task<AlbumArt> GetReleaseGroupArt(string musicBrainzId, CancellationToken token);
|
||||
Task<ReleaseGroup> GetAlbum(string albumId);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Core.Models.Search.V2;
|
||||
|
||||
namespace Ombi.Core
|
||||
|
@ -7,5 +9,6 @@ namespace Ombi.Core
|
|||
{
|
||||
Task<SearchFullInfoTvShowViewModel> GetShowInformation(int tvdbid);
|
||||
Task<SearchFullInfoTvShowViewModel> GetShowByRequest(int requestId);
|
||||
Task<IEnumerable<StreamingData>> GetStreamInformation(int tvDbId, int tvMazeId, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -67,6 +67,22 @@ namespace Ombi.Core.Engine
|
|||
$"{movieInfo.Title}{(!string.IsNullOrEmpty(movieInfo.ReleaseDate) ? $" ({DateTime.Parse(movieInfo.ReleaseDate).Year})" : string.Empty)}";
|
||||
|
||||
var userDetails = await GetUser();
|
||||
var canRequestOnBehalf = false;
|
||||
|
||||
if (model.RequestOnBehalf.HasValue())
|
||||
{
|
||||
canRequestOnBehalf = await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin);
|
||||
|
||||
if (!canRequestOnBehalf)
|
||||
{
|
||||
return new RequestEngineResult
|
||||
{
|
||||
Result = false,
|
||||
Message = "You do not have the correct permissions to request on behalf of users!",
|
||||
ErrorMessage = $"You do not have the correct permissions to request on behalf of users!"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var requestModel = new MovieRequests
|
||||
{
|
||||
|
@ -82,7 +98,7 @@ namespace Ombi.Core.Engine
|
|||
Status = movieInfo.Status,
|
||||
RequestedDate = DateTime.UtcNow,
|
||||
Approved = false,
|
||||
RequestedUserId = userDetails.Id,
|
||||
RequestedUserId = canRequestOnBehalf ? model.RequestOnBehalf : userDetails.Id,
|
||||
Background = movieInfo.BackdropPath,
|
||||
LangCode = model.LanguageCode,
|
||||
RequestedByAlias = model.RequestedByAlias
|
||||
|
@ -103,7 +119,7 @@ namespace Ombi.Core.Engine
|
|||
|
||||
if (requestModel.Approved) // The rules have auto approved this
|
||||
{
|
||||
var requestEngineResult = await AddMovieRequest(requestModel, fullMovieName);
|
||||
var requestEngineResult = await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf);
|
||||
if (requestEngineResult.Result)
|
||||
{
|
||||
var result = await ApproveMovie(requestModel);
|
||||
|
@ -124,7 +140,7 @@ namespace Ombi.Core.Engine
|
|||
// If there are no providers then it's successful but movie has not been sent
|
||||
}
|
||||
|
||||
return await AddMovieRequest(requestModel, fullMovieName);
|
||||
return await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf);
|
||||
}
|
||||
|
||||
|
||||
|
@ -270,7 +286,7 @@ namespace Ombi.Core.Engine
|
|||
allRequests = allRequests.Where(x => x.Available);
|
||||
break;
|
||||
case RequestStatus.Denied:
|
||||
allRequests = allRequests.Where(x => x.Denied.HasValue && x.Denied.Value && !x.Available);
|
||||
allRequests = allRequests.Where(x => x.Denied.HasValue && x.Denied.Value && !x.Available);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -429,7 +445,7 @@ namespace Ombi.Core.Engine
|
|||
public async Task<MovieRequests> GetRequest(int requestId)
|
||||
{
|
||||
var request = await MovieRepository.GetWithUser().Where(x => x.Id == requestId).FirstOrDefaultAsync();
|
||||
await CheckForSubscription(new HideResult(), new List<MovieRequests>{request });
|
||||
await CheckForSubscription(new HideResult(), new List<MovieRequests> { request });
|
||||
|
||||
return request;
|
||||
}
|
||||
|
@ -654,19 +670,19 @@ namespace Ombi.Core.Engine
|
|||
};
|
||||
}
|
||||
|
||||
private async Task<RequestEngineResult> AddMovieRequest(MovieRequests model, string movieName)
|
||||
private async Task<RequestEngineResult> AddMovieRequest(MovieRequests model, string movieName, string requestOnBehalf)
|
||||
{
|
||||
await MovieRepository.Add(model);
|
||||
|
||||
var result = await RunSpecificRule(model, SpecificRules.CanSendNotification);
|
||||
if (result.Success)
|
||||
{
|
||||
{
|
||||
await NotificationHelper.NewRequest(model);
|
||||
}
|
||||
|
||||
await _requestLog.Add(new RequestLog
|
||||
{
|
||||
UserId = (await GetUser()).Id,
|
||||
UserId = requestOnBehalf.HasValue() ? requestOnBehalf : (await GetUser()).Id,
|
||||
RequestDate = DateTime.UtcNow,
|
||||
RequestId = model.Id,
|
||||
RequestType = RequestType.Movie,
|
||||
|
|
|
@ -13,15 +13,17 @@ namespace Ombi.Core.Engine
|
|||
{
|
||||
public class RecentlyAddedEngine : IRecentlyAddedEngine
|
||||
{
|
||||
public RecentlyAddedEngine(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository<RecentlyAddedLog> recentlyAdded)
|
||||
public RecentlyAddedEngine(IPlexContentRepository plex, IEmbyContentRepository emby, IJellyfinContentRepository jellyfin, IRepository<RecentlyAddedLog> recentlyAdded)
|
||||
{
|
||||
_plex = plex;
|
||||
_emby = emby;
|
||||
_jellyfin = jellyfin;
|
||||
_recentlyAddedLog = recentlyAdded;
|
||||
}
|
||||
|
||||
private readonly IPlexContentRepository _plex;
|
||||
private readonly IEmbyContentRepository _emby;
|
||||
private readonly IJellyfinContentRepository _jellyfin;
|
||||
private readonly IRepository<RecentlyAddedLog> _recentlyAddedLog;
|
||||
|
||||
|
||||
|
@ -30,23 +32,26 @@ namespace Ombi.Core.Engine
|
|||
{
|
||||
var plexMovies = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Movie && x.AddedAt > from && x.AddedAt < to);
|
||||
var embyMovies = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Movie && x.AddedAt > from && x.AddedAt < to);
|
||||
var jellyfinMovies = _jellyfin.GetAll().Where(x => x.Type == JellyfinMediaType.Movie && x.AddedAt > from && x.AddedAt < to);
|
||||
|
||||
return GetRecentlyAddedMovies(plexMovies, embyMovies).Take(30);
|
||||
return GetRecentlyAddedMovies(plexMovies, embyMovies, jellyfinMovies).Take(30);
|
||||
}
|
||||
|
||||
public IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies()
|
||||
{
|
||||
var plexMovies = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Movie);
|
||||
var embyMovies = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Movie);
|
||||
return GetRecentlyAddedMovies(plexMovies, embyMovies);
|
||||
var jellyfinMovies = _jellyfin.GetAll().Where(x => x.Type == JellyfinMediaType.Movie);
|
||||
return GetRecentlyAddedMovies(plexMovies, embyMovies, jellyfinMovies);
|
||||
}
|
||||
|
||||
public IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(DateTime from, DateTime to, bool groupBySeason)
|
||||
{
|
||||
var plexTv = _plex.GetAll().Include(x => x.Seasons).Include(x => x.Episodes).Where(x => x.Type == PlexMediaTypeEntity.Show && x.AddedAt > from && x.AddedAt < to);
|
||||
var embyTv = _emby.GetAll().Include(x => x.Episodes).Where(x => x.Type == EmbyMediaType.Series && x.AddedAt > from && x.AddedAt < to);
|
||||
var jellyfinTv = _jellyfin.GetAll().Include(x => x.Episodes).Where(x => x.Type == JellyfinMediaType.Series && x.AddedAt > from && x.AddedAt < to);
|
||||
|
||||
return GetRecentlyAddedTv(plexTv, embyTv, groupBySeason).Take(30);
|
||||
return GetRecentlyAddedTv(plexTv, embyTv, jellyfinTv, groupBySeason).Take(30);
|
||||
}
|
||||
|
||||
|
||||
|
@ -54,14 +59,16 @@ namespace Ombi.Core.Engine
|
|||
{
|
||||
var plexTv = _plex.GetAll().Include(x => x.Seasons).Include(x => x.Episodes).Where(x => x.Type == PlexMediaTypeEntity.Show);
|
||||
var embyTv = _emby.GetAll().Include(x => x.Episodes).Where(x => x.Type == EmbyMediaType.Series);
|
||||
var jellyfinTv = _jellyfin.GetAll().Include(x => x.Episodes).Where(x => x.Type == JellyfinMediaType.Series);
|
||||
|
||||
return GetRecentlyAddedTv(plexTv, embyTv, groupBySeason);
|
||||
return GetRecentlyAddedTv(plexTv, embyTv, jellyfinTv, groupBySeason);
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateRecentlyAddedDatabase()
|
||||
{
|
||||
var plexContent = _plex.GetAll().Include(x => x.Episodes);
|
||||
var embyContent = _emby.GetAll().Include(x => x.Episodes);
|
||||
var jellyfinContent = _jellyfin.GetAll().Include(x => x.Episodes);
|
||||
var recentlyAddedLog = new HashSet<RecentlyAddedLog>();
|
||||
foreach (var p in plexContent)
|
||||
{
|
||||
|
@ -138,17 +145,56 @@ namespace Ombi.Core.Engine
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var e in jellyfinContent)
|
||||
{
|
||||
if (e.TheMovieDbId.IsNullOrEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (e.Type == JellyfinMediaType.Movie)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Jellyfin,
|
||||
ContentId = int.Parse(e.TheMovieDbId),
|
||||
ContentType = ContentType.Parent
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the episodes
|
||||
foreach (var ep in e.Episodes)
|
||||
{
|
||||
if (ep.Series.TvDbId.IsNullOrEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Jellyfin,
|
||||
ContentId = int.Parse(ep.Series.TvDbId),
|
||||
ContentType = ContentType.Episode,
|
||||
EpisodeNumber = ep.EpisodeNumber,
|
||||
SeasonNumber = ep.SeasonNumber
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
await _recentlyAddedLog.AddRange(recentlyAddedLog);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(IQueryable<PlexServerContent> plexTv, IQueryable<EmbyContent> embyTv,
|
||||
private IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(IQueryable<PlexServerContent> plexTv, IQueryable<EmbyContent> embyTv, IQueryable<JellyfinContent> jellyfinTv,
|
||||
bool groupBySeason)
|
||||
{
|
||||
var model = new HashSet<RecentlyAddedTvModel>();
|
||||
TransformPlexShows(plexTv, model);
|
||||
TransformEmbyShows(embyTv, model);
|
||||
TransformJellyfinShows(jellyfinTv, model);
|
||||
|
||||
if (groupBySeason)
|
||||
{
|
||||
|
@ -158,11 +204,12 @@ namespace Ombi.Core.Engine
|
|||
return model;
|
||||
}
|
||||
|
||||
private IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies(IQueryable<PlexServerContent> plexMovies, IQueryable<EmbyContent> embyMovies)
|
||||
private IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies(IQueryable<PlexServerContent> plexMovies, IQueryable<EmbyContent> embyMovies, IQueryable<JellyfinContent> jellyfinMovies)
|
||||
{
|
||||
var model = new HashSet<RecentlyAddedMovieModel>();
|
||||
TransformPlexMovies(plexMovies, model);
|
||||
TransformEmbyMovies(embyMovies, model);
|
||||
TransformJellyfinMovies(jellyfinMovies, model);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
@ -183,6 +230,22 @@ namespace Ombi.Core.Engine
|
|||
}
|
||||
}
|
||||
|
||||
private static void TransformJellyfinMovies(IQueryable<JellyfinContent> jellyfinMovies, HashSet<RecentlyAddedMovieModel> model)
|
||||
{
|
||||
foreach (var jellyfin in jellyfinMovies)
|
||||
{
|
||||
model.Add(new RecentlyAddedMovieModel
|
||||
{
|
||||
Id = jellyfin.Id,
|
||||
ImdbId = jellyfin.ImdbId,
|
||||
TheMovieDbId = jellyfin.TheMovieDbId,
|
||||
TvDbId = jellyfin.TvDbId,
|
||||
AddedAt = jellyfin.AddedAt,
|
||||
Title = jellyfin.Title,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void TransformPlexMovies(IQueryable<PlexServerContent> plexMovies, HashSet<RecentlyAddedMovieModel> model)
|
||||
{
|
||||
foreach (var plex in plexMovies)
|
||||
|
@ -246,5 +309,26 @@ namespace Ombi.Core.Engine
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void TransformJellyfinShows(IQueryable<JellyfinContent> jellyfinShows, HashSet<RecentlyAddedTvModel> model)
|
||||
{
|
||||
foreach (var jellyfin in jellyfinShows)
|
||||
{
|
||||
foreach (var episode in jellyfin.Episodes)
|
||||
{
|
||||
model.Add(new RecentlyAddedTvModel
|
||||
{
|
||||
Id = jellyfin.Id,
|
||||
ImdbId = jellyfin.ImdbId,
|
||||
TvDbId = jellyfin.TvDbId,
|
||||
TheMovieDbId = jellyfin.TheMovieDbId,
|
||||
AddedAt = jellyfin.AddedAt,
|
||||
Title = jellyfin.Title,
|
||||
EpisodeNumber = episode.EpisodeNumber,
|
||||
SeasonNumber = episode.SeasonNumber
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,12 +51,28 @@ namespace Ombi.Core.Engine
|
|||
public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv)
|
||||
{
|
||||
var user = await GetUser();
|
||||
var canRequestOnBehalf = false;
|
||||
|
||||
if (tv.RequestOnBehalf.HasValue())
|
||||
{
|
||||
canRequestOnBehalf = await UserManager.IsInRoleAsync(user, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(user, OmbiRoles.Admin);
|
||||
|
||||
if (!canRequestOnBehalf)
|
||||
{
|
||||
return new RequestEngineResult
|
||||
{
|
||||
Result = false,
|
||||
Message = "You do not have the correct permissions to request on behalf of users!",
|
||||
ErrorMessage = $"You do not have the correct permissions to request on behalf of users!"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var tvBuilder = new TvShowRequestBuilder(TvApi, MovieDbApi);
|
||||
(await tvBuilder
|
||||
.GetShowInfo(tv.TvDbId))
|
||||
.CreateTvList(tv)
|
||||
.CreateChild(tv, user.Id);
|
||||
.CreateChild(tv, canRequestOnBehalf ? tv.RequestOnBehalf : user.Id);
|
||||
|
||||
await tvBuilder.BuildEpisodes(tv);
|
||||
|
||||
|
@ -124,12 +140,12 @@ namespace Ombi.Core.Engine
|
|||
ErrorMessage = "This has already been requested"
|
||||
};
|
||||
}
|
||||
return await AddExistingRequest(tvBuilder.ChildRequest, existingRequest);
|
||||
return await AddExistingRequest(tvBuilder.ChildRequest, existingRequest, tv.RequestOnBehalf);
|
||||
}
|
||||
|
||||
// This is a new request
|
||||
var newRequest = tvBuilder.CreateNewRequest(tv);
|
||||
return await AddRequest(newRequest.NewRequest);
|
||||
return await AddRequest(newRequest.NewRequest, tv.RequestOnBehalf);
|
||||
}
|
||||
|
||||
public async Task<RequestsViewModel<TvRequests>> GetRequests(int count, int position, OrderFilterModel type)
|
||||
|
@ -736,21 +752,21 @@ namespace Ombi.Core.Engine
|
|||
}
|
||||
}
|
||||
|
||||
private async Task<RequestEngineResult> AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest)
|
||||
private async Task<RequestEngineResult> AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest, string requestOnBehalf)
|
||||
{
|
||||
// Add the child
|
||||
existingRequest.ChildRequests.Add(newRequest);
|
||||
|
||||
await TvRepository.Update(existingRequest);
|
||||
|
||||
return await AfterRequest(newRequest);
|
||||
return await AfterRequest(newRequest, requestOnBehalf);
|
||||
}
|
||||
|
||||
private async Task<RequestEngineResult> AddRequest(TvRequests model)
|
||||
private async Task<RequestEngineResult> AddRequest(TvRequests model, string requestOnBehalf)
|
||||
{
|
||||
await TvRepository.Add(model);
|
||||
// This is a new request so we should only have 1 child
|
||||
return await AfterRequest(model.ChildRequests.FirstOrDefault());
|
||||
return await AfterRequest(model.ChildRequests.FirstOrDefault(), requestOnBehalf);
|
||||
}
|
||||
|
||||
private static List<ChildRequests> SortEpisodes(List<ChildRequests> items)
|
||||
|
@ -766,7 +782,7 @@ namespace Ombi.Core.Engine
|
|||
}
|
||||
|
||||
|
||||
private async Task<RequestEngineResult> AfterRequest(ChildRequests model)
|
||||
private async Task<RequestEngineResult> AfterRequest(ChildRequests model, string requestOnBehalf)
|
||||
{
|
||||
var sendRuleResult = await RunSpecificRule(model, SpecificRules.CanSendNotification);
|
||||
if (sendRuleResult.Success)
|
||||
|
@ -776,7 +792,7 @@ namespace Ombi.Core.Engine
|
|||
|
||||
await _requestLog.Add(new RequestLog
|
||||
{
|
||||
UserId = (await GetUser()).Id,
|
||||
UserId = requestOnBehalf.HasValue() ? requestOnBehalf : (await GetUser()).Id,
|
||||
RequestDate = DateTime.UtcNow,
|
||||
RequestId = model.Id,
|
||||
RequestType = RequestType.TvShow,
|
||||
|
|
|
@ -249,6 +249,26 @@ namespace Ombi.Core.Engine.V2
|
|||
return result;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<StreamingData>> GetStreamInformation(int movieDbId, CancellationToken cancellationToken)
|
||||
{
|
||||
var providers = await MovieApi.GetMovieWatchProviders(movieDbId, cancellationToken);
|
||||
var results = await GetUserWatchProvider(providers);
|
||||
|
||||
var data = new List<StreamingData>();
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
data.Add(new StreamingData
|
||||
{
|
||||
Logo = result.logo_path,
|
||||
Order = result.display_priority,
|
||||
StreamingProvider = result.provider_name
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
protected async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse(
|
||||
IEnumerable<MovieSearchResult> movies)
|
||||
{
|
||||
|
@ -287,6 +307,7 @@ namespace Ombi.Core.Engine.V2
|
|||
mapped.Requested = viewMovie.Requested;
|
||||
mapped.PlexUrl = viewMovie.PlexUrl;
|
||||
mapped.EmbyUrl = viewMovie.EmbyUrl;
|
||||
mapped.JellyfinUrl = viewMovie.JellyfinUrl;
|
||||
mapped.Subscribed = viewMovie.Subscribed;
|
||||
mapped.ShowSubscribe = viewMovie.ShowSubscribe;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Ombi.Api.Lidarr;
|
||||
using Ombi.Api.Lidarr.Models;
|
||||
using Ombi.Api.MusicBrainz;
|
||||
|
@ -41,6 +42,21 @@ namespace Ombi.Core.Engine.V2
|
|||
_lidarrApi = lidarrApi;
|
||||
}
|
||||
|
||||
public async Task<ReleaseGroup> GetAlbum(string albumId)
|
||||
{
|
||||
var g = await _musicBrainzApi.GetAlbumInformation(albumId);
|
||||
var release = new ReleaseGroup
|
||||
{
|
||||
ReleaseType = g.ReleaseGroup.PrimaryType,
|
||||
Id = g.Id,
|
||||
Title = g.Title,
|
||||
ReleaseDate = g.ReleaseGroup.FirstReleaseDate,
|
||||
};
|
||||
|
||||
await RunSearchRules(release);
|
||||
return release;
|
||||
}
|
||||
|
||||
public async Task<ArtistInformation> GetArtistInformation(string artistId)
|
||||
{
|
||||
var artist = await _musicBrainzApi.GetArtistInformation(artistId);
|
||||
|
@ -84,12 +100,19 @@ namespace Ombi.Core.Engine.V2
|
|||
|
||||
if (lidarrArtistTask != null)
|
||||
{
|
||||
var artistResult = await lidarrArtistTask;
|
||||
info.Banner = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("banner", StringComparison.InvariantCultureIgnoreCase))?.url.ToHttpsUrl();
|
||||
info.Logo = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("logo", StringComparison.InvariantCultureIgnoreCase))?.url.ToHttpsUrl();
|
||||
info.Poster = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("poster", StringComparison.InvariantCultureIgnoreCase))?.url.ToHttpsUrl();
|
||||
info.FanArt = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("fanart", StringComparison.InvariantCultureIgnoreCase))?.url.ToHttpsUrl();
|
||||
info.Overview = artistResult.overview;
|
||||
try
|
||||
{
|
||||
var artistResult = await lidarrArtistTask;
|
||||
info.Banner = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("banner", StringComparison.InvariantCultureIgnoreCase))?.url.ToHttpsUrl();
|
||||
info.Logo = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("logo", StringComparison.InvariantCultureIgnoreCase))?.url.ToHttpsUrl();
|
||||
info.Poster = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("poster", StringComparison.InvariantCultureIgnoreCase))?.url.ToHttpsUrl();
|
||||
info.FanArt = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("fanart", StringComparison.InvariantCultureIgnoreCase))?.url.ToHttpsUrl();
|
||||
info.Overview = artistResult.overview;
|
||||
}
|
||||
catch (JsonSerializationException)
|
||||
{
|
||||
// swallow, Lidarr probably doesn't have this artist
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
|
@ -118,7 +141,7 @@ namespace Ombi.Core.Engine.V2
|
|||
|
||||
return new AlbumArt();
|
||||
}
|
||||
|
||||
|
||||
public async Task<ArtistInformation> GetArtistInformationByRequestId(int requestId)
|
||||
{
|
||||
var request = await RequestService.MusicRequestRepository.Find(requestId);
|
||||
|
|
|
@ -19,6 +19,8 @@ using Ombi.Core.Settings;
|
|||
using Ombi.Store.Repository;
|
||||
using TraktSharp.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Threading;
|
||||
using Ombi.Api.TheMovieDb;
|
||||
|
||||
namespace Ombi.Core.Engine.V2
|
||||
{
|
||||
|
@ -27,15 +29,17 @@ namespace Ombi.Core.Engine.V2
|
|||
private readonly ITvMazeApi _tvMaze;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ITraktApi _traktApi;
|
||||
private readonly IMovieDbApi _movieApi;
|
||||
|
||||
public TvSearchEngineV2(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper,
|
||||
ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, ICacheService memCache, ISettingsService<OmbiSettings> s,
|
||||
IRepository<RequestSubscription> sub)
|
||||
IRepository<RequestSubscription> sub, IMovieDbApi movieApi)
|
||||
: base(identity, service, r, um, memCache, s, sub)
|
||||
{
|
||||
_tvMaze = tvMaze;
|
||||
_mapper = mapper;
|
||||
_traktApi = trakt;
|
||||
_movieApi = movieApi;
|
||||
}
|
||||
|
||||
|
||||
|
@ -106,6 +110,39 @@ namespace Ombi.Core.Engine.V2
|
|||
return await ProcessResult(mapped, traktInfoTask);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<StreamingData>> GetStreamInformation(int tvDbId, int tvMazeId, CancellationToken cancellationToken)
|
||||
{
|
||||
var tvdbshow = await Cache.GetOrAdd(nameof(GetShowInformation) + tvMazeId,
|
||||
async () => await _tvMaze.ShowLookupByTheTvDbId(tvMazeId), DateTime.Now.AddHours(12));
|
||||
if (tvdbshow == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// this is a best effort guess since TV maze do not provide the TheMovieDbId
|
||||
var movieDbResults = await _movieApi.SearchTv(tvdbshow.name, tvdbshow.premiered.Substring(0, 4));
|
||||
var potential = movieDbResults.FirstOrDefault();
|
||||
tvDbId = potential.Id;
|
||||
// end guess
|
||||
|
||||
var providers = await _movieApi.GetTvWatchProviders(tvDbId, cancellationToken);
|
||||
var results = await GetUserWatchProvider(providers);
|
||||
|
||||
var data = new List<StreamingData>();
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
data.Add(new StreamingData
|
||||
{
|
||||
Logo = result.logo_path,
|
||||
Order = result.display_priority,
|
||||
StreamingProvider = result.provider_name
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private IEnumerable<SearchTvShowViewModel> ProcessResults<T>(IEnumerable<T> items)
|
||||
{
|
||||
var retVal = new List<SearchTvShowViewModel>();
|
||||
|
@ -141,7 +178,7 @@ namespace Ombi.Core.Engine.V2
|
|||
{
|
||||
item.Images.Medium = item.Images.Medium.ToHttpsUrl();
|
||||
}
|
||||
|
||||
|
||||
if (item.Cast?.Any() ?? false)
|
||||
{
|
||||
foreach (var cast in item.Cast)
|
||||
|
|
35
src/Ombi.Core/Helpers/WatchProviderParser.cs
Normal file
35
src/Ombi.Core/Helpers/WatchProviderParser.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using Ombi.Api.TheMovieDb.Models;
|
||||
using Ombi.Store.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ombi.Core.Helpers
|
||||
{
|
||||
public static class WatchProviderParser
|
||||
{
|
||||
public static List<StreamData> GetUserWatchProviders(WatchProviders providers, OmbiUser user)
|
||||
{
|
||||
var data = new List<StreamData>();
|
||||
|
||||
if (providers?.Results == null)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
var resultsProp = providers.Results.GetType().GetProperties();
|
||||
var matchingStreamingCountry = resultsProp.FirstOrDefault(x => x.Name.Equals(user.StreamingCountry, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (matchingStreamingCountry == null)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
var result = (WatchProviderData)matchingStreamingCountry.GetValue(providers.Results);
|
||||
if (result == null || result.StreamInformation == null)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
return result.StreamInformation;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ namespace Ombi.Core.Models
|
|||
public enum RecentlyAddedType
|
||||
{
|
||||
Plex,
|
||||
Emby
|
||||
Emby,
|
||||
Jellyfin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ namespace Ombi.Core.Models.Requests
|
|||
{
|
||||
public int TheMovieDbId { get; set; }
|
||||
public string LanguageCode { get; set; } = "en";
|
||||
public string RequestOnBehalf { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This is only set from a HTTP Header
|
||||
|
|
|
@ -12,6 +12,8 @@ namespace Ombi.Core.Models.Requests
|
|||
public List<SeasonsViewModel> Seasons { get; set; } = new List<SeasonsViewModel>();
|
||||
[JsonIgnore]
|
||||
public string RequestedByAlias { get; set; }
|
||||
|
||||
public string RequestOnBehalf { get; set; }
|
||||
}
|
||||
|
||||
public class SeasonsViewModel
|
||||
|
|
|
@ -14,11 +14,12 @@ namespace Ombi.Core.Models.Search
|
|||
public bool Available { get; set; }
|
||||
public string PlexUrl { get; set; }
|
||||
public string EmbyUrl { get; set; }
|
||||
public string JellyfinUrl { get; set; }
|
||||
public string Quality { get; set; }
|
||||
public abstract RequestType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This is used for the PlexAvailabilityCheck/EmbyAvailabilityRule rule
|
||||
/// This is used for the PlexAvailabilityCheck/EmbyAvailabilityRule/JellyfinAvailabilityRule rule
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The custom identifier.
|
||||
|
@ -35,4 +36,4 @@ namespace Ombi.Core.Models.Search
|
|||
[NotMapped]
|
||||
public bool ShowSubscribe { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
9
src/Ombi.Core/Models/Search/V2/StreamingData.cs
Normal file
9
src/Ombi.Core/Models/Search/V2/StreamingData.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ombi.Core.Models.Search.V2
|
||||
{
|
||||
public class StreamingData
|
||||
{
|
||||
public int Order { get; set; }
|
||||
public string StreamingProvider { get; set; }
|
||||
public string Logo { get; set; }
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ namespace Ombi.Core.Models.UI
|
|||
public UserType UserType { get; set; }
|
||||
public int MovieRequestLimit { get; set; }
|
||||
public int EpisodeRequestLimit { get; set; }
|
||||
public string StreamingCountry { get; set; }
|
||||
public RequestQuotaCountModel EpisodeRequestQuota { get; set; }
|
||||
public RequestQuotaCountModel MovieRequestQuota { get; set; }
|
||||
public RequestQuotaCountModel MusicRequestQuota { get; set; }
|
||||
|
@ -30,4 +31,10 @@ namespace Ombi.Core.Models.UI
|
|||
public string Value { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
|
||||
public class UserViewModelDropdown
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Username { get; set; }
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ namespace Ombi.Core.Models
|
|||
{
|
||||
LocalUser = 1,
|
||||
PlexUser = 2,
|
||||
EmbyUser = 3
|
||||
EmbyUser = 3,
|
||||
JellyfinUser = 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
<ProjectReference Include="..\Ombi.Api.CouchPotato\Ombi.Api.CouchPotato.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.DogNzb\Ombi.Api.DogNzb.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Emby\Ombi.Api.Emby.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Jellyfin\Ombi.Api.Jellyfin.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.FanartTv\Ombi.Api.FanartTv.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.MusicBrainz\Ombi.Api.MusicBrainz.csproj" />
|
||||
|
@ -40,4 +41,4 @@
|
|||
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -108,10 +108,40 @@ namespace Ombi.Core.Rule.Rules.Search
|
|||
x.Series.TvDbId == item.TvDbId);
|
||||
}
|
||||
|
||||
if (epExists != null)
|
||||
{
|
||||
episode.Available = true;
|
||||
}
|
||||
}
|
||||
public static async Task SingleEpisodeCheck(bool useImdb, IQueryable<JellyfinEpisode> allEpisodes, EpisodeRequests episode,
|
||||
SeasonRequests season, JellyfinContent item, bool useTheMovieDb, bool useTvDb)
|
||||
{
|
||||
JellyfinEpisode epExists = null;
|
||||
if (useImdb)
|
||||
{
|
||||
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
|
||||
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
|
||||
x.Series.ImdbId == item.ImdbId);
|
||||
}
|
||||
|
||||
if (useTheMovieDb)
|
||||
{
|
||||
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
|
||||
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
|
||||
x.Series.TheMovieDbId == item.TheMovieDbId);
|
||||
}
|
||||
|
||||
if (useTvDb)
|
||||
{
|
||||
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
|
||||
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
|
||||
x.Series.TvDbId == item.TvDbId);
|
||||
}
|
||||
|
||||
if (epExists != null)
|
||||
{
|
||||
episode.Available = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,11 +70,11 @@ namespace Ombi.Core.Rule.Rules.Search
|
|||
var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null);
|
||||
if ((server?.ServerHostname ?? string.Empty).HasValue())
|
||||
{
|
||||
obj.EmbyUrl = EmbyHelper.GetEmbyMediaUrl(item.EmbyId, server?.ServerId, server?.ServerHostname, s.IsJellyfin);
|
||||
obj.EmbyUrl = EmbyHelper.GetEmbyMediaUrl(item.EmbyId, server?.ServerId, server?.ServerHostname);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj.EmbyUrl = EmbyHelper.GetEmbyMediaUrl(item.EmbyId, server?.ServerId, null, s.IsJellyfin);
|
||||
obj.EmbyUrl = EmbyHelper.GetEmbyMediaUrl(item.EmbyId, server?.ServerId, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,4 +100,4 @@ namespace Ombi.Core.Rule.Rules.Search
|
|||
return Success();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
104
src/Ombi.Core/Rule/Rules/Search/JellyfinAvailabilityRule.cs
Normal file
104
src/Ombi.Core/Rule/Rules/Search/JellyfinAvailabilityRule.cs
Normal file
|
@ -0,0 +1,104 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Models.Search;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
|
||||
namespace Ombi.Core.Rule.Rules.Search
|
||||
{
|
||||
public class JellyfinAvailabilityRule : BaseSearchRule, IRules<SearchViewModel>
|
||||
{
|
||||
public JellyfinAvailabilityRule(IJellyfinContentRepository repo, ISettingsService<JellyfinSettings> s)
|
||||
{
|
||||
JellyfinContentRepository = repo;
|
||||
JellyfinSettings = s;
|
||||
}
|
||||
|
||||
private IJellyfinContentRepository JellyfinContentRepository { get; }
|
||||
private ISettingsService<JellyfinSettings> JellyfinSettings { get; }
|
||||
|
||||
public async Task<RuleResult> Execute(SearchViewModel obj)
|
||||
{
|
||||
JellyfinContent item = null;
|
||||
var useImdb = false;
|
||||
var useTheMovieDb = false;
|
||||
var useTvDb = false;
|
||||
|
||||
if (obj.ImdbId.HasValue())
|
||||
{
|
||||
item = await JellyfinContentRepository.GetByImdbId(obj.ImdbId);
|
||||
if (item != null)
|
||||
{
|
||||
useImdb = true;
|
||||
}
|
||||
}
|
||||
if (item == null)
|
||||
{
|
||||
if (obj.TheMovieDbId.HasValue())
|
||||
{
|
||||
item = await JellyfinContentRepository.GetByTheMovieDbId(obj.TheMovieDbId);
|
||||
if (item != null)
|
||||
{
|
||||
useTheMovieDb = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
if (obj.TheTvDbId.HasValue())
|
||||
{
|
||||
item = await JellyfinContentRepository.GetByTvDbId(obj.TheTvDbId);
|
||||
if (item != null)
|
||||
{
|
||||
useTvDb = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
obj.Available = true;
|
||||
var s = await JellyfinSettings.GetSettingsAsync();
|
||||
if (s.Enable)
|
||||
{
|
||||
var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null);
|
||||
if ((server?.ServerHostname ?? string.Empty).HasValue())
|
||||
{
|
||||
obj.JellyfinUrl = JellyfinHelper.GetJellyfinMediaUrl(item.JellyfinId, server?.ServerId, server?.ServerHostname);
|
||||
}
|
||||
else
|
||||
{
|
||||
var firstServer = s.Servers?.FirstOrDefault();
|
||||
obj.JellyfinUrl = JellyfinHelper.GetJellyfinMediaUrl(item.JellyfinId, firstServer.ServerId, firstServer.FullUri);
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.Type == RequestType.TvShow)
|
||||
{
|
||||
var search = (SearchTvShowViewModel)obj;
|
||||
// Let's go through the episodes now
|
||||
if (search.SeasonRequests.Any())
|
||||
{
|
||||
var allEpisodes = JellyfinContentRepository.GetAllEpisodes().Include(x => x.Series);
|
||||
foreach (var season in search.SeasonRequests)
|
||||
{
|
||||
foreach (var episode in season.Episodes)
|
||||
{
|
||||
await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AvailabilityRuleHelper.CheckForUnairedEpisodes(search);
|
||||
}
|
||||
}
|
||||
return Success();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||
|
||||
using Ombi.Api.Discord;
|
||||
using Ombi.Api.Emby;
|
||||
using Ombi.Api.Jellyfin;
|
||||
using Ombi.Api.Plex;
|
||||
using Ombi.Api.Radarr;
|
||||
using Ombi.Api.Sonarr;
|
||||
|
@ -47,6 +48,7 @@ using Ombi.Core.Senders;
|
|||
using Ombi.Helpers;
|
||||
using Ombi.Schedule.Jobs.Couchpotato;
|
||||
using Ombi.Schedule.Jobs.Emby;
|
||||
using Ombi.Schedule.Jobs.Jellyfin;
|
||||
using Ombi.Schedule.Jobs.Ombi;
|
||||
using Ombi.Schedule.Jobs.Plex;
|
||||
using Ombi.Schedule.Jobs.Sonarr;
|
||||
|
@ -65,6 +67,7 @@ using Quartz.Spi;
|
|||
using Ombi.Api.MusicBrainz;
|
||||
using Ombi.Api.Twilio;
|
||||
using Ombi.Api.CloudService;
|
||||
using Ombi.Api.RottenTomatoes;
|
||||
|
||||
namespace Ombi.DependencyInjection
|
||||
{
|
||||
|
@ -126,6 +129,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<IMovieDbApi, Api.TheMovieDb.TheMovieDbApi>();
|
||||
services.AddTransient<IPlexApi, PlexApi>();
|
||||
services.AddTransient<IEmbyApi, EmbyApi>();
|
||||
services.AddTransient<IJellyfinApi, JellyfinApi>();
|
||||
services.AddTransient<ISonarrApi, SonarrApi>();
|
||||
services.AddTransient<ISonarrV3Api, SonarrV3Api>();
|
||||
services.AddTransient<ISlackApi, SlackApi>();
|
||||
|
@ -153,8 +157,9 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<IMusicBrainzApi, MusicBrainzApi>();
|
||||
services.AddTransient<IWhatsAppApi, WhatsAppApi>();
|
||||
services.AddTransient<ICloudMobileNotification, CloudMobileNotification>();
|
||||
services.AddTransient<IBaseEmbyApi, JellyfinApi>();
|
||||
services.AddTransient<IEmbyApiFactory, EmbyApiFactory>();
|
||||
services.AddTransient<IJellyfinApiFactory, JellyfinApiFactory>();
|
||||
services.AddTransient<IRottenTomatoesApi, RottenTomatoesApi>();
|
||||
}
|
||||
|
||||
public static void RegisterStore(this IServiceCollection services) {
|
||||
|
@ -169,6 +174,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddScoped<ISettingsResolver, SettingsResolver>();
|
||||
services.AddScoped<IPlexContentRepository, PlexServerContentRepository>();
|
||||
services.AddScoped<IEmbyContentRepository, EmbyContentRepository>();
|
||||
services.AddScoped<IJellyfinContentRepository, JellyfinContentRepository>();
|
||||
services.AddScoped<INotificationTemplatesRepository, NotificationTemplatesRepository>();
|
||||
|
||||
services.AddScoped<ITvRequestRepository, TvRequestRepository>();
|
||||
|
@ -213,6 +219,9 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<IEmbyContentSync, EmbyContentSync>();
|
||||
services.AddTransient<IEmbyEpisodeSync, EmbyEpisodeSync>();
|
||||
services.AddTransient<IEmbyAvaliabilityChecker, EmbyAvaliabilityChecker>();
|
||||
services.AddTransient<IJellyfinContentSync, JellyfinContentSync>();
|
||||
services.AddTransient<IJellyfinEpisodeSync, JellyfinEpisodeSync>();
|
||||
services.AddTransient<IJellyfinAvaliabilityChecker, JellyfinAvaliabilityChecker>();
|
||||
services.AddTransient<IPlexEpisodeSync, PlexEpisodeSync>();
|
||||
services.AddTransient<IPlexAvailabilityChecker, PlexAvailabilityChecker>();
|
||||
services.AddTransient<IRadarrSync, RadarrSync>();
|
||||
|
@ -220,6 +229,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<IOmbiAutomaticUpdater, OmbiAutomaticUpdater>();
|
||||
services.AddTransient<IPlexUserImporter, PlexUserImporter>();
|
||||
services.AddTransient<IEmbyUserImporter, EmbyUserImporter>();
|
||||
services.AddTransient<IJellyfinUserImporter, JellyfinUserImporter>();
|
||||
services.AddTransient<IWelcomeEmail, WelcomeEmail>();
|
||||
services.AddTransient<ICouchPotatoSync, CouchPotatoSync>();
|
||||
services.AddTransient<IProcessProvider, ProcessProvider>();
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
<ProjectReference Include="..\Ombi.Api.Pushbullet\Ombi.Api.Pushbullet.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Pushover\Ombi.Api.Pushover.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Radarr\Ombi.Api.Radarr.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.RottenTomatoes\Ombi.Api.RottenTomatoes.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Service\Ombi.Api.Service.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.SickRage\Ombi.Api.SickRage.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Slack\Ombi.Api.Slack.csproj" />
|
||||
|
|
|
@ -3,6 +3,8 @@ using Microsoft.Extensions.Diagnostics.HealthChecks;
|
|||
using Ombi.Api.CouchPotato;
|
||||
using Ombi.Api.Emby;
|
||||
using Ombi.Api.Emby.Models;
|
||||
using Ombi.Api.Jellyfin;
|
||||
using Ombi.Api.Jellyfin.Models;
|
||||
using Ombi.Api.Plex;
|
||||
using Ombi.Api.Plex.Models.Status;
|
||||
using Ombi.Core.Settings;
|
||||
|
|
54
src/Ombi.HealthChecks/Checks/JellyfinHealthCheck.cs
Normal file
54
src/Ombi.HealthChecks/Checks/JellyfinHealthCheck.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Ombi.Api.Jellyfin;
|
||||
using Ombi.Api.Jellyfin.Models;
|
||||
using Ombi.Api.Plex;
|
||||
using Ombi.Api.Plex.Models.Status;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.HealthChecks.Checks
|
||||
{
|
||||
public class JellyfinHealthCheck : BaseHealthCheck
|
||||
{
|
||||
public JellyfinHealthCheck(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory)
|
||||
{
|
||||
}
|
||||
public override async Task<HealthCheckResult> CheckHealthAsync(
|
||||
HealthCheckContext context,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using (var scope = CreateScope())
|
||||
{
|
||||
var settingsProvider = scope.ServiceProvider.GetRequiredService<ISettingsService<JellyfinSettings>>();
|
||||
var api = scope.ServiceProvider.GetRequiredService<IJellyfinApiFactory>();
|
||||
var settings = await settingsProvider.GetSettingsAsync();
|
||||
if (settings == null)
|
||||
{
|
||||
return HealthCheckResult.Healthy("Jellyfin is not configured.");
|
||||
}
|
||||
|
||||
var client = api.CreateClient(settings);
|
||||
var taskResult = new List<Task<JellyfinSystemInfo>>();
|
||||
foreach (var server in settings.Servers)
|
||||
{
|
||||
taskResult.Add(client.GetSystemInformation(server.ApiKey, server.FullUri));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await Task.WhenAll(taskResult.ToArray());
|
||||
return HealthCheckResult.Healthy();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return HealthCheckResult.Unhealthy("Could not communicate with Jellyfin", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ using Microsoft.Extensions.Diagnostics.HealthChecks;
|
|||
using Ombi.Api.CouchPotato;
|
||||
using Ombi.Api.Emby;
|
||||
using Ombi.Api.Emby.Models;
|
||||
using Ombi.Api.Jellyfin;
|
||||
using Ombi.Api.Jellyfin.Models;
|
||||
using Ombi.Api.Plex;
|
||||
using Ombi.Api.Plex.Models.Status;
|
||||
using Ombi.Api.SickRage;
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace Ombi.HealthChecks
|
|||
{
|
||||
builder.AddCheck<PlexHealthCheck>("Plex", tags: new string[] { "MediaServer" });
|
||||
builder.AddCheck<EmbyHealthCheck>("Emby", tags: new string[] { "MediaServer" });
|
||||
builder.AddCheck<JellyfinHealthCheck>("Jellyfin", tags: new string[] { "MediaServer" });
|
||||
builder.AddCheck<LidarrHealthCheck>("Lidarr", tags: new string[] { "DVR" });
|
||||
builder.AddCheck<SonarrHealthCheck>("Sonarr", tags: new string[] { "DVR" });
|
||||
builder.AddCheck<RadarrHealthCheck>("Radarr", tags: new string[] { "DVR" });
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ombi.Api.CouchPotato\Ombi.Api.CouchPotato.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Emby\Ombi.Api.Emby.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Jellyfin\Ombi.Api.Jellyfin.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Radarr\Ombi.Api.Radarr.csproj" />
|
||||
|
|
|
@ -15,13 +15,6 @@ namespace Ombi.Helpers.Tests
|
|||
return EmbyHelper.GetEmbyMediaUrl(mediaId, serverId, url);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(JellyfinUrlData))]
|
||||
public string TestJellyfinUrl(string mediaId, string url, string serverId)
|
||||
{
|
||||
// http://192.168.68.X:8097/web/index.html#!/details?id=7ffe222498445d5ebfddb31bc4fa9a6d&serverId=50cce67f0baa425093d189b3017331fb
|
||||
return EmbyHelper.GetEmbyMediaUrl(mediaId, serverId, url, true);
|
||||
}
|
||||
|
||||
public static IEnumerable<TestCaseData> UrlData
|
||||
{
|
||||
get
|
||||
|
@ -33,16 +26,5 @@ namespace Ombi.Helpers.Tests
|
|||
yield return new TestCaseData(mediaId.ToString(), string.Empty, "1").Returns($"https://app.emby.media/web/index.html#!/item?id={mediaId}&serverId=1").SetName("EmbyHelper_GetMediaUrl_WithOutCustomDomain");
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<TestCaseData> JellyfinUrlData
|
||||
{
|
||||
get
|
||||
{
|
||||
var mediaId = 1;
|
||||
yield return new TestCaseData(mediaId.ToString(), "http://google.com", "1").Returns($"http://google.com/web/index.html#!/details?id={mediaId}&serverId=1").SetName("EmbyHelperJellyfin_GetMediaUrl_WithCustomDomain_WithoutTrailingSlash");
|
||||
yield return new TestCaseData(mediaId.ToString(), "http://google.com/", "1").Returns($"http://google.com/web/index.html#!/details?id={mediaId}&serverId=1").SetName("EmbyHelperJellyfin_GetMediaUrl_WithCustomDomain");
|
||||
yield return new TestCaseData(mediaId.ToString(), "https://google.com/", "1").Returns($"https://google.com/web/index.html#!/details?id={mediaId}&serverId=1").SetName("EmbyHelperJellyfin_GetMediaUrl_WithCustomDomain_Https");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
29
src/Ombi.Helpers.Tests/JellyfinHelperTests.cs
Normal file
29
src/Ombi.Helpers.Tests/JellyfinHelperTests.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Ombi.Helpers.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class JellyfinHelperTests
|
||||
{
|
||||
[TestCaseSource(nameof(UrlData))]
|
||||
public string TestUrl(string mediaId, string url, string serverId)
|
||||
{
|
||||
// http://192.168.68.X:8097/web/index.html#!/details?id=7ffe222498445d5ebfddb31bc4fa9a6d&serverId=50cce67f0baa425093d189b3017331fb
|
||||
return JellyfinHelper.GetJellyfinMediaUrl(mediaId, serverId, url);
|
||||
}
|
||||
|
||||
public static IEnumerable<TestCaseData> UrlData
|
||||
{
|
||||
get
|
||||
{
|
||||
var mediaId = 1;
|
||||
yield return new TestCaseData(mediaId.ToString(), "http://google.com", "1").Returns($"http://google.com/web/index.html#!/details?id={mediaId}&serverId=1").SetName("JellyfinHelper_GetMediaUrl_WithCustomDomain_WithoutTrailingSlash");
|
||||
yield return new TestCaseData(mediaId.ToString(), "http://google.com/", "1").Returns($"http://google.com/web/index.html#!/details?id={mediaId}&serverId=1").SetName("JellyfinHelper_GetMediaUrl_WithCustomDomain");
|
||||
yield return new TestCaseData(mediaId.ToString(), "https://google.com/", "1").Returns($"https://google.com/web/index.html#!/details?id={mediaId}&serverId=1").SetName("JellyfinHelper_GetMediaUrl_WithCustomDomain_Https");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,14 +2,10 @@
|
|||
{
|
||||
public static class EmbyHelper
|
||||
{
|
||||
public static string GetEmbyMediaUrl(string mediaId, string serverId, string customerServerUrl = null, bool isJellyfin = false)
|
||||
public static string GetEmbyMediaUrl(string mediaId, string serverId, string customerServerUrl = null)
|
||||
{
|
||||
//web/index.html#!/details|item
|
||||
string path = "item";
|
||||
if (isJellyfin)
|
||||
{
|
||||
path = "details";
|
||||
}
|
||||
if (customerServerUrl.HasValue())
|
||||
{
|
||||
if (!customerServerUrl.EndsWith("/"))
|
||||
|
|
23
src/Ombi.Helpers/JellyfinHelper.cs
Normal file
23
src/Ombi.Helpers/JellyfinHelper.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
namespace Ombi.Helpers
|
||||
{
|
||||
public static class JellyfinHelper
|
||||
{
|
||||
public static string GetJellyfinMediaUrl(string mediaId, string serverId, string customerServerUrl = null)
|
||||
{
|
||||
//web/index.html#!/details|item
|
||||
string path = "details";
|
||||
if (customerServerUrl.HasValue())
|
||||
{
|
||||
if (!customerServerUrl.EndsWith("/"))
|
||||
{
|
||||
return $"{customerServerUrl}/web/index.html#!/{path}?id={mediaId}&serverId={serverId}";
|
||||
}
|
||||
return $"{customerServerUrl}web/index.html#!/{path}?id={mediaId}&serverId={serverId}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"http://localhost:8096/web/index.html#!/{path}?id={mediaId}&serverId={serverId}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,8 +14,10 @@ namespace Ombi.Helpers
|
|||
public static EventId RadarrCacher => new EventId(2001);
|
||||
public static EventId PlexEpisodeCacher => new EventId(2002);
|
||||
public static EventId EmbyContentCacher => new EventId(2003);
|
||||
public static EventId JellyfinContentCacher => new EventId(2012);
|
||||
public static EventId PlexUserImporter => new EventId(2004);
|
||||
public static EventId EmbyUserImporter => new EventId(2005);
|
||||
public static EventId JellyfinUserImporter => new EventId(2013);
|
||||
public static EventId SonarrCacher => new EventId(2006);
|
||||
public static EventId CouchPotatoCacher => new EventId(2007);
|
||||
public static EventId PlexContentCacher => new EventId(2008);
|
||||
|
@ -43,4 +45,4 @@ namespace Ombi.Helpers
|
|||
public static EventId Updater => new EventId(6000);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,5 +11,8 @@
|
|||
public string StoragePath { get; set; }
|
||||
|
||||
public string SecurityKey { get; set; }
|
||||
#if DEBUG
|
||||
= "test";
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -58,7 +58,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
|||
{
|
||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||
.SendAsync(NotificationHub.NotificationEvent, "Emby Content Sync Failed");
|
||||
_logger.LogError(e, "Exception when caching {1} for server {0}", server.Name, embySettings.IsJellyfin ? "Jellyfin" : "Emby");
|
||||
_logger.LogError(e, "Exception when caching Emby for server {0}", server.Name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,7 +145,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
|||
Title = tvShow.Name,
|
||||
Type = EmbyMediaType.Series,
|
||||
EmbyId = tvShow.Id,
|
||||
Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id, server?.ServerId, server.ServerHostname, settings.IsJellyfin),
|
||||
Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id, server?.ServerId, server.ServerHostname),
|
||||
AddedAt = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
@ -228,4 +228,4 @@ namespace Ombi.Schedule.Jobs.Emby
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
|||
Api = _apiFactory.CreateClient(settings);
|
||||
|
||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||
.SendAsync(NotificationHub.NotificationEvent, $"{(settings.IsJellyfin ? "Jellyfin" : "Emby")} User Importer Started");
|
||||
.SendAsync(NotificationHub.NotificationEvent, $"Emby User Importer Started");
|
||||
var allUsers = await _userManager.Users.Where(x => x.UserType == UserType.EmbyUser || x.UserType == UserType.EmbyConnectUser).ToListAsync();
|
||||
foreach (var server in settings.Servers)
|
||||
{
|
||||
|
@ -117,7 +117,8 @@ namespace Ombi.Schedule.Jobs.Emby
|
|||
ProviderUserId = embyUser.Id,
|
||||
Alias = isConnectUser ? embyUser.Name : string.Empty,
|
||||
MovieRequestLimit = userManagementSettings.MovieRequestLimit,
|
||||
EpisodeRequestLimit = userManagementSettings.EpisodeRequestLimit
|
||||
EpisodeRequestLimit = userManagementSettings.EpisodeRequestLimit,
|
||||
StreamingCountry = userManagementSettings.DefaultStreamingCountry
|
||||
};
|
||||
var result = await _userManager.CreateAsync(newUser);
|
||||
if (!result.Succeeded)
|
||||
|
@ -180,4 +181,4 @@ namespace Ombi.Schedule.Jobs.Emby
|
|||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Jellyfin
|
||||
{
|
||||
public interface IJellyfinAvaliabilityChecker : IBaseJob
|
||||
{
|
||||
}
|
||||
}
|
8
src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinContentSync.cs
Normal file
8
src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinContentSync.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Jellyfin
|
||||
{
|
||||
public interface IJellyfinContentSync : IBaseJob
|
||||
{
|
||||
}
|
||||
}
|
8
src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinEpisodeSync.cs
Normal file
8
src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinEpisodeSync.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Jellyfin
|
||||
{
|
||||
public interface IJellyfinEpisodeSync : IBaseJob
|
||||
{
|
||||
}
|
||||
}
|
8
src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinUserImporter.cs
Normal file
8
src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinUserImporter.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Jellyfin
|
||||
{
|
||||
public interface IJellyfinUserImporter : IBaseJob
|
||||
{
|
||||
}
|
||||
}
|
235
src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs
Normal file
235
src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs
Normal file
|
@ -0,0 +1,235 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2017 Jamie Rees
|
||||
// File: JellyfinAvaliabilityCheker.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Core;
|
||||
using Ombi.Core.Notifications;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Hubs;
|
||||
using Ombi.Notifications.Models;
|
||||
using Ombi.Schedule.Jobs.Ombi;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using Ombi.Store.Repository.Requests;
|
||||
using Quartz;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Jellyfin
|
||||
{
|
||||
public class JellyfinAvaliabilityChecker : IJellyfinAvaliabilityChecker
|
||||
{
|
||||
public JellyfinAvaliabilityChecker(IJellyfinContentRepository repo, ITvRequestRepository t, IMovieRequestRepository m,
|
||||
INotificationHelper n, ILogger<JellyfinAvaliabilityChecker> log, IHubContext<NotificationHub> notification)
|
||||
{
|
||||
_repo = repo;
|
||||
_tvRepo = t;
|
||||
_movieRepo = m;
|
||||
_notificationService = n;
|
||||
_log = log;
|
||||
_notification = notification;
|
||||
}
|
||||
|
||||
private readonly ITvRequestRepository _tvRepo;
|
||||
private readonly IMovieRequestRepository _movieRepo;
|
||||
private readonly IJellyfinContentRepository _repo;
|
||||
private readonly INotificationHelper _notificationService;
|
||||
private readonly ILogger<JellyfinAvaliabilityChecker> _log;
|
||||
private readonly IHubContext<NotificationHub> _notification;
|
||||
|
||||
public async Task Execute(IJobExecutionContext job)
|
||||
{
|
||||
_log.LogInformation("Starting Jellyfin Availability Check");
|
||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin Availability Checker Started");
|
||||
await ProcessMovies();
|
||||
await ProcessTv();
|
||||
|
||||
_log.LogInformation("Finished Jellyfin Availability Check");
|
||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin Availability Checker Finished");
|
||||
}
|
||||
|
||||
private async Task ProcessMovies()
|
||||
{
|
||||
var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available);
|
||||
|
||||
foreach (var movie in movies)
|
||||
{
|
||||
JellyfinContent jellyfinContent = null;
|
||||
if (movie.TheMovieDbId > 0)
|
||||
{
|
||||
jellyfinContent = await _repo.GetByTheMovieDbId(movie.TheMovieDbId.ToString());
|
||||
}
|
||||
else if(movie.ImdbId.HasValue())
|
||||
{
|
||||
jellyfinContent = await _repo.GetByImdbId(movie.ImdbId);
|
||||
}
|
||||
|
||||
if (jellyfinContent == null)
|
||||
{
|
||||
// We don't have this yet
|
||||
continue;
|
||||
}
|
||||
|
||||
_log.LogInformation("We have found the request {0} on Jellyfin, sending the notification", movie?.Title ?? string.Empty);
|
||||
|
||||
movie.Available = true;
|
||||
movie.MarkedAsAvailable = DateTime.Now;
|
||||
if (movie.Available)
|
||||
{
|
||||
var recipient = movie.RequestedUser.Email.HasValue() ? movie.RequestedUser.Email : string.Empty;
|
||||
|
||||
_log.LogDebug("MovieId: {0}, RequestUser: {1}", movie.Id, recipient);
|
||||
|
||||
await _notificationService.Notify(new NotificationOptions
|
||||
{
|
||||
DateTime = DateTime.Now,
|
||||
NotificationType = NotificationType.RequestAvailable,
|
||||
RequestId = movie.Id,
|
||||
RequestType = RequestType.Movie,
|
||||
Recipient = recipient,
|
||||
});
|
||||
}
|
||||
}
|
||||
await _movieRepo.Save();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// TODO This is EXCATLY the same as the PlexAvailabilityChecker. Refactor Please future Jamie
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task ProcessTv()
|
||||
{
|
||||
var tv = _tvRepo.GetChild().Where(x => !x.Available);
|
||||
var jellyfinEpisodes = _repo.GetAllEpisodes().Include(x => x.Series);
|
||||
|
||||
foreach (var child in tv)
|
||||
{
|
||||
|
||||
var useImdb = false;
|
||||
var useTvDb = false;
|
||||
if (child.ParentRequest.ImdbId.HasValue())
|
||||
{
|
||||
useImdb = true;
|
||||
}
|
||||
|
||||
if (child.ParentRequest.TvDbId.ToString().HasValue())
|
||||
{
|
||||
useTvDb = true;
|
||||
}
|
||||
|
||||
var tvDbId = child.ParentRequest.TvDbId;
|
||||
var imdbId = child.ParentRequest.ImdbId;
|
||||
IQueryable<JellyfinEpisode> seriesEpisodes = null;
|
||||
if (useImdb)
|
||||
{
|
||||
seriesEpisodes = jellyfinEpisodes.Where(x => x.Series.ImdbId == imdbId.ToString());
|
||||
}
|
||||
|
||||
if (useTvDb && (seriesEpisodes == null || !seriesEpisodes.Any()))
|
||||
{
|
||||
seriesEpisodes = jellyfinEpisodes.Where(x => x.Series.TvDbId == tvDbId.ToString());
|
||||
}
|
||||
|
||||
if (seriesEpisodes == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!seriesEpisodes.Any())
|
||||
{
|
||||
// Let's try and match the series by name
|
||||
seriesEpisodes = jellyfinEpisodes.Where(x =>
|
||||
x.Series.Title == child.Title);
|
||||
}
|
||||
|
||||
foreach (var season in child.SeasonRequests)
|
||||
{
|
||||
foreach (var episode in season.Episodes)
|
||||
{
|
||||
if (episode.Available)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var foundEp = await seriesEpisodes.FirstOrDefaultAsync(
|
||||
x => x.EpisodeNumber == episode.EpisodeNumber &&
|
||||
x.SeasonNumber == episode.Season.SeasonNumber);
|
||||
|
||||
if (foundEp != null)
|
||||
{
|
||||
episode.Available = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if all of the episodes in all seasons are available for this request
|
||||
var allAvailable = child.SeasonRequests.All(x => x.Episodes.All(c => c.Available));
|
||||
if (allAvailable)
|
||||
{
|
||||
// We have fulfulled this request!
|
||||
child.Available = true;
|
||||
child.MarkedAsAvailable = DateTime.Now;
|
||||
await _notificationService.Notify(new NotificationOptions
|
||||
{
|
||||
DateTime = DateTime.Now,
|
||||
NotificationType = NotificationType.RequestAvailable,
|
||||
RequestId = child.Id,
|
||||
RequestType = RequestType.TvShow,
|
||||
Recipient = child.RequestedUser.Email
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await _tvRepo.Save();
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
231
src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs
Normal file
231
src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs
Normal file
|
@ -0,0 +1,231 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Api.Jellyfin;
|
||||
using Ombi.Api.Jellyfin.Models.Movie;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Hubs;
|
||||
using Ombi.Schedule.Jobs.Ombi;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using Quartz;
|
||||
using JellyfinMediaType = Ombi.Store.Entities.JellyfinMediaType;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Jellyfin
|
||||
{
|
||||
public class JellyfinContentSync : IJellyfinContentSync
|
||||
{
|
||||
public JellyfinContentSync(ISettingsService<JellyfinSettings> settings, IJellyfinApiFactory api, ILogger<JellyfinContentSync> logger,
|
||||
IJellyfinContentRepository repo, IHubContext<NotificationHub> notification)
|
||||
{
|
||||
_logger = logger;
|
||||
_settings = settings;
|
||||
_apiFactory = api;
|
||||
_repo = repo;
|
||||
_notification = notification;
|
||||
}
|
||||
|
||||
private readonly ILogger<JellyfinContentSync> _logger;
|
||||
private readonly ISettingsService<JellyfinSettings> _settings;
|
||||
private readonly IJellyfinApiFactory _apiFactory;
|
||||
private readonly IJellyfinContentRepository _repo;
|
||||
private readonly IHubContext<NotificationHub> _notification;
|
||||
private IJellyfinApi Api { get; set; }
|
||||
|
||||
public async Task Execute(IJobExecutionContext job)
|
||||
{
|
||||
var jellyfinSettings = await _settings.GetSettingsAsync();
|
||||
if (!jellyfinSettings.Enable)
|
||||
return;
|
||||
|
||||
Api = _apiFactory.CreateClient(jellyfinSettings);
|
||||
|
||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin Content Sync Started");
|
||||
|
||||
foreach (var server in jellyfinSettings.Servers)
|
||||
{
|
||||
try
|
||||
{
|
||||
await StartServerCache(server, jellyfinSettings);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin Content Sync Failed");
|
||||
_logger.LogError(e, "Exception when caching Jellyfin for server {0}", server.Name);
|
||||
}
|
||||
}
|
||||
|
||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin Content Sync Finished");
|
||||
// Episodes
|
||||
|
||||
await OmbiQuartz.TriggerJob(nameof(IJellyfinEpisodeSync), "Jellyfin");
|
||||
}
|
||||
|
||||
|
||||
private async Task StartServerCache(JellyfinServers server, JellyfinSettings settings)
|
||||
{
|
||||
if (!ValidateSettings(server))
|
||||
return;
|
||||
|
||||
//await _repo.ExecuteSql("DELETE FROM JellyfinEpisode");
|
||||
//await _repo.ExecuteSql("DELETE FROM JellyfinContent");
|
||||
|
||||
var movies = await Api.GetAllMovies(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
|
||||
var totalCount = movies.TotalRecordCount;
|
||||
var processed = 1;
|
||||
|
||||
var mediaToAdd = new HashSet<JellyfinContent>();
|
||||
|
||||
while (processed < totalCount)
|
||||
{
|
||||
foreach (var movie in movies.Items)
|
||||
{
|
||||
if (movie.Type.Equals("boxset", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var movieInfo =
|
||||
await Api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri);
|
||||
foreach (var item in movieInfo.Items)
|
||||
{
|
||||
await ProcessMovies(item, mediaToAdd, server);
|
||||
}
|
||||
|
||||
processed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
processed++;
|
||||
// Regular movie
|
||||
await ProcessMovies(movie, mediaToAdd, server);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the next batch
|
||||
movies = await Api.GetAllMovies(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
|
||||
await _repo.AddRange(mediaToAdd);
|
||||
mediaToAdd.Clear();
|
||||
|
||||
}
|
||||
|
||||
|
||||
// TV Time
|
||||
var tv = await Api.GetAllShows(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
|
||||
var totalTv = tv.TotalRecordCount;
|
||||
processed = 1;
|
||||
while (processed < totalTv)
|
||||
{
|
||||
foreach (var tvShow in tv.Items)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
processed++;
|
||||
if (string.IsNullOrEmpty(tvShow.ProviderIds?.Tvdb))
|
||||
{
|
||||
_logger.LogInformation("Provider Id on tv {0} is null", tvShow.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
var existingTv = await _repo.GetByJellyfinId(tvShow.Id);
|
||||
if (existingTv == null)
|
||||
{
|
||||
_logger.LogDebug("Adding new TV Show {0}", tvShow.Name);
|
||||
mediaToAdd.Add(new JellyfinContent
|
||||
{
|
||||
TvDbId = tvShow.ProviderIds?.Tvdb,
|
||||
ImdbId = tvShow.ProviderIds?.Imdb,
|
||||
TheMovieDbId = tvShow.ProviderIds?.Tmdb,
|
||||
Title = tvShow.Name,
|
||||
Type = JellyfinMediaType.Series,
|
||||
JellyfinId = tvShow.Id,
|
||||
Url = JellyfinHelper.GetJellyfinMediaUrl(tvShow.Id, server?.ServerId, server.ServerHostname),
|
||||
AddedAt = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("We already have TV Show {0}", tvShow.Name);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
// Get the next batch
|
||||
tv = await Api.GetAllShows(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
|
||||
await _repo.AddRange(mediaToAdd);
|
||||
mediaToAdd.Clear();
|
||||
}
|
||||
|
||||
if (mediaToAdd.Any())
|
||||
await _repo.AddRange(mediaToAdd);
|
||||
}
|
||||
|
||||
private async Task ProcessMovies(JellyfinMovie movieInfo, ICollection<JellyfinContent> content, JellyfinServers server)
|
||||
{
|
||||
// Check if it exists
|
||||
var existingMovie = await _repo.GetByJellyfinId(movieInfo.Id);
|
||||
var alreadyGoingToAdd = content.Any(x => x.JellyfinId == movieInfo.Id);
|
||||
if (existingMovie == null && !alreadyGoingToAdd)
|
||||
{
|
||||
_logger.LogDebug("Adding new movie {0}", movieInfo.Name);
|
||||
content.Add(new JellyfinContent
|
||||
{
|
||||
ImdbId = movieInfo.ProviderIds.Imdb,
|
||||
TheMovieDbId = movieInfo.ProviderIds?.Tmdb,
|
||||
Title = movieInfo.Name,
|
||||
Type = JellyfinMediaType.Movie,
|
||||
JellyfinId = movieInfo.Id,
|
||||
Url = JellyfinHelper.GetJellyfinMediaUrl(movieInfo.Id, server?.ServerId, server.ServerHostname),
|
||||
AddedAt = DateTime.UtcNow,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// we have this
|
||||
_logger.LogDebug("We already have movie {0}", movieInfo.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateSettings(JellyfinServers server)
|
||||
{
|
||||
if (server?.Ip == null || string.IsNullOrEmpty(server?.ApiKey))
|
||||
{
|
||||
_logger.LogInformation(LoggingEvents.JellyfinContentCacher, $"Server {server?.Name} is not configured correctly");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
//_settings?.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
181
src/Ombi.Schedule/Jobs/Jellyfin/JellyfinEpisodeSync.cs
Normal file
181
src/Ombi.Schedule/Jobs/Jellyfin/JellyfinEpisodeSync.cs
Normal file
|
@ -0,0 +1,181 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2017 Jamie Rees
|
||||
// File: JellyfinEpisodeCacher.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Api.Jellyfin;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Hubs;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using Quartz;
|
||||
using Ombi.Schedule.Jobs.Ombi;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Jellyfin
|
||||
{
|
||||
public class JellyfinEpisodeSync : IJellyfinEpisodeSync
|
||||
{
|
||||
public JellyfinEpisodeSync(ISettingsService<JellyfinSettings> s, IJellyfinApiFactory api, ILogger<JellyfinEpisodeSync> l, IJellyfinContentRepository repo
|
||||
, IHubContext<NotificationHub> notification)
|
||||
{
|
||||
_apiFactory = api;
|
||||
_logger = l;
|
||||
_settings = s;
|
||||
_repo = repo;
|
||||
_notification = notification;
|
||||
}
|
||||
|
||||
private readonly ISettingsService<JellyfinSettings> _settings;
|
||||
private readonly IJellyfinApiFactory _apiFactory;
|
||||
private readonly ILogger<JellyfinEpisodeSync> _logger;
|
||||
private readonly IJellyfinContentRepository _repo;
|
||||
private readonly IHubContext<NotificationHub> _notification;
|
||||
private IJellyfinApi Api { get; set; }
|
||||
|
||||
|
||||
public async Task Execute(IJobExecutionContext job)
|
||||
{
|
||||
var settings = await _settings.GetSettingsAsync();
|
||||
|
||||
Api = _apiFactory.CreateClient(settings);
|
||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin Episode Sync Started");
|
||||
foreach (var server in settings.Servers)
|
||||
{
|
||||
await CacheEpisodes(server);
|
||||
}
|
||||
|
||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin Episode Sync Finished");
|
||||
_logger.LogInformation("Jellyfin Episode Sync Finished - Triggering Metadata refresh");
|
||||
await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System");
|
||||
}
|
||||
|
||||
private async Task CacheEpisodes(JellyfinServers server)
|
||||
{
|
||||
var allEpisodes = await Api.GetAllEpisodes(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
|
||||
var total = allEpisodes.TotalRecordCount;
|
||||
var processed = 1;
|
||||
var epToAdd = new HashSet<JellyfinEpisode>();
|
||||
while (processed < total)
|
||||
{
|
||||
foreach (var ep in allEpisodes.Items)
|
||||
{
|
||||
processed++;
|
||||
|
||||
if (ep.LocationType?.Equals("Virtual", StringComparison.InvariantCultureIgnoreCase) ?? false)
|
||||
{
|
||||
// For some reason Jellyfin is not respecting the `IsVirtualItem` field.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Let's make sure we have the parent request, stop those pesky forign key errors,
|
||||
// Damn me having data integrity
|
||||
var parent = await _repo.GetByJellyfinId(ep.SeriesId);
|
||||
if (parent == null)
|
||||
{
|
||||
_logger.LogInformation("The episode {0} does not relate to a series, so we cannot save this",
|
||||
ep.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
var existingEpisode = await _repo.GetEpisodeByJellyfinId(ep.Id);
|
||||
// Make sure it's not in the hashset too
|
||||
var existingInList = epToAdd.Any(x => x.JellyfinId == ep.Id);
|
||||
|
||||
if (existingEpisode == null && !existingInList)
|
||||
{
|
||||
_logger.LogDebug("Adding new episode {0} to parent {1}", ep.Name, ep.SeriesName);
|
||||
// add it
|
||||
epToAdd.Add(new JellyfinEpisode
|
||||
{
|
||||
JellyfinId = ep.Id,
|
||||
EpisodeNumber = ep.IndexNumber,
|
||||
SeasonNumber = ep.ParentIndexNumber,
|
||||
ParentId = ep.SeriesId,
|
||||
TvDbId = ep.ProviderIds.Tvdb,
|
||||
TheMovieDbId = ep.ProviderIds.Tmdb,
|
||||
ImdbId = ep.ProviderIds.Imdb,
|
||||
Title = ep.Name,
|
||||
AddedAt = DateTime.UtcNow
|
||||
});
|
||||
|
||||
if (ep.IndexNumberEnd.HasValue && ep.IndexNumberEnd.Value != ep.IndexNumber)
|
||||
{
|
||||
epToAdd.Add(new JellyfinEpisode
|
||||
{
|
||||
JellyfinId = ep.Id,
|
||||
EpisodeNumber = ep.IndexNumberEnd.Value,
|
||||
SeasonNumber = ep.ParentIndexNumber,
|
||||
ParentId = ep.SeriesId,
|
||||
TvDbId = ep.ProviderIds.Tvdb,
|
||||
TheMovieDbId = ep.ProviderIds.Tmdb,
|
||||
ImdbId = ep.ProviderIds.Imdb,
|
||||
Title = ep.Name,
|
||||
AddedAt = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _repo.AddRange(epToAdd);
|
||||
epToAdd.Clear();
|
||||
allEpisodes = await Api.GetAllEpisodes(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
|
||||
}
|
||||
|
||||
if (epToAdd.Any())
|
||||
{
|
||||
await _repo.AddRange(epToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
//_settings?.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
174
src/Ombi.Schedule/Jobs/Jellyfin/JellyfinUserImporter.cs
Normal file
174
src/Ombi.Schedule/Jobs/Jellyfin/JellyfinUserImporter.cs
Normal file
|
@ -0,0 +1,174 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2017 Jamie Rees
|
||||
// File: JellyfinUserImporter.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Api.Jellyfin;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Hubs;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Store.Entities;
|
||||
using Quartz;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Jellyfin
|
||||
{
|
||||
public class JellyfinUserImporter : IJellyfinUserImporter
|
||||
{
|
||||
public JellyfinUserImporter(IJellyfinApiFactory api, UserManager<OmbiUser> um, ILogger<JellyfinUserImporter> log,
|
||||
ISettingsService<JellyfinSettings> jellyfinSettings, ISettingsService<UserManagementSettings> ums, IHubContext<NotificationHub> notification)
|
||||
{
|
||||
_apiFactory = api;
|
||||
_userManager = um;
|
||||
_log = log;
|
||||
_jellyfinSettings = jellyfinSettings;
|
||||
_userManagementSettings = ums;
|
||||
_notification = notification;
|
||||
}
|
||||
|
||||
private readonly IJellyfinApiFactory _apiFactory;
|
||||
private readonly UserManager<OmbiUser> _userManager;
|
||||
private readonly ILogger<JellyfinUserImporter> _log;
|
||||
private readonly ISettingsService<JellyfinSettings> _jellyfinSettings;
|
||||
private readonly ISettingsService<UserManagementSettings> _userManagementSettings;
|
||||
private readonly IHubContext<NotificationHub> _notification;
|
||||
private IJellyfinApi Api { get; set; }
|
||||
|
||||
public async Task Execute(IJobExecutionContext job)
|
||||
{
|
||||
var userManagementSettings = await _userManagementSettings.GetSettingsAsync();
|
||||
if (!userManagementSettings.ImportJellyfinUsers)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var settings = await _jellyfinSettings.GetSettingsAsync();
|
||||
if (!settings.Enable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Api = _apiFactory.CreateClient(settings);
|
||||
|
||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||
.SendAsync(NotificationHub.NotificationEvent, $"Jellyfin User Importer Started");
|
||||
var allUsers = await _userManager.Users.Where(x => x.UserType == UserType.JellyfinUser).ToListAsync();
|
||||
foreach (var server in settings.Servers)
|
||||
{
|
||||
if (string.IsNullOrEmpty(server.ApiKey))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var jellyfinUsers = await Api.GetUsers(server.FullUri, server.ApiKey);
|
||||
foreach (var jellyfinUser in jellyfinUsers)
|
||||
{
|
||||
// Check if we should import this user
|
||||
if (userManagementSettings.BannedJellyfinUserIds.Contains(jellyfinUser.Id))
|
||||
{
|
||||
// Do not import these, they are not allowed into the country.
|
||||
continue;
|
||||
}
|
||||
// Check if this Jellyfin User already exists
|
||||
var existingJellyfinUser = allUsers.FirstOrDefault(x => x.ProviderUserId == jellyfinUser.Id);
|
||||
if (existingJellyfinUser == null)
|
||||
{
|
||||
|
||||
if (!jellyfinUser.Name.HasValue())
|
||||
{
|
||||
_log.LogInformation("Could not create Jellyfin user since the have no username, JellyfinUserId: {0}", jellyfinUser.Id);
|
||||
continue;
|
||||
}
|
||||
// Create this users
|
||||
var newUser = new OmbiUser
|
||||
{
|
||||
UserName = jellyfinUser.Name,
|
||||
UserType = UserType.JellyfinUser,
|
||||
ProviderUserId = jellyfinUser.Id,
|
||||
MovieRequestLimit = userManagementSettings.MovieRequestLimit,
|
||||
EpisodeRequestLimit = userManagementSettings.EpisodeRequestLimit,
|
||||
StreamingCountry = userManagementSettings.DefaultStreamingCountry
|
||||
};
|
||||
_log.LogInformation("Creating Jellyfin user {0}", newUser.UserName);
|
||||
var result = await _userManager.CreateAsync(newUser);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
foreach (var identityError in result.Errors)
|
||||
{
|
||||
_log.LogError(LoggingEvents.JellyfinUserImporter, identityError.Description);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (userManagementSettings.DefaultRoles.Any())
|
||||
{
|
||||
foreach (var defaultRole in userManagementSettings.DefaultRoles)
|
||||
{
|
||||
await _userManager.AddToRoleAsync(newUser, defaultRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do we need to update this user?
|
||||
existingJellyfinUser.UserName = jellyfinUser.Name;
|
||||
|
||||
await _userManager.UpdateAsync(existingJellyfinUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin User Importer Finished");
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_userManager?.Dispose();
|
||||
//_jellyfinSettings?.Dispose();
|
||||
//_userManagementSettings?.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ using Ombi.Core.Settings;
|
|||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Schedule.Jobs.Emby;
|
||||
using Ombi.Schedule.Jobs.Jellyfin;
|
||||
using Ombi.Schedule.Jobs.Plex.Interfaces;
|
||||
using Ombi.Store.Repository;
|
||||
using Quartz;
|
||||
|
@ -14,12 +15,13 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
public class MediaDatabaseRefresh : IMediaDatabaseRefresh
|
||||
{
|
||||
public MediaDatabaseRefresh(ISettingsService<PlexSettings> s, ILogger<MediaDatabaseRefresh> log,
|
||||
IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo)
|
||||
IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, IJellyfinContentRepository jellyfinRepo)
|
||||
{
|
||||
_settings = s;
|
||||
_log = log;
|
||||
_plexRepo = plexRepo;
|
||||
_embyRepo = embyRepo;
|
||||
_jellyfinRepo = jellyfinRepo;
|
||||
_settings.ClearCache();
|
||||
}
|
||||
|
||||
|
@ -27,6 +29,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
private readonly ILogger _log;
|
||||
private readonly IPlexContentRepository _plexRepo;
|
||||
private readonly IEmbyContentRepository _embyRepo;
|
||||
private readonly IJellyfinContentRepository _jellyfinRepo;
|
||||
|
||||
public async Task Execute(IJobExecutionContext job)
|
||||
{
|
||||
|
@ -34,6 +37,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
{
|
||||
await RemovePlexData();
|
||||
await RemoveEmbyData();
|
||||
await RemoveJellyfinData();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -64,6 +68,28 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
}
|
||||
}
|
||||
|
||||
private async Task RemoveJellyfinData()
|
||||
{
|
||||
try
|
||||
{
|
||||
var s = await _settings.GetSettingsAsync();
|
||||
if (!s.Enable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const string episodeSQL = "DELETE FROM JellyfinEpisode";
|
||||
const string mainSql = "DELETE FROM JellyfinContent";
|
||||
await _jellyfinRepo.ExecuteSql(episodeSQL);
|
||||
await _jellyfinRepo.ExecuteSql(mainSql);
|
||||
|
||||
await OmbiQuartz.TriggerJob(nameof(IJellyfinContentSync), "Jellyfin");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.LogError(LoggingEvents.MediaReferesh, e, "Refreshing Jellyfin Data Failed");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemovePlexData()
|
||||
{
|
||||
try
|
||||
|
@ -108,4 +134,4 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,16 +38,17 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
{
|
||||
public class NewsletterJob : HtmlTemplateGenerator, INewsletterJob
|
||||
{
|
||||
public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository<RecentlyAddedLog> addedLog,
|
||||
public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IJellyfinContentRepository jellyfin, IRepository<RecentlyAddedLog> addedLog,
|
||||
IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService<CustomizationSettings> custom,
|
||||
ISettingsService<EmailNotificationSettings> emailSettings, INotificationTemplatesRepository templateRepo,
|
||||
UserManager<OmbiUser> um, ISettingsService<NewsletterSettings> newsletter, ILogger<NewsletterJob> log,
|
||||
ILidarrApi lidarrApi, IExternalRepository<LidarrAlbumCache> albumCache, ISettingsService<LidarrSettings> lidarrSettings,
|
||||
ISettingsService<OmbiSettings> ombiSettings, ISettingsService<PlexSettings> plexSettings, ISettingsService<EmbySettings> embySettings
|
||||
, IHubContext<NotificationHub> notification, IRefreshMetadata refreshMetadata)
|
||||
ISettingsService<OmbiSettings> ombiSettings, ISettingsService<PlexSettings> plexSettings, ISettingsService<EmbySettings> embySettings, ISettingsService<JellyfinSettings> jellyfinSettings,
|
||||
IHubContext<NotificationHub> notification, IRefreshMetadata refreshMetadata)
|
||||
{
|
||||
_plex = plex;
|
||||
_emby = emby;
|
||||
_jellyfin = jellyfin;
|
||||
_recentlyAddedLog = addedLog;
|
||||
_movieApi = movieApi;
|
||||
_tvApi = tvApi;
|
||||
|
@ -64,6 +65,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
_ombiSettings = ombiSettings;
|
||||
_plexSettings = plexSettings;
|
||||
_embySettings = embySettings;
|
||||
_jellyfinSettings = jellyfinSettings;
|
||||
_notification = notification;
|
||||
_ombiSettings.ClearCache();
|
||||
_plexSettings.ClearCache();
|
||||
|
@ -74,6 +76,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
|
||||
private readonly IPlexContentRepository _plex;
|
||||
private readonly IEmbyContentRepository _emby;
|
||||
private readonly IJellyfinContentRepository _jellyfin;
|
||||
private readonly IRepository<RecentlyAddedLog> _recentlyAddedLog;
|
||||
private readonly IMovieDbApi _movieApi;
|
||||
private readonly ITvMazeApi _tvApi;
|
||||
|
@ -90,6 +93,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
|
||||
private readonly ISettingsService<PlexSettings> _plexSettings;
|
||||
private readonly ISettingsService<EmbySettings> _embySettings;
|
||||
private readonly ISettingsService<JellyfinSettings> _jellyfinSettings;
|
||||
private readonly IHubContext<NotificationHub> _notification;
|
||||
private readonly IRefreshMetadata _refreshMetadata;
|
||||
|
||||
|
@ -123,36 +127,46 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
// Get the Content
|
||||
var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking();
|
||||
var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking();
|
||||
var jellyfinContent = _jellyfin.GetAll().Include(x => x.Episodes).AsNoTracking();
|
||||
var lidarrContent = _lidarrAlbumRepository.GetAll().AsNoTracking().ToList().Where(x => x.FullyAvailable);
|
||||
|
||||
var addedLog = _recentlyAddedLog.GetAll();
|
||||
var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId).ToHashSet();
|
||||
var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId).ToHashSet();
|
||||
var addedJellyfinMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Jellyfin && x.ContentType == ContentType.Parent).Select(x => x.ContentId).ToHashSet();
|
||||
var addedAlbumLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Lidarr && x.ContentType == ContentType.Album).Select(x => x.AlbumId).ToHashSet();
|
||||
|
||||
var addedPlexEpisodesLogIds =
|
||||
addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode);
|
||||
var addedEmbyEpisodesLogIds =
|
||||
addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode);
|
||||
var addedJellyfinEpisodesLogIds =
|
||||
addedLog.Where(x => x.Type == RecentlyAddedType.Jellyfin && x.ContentType == ContentType.Episode);
|
||||
|
||||
|
||||
// Filter out the ones that we haven't sent yet
|
||||
var plexContentLocalDataset = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !string.IsNullOrEmpty(x.TheMovieDbId)).ToHashSet();
|
||||
var embyContentLocalDataset = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !string.IsNullOrEmpty(x.TheMovieDbId)).ToHashSet();
|
||||
var jellyfinContentLocalDataset = jellyfinContent.Where(x => x.Type == JellyfinMediaType.Movie && !string.IsNullOrEmpty(x.TheMovieDbId)).ToHashSet();
|
||||
var plexContentMoviesToSend = plexContentLocalDataset.Where(x => !addedPlexMovieLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId))).ToHashSet();
|
||||
var embyContentMoviesToSend = embyContentLocalDataset.Where(x => !addedEmbyMoviesLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId))).ToHashSet();
|
||||
var jellyfinContentMoviesToSend = jellyfinContentLocalDataset.Where(x => !addedJellyfinMoviesLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId))).ToHashSet();
|
||||
var lidarrContentAlbumsToSend = lidarrContent.Where(x => !addedAlbumLogIds.Contains(x.ForeignAlbumId)).ToHashSet();
|
||||
_log.LogInformation("Plex Movies to send: {0}", plexContentMoviesToSend.Count());
|
||||
_log.LogInformation("Emby Movies to send: {0}", embyContentMoviesToSend.Count());
|
||||
_log.LogInformation("Jellyfin Movies to send: {0}", jellyfinContentMoviesToSend.Count());
|
||||
_log.LogInformation("Albums to send: {0}", lidarrContentAlbumsToSend.Count());
|
||||
|
||||
// Find the movies that do not yet have MovieDbIds
|
||||
var needsMovieDbPlex = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !string.IsNullOrEmpty(x.TheMovieDbId)).ToHashSet();
|
||||
var needsMovieDbEmby = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !string.IsNullOrEmpty(x.TheMovieDbId)).ToHashSet();
|
||||
var needsMovieDbJellyfin = jellyfinContent.Where(x => x.Type == JellyfinMediaType.Movie && !string.IsNullOrEmpty(x.TheMovieDbId)).ToHashSet();
|
||||
var newPlexMovies = await GetMoviesWithoutId(addedPlexMovieLogIds, needsMovieDbPlex);
|
||||
var newEmbyMovies = await GetMoviesWithoutId(addedEmbyMoviesLogIds, needsMovieDbEmby);
|
||||
var newJellyfinMovies = await GetMoviesWithoutId(addedJellyfinMoviesLogIds, needsMovieDbJellyfin);
|
||||
plexContentMoviesToSend = plexContentMoviesToSend.Union(newPlexMovies).ToHashSet();
|
||||
embyContentMoviesToSend = embyContentMoviesToSend.Union(newEmbyMovies).ToHashSet();
|
||||
jellyfinContentMoviesToSend = jellyfinContentMoviesToSend.Union(newJellyfinMovies).ToHashSet();
|
||||
|
||||
plexContentMoviesToSend = plexContentMoviesToSend.DistinctBy(x => x.Id).ToHashSet();
|
||||
embyContentMoviesToSend = embyContentMoviesToSend.DistinctBy(x => x.Id).ToHashSet();
|
||||
|
@ -161,24 +175,30 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
FilterPlexEpisodes(_plex.GetAllEpisodes().Include(x => x.Series).AsNoTracking(), addedPlexEpisodesLogIds);
|
||||
var embyEpisodesToSend = FilterEmbyEpisodes(_emby.GetAllEpisodes().Include(x => x.Series).AsNoTracking(),
|
||||
addedEmbyEpisodesLogIds);
|
||||
var jellyfinEpisodesToSend = FilterJellyfinEpisodes(_jellyfin.GetAllEpisodes().Include(x => x.Series).AsNoTracking(),
|
||||
addedJellyfinEpisodesLogIds);
|
||||
|
||||
_log.LogInformation("Plex Episodes to send: {0}", plexEpisodesToSend.Count());
|
||||
_log.LogInformation("Emby Episodes to send: {0}", embyEpisodesToSend.Count());
|
||||
_log.LogInformation("Jellyfin Episodes to send: {0}", jellyfinEpisodesToSend.Count());
|
||||
var plexSettings = await _plexSettings.GetSettingsAsync();
|
||||
var embySettings = await _embySettings.GetSettingsAsync();
|
||||
var jellyfinSettings = await _jellyfinSettings.GetSettingsAsync();
|
||||
var body = string.Empty;
|
||||
if (test)
|
||||
{
|
||||
var plexm = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie).OrderByDescending(x => x.AddedAt).Take(10);
|
||||
var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie).OrderByDescending(x => x.AddedAt).Take(10);
|
||||
var jellyfinm = jellyfinContent.Where(x => x.Type == JellyfinMediaType.Movie).OrderByDescending(x => x.AddedAt).Take(10);
|
||||
var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10).ToHashSet();
|
||||
var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet();
|
||||
var jellyfint = _jellyfin.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet();
|
||||
var lidarr = lidarrContent.OrderByDescending(x => x.AddedAt).Take(10).ToHashSet();
|
||||
body = await BuildHtml(plexm, embym, plext, embyt, lidarr, settings, embySettings, plexSettings);
|
||||
body = await BuildHtml(plexm, embym, jellyfinm, plext, embyt, jellyfint, lidarr, settings, embySettings, jellyfinSettings, plexSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
body = await BuildHtml(plexContentMoviesToSend.AsQueryable(), embyContentMoviesToSend.AsQueryable(), plexEpisodesToSend, embyEpisodesToSend, lidarrContentAlbumsToSend, settings, embySettings, plexSettings);
|
||||
body = await BuildHtml(plexContentMoviesToSend.AsQueryable(), embyContentMoviesToSend.AsQueryable(), jellyfinContentMoviesToSend.AsQueryable(), plexEpisodesToSend, embyEpisodesToSend, jellyfinEpisodesToSend, lidarrContentAlbumsToSend, settings, embySettings, jellyfinSettings, plexSettings);
|
||||
if (body.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
|
@ -285,6 +305,34 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
SeasonNumber = p.SeasonNumber
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var e in jellyfinContentMoviesToSend)
|
||||
{
|
||||
if (e.Type == JellyfinMediaType.Movie)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Jellyfin,
|
||||
ContentType = ContentType.Parent,
|
||||
ContentId = StringHelper.IntParseLinq(e.TheMovieDbId),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var p in jellyfinEpisodesToSend)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Jellyfin,
|
||||
ContentType = ContentType.Episode,
|
||||
ContentId = StringHelper.IntParseLinq(p.Series.TvDbId),
|
||||
EpisodeNumber = p.EpisodeNumber,
|
||||
SeasonNumber = p.SeasonNumber
|
||||
});
|
||||
}
|
||||
|
||||
await _recentlyAddedLog.AddRange(recentlyAddedLog);
|
||||
}
|
||||
else
|
||||
|
@ -349,6 +397,20 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
return result.ToHashSet();
|
||||
}
|
||||
|
||||
private async Task<HashSet<JellyfinContent>> GetMoviesWithoutId(HashSet<int> addedMovieLogIds, HashSet<JellyfinContent> needsMovieDbJellyfin)
|
||||
{
|
||||
foreach (var movie in needsMovieDbJellyfin)
|
||||
{
|
||||
var id = await _refreshMetadata.GetTheMovieDbId(false, true, null, movie.ImdbId, movie.Title, true);
|
||||
movie.TheMovieDbId = id.ToString();
|
||||
}
|
||||
|
||||
var result = needsMovieDbJellyfin.Where(x => x.HasTheMovieDb && !addedMovieLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId)));
|
||||
await UpdateTheMovieDbId(result);
|
||||
// Filter them out now
|
||||
return result.ToHashSet();
|
||||
}
|
||||
|
||||
private async Task UpdateTheMovieDbId(IEnumerable<PlexServerContent> content)
|
||||
{
|
||||
foreach (var movie in content)
|
||||
|
@ -358,6 +420,10 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
continue;
|
||||
}
|
||||
var entity = await _plex.Find(movie.Id);
|
||||
if (entity == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
entity.TheMovieDbId = movie.TheMovieDbId;
|
||||
_plex.UpdateWithoutSave(entity);
|
||||
}
|
||||
|
@ -379,6 +445,21 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
await _plex.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async Task UpdateTheMovieDbId(IEnumerable<JellyfinContent> content)
|
||||
{
|
||||
foreach (var movie in content)
|
||||
{
|
||||
if (!movie.HasTheMovieDb)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var entity = await _jellyfin.Find(movie.Id);
|
||||
entity.TheMovieDbId = movie.TheMovieDbId;
|
||||
_jellyfin.UpdateWithoutSave(entity);
|
||||
}
|
||||
await _plex.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task Execute(IJobExecutionContext job)
|
||||
{
|
||||
var newsletterSettings = await _newsletterSettings.GetSettingsAsync();
|
||||
|
@ -419,6 +500,23 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
return itemsToReturn;
|
||||
}
|
||||
|
||||
private HashSet<JellyfinEpisode> FilterJellyfinEpisodes(IEnumerable<JellyfinEpisode> source, IQueryable<RecentlyAddedLog> recentlyAdded)
|
||||
{
|
||||
var itemsToReturn = new HashSet<JellyfinEpisode>();
|
||||
foreach (var ep in source.Where(x => x.Series.HasTvDb))
|
||||
{
|
||||
var tvDbId = StringHelper.IntParseLinq(ep.Series.TvDbId);
|
||||
if (recentlyAdded.Any(x => x.ContentId == tvDbId && x.EpisodeNumber == ep.EpisodeNumber && x.SeasonNumber == ep.SeasonNumber))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
itemsToReturn.Add(ep);
|
||||
}
|
||||
|
||||
return itemsToReturn;
|
||||
}
|
||||
|
||||
private NotificationMessageContent ParseTemplate(NotificationTemplates template, CustomizationSettings settings)
|
||||
{
|
||||
var resolver = new NotificationMessageResolver();
|
||||
|
@ -429,8 +527,8 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
return resolver.ParseMessage(template, curlys);
|
||||
}
|
||||
|
||||
private async Task<string> BuildHtml(IQueryable<PlexServerContent> plexContentToSend, IQueryable<EmbyContent> embyContentToSend,
|
||||
HashSet<PlexEpisode> plexEpisodes, HashSet<EmbyEpisode> embyEp, HashSet<LidarrAlbumCache> albums, NewsletterSettings settings, EmbySettings embySettings,
|
||||
private async Task<string> BuildHtml(IQueryable<PlexServerContent> plexContentToSend, IQueryable<EmbyContent> embyContentToSend, IQueryable<JellyfinContent> jellyfinContentToSend,
|
||||
HashSet<PlexEpisode> plexEpisodes, HashSet<EmbyEpisode> embyEp, HashSet<JellyfinEpisode> jellyfinEp, HashSet<LidarrAlbumCache> albums, NewsletterSettings settings, EmbySettings embySettings, JellyfinSettings jellyfinSettings,
|
||||
PlexSettings plexSettings)
|
||||
{
|
||||
var ombiSettings = await _ombiSettings.GetSettingsAsync();
|
||||
|
@ -438,6 +536,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
|
||||
var plexMovies = plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Movie);
|
||||
var embyMovies = embyContentToSend.Where(x => x.Type == EmbyMediaType.Movie);
|
||||
var jellyfinMovies = jellyfinContentToSend.Where(x => x.Type == JellyfinMediaType.Movie);
|
||||
if ((plexMovies.Any() || embyMovies.Any()) && !settings.DisableMovies)
|
||||
{
|
||||
sb.Append("<h1 style=\"text-align: center; max-width: 1042px;\">New Movies</h1><br /><br />");
|
||||
|
@ -457,6 +556,11 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
await ProcessEmbyMovies(embyMovies, sb, ombiSettings.DefaultLanguageCode, embySettings.Servers.FirstOrDefault()?.ServerHostname ?? string.Empty);
|
||||
}
|
||||
|
||||
if (jellyfinSettings.Enable)
|
||||
{
|
||||
await ProcessJellyfinMovies(jellyfinMovies, sb, ombiSettings.DefaultLanguageCode, jellyfinSettings.Servers.FirstOrDefault()?.ServerHostname ?? string.Empty);
|
||||
}
|
||||
|
||||
sb.Append("</tr>");
|
||||
sb.Append("</table>");
|
||||
sb.Append("</td>");
|
||||
|
@ -464,7 +568,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
sb.Append("</table>");
|
||||
}
|
||||
|
||||
if ((plexEpisodes.Any() || embyEp.Any()) && !settings.DisableTv)
|
||||
if ((plexEpisodes.Any() || embyEp.Any()) || jellyfinEp.Any() && !settings.DisableTv)
|
||||
{
|
||||
sb.Append("<br /><br /><h1 style=\"text-align: center; max-width: 1042px;\">New TV</h1><br /><br />");
|
||||
sb.Append(
|
||||
|
@ -483,6 +587,11 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
await ProcessEmbyTv(embyEp, sb, embySettings.Servers.FirstOrDefault()?.ServerHostname ?? string.Empty);
|
||||
}
|
||||
|
||||
if (jellyfinSettings.Enable)
|
||||
{
|
||||
await ProcessJellyfinTv(jellyfinEp, sb, jellyfinSettings.Servers.FirstOrDefault()?.ServerHostname ?? string.Empty);
|
||||
}
|
||||
|
||||
sb.Append("</tr>");
|
||||
sb.Append("</table>");
|
||||
sb.Append("</td>");
|
||||
|
@ -638,6 +747,59 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
}
|
||||
}
|
||||
|
||||
private async Task ProcessJellyfinMovies(IQueryable<JellyfinContent> embyContent, StringBuilder sb, string defaultLangaugeCode, string customUrl)
|
||||
{
|
||||
int count = 0;
|
||||
var ordered = embyContent.OrderByDescending(x => x.AddedAt);
|
||||
foreach (var content in ordered)
|
||||
{
|
||||
var theMovieDbId = content.TheMovieDbId;
|
||||
if (!content.TheMovieDbId.HasValue())
|
||||
{
|
||||
var imdbId = content.ImdbId;
|
||||
var findResult = await _movieApi.Find(imdbId, ExternalSource.imdb_id);
|
||||
var result = findResult.movie_results?.FirstOrDefault();
|
||||
if (result == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
theMovieDbId = result.id.ToString();
|
||||
}
|
||||
|
||||
var mediaurl = content.Url;
|
||||
if (customUrl.HasValue())
|
||||
{
|
||||
mediaurl = customUrl;
|
||||
}
|
||||
var info = await _movieApi.GetMovieInformationWithExtraInfo(StringHelper.IntParseLinq(theMovieDbId), defaultLangaugeCode);
|
||||
if (info == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
try
|
||||
{
|
||||
CreateMovieHtmlContent(sb, info, mediaurl);
|
||||
count += 1;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.LogError(e, "Error when processing Jellyfin Movies {0}", info.Title);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndLoopHtml(sb);
|
||||
}
|
||||
|
||||
if (count == 2)
|
||||
{
|
||||
count = 0;
|
||||
sb.Append("</tr>");
|
||||
sb.Append("<tr>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateMovieHtmlContent(StringBuilder sb, MovieResponseDto info, string mediaurl)
|
||||
{
|
||||
AddBackgroundInsideTable(sb, $"https://image.tmdb.org/t/p/w1280/{info.BackdropPath}");
|
||||
|
@ -981,6 +1143,129 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
}
|
||||
}
|
||||
|
||||
private async Task ProcessJellyfinTv(HashSet<JellyfinEpisode> embyContent, StringBuilder sb, string serverUrl)
|
||||
{
|
||||
var series = new List<JellyfinContent>();
|
||||
foreach (var episode in embyContent)
|
||||
{
|
||||
var alreadyAdded = series.FirstOrDefault(x => x.JellyfinId == episode.Series.JellyfinId);
|
||||
if (alreadyAdded != null)
|
||||
{
|
||||
alreadyAdded.Episodes.Add(episode);
|
||||
}
|
||||
else
|
||||
{
|
||||
episode.Series.Episodes = new List<JellyfinEpisode>
|
||||
{
|
||||
episode
|
||||
};
|
||||
series.Add(episode.Series);
|
||||
}
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
var orderedTv = series.OrderByDescending(x => x.AddedAt);
|
||||
foreach (var t in orderedTv)
|
||||
{
|
||||
if (!t.TvDbId.HasValue())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int.TryParse(t.TvDbId, out var tvdbId);
|
||||
var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId);
|
||||
if (info == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var banner = info.image?.original;
|
||||
if (!string.IsNullOrEmpty(banner))
|
||||
{
|
||||
banner = banner.ToHttpsUrl(); // Always use the Https banners
|
||||
}
|
||||
|
||||
var tvInfo = await _movieApi.GetTVInfo(t.TheMovieDbId);
|
||||
if (tvInfo != null && tvInfo.backdrop_path.HasValue())
|
||||
{
|
||||
|
||||
AddBackgroundInsideTable(sb, $"https://image.tmdb.org/t/p/w500{tvInfo.backdrop_path}");
|
||||
}
|
||||
else
|
||||
{
|
||||
AddBackgroundInsideTable(sb, $"https://image.tmdb.org/t/p/w1280/");
|
||||
}
|
||||
AddPosterInsideTable(sb, banner);
|
||||
AddMediaServerUrl(sb, serverUrl.HasValue() ? serverUrl : t.Url, banner);
|
||||
AddInfoTable(sb);
|
||||
|
||||
var title = "";
|
||||
if (!String.IsNullOrEmpty(info.premiered) && info.premiered.Length > 4)
|
||||
{
|
||||
title = $"{t.Title} ({info.premiered.Remove(4)})";
|
||||
}
|
||||
else
|
||||
{
|
||||
title = $"{t.Title}";
|
||||
}
|
||||
AddTitle(sb, $"https://www.imdb.com/title/{info.externals.imdb}/", title);
|
||||
|
||||
// Group by the season number
|
||||
var results = t.Episodes?.GroupBy(p => p.SeasonNumber,
|
||||
(key, g) => new
|
||||
{
|
||||
SeasonNumber = key,
|
||||
Episodes = g.ToList(),
|
||||
EpisodeAirDate = tvInfo?.seasons?.Where(x => x.season_number == key)?.Select(x => x.air_date).FirstOrDefault()
|
||||
}
|
||||
);
|
||||
|
||||
// Group the episodes
|
||||
var finalsb = new StringBuilder();
|
||||
foreach (var epInformation in results.OrderBy(x => x.SeasonNumber))
|
||||
{
|
||||
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
||||
var episodeString = StringHelper.BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber));
|
||||
var episodeAirDate = epInformation.EpisodeAirDate;
|
||||
finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString} {episodeAirDate}");
|
||||
finalsb.Append("<br />");
|
||||
}
|
||||
|
||||
var summary = info.summary;
|
||||
if (summary.Length > 280)
|
||||
{
|
||||
summary = summary.Remove(280);
|
||||
summary = summary + "...</p>";
|
||||
}
|
||||
AddTvParagraph(sb, finalsb.ToString(), summary);
|
||||
|
||||
if (info.genres.Any())
|
||||
{
|
||||
AddGenres(sb, $"Genres: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.LogError(e, "Error when processing Jellyfin TV {0}", t.Title);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndLoopHtml(sb);
|
||||
count += 1;
|
||||
}
|
||||
|
||||
if (count == 2)
|
||||
{
|
||||
count = 0;
|
||||
sb.Append("</tr>");
|
||||
sb.Append("<tr>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EndLoopHtml(StringBuilder sb)
|
||||
{
|
||||
//NOTE: BR have to be in TD's as per html spec or it will be put outside of the table...
|
||||
|
@ -1040,4 +1325,4 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ using Microsoft.AspNetCore.SignalR;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Api.Emby;
|
||||
using Ombi.Api.Jellyfin;
|
||||
using Ombi.Api.TheMovieDb;
|
||||
using Ombi.Api.TheMovieDb.Models;
|
||||
using Ombi.Api.TvMaze;
|
||||
|
@ -14,6 +15,7 @@ using Ombi.Core.Settings.Models.External;
|
|||
using Ombi.Helpers;
|
||||
using Ombi.Hubs;
|
||||
using Ombi.Schedule.Jobs.Emby;
|
||||
using Ombi.Schedule.Jobs.Jellyfin;
|
||||
using Ombi.Schedule.Jobs.Plex;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
|
@ -23,31 +25,41 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
{
|
||||
public class RefreshMetadata : IRefreshMetadata
|
||||
{
|
||||
public RefreshMetadata(IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo,
|
||||
public RefreshMetadata(IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, IJellyfinContentRepository jellyfinRepo,
|
||||
ILogger<RefreshMetadata> log, ITvMazeApi tvApi, ISettingsService<PlexSettings> plexSettings,
|
||||
IMovieDbApi movieApi, ISettingsService<EmbySettings> embySettings, IEmbyApiFactory embyApi, IHubContext<NotificationHub> notification)
|
||||
IMovieDbApi movieApi,
|
||||
ISettingsService<EmbySettings> embySettings, IEmbyApiFactory embyApi,
|
||||
ISettingsService<JellyfinSettings> jellyfinSettings, IJellyfinApiFactory jellyfinApi,
|
||||
IHubContext<NotificationHub> notification)
|
||||
{
|
||||
_plexRepo = plexRepo;
|
||||
_embyRepo = embyRepo;
|
||||
_jellyfinRepo = jellyfinRepo;
|
||||
_log = log;
|
||||
_movieApi = movieApi;
|
||||
_tvApi = tvApi;
|
||||
_plexSettings = plexSettings;
|
||||
_embySettings = embySettings;
|
||||
_embyApiFactory = embyApi;
|
||||
_jellyfinSettings = jellyfinSettings;
|
||||
_jellyfinApiFactory = jellyfinApi;
|
||||
_notification = notification;
|
||||
}
|
||||
|
||||
private readonly IPlexContentRepository _plexRepo;
|
||||
private readonly IEmbyContentRepository _embyRepo;
|
||||
private readonly IJellyfinContentRepository _jellyfinRepo;
|
||||
private readonly ILogger _log;
|
||||
private readonly IMovieDbApi _movieApi;
|
||||
private readonly ITvMazeApi _tvApi;
|
||||
private readonly ISettingsService<PlexSettings> _plexSettings;
|
||||
private readonly ISettingsService<EmbySettings> _embySettings;
|
||||
private readonly ISettingsService<JellyfinSettings> _jellyfinSettings;
|
||||
private readonly IEmbyApiFactory _embyApiFactory;
|
||||
private readonly IJellyfinApiFactory _jellyfinApiFactory;
|
||||
private readonly IHubContext<NotificationHub> _notification;
|
||||
private IEmbyApi EmbyApi { get; set; }
|
||||
private IJellyfinApi JellyfinApi { get; set; }
|
||||
|
||||
public async Task Execute(IJobExecutionContext job)
|
||||
{
|
||||
|
@ -72,6 +84,14 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
|
||||
await OmbiQuartz.TriggerJob(nameof(IEmbyAvaliabilityChecker), "Emby");
|
||||
}
|
||||
|
||||
var jellyfinSettings = await _jellyfinSettings.GetSettingsAsync();
|
||||
if (jellyfinSettings.Enable)
|
||||
{
|
||||
await StartJellyfin(jellyfinSettings);
|
||||
|
||||
await OmbiQuartz.TriggerJob(nameof(IJellyfinAvaliabilityChecker), "Jellyfin");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -107,6 +127,13 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
await StartEmbyTv();
|
||||
}
|
||||
|
||||
private async Task StartJellyfin(JellyfinSettings s)
|
||||
{
|
||||
JellyfinApi = _jellyfinApiFactory.CreateClient(s);
|
||||
await StartJellyfinMovies(s);
|
||||
await StartJellyfinTv();
|
||||
}
|
||||
|
||||
private async Task StartPlexTv(List<PlexServerContent> allTv)
|
||||
{
|
||||
foreach (var show in allTv)
|
||||
|
@ -178,6 +205,41 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
}
|
||||
}
|
||||
|
||||
private async Task StartJellyfinTv()
|
||||
{
|
||||
var allTv = await _jellyfinRepo.GetAll().Where(x =>
|
||||
x.Type == JellyfinMediaType.Series && (x.TheMovieDbId == null || x.ImdbId == null || x.TvDbId == null)).ToListAsync();
|
||||
|
||||
foreach (var show in allTv)
|
||||
{
|
||||
var hasImdb = show.ImdbId.HasValue();
|
||||
var hasTheMovieDb = show.TheMovieDbId.HasValue();
|
||||
var hasTvDbId = show.TvDbId.HasValue();
|
||||
|
||||
if (!hasTheMovieDb)
|
||||
{
|
||||
var id = await GetTheMovieDbId(hasTvDbId, hasImdb, show.TvDbId, show.ImdbId, show.Title, false);
|
||||
show.TheMovieDbId = id;
|
||||
}
|
||||
|
||||
if (!hasImdb)
|
||||
{
|
||||
var id = await GetImdbId(hasTheMovieDb, hasTvDbId, show.Title, show.TheMovieDbId, show.TvDbId, RequestType.TvShow);
|
||||
show.ImdbId = id;
|
||||
_jellyfinRepo.UpdateWithoutSave(show);
|
||||
}
|
||||
|
||||
if (!hasTvDbId)
|
||||
{
|
||||
var id = await GetTvDbId(hasTheMovieDb, hasImdb, show.TheMovieDbId, show.ImdbId, show.Title);
|
||||
show.TvDbId = id;
|
||||
_jellyfinRepo.UpdateWithoutSave(show);
|
||||
}
|
||||
|
||||
await _jellyfinRepo.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartPlexMovies(List<PlexServerContent> allMovies)
|
||||
{
|
||||
foreach (var movie in allMovies)
|
||||
|
@ -263,6 +325,61 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
}
|
||||
}
|
||||
|
||||
private async Task StartJellyfinMovies(JellyfinSettings settings)
|
||||
{
|
||||
var allMovies = await _jellyfinRepo.GetAll().Where(x =>
|
||||
x.Type == JellyfinMediaType.Movie && (x.TheMovieDbId == null || x.ImdbId == null)).ToListAsync();
|
||||
foreach (var movie in allMovies)
|
||||
{
|
||||
movie.ImdbId.HasValue();
|
||||
movie.TheMovieDbId.HasValue();
|
||||
// Movies don't really use TheTvDb
|
||||
|
||||
// Check if it even has 1 ID
|
||||
if (!movie.HasImdb && !movie.HasTheMovieDb)
|
||||
{
|
||||
// Ok this sucks,
|
||||
// The only think I can think that has happened is that we scanned Jellyfin before Jellyfin has got the metadata
|
||||
// So let's recheck jellyfin to see if they have got the metadata now
|
||||
//
|
||||
// Yeah your right that does suck - Future Jamie
|
||||
_log.LogInformation($"Movie {movie.Title} does not have a ImdbId or TheMovieDbId, so rechecking jellyfin");
|
||||
foreach (var server in settings.Servers)
|
||||
{
|
||||
_log.LogInformation($"Checking server {server.Name} for upto date metadata");
|
||||
var movieInfo = await JellyfinApi.GetMovieInformation(movie.JellyfinId, server.ApiKey, server.AdministratorId,
|
||||
server.FullUri);
|
||||
|
||||
if (movieInfo.ProviderIds?.Imdb.HasValue() ?? false)
|
||||
{
|
||||
movie.ImdbId = movieInfo.ProviderIds.Imdb;
|
||||
}
|
||||
|
||||
if (movieInfo.ProviderIds?.Tmdb.HasValue() ?? false)
|
||||
{
|
||||
movie.TheMovieDbId = movieInfo.ProviderIds.Tmdb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!movie.HasImdb)
|
||||
{
|
||||
var imdbId = await GetImdbId(movie.HasTheMovieDb, false, movie.Title, movie.TheMovieDbId, string.Empty, RequestType.Movie);
|
||||
movie.ImdbId = imdbId;
|
||||
_jellyfinRepo.UpdateWithoutSave(movie);
|
||||
}
|
||||
if (!movie.HasTheMovieDb)
|
||||
{
|
||||
var id = await GetTheMovieDbId(false, movie.HasImdb, string.Empty, movie.ImdbId, movie.Title, true);
|
||||
movie.TheMovieDbId = id;
|
||||
_jellyfinRepo.UpdateWithoutSave(movie);
|
||||
}
|
||||
|
||||
await _jellyfinRepo.SaveChangesAsync();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetTheMovieDbId(bool hasTvDbId, bool hasImdb, string tvdbID, string imdbId, string title, bool movie)
|
||||
{
|
||||
_log.LogInformation("The Media item {0} does not have a TheMovieDbId, searching for TheMovieDbId", title);
|
||||
|
@ -392,4 +509,4 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,6 +88,12 @@ namespace Ombi.Schedule.Jobs.Plex
|
|||
_log.LogInformation("Could not create Plex user since the have no username, PlexUserId: {0}", plexUser.Id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((plexUser.Email.HasValue()) && await _userManager.FindByEmailAsync(plexUser.Email) != null)
|
||||
{
|
||||
_log.LogWarning($"Cannot add user {plexUser.Username} because their email address is already in Ombi, skipping this user");
|
||||
continue;
|
||||
}
|
||||
// Create this users
|
||||
// We do not store a password against the user since they will authenticate via Plex
|
||||
var newUser = new OmbiUser
|
||||
|
@ -98,7 +104,8 @@ namespace Ombi.Schedule.Jobs.Plex
|
|||
Email = plexUser?.Email ?? string.Empty,
|
||||
Alias = string.Empty,
|
||||
MovieRequestLimit = userManagementSettings.MovieRequestLimit,
|
||||
EpisodeRequestLimit = userManagementSettings.EpisodeRequestLimit
|
||||
EpisodeRequestLimit = userManagementSettings.EpisodeRequestLimit,
|
||||
StreamingCountry = userManagementSettings.DefaultStreamingCountry
|
||||
};
|
||||
_log.LogInformation("Creating Plex user {0}", newUser.UserName);
|
||||
var result = await _userManager.CreateAsync(newUser);
|
||||
|
@ -161,7 +168,8 @@ namespace Ombi.Schedule.Jobs.Plex
|
|||
UserName = plexAdmin.username ?? plexAdmin.id,
|
||||
ProviderUserId = plexAdmin.id,
|
||||
Email = plexAdmin.email ?? string.Empty,
|
||||
Alias = string.Empty
|
||||
Alias = string.Empty,
|
||||
StreamingCountry = settings.DefaultStreamingCountry
|
||||
};
|
||||
|
||||
var result = await _userManager.CreateAsync(newUser);
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ombi.Api.CouchPotato\Ombi.Api.CouchPotato.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Emby\Ombi.Api.Emby.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Jellyfin\Ombi.Api.Jellyfin.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Radarr\Ombi.Api.Radarr.csproj" />
|
||||
|
@ -37,4 +38,4 @@
|
|||
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -7,6 +7,7 @@ using Ombi.Helpers;
|
|||
using Ombi.Schedule.Jobs;
|
||||
using Ombi.Schedule.Jobs.Couchpotato;
|
||||
using Ombi.Schedule.Jobs.Emby;
|
||||
using Ombi.Schedule.Jobs.Jellyfin;
|
||||
using Ombi.Schedule.Jobs.Lidarr;
|
||||
using Ombi.Schedule.Jobs.Ombi;
|
||||
using Ombi.Schedule.Jobs.Plex;
|
||||
|
@ -51,6 +52,7 @@ namespace Ombi.Schedule
|
|||
// Run configuration
|
||||
await AddPlex(s);
|
||||
await AddEmby(s);
|
||||
await AddJellyfin(s);
|
||||
await AddDvrApps(s);
|
||||
await AddSystem(s);
|
||||
await AddNotifications(s);
|
||||
|
@ -98,9 +100,18 @@ namespace Ombi.Schedule
|
|||
await OmbiQuartz.Instance.AddJob<IEmbyAvaliabilityChecker>(nameof(IEmbyAvaliabilityChecker), "Emby", null);
|
||||
await OmbiQuartz.Instance.AddJob<IEmbyUserImporter>(nameof(IEmbyUserImporter), "Emby", JobSettingsHelper.UserImporter(s));
|
||||
}
|
||||
|
||||
private static async Task AddJellyfin(JobSettings s)
|
||||
{
|
||||
await OmbiQuartz.Instance.AddJob<IJellyfinContentSync>(nameof(IJellyfinContentSync), "Jellyfin", JobSettingsHelper.JellyfinContent(s));
|
||||
await OmbiQuartz.Instance.AddJob<IJellyfinEpisodeSync>(nameof(IJellyfinEpisodeSync), "Jellyfin", null);
|
||||
await OmbiQuartz.Instance.AddJob<IJellyfinAvaliabilityChecker>(nameof(IJellyfinAvaliabilityChecker), "Jellyfin", null);
|
||||
await OmbiQuartz.Instance.AddJob<IJellyfinUserImporter>(nameof(IJellyfinUserImporter), "Jellyfin", JobSettingsHelper.UserImporter(s));
|
||||
}
|
||||
|
||||
private static async Task AddNotifications(JobSettings s)
|
||||
{
|
||||
await OmbiQuartz.Instance.AddJob<INotificationService>(nameof(INotificationService), "Notifications", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ namespace Ombi.Core.Settings.Models.External
|
|||
public sealed class EmbySettings : Ombi.Settings.Settings.Models.Settings
|
||||
{
|
||||
public bool Enable { get; set; }
|
||||
public bool IsJellyfin { get; set; }
|
||||
public List<EmbyServers> Servers { get; set; } = new List<EmbyServers>();
|
||||
}
|
||||
|
||||
|
@ -19,4 +18,4 @@ namespace Ombi.Core.Settings.Models.External
|
|||
public string ServerHostname { get; set; }
|
||||
public bool EnableEpisodeSearching { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
21
src/Ombi.Settings/Settings/Models/External/JellyfinSettings.cs
vendored
Normal file
21
src/Ombi.Settings/Settings/Models/External/JellyfinSettings.cs
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System.Collections.Generic;
|
||||
using Ombi.Settings.Settings.Models.External;
|
||||
|
||||
namespace Ombi.Core.Settings.Models.External
|
||||
{
|
||||
public sealed class JellyfinSettings : Ombi.Settings.Settings.Models.Settings
|
||||
{
|
||||
public bool Enable { get; set; }
|
||||
public List<JellyfinServers> Servers { get; set; } = new List<JellyfinServers>();
|
||||
}
|
||||
|
||||
public class JellyfinServers : ExternalSettings
|
||||
{
|
||||
public string ServerId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string ApiKey { get; set; }
|
||||
public string AdministratorId { get; set; }
|
||||
public string ServerHostname { get; set; }
|
||||
public bool EnableEpisodeSearching { get; set; }
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue