Skip to content

Commit

Permalink
Jellyfin 10.9 compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
shemanaev committed May 13, 2024
1 parent 580500f commit b00f4ad
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 176 deletions.
3 changes: 2 additions & 1 deletion Jellyfin.Webhooks/Api/ConfigurationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
using System.Linq;
using System.Net.Mime;
using Jellyfin.Webhooks.Configuration;
using MediaBrowser.Common.Api;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace Jellyfin.Webhooks.Api
{
[ApiController]
[Authorize(Policy = "RequiresElevation")]
[Authorize(Policy = Policies.RequiresElevation)]
[Route("Webhooks")]
[Produces(MediaTypeNames.Application.Json)]
public class ConfigurationController : ControllerBase
Expand Down
213 changes: 62 additions & 151 deletions Jellyfin.Webhooks/EntryPoint.cs
Original file line number Diff line number Diff line change
@@ -1,104 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Events;
using Jellyfin.Webhooks.Configuration;
using Jellyfin.Webhooks.Dto;
using Jellyfin.Webhooks.Formats;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Hosting;

namespace Jellyfin.Webhooks
{
public class EntryPoint : IServerEntryPoint
public class EntryPoint(
ISessionManager sessionManager,
IUserDataManager userDataManager,
IUserManager userManager,
ILibraryManager libraryManager,
ISubtitleManager subtitleManager,
IServerApplicationHost appHost,
ISender _sender
) : IHostedService
{
private readonly ILogger _logger;
private readonly ISessionManager _sessionManager;
private readonly IUserDataManager _userDataManager;
private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager;
private readonly ISubtitleManager _subtitleManager;
private readonly IServerApplicationHost _appHost;
private readonly List<Guid> _scrobbled;
private readonly Dictionary<string, DeviceState> _deviceStates;
private readonly FormatFactory _formatFactory;

public EntryPoint(
ILoggerFactory logger,
ISessionManager sessionManager,
IUserDataManager userDataManager,
IUserManager userManager,
ILibraryManager libraryManager,
ISubtitleManager subtitleManager,
IDtoService dtoService,
IServerApplicationHost appHost,
IHttpClientFactory httpClientFactory
)
{
_logger = logger.CreateLogger("Webhooks");
_sessionManager = sessionManager;
_userDataManager = userDataManager;
_userManager = userManager;
_libraryManager = libraryManager;
_subtitleManager = subtitleManager;
_appHost = appHost;

_scrobbled = new List<Guid>();
_deviceStates = new Dictionary<string, DeviceState>();
_formatFactory = new FormatFactory(httpClientFactory, dtoService, _userManager);
}
private readonly List<Guid> _scrobbled = [];
private readonly Dictionary<string, DeviceState> _deviceStates = [];

public void Dispose()
public Task StopAsync(CancellationToken cancellationToken)
{
_sessionManager.PlaybackStart -= OnPlaybackStart;
_sessionManager.PlaybackStopped -= OnPlaybackStopped;
_sessionManager.PlaybackProgress -= OnPlaybackProgress;
_sessionManager.AuthenticationFailed -= OnAuthenticationFailed;
_sessionManager.AuthenticationSucceeded -= OnAuthenticationSucceeded;
_sessionManager.SessionStarted -= OnSessionStarted;
_sessionManager.SessionEnded -= OnSessionEnded;
sessionManager.PlaybackStart -= OnPlaybackStart;
sessionManager.PlaybackStopped -= OnPlaybackStopped;
sessionManager.PlaybackProgress -= OnPlaybackProgress;
sessionManager.SessionStarted -= OnSessionStarted;
sessionManager.SessionEnded -= OnSessionEnded;

userDataManager.UserDataSaved -= OnUserDataSaved;

_userDataManager.UserDataSaved -= OnUserDataSaved;
libraryManager.ItemAdded -= OnItemAdded;
libraryManager.ItemRemoved -= OnItemRemoved;
libraryManager.ItemUpdated -= OnItemUpdated;

_libraryManager.ItemAdded -= OnItemAdded;
_libraryManager.ItemRemoved -= OnItemRemoved;
_libraryManager.ItemUpdated -= OnItemUpdated;
subtitleManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;

_subtitleManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;
appHost.HasPendingRestartChanged -= HasPendingRestartChanged;

_appHost.HasPendingRestartChanged -= HasPendingRestartChanged;
return Task.CompletedTask;
}

public Task RunAsync()
public Task StartAsync(CancellationToken cancellationToken)
{
_sessionManager.PlaybackStart += OnPlaybackStart;
_sessionManager.PlaybackStopped += OnPlaybackStopped;
_sessionManager.PlaybackProgress += OnPlaybackProgress;
_sessionManager.AuthenticationFailed += OnAuthenticationFailed;
_sessionManager.AuthenticationSucceeded += OnAuthenticationSucceeded;
_sessionManager.SessionStarted += OnSessionStarted;
_sessionManager.SessionEnded += OnSessionEnded;
sessionManager.PlaybackStart += OnPlaybackStart;
sessionManager.PlaybackStopped += OnPlaybackStopped;
sessionManager.PlaybackProgress += OnPlaybackProgress;
sessionManager.SessionStarted += OnSessionStarted;
sessionManager.SessionEnded += OnSessionEnded;

_userDataManager.UserDataSaved += OnUserDataSaved;
userDataManager.UserDataSaved += OnUserDataSaved;

_libraryManager.ItemAdded += OnItemAdded;
_libraryManager.ItemRemoved += OnItemRemoved;
_libraryManager.ItemUpdated += OnItemUpdated;
libraryManager.ItemAdded += OnItemAdded;
libraryManager.ItemRemoved += OnItemRemoved;
libraryManager.ItemUpdated += OnItemUpdated;

_subtitleManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;
subtitleManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;

_appHost.HasPendingRestartChanged += HasPendingRestartChanged;
appHost.HasPendingRestartChanged += HasPendingRestartChanged;

return Task.CompletedTask;
}
Expand Down Expand Up @@ -165,7 +132,7 @@ private async void OnUserDataSaved(object sender, UserDataSaveEventArgs e)
return;
}

var user = _userManager.GetUserById(e.UserId);
var user = userManager.GetUserById(e.UserId);
await PlaybackEvent(evt, e.Item, null, user);
}

Expand All @@ -184,38 +151,6 @@ private async void OnItemUpdated(object sender, ItemChangeEventArgs e)
await LibraryEvent(HookEvent.ItemUpdated, e.Item, e.UpdateReason);
}

private async void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
{
var user = _userManager.GetUserById(e.Argument.User.Id);
await ExecuteWebhook(new EventInfo
{
Event = HookEvent.AuthenticationSucceeded,
Session = new SessionInfoDto(e.Argument.SessionInfo),
User = user,
Server = new ServerInfoDto
{
Id = _appHost.SystemId,
Name = _appHost.FriendlyName,
Version = _appHost.ApplicationVersion.ToString(),
},
});
}

private async void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
{
await ExecuteWebhook(new EventInfo
{
Event = HookEvent.AuthenticationFailed,
AdditionalData = e.Argument,
Server = new ServerInfoDto
{
Id = _appHost.SystemId,
Name = _appHost.FriendlyName,
Version = _appHost.ApplicationVersion.ToString(),
},
});
}

private async void OnSessionStarted(object sender, SessionEventArgs e)
{
await SessionEvent(HookEvent.SessionStarted, e.SessionInfo);
Expand All @@ -225,7 +160,7 @@ private async void OnSessionEnded(object sender, SessionEventArgs e)
{
if (GetDeviceState(e.SessionInfo.DeviceId) != DeviceState.Stopped)
{
var user = _userManager.GetUserById(e.SessionInfo.UserId);
var user = userManager.GetUserById(e.SessionInfo.UserId);
await PlaybackEvent(HookEvent.Stop, e.SessionInfo.FullNowPlayingItem, e.SessionInfo, user);
}

Expand All @@ -235,30 +170,30 @@ private async void OnSessionEnded(object sender, SessionEventArgs e)

private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
{
await ExecuteWebhook(new EventInfo
await _sender.Send(new EventInfo
{
Event = HookEvent.SubtitleDownloadFailure,
Item = e.Item,
AdditionalData = e.Exception,
Server = new ServerInfoDto
{
Id = _appHost.SystemId,
Name = _appHost.FriendlyName,
Version = _appHost.ApplicationVersion.ToString(),
Id = appHost.SystemId,
Name = appHost.FriendlyName,
Version = appHost.ApplicationVersion.ToString(),
},
});
}

private async void HasPendingRestartChanged(object sender, EventArgs e)
{
await ExecuteWebhook(new EventInfo
await _sender.Send(new EventInfo
{
Event = HookEvent.HasPendingRestartChanged,
Server = new ServerInfoDto
{
Id = _appHost.SystemId,
Name = _appHost.FriendlyName,
Version = _appHost.ApplicationVersion.ToString(),
Id = appHost.SystemId,
Name = appHost.FriendlyName,
Version = appHost.ApplicationVersion.ToString(),
},
});
}
Expand All @@ -270,19 +205,19 @@ private async Task SessionEvent(HookEvent evt, SessionInfo session)
User user = null;
if (session.UserId != Guid.Empty)
{
user = _userManager.GetUserById(session.UserId);
user = userManager.GetUserById(session.UserId);
}

await ExecuteWebhook(new EventInfo
await _sender.Send(new EventInfo
{
Event = evt,
User = user,
Session = new SessionInfoDto(session),
Server = new ServerInfoDto
{
Id = _appHost.SystemId,
Name = _appHost.FriendlyName,
Version = _appHost.ApplicationVersion.ToString(),
Id = appHost.SystemId,
Name = appHost.FriendlyName,
Version = appHost.ApplicationVersion.ToString(),
},
});
}
Expand All @@ -292,16 +227,16 @@ private async Task LibraryEvent(HookEvent evt, BaseItem item, ItemUpdateType upd
if (item == null) return;
if (item.IsVirtualItem) return;

await ExecuteWebhook(new EventInfo
await _sender.Send(new EventInfo
{
Event = evt,
Item = item,
AdditionalData = updateReason,
Server = new ServerInfoDto
{
Id = _appHost.SystemId,
Name = _appHost.FriendlyName,
Version = _appHost.ApplicationVersion.ToString(),
Id = appHost.SystemId,
Name = appHost.FriendlyName,
Version = appHost.ApplicationVersion.ToString(),
},
});
}
Expand All @@ -311,17 +246,17 @@ private async Task PlaybackEvent(HookEvent evt, BaseItem item, SessionInfo sessi
if (user == null) return;
if (item == null) return;

await ExecuteWebhook(new EventInfo
await _sender.Send(new EventInfo
{
Event = evt,
Item = item,
User = user,
Session = session == null ? null : new SessionInfoDto(session),
Server = new ServerInfoDto
{
Id = _appHost.SystemId,
Name = _appHost.FriendlyName,
Version = _appHost.ApplicationVersion.ToString(),
Id = appHost.SystemId,
Name = appHost.FriendlyName,
Version = appHost.ApplicationVersion.ToString(),
},
});
}
Expand All @@ -337,30 +272,6 @@ private async Task PlaybackEvent(HookEvent evt, BaseItem item, SessionInfo sessi
}
}

private async Task ExecuteWebhook(EventInfo request)
{
var hooks = Plugin.Instance.Configuration.Hooks
.Where(h => h.Events.Contains(request.Event));
foreach (var hook in hooks)
{
if (request.User != null && !string.IsNullOrEmpty(hook.UserId) && request.User?.Id.ToString("N") != hook.UserId)
{
_logger.LogWarning("ExecuteWebhook: user mismatch, hook.UserId: {hookUserId}, request.User: {reqUser}, event: {evt}", hook.UserId, request.User, request.Event);
continue;
}

var formatter = _formatFactory.CreateFormat(hook.Format);
try
{
await formatter.Format(new Uri(hook.Url), request);
}
catch (Exception e)
{
_logger.LogWarning(e, "Error during executing webhook: {0}", hook.Url);
}
}
}

private DeviceState GetDeviceState(string id)
{
if (!_deviceStates.TryGetValue(id, out var val))
Expand Down
19 changes: 9 additions & 10 deletions Jellyfin.Webhooks/EventInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
using Jellyfin.Webhooks.Dto;
using MediaBrowser.Controller.Entities;

namespace Jellyfin.Webhooks
namespace Jellyfin.Webhooks;

public class EventInfo
{
internal class EventInfo
{
public HookEvent Event { get; set; }
public User User { get; set; }
public BaseItem Item { get; set; }
public SessionInfoDto Session { get; set; }
public ServerInfoDto Server { get; set; }
public object AdditionalData { get; set; }
}
public HookEvent Event { get; set; }
public User User { get; set; }
public BaseItem Item { get; set; }
public SessionInfoDto Session { get; set; }
public ServerInfoDto Server { get; set; }
public object AdditionalData { get; set; }
}
2 changes: 1 addition & 1 deletion Jellyfin.Webhooks/Formats/GetFormat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public async Task Format(Uri url, EventInfo info)
}
if (info.Item != null)
{
query["media_type"] = info.Item.MediaType;
query["media_type"] = info.Item.MediaType.ToString();
}
builder.Query = query.ToString();

Expand Down
8 changes: 8 additions & 0 deletions Jellyfin.Webhooks/ISender.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Threading.Tasks;

namespace Jellyfin.Webhooks;

public interface ISender
{
Task Send(EventInfo request);
}
Loading

0 comments on commit b00f4ad

Please sign in to comment.