mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-30 11:38:32 -07:00
Add separate Jellyfin server type
Due to forthcoming changes to the Jellyfin API, this adds support for Jellyfin as server type completely independent from Emby. It also undoes the workarounds that treated Jellyfin as a subset of Emby.
This commit is contained in:
parent
ce39f205f3
commit
9fe644f3db
116 changed files with 3672 additions and 103 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,38 +13,43 @@ 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;
|
||||
|
||||
public IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies(DateTime from, DateTime to)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -52,14 +57,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)
|
||||
{
|
||||
|
@ -136,17 +143,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)
|
||||
{
|
||||
|
@ -156,11 +202,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;
|
||||
}
|
||||
|
@ -181,6 +228,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)
|
||||
|
@ -244,5 +307,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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace Ombi.Core.Models
|
|||
public enum RecentlyAddedType
|
||||
{
|
||||
Plex,
|
||||
Emby
|
||||
Emby,
|
||||
Jellyfin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace Ombi.Core.Models
|
|||
{
|
||||
LocalUser = 1,
|
||||
PlexUser = 2,
|
||||
EmbyUser = 3
|
||||
EmbyUser = 3,
|
||||
JellyfinUser = 4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
103
src/Ombi.Core/Rule/Rules/Search/JellyfinAvailabilityRule.cs
Normal file
103
src/Ombi.Core/Rule/Rules/Search/JellyfinAvailabilityRule.cs
Normal file
|
@ -0,0 +1,103 @@
|
|||
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, s.IsJellyfin);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj.JellyfinUrl = JellyfinHelper.GetJellyfinMediaUrl(item.JellyfinId, server?.ServerId, null, s.IsJellyfin);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue