Update dependencies and fix stylecop (#138)

This commit is contained in:
Cody Robibero 2022-10-10 08:06:59 -06:00 committed by GitHub
parent 4ec22bd5a2
commit 4c454578ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 2647 additions and 2705 deletions

View File

@ -10,78 +10,77 @@ using Jellyfin.Plugin.Webhook.Destinations.Slack;
using Jellyfin.Plugin.Webhook.Destinations.Smtp;
using MediaBrowser.Model.Plugins;
namespace Jellyfin.Plugin.Webhook.Configuration
namespace Jellyfin.Plugin.Webhook.Configuration;
/// <summary>
/// Webhook plugin configuration.
/// </summary>
public class PluginConfiguration : BasePluginConfiguration
{
/// <summary>
/// Webhook plugin configuration.
/// Initializes a new instance of the <see cref="PluginConfiguration"/> class.
/// </summary>
public class PluginConfiguration : BasePluginConfiguration
public PluginConfiguration()
{
/// <summary>
/// Initializes a new instance of the <see cref="PluginConfiguration"/> class.
/// </summary>
public PluginConfiguration()
{
ServerUrl = string.Empty;
DiscordOptions = Array.Empty<DiscordOption>();
GenericOptions = Array.Empty<GenericOption>();
GenericFormOptions = Array.Empty<GenericFormOption>();
GotifyOptions = Array.Empty<GotifyOption>();
PushbulletOptions = Array.Empty<PushbulletOption>();
PushoverOptions = Array.Empty<PushoverOption>();
SlackOptions = Array.Empty<SlackOption>();
SmtpOptions = Array.Empty<SmtpOption>();
MqttOptions = Array.Empty<MqttOption>();
}
/// <summary>
/// Gets or sets the jellyfin server url.
/// </summary>
public string ServerUrl { get; set; }
/// <summary>
/// Gets or sets the discord options.
/// </summary>
public DiscordOption[] DiscordOptions { get; set; }
/// <summary>
/// Gets or sets the generic options.
/// </summary>
public GenericOption[] GenericOptions { get; set; }
/// <summary>
/// Gets or sets the generic form options.
/// </summary>
public GenericFormOption[] GenericFormOptions { get; set; }
/// <summary>
/// Gets or sets the gotify options.
/// </summary>
public GotifyOption[] GotifyOptions { get; set; }
/// <summary>
/// Gets or sets the pushbullet options.
/// </summary>
public PushbulletOption[] PushbulletOptions { get; set; }
/// <summary>
/// Gets or sets the pushover options.
/// </summary>
public PushoverOption[] PushoverOptions { get; set; }
/// <summary>
/// Gets or sets the slack options.
/// </summary>
public SlackOption[] SlackOptions { get; set; }
/// <summary>
/// Gets or sets the smtp options.
/// </summary>
public SmtpOption[] SmtpOptions { get; set; }
/// <summary>
/// Gets or sets the mqtt options.
/// </summary>
public MqttOption[] MqttOptions { get; set; }
ServerUrl = string.Empty;
DiscordOptions = Array.Empty<DiscordOption>();
GenericOptions = Array.Empty<GenericOption>();
GenericFormOptions = Array.Empty<GenericFormOption>();
GotifyOptions = Array.Empty<GotifyOption>();
PushbulletOptions = Array.Empty<PushbulletOption>();
PushoverOptions = Array.Empty<PushoverOption>();
SlackOptions = Array.Empty<SlackOption>();
SmtpOptions = Array.Empty<SmtpOption>();
MqttOptions = Array.Empty<MqttOption>();
}
/// <summary>
/// Gets or sets the jellyfin server url.
/// </summary>
public string ServerUrl { get; set; }
/// <summary>
/// Gets or sets the discord options.
/// </summary>
public DiscordOption[] DiscordOptions { get; set; }
/// <summary>
/// Gets or sets the generic options.
/// </summary>
public GenericOption[] GenericOptions { get; set; }
/// <summary>
/// Gets or sets the generic form options.
/// </summary>
public GenericFormOption[] GenericFormOptions { get; set; }
/// <summary>
/// Gets or sets the gotify options.
/// </summary>
public GotifyOption[] GotifyOptions { get; set; }
/// <summary>
/// Gets or sets the pushbullet options.
/// </summary>
public PushbulletOption[] PushbulletOptions { get; set; }
/// <summary>
/// Gets or sets the pushover options.
/// </summary>
public PushoverOption[] PushoverOptions { get; set; }
/// <summary>
/// Gets or sets the slack options.
/// </summary>
public SlackOption[] SlackOptions { get; set; }
/// <summary>
/// Gets or sets the smtp options.
/// </summary>
public SmtpOption[] SmtpOptions { get; set; }
/// <summary>
/// Gets or sets the mqtt options.
/// </summary>
public MqttOption[] MqttOptions { get; set; }
}

View File

@ -2,39 +2,38 @@ using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Webhook.Destinations
namespace Jellyfin.Plugin.Webhook.Destinations;
/// <summary>
/// The base destination.
/// </summary>
public class BaseClient
{
/// <summary>
/// The base destination.
/// Determines whether the client should send the webhook.
/// </summary>
public class BaseClient
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
/// <param name="option">The sender option.</param>
/// <param name="data">The webhook data.</param>
/// <returns>Whether the client should send the webhook.</returns>
protected bool SendWebhook(
ILogger logger,
BaseOption option,
Dictionary<string, object> data)
{
/// <summary>
/// Determines whether the client should send the webhook.
/// </summary>
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
/// <param name="option">The sender option.</param>
/// <param name="data">The webhook data.</param>
/// <returns>Whether the client should send the webhook.</returns>
protected bool SendWebhook(
ILogger logger,
BaseOption option,
Dictionary<string, object> data)
var notificationType = data[nameof(NotificationType)] as NotificationType? ?? NotificationType.None;
// Don't filter on UserId if the notification type is UserCreated.
if (notificationType is not NotificationType.UserCreated
&& option.UserFilter.Length is not 0
&& data.TryGetValue("UserId", out var userIdObj)
&& userIdObj is Guid userId
&& Array.IndexOf(option.UserFilter, userId) is -1)
{
var notificationType = data[nameof(NotificationType)] as NotificationType? ?? NotificationType.None;
// Don't filter on UserId if the notification type is UserCreated.
if (notificationType is not NotificationType.UserCreated
&& option.UserFilter.Length is not 0
&& data.TryGetValue("UserId", out var userIdObj)
&& userIdObj is Guid userId
&& Array.IndexOf(option.UserFilter, userId) is -1)
{
logger.LogDebug("UserId {UserId} not found in user filter, ignoring event", userId);
return false;
}
return true;
logger.LogDebug("UserId {UserId} not found in user filter, ignoring event", userId);
return false;
}
return true;
}
}

View File

@ -5,97 +5,96 @@ using HandlebarsDotNet;
using Jellyfin.Extensions.Json;
using Jellyfin.Plugin.Webhook.Helpers;
namespace Jellyfin.Plugin.Webhook.Destinations
namespace Jellyfin.Plugin.Webhook.Destinations;
/// <summary>
/// Base options for destination.
/// </summary>
public abstract class BaseOption
{
private HandlebarsTemplate<object, string>? _compiledTemplate;
/// <summary>
/// Base options for destination.
/// Gets or sets the notification type.
/// </summary>
public abstract class BaseOption
public NotificationType[] NotificationTypes { get; set; } = Array.Empty<NotificationType>();
/// <summary>
/// Gets or sets the webhook name.
/// </summary>
/// <remarks>
/// Only used for display.
/// </remarks>
public string? WebhookName { get; set; }
/// <summary>
/// Gets or sets the webhook uri.
/// </summary>
public string? WebhookUri { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to notify on movies.
/// </summary>
public bool EnableMovies { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to notify on episodes.
/// </summary>
public bool EnableEpisodes { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to notify on series.
/// </summary>
public bool EnableSeries { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to notify on seasons.
/// </summary>
public bool EnableSeasons { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to notify on albums.
/// </summary>
public bool EnableAlbums { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to notify on songs.
/// </summary>
public bool EnableSongs { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to send all possible properties.
/// </summary>
public bool SendAllProperties { get; set; }
/// <summary>
/// Gets or sets the handlebars template.
/// </summary>
public string? Template { get; set; }
/// <summary>
/// Gets or sets the webhook user filter.
/// </summary>
public Guid[] UserFilter { get; set; } = Array.Empty<Guid>();
/// <summary>
/// Gets the compiled handlebars template.
/// </summary>
/// <returns>The compiled handlebars template.</returns>
public HandlebarsTemplate<object, string> GetCompiledTemplate()
{
private HandlebarsTemplate<object, string>? _compiledTemplate;
return _compiledTemplate ??= Handlebars.Compile(HandlebarsFunctionHelpers.Base64Decode(Template));
}
/// <summary>
/// Gets or sets the notification type.
/// </summary>
public NotificationType[] NotificationTypes { get; set; } = Array.Empty<NotificationType>();
/// <summary>
/// Gets or sets the webhook name.
/// </summary>
/// <remarks>
/// Only used for display.
/// </remarks>
public string? WebhookName { get; set; }
/// <summary>
/// Gets or sets the webhook uri.
/// </summary>
public string? WebhookUri { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to notify on movies.
/// </summary>
public bool EnableMovies { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to notify on episodes.
/// </summary>
public bool EnableEpisodes { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to notify on series.
/// </summary>
public bool EnableSeries { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to notify on seasons.
/// </summary>
public bool EnableSeasons { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to notify on albums.
/// </summary>
public bool EnableAlbums { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to notify on songs.
/// </summary>
public bool EnableSongs { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to send all possible properties.
/// </summary>
public bool SendAllProperties { get; set; }
/// <summary>
/// Gets or sets the handlebars template.
/// </summary>
public string? Template { get; set; }
/// <summary>
/// Gets or sets the webhook user filter.
/// </summary>
public Guid[] UserFilter { get; set; } = Array.Empty<Guid>();
/// <summary>
/// Gets the compiled handlebars template.
/// </summary>
/// <returns>The compiled handlebars template.</returns>
public HandlebarsTemplate<object, string> GetCompiledTemplate()
{
return _compiledTemplate ??= Handlebars.Compile(HandlebarsFunctionHelpers.Base64Decode(Template));
}
/// <summary>
/// Gets the message body.
/// </summary>
/// <param name="data">The notification body.</param>
/// <returns>The string message body.</returns>
public string GetMessageBody(Dictionary<string, object> data)
{
return SendAllProperties
? JsonSerializer.Serialize(data, JsonDefaults.Options)
: GetCompiledTemplate()(data);
}
/// <summary>
/// Gets the message body.
/// </summary>
/// <param name="data">The notification body.</param>
/// <returns>The string message body.</returns>
public string GetMessageBody(Dictionary<string, object> data)
{
return SendAllProperties
? JsonSerializer.Serialize(data, JsonDefaults.Options)
: GetCompiledTemplate()(data);
}
}

View File

@ -9,88 +9,87 @@ using Jellyfin.Plugin.Webhook.Extensions;
using MediaBrowser.Common.Net;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Webhook.Destinations.Discord
namespace Jellyfin.Plugin.Webhook.Destinations.Discord;
/// <summary>
/// Client for the <see cref="DiscordOption"/>.
/// </summary>
public class DiscordClient : BaseClient, IWebhookClient<DiscordOption>
{
private readonly ILogger<DiscordClient> _logger;
private readonly IHttpClientFactory _httpClientFactory;
/// <summary>
/// Client for the <see cref="DiscordOption"/>.
/// Initializes a new instance of the <see cref="DiscordClient"/> class.
/// </summary>
public class DiscordClient : BaseClient, IWebhookClient<DiscordOption>
/// <param name="logger">Instance of the <see cref="ILogger{DiscordDestination}"/> interface.</param>
/// <param name="httpClientFactory">Instance of the<see cref="IHttpClientFactory"/> interface.</param>
public DiscordClient(ILogger<DiscordClient> logger, IHttpClientFactory httpClientFactory)
{
private readonly ILogger<DiscordClient> _logger;
private readonly IHttpClientFactory _httpClientFactory;
_logger = logger;
_httpClientFactory = httpClientFactory;
}
/// <summary>
/// Initializes a new instance of the <see cref="DiscordClient"/> class.
/// </summary>
/// <param name="logger">Instance of the <see cref="ILogger{DiscordDestination}"/> interface.</param>
/// <param name="httpClientFactory">Instance of the<see cref="IHttpClientFactory"/> interface.</param>
public DiscordClient(ILogger<DiscordClient> logger, IHttpClientFactory httpClientFactory)
/// <inheritdoc />
public async Task SendAsync(DiscordOption option, Dictionary<string, object> data)
{
try
{
_logger = logger;
_httpClientFactory = httpClientFactory;
}
/// <inheritdoc />
public async Task SendAsync(DiscordOption option, Dictionary<string, object> data)
{
try
if (string.IsNullOrEmpty(option.WebhookUri))
{
if (string.IsNullOrEmpty(option.WebhookUri))
{
throw new ArgumentException(nameof(option.WebhookUri));
}
if (!SendWebhook(_logger, option, data))
{
return;
}
// Add discord specific properties.
data["MentionType"] = GetMentionType(option.MentionType);
if (!string.IsNullOrEmpty(option.EmbedColor))
{
data["EmbedColor"] = FormatColorCode(option.EmbedColor);
}
if (!string.IsNullOrEmpty(option.AvatarUrl))
{
data["AvatarUrl"] = option.AvatarUrl;
}
if (!string.IsNullOrEmpty(option.Username))
{
data["Username"] = option.Username;
data["BotUsername"] = option.Username;
}
var body = option.GetMessageBody(data);
_logger.LogDebug("SendAsync Body: {@Body}", body);
using var content = new StringContent(body, Encoding.UTF8, MediaTypeNames.Application.Json);
using var response = await _httpClientFactory
.CreateClient(NamedClient.Default)
.PostAsync(new Uri(option.WebhookUri), content)
.ConfigureAwait(false);
await response.LogIfFailedAsync(_logger).ConfigureAwait(false);
throw new ArgumentException(nameof(option.WebhookUri));
}
catch (HttpRequestException e)
if (!SendWebhook(_logger, option, data))
{
_logger.LogWarning(e, "Error sending notification");
return;
}
}
private static int FormatColorCode(string hexCode)
{
return int.Parse(hexCode[1..6], NumberStyles.HexNumber, CultureInfo.InvariantCulture);
}
private static string GetMentionType(DiscordMentionType mentionType)
{
return mentionType switch
// Add discord specific properties.
data["MentionType"] = GetMentionType(option.MentionType);
if (!string.IsNullOrEmpty(option.EmbedColor))
{
DiscordMentionType.Everyone => "@everyone",
DiscordMentionType.Here => "@here",
_ => string.Empty
};
data["EmbedColor"] = FormatColorCode(option.EmbedColor);
}
if (!string.IsNullOrEmpty(option.AvatarUrl))
{
data["AvatarUrl"] = option.AvatarUrl;
}
if (!string.IsNullOrEmpty(option.Username))
{
data["Username"] = option.Username;
data["BotUsername"] = option.Username;
}
var body = option.GetMessageBody(data);
_logger.LogDebug("SendAsync Body: {@Body}", body);
using var content = new StringContent(body, Encoding.UTF8, MediaTypeNames.Application.Json);
using var response = await _httpClientFactory
.CreateClient(NamedClient.Default)
.PostAsync(new Uri(option.WebhookUri), content)
.ConfigureAwait(false);
await response.LogIfFailedAsync(_logger).ConfigureAwait(false);
}
catch (HttpRequestException e)
{
_logger.LogWarning(e, "Error sending notification");
}
}
private static int FormatColorCode(string hexCode)
{
return int.Parse(hexCode[1..6], NumberStyles.HexNumber, CultureInfo.InvariantCulture);
}
private static string GetMentionType(DiscordMentionType mentionType)
{
return mentionType switch
{
DiscordMentionType.Everyone => "@everyone",
DiscordMentionType.Here => "@here",
_ => string.Empty
};
}
}

View File

@ -1,23 +1,22 @@
namespace Jellyfin.Plugin.Webhook.Destinations.Discord
namespace Jellyfin.Plugin.Webhook.Destinations.Discord;
/// <summary>
/// Discord mention type.
/// </summary>
public enum DiscordMentionType
{
/// <summary>
/// Discord mention type.
/// Mention @everyone.
/// </summary>
public enum DiscordMentionType
{
/// <summary>
/// Mention @everyone.
/// </summary>
Everyone = 2,
Everyone = 2,
/// <summary>
/// Mention @here.
/// </summary>
Here = 1,
/// <summary>
/// Mention @here.
/// </summary>
Here = 1,
/// <summary>
/// Mention none.
/// </summary>
None = 0
}
}
/// <summary>
/// Mention none.
/// </summary>
None = 0
}

View File

@ -1,28 +1,27 @@
namespace Jellyfin.Plugin.Webhook.Destinations.Discord
namespace Jellyfin.Plugin.Webhook.Destinations.Discord;
/// <summary>
/// Discord specific options.
/// </summary>
public class DiscordOption : BaseOption
{
/// <summary>
/// Discord specific options.
/// Gets or sets the embed color.
/// </summary>
public class DiscordOption : BaseOption
{
/// <summary>
/// Gets or sets the embed color.
/// </summary>
public string? EmbedColor { get; set; }
public string? EmbedColor { get; set; }
/// <summary>
/// Gets or sets the avatar url.
/// </summary>
public string? AvatarUrl { get; set; }
/// <summary>
/// Gets or sets the avatar url.
/// </summary>
public string? AvatarUrl { get; set; }
/// <summary>
/// Gets or sets the bot username.
/// </summary>
public string? Username { get; set; }
/// <summary>
/// Gets or sets the bot username.
/// </summary>
public string? Username { get; set; }
/// <summary>
/// Gets or sets the mention type.
/// </summary>
public DiscordMentionType MentionType { get; set; }
}
}
/// <summary>
/// Gets or sets the mention type.
/// </summary>
public DiscordMentionType MentionType { get; set; }
}

View File

@ -9,83 +9,82 @@ using MediaBrowser.Common.Net;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Jellyfin.Plugin.Webhook.Destinations.Generic
namespace Jellyfin.Plugin.Webhook.Destinations.Generic;
/// <summary>
/// Client for the <see cref="GenericOption"/>.
/// </summary>
public class GenericClient : BaseClient, IWebhookClient<GenericOption>
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<GenericClient> _logger;
/// <summary>
/// Client for the <see cref="GenericOption"/>.
/// Initializes a new instance of the <see cref="GenericClient"/> class.
/// </summary>
public class GenericClient : BaseClient, IWebhookClient<GenericOption>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/>.</param>
/// <param name="logger">Instance of the <see cref="ILogger{GenericClient}"/> interface.</param>
public GenericClient(
IHttpClientFactory httpClientFactory,
ILogger<GenericClient> logger)
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<GenericClient> _logger;
_httpClientFactory = httpClientFactory;
_logger = logger;
}
/// <summary>
/// Initializes a new instance of the <see cref="GenericClient"/> class.
/// </summary>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/>.</param>
/// <param name="logger">Instance of the <see cref="ILogger{GenericClient}"/> interface.</param>
public GenericClient(
IHttpClientFactory httpClientFactory,
ILogger<GenericClient> logger)
/// <inheritdoc />
public async Task SendAsync(GenericOption option, Dictionary<string, object> data)
{
try
{
_httpClientFactory = httpClientFactory;
_logger = logger;
if (!SendWebhook(_logger, option, data))
{
return;
}
foreach (var field in option.Fields)
{
if (string.IsNullOrEmpty(field.Key) || string.IsNullOrEmpty(field.Value))
{
continue;
}
data[field.Key] = field.Value;
}
var body = option.GetMessageBody(data);
_logger.LogDebug("SendAsync Body: {@Body}", body);
using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, option.WebhookUri);
var contentType = MediaTypeNames.Text.Plain;
foreach (var header in option.Headers)
{
if (string.IsNullOrEmpty(header.Key) || string.IsNullOrEmpty(header.Value))
{
continue;
}
// Content-Type cannot be set manually, must be set on the content.
if (string.Equals(HeaderNames.ContentType, header.Key, StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrEmpty(header.Value))
{
contentType = header.Value;
}
else
{
httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
httpRequestMessage.Content = new StringContent(body, Encoding.UTF8, contentType);
using var response = await _httpClientFactory
.CreateClient(NamedClient.Default)
.SendAsync(httpRequestMessage)
.ConfigureAwait(false);
await response.LogIfFailedAsync(_logger).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task SendAsync(GenericOption option, Dictionary<string, object> data)
catch (HttpRequestException e)
{
try
{
if (!SendWebhook(_logger, option, data))
{
return;
}
foreach (var field in option.Fields)
{
if (string.IsNullOrEmpty(field.Key) || string.IsNullOrEmpty(field.Value))
{
continue;
}
data[field.Key] = field.Value;
}
var body = option.GetMessageBody(data);
_logger.LogDebug("SendAsync Body: {@Body}", body);
using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, option.WebhookUri);
var contentType = MediaTypeNames.Text.Plain;
foreach (var header in option.Headers)
{
if (string.IsNullOrEmpty(header.Key) || string.IsNullOrEmpty(header.Value))
{
continue;
}
// Content-Type cannot be set manually, must be set on the content.
if (string.Equals(HeaderNames.ContentType, header.Key, StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrEmpty(header.Value))
{
contentType = header.Value;
}
else
{
httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
httpRequestMessage.Content = new StringContent(body, Encoding.UTF8, contentType);
using var response = await _httpClientFactory
.CreateClient(NamedClient.Default)
.SendAsync(httpRequestMessage)
.ConfigureAwait(false);
await response.LogIfFailedAsync(_logger).ConfigureAwait(false);
}
catch (HttpRequestException e)
{
_logger.LogWarning(e, "Error sending notification");
}
_logger.LogWarning(e, "Error sending notification");
}
}
}

View File

@ -1,29 +1,28 @@
using System;
namespace Jellyfin.Plugin.Webhook.Destinations.Generic
namespace Jellyfin.Plugin.Webhook.Destinations.Generic;
/// <summary>
/// Generic webhook options.
/// </summary>
public class GenericOption : BaseOption
{
/// <summary>
/// Generic webhook options.
/// Initializes a new instance of the <see cref="GenericOption"/> class.
/// </summary>
public class GenericOption : BaseOption
public GenericOption()
{
/// <summary>
/// Initializes a new instance of the <see cref="GenericOption"/> class.
/// </summary>
public GenericOption()
{
Headers = Array.Empty<GenericOptionValue>();
Fields = Array.Empty<GenericOptionValue>();
}
/// <summary>
/// Gets or sets the headers.
/// </summary>
public GenericOptionValue[] Headers { get; set; }
/// <summary>
/// Gets or sets the fields.
/// </summary>
public GenericOptionValue[] Fields { get; set; }
Headers = Array.Empty<GenericOptionValue>();
Fields = Array.Empty<GenericOptionValue>();
}
}
/// <summary>
/// Gets or sets the headers.
/// </summary>
public GenericOptionValue[] Headers { get; set; }
/// <summary>
/// Gets or sets the fields.
/// </summary>
public GenericOptionValue[] Fields { get; set; }
}

View File

@ -1,18 +1,17 @@
namespace Jellyfin.Plugin.Webhook.Destinations.Generic
namespace Jellyfin.Plugin.Webhook.Destinations.Generic;
/// <summary>
/// Generic option value.
/// </summary>
public class GenericOptionValue
{
/// <summary>
/// Generic option value.
/// Gets or sets the option key.
/// </summary>
public class GenericOptionValue
{
/// <summary>
/// Gets or sets the option key.
/// </summary>
public string Key { get; set; } = string.Empty;
public string Key { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the option value.
/// </summary>
public string Value { get; set; } = string.Empty;
}
}
/// <summary>
/// Gets or sets the option value.
/// </summary>
public string Value { get; set; } = string.Empty;
}

View File

@ -6,80 +6,79 @@ using Jellyfin.Plugin.Webhook.Extensions;
using MediaBrowser.Common.Net;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Webhook.Destinations.GenericForm
namespace Jellyfin.Plugin.Webhook.Destinations.GenericForm;
/// <summary>
/// Client for the <see cref="GenericFormOption"/>.
/// </summary>
public class GenericFormClient : BaseClient, IWebhookClient<GenericFormOption>
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<GenericFormClient> _logger;
/// <summary>
/// Client for the <see cref="GenericFormOption"/>.
/// Initializes a new instance of the <see cref="GenericFormClient"/> class.
/// </summary>
public class GenericFormClient : BaseClient, IWebhookClient<GenericFormOption>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/>.</param>
/// <param name="logger">Instance of the <see cref="ILogger{GenericFormClient}"/> interface.</param>
public GenericFormClient(
IHttpClientFactory httpClientFactory,
ILogger<GenericFormClient> logger)
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<GenericFormClient> _logger;
_httpClientFactory = httpClientFactory;
_logger = logger;
}
/// <summary>
/// Initializes a new instance of the <see cref="GenericFormClient"/> class.
/// </summary>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/>.</param>
/// <param name="logger">Instance of the <see cref="ILogger{GenericFormClient}"/> interface.</param>
public GenericFormClient(
IHttpClientFactory httpClientFactory,
ILogger<GenericFormClient> logger)
/// <inheritdoc />
public async Task SendAsync(GenericFormOption option, Dictionary<string, object> data)
{
try
{
_httpClientFactory = httpClientFactory;
_logger = logger;
if (!SendWebhook(_logger, option, data))
{
return;
}
foreach (var field in option.Fields)
{
if (string.IsNullOrEmpty(field.Key) || string.IsNullOrEmpty(field.Value))
{
continue;
}
data[field.Key] = field.Value;
}
var body = option.GetMessageBody(data);
var dictionaryBody = JsonSerializer.Deserialize<Dictionary<string, string>>(body);
if (dictionaryBody is null)
{
_logger.LogWarning("Body is null, unable to send webhook");
return;
}
_logger.LogDebug("SendAsync Body: {@Body}", body);
using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, option.WebhookUri);
foreach (var header in option.Headers)
{
if (string.IsNullOrEmpty(header.Key) || string.IsNullOrEmpty(header.Value))
{
continue;
}
httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
httpRequestMessage.Content = new FormUrlEncodedContent(dictionaryBody!);
using var response = await _httpClientFactory
.CreateClient(NamedClient.Default)
.SendAsync(httpRequestMessage)
.ConfigureAwait(false);
await response.LogIfFailedAsync(_logger).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task SendAsync(GenericFormOption option, Dictionary<string, object> data)
catch (HttpRequestException e)
{
try
{
if (!SendWebhook(_logger, option, data))
{
return;
}
foreach (var field in option.Fields)
{
if (string.IsNullOrEmpty(field.Key) || string.IsNullOrEmpty(field.Value))
{
continue;
}
data[field.Key] = field.Value;
}
var body = option.GetMessageBody(data);
var dictionaryBody = JsonSerializer.Deserialize<Dictionary<string, string>>(body);
if (dictionaryBody is null)
{
_logger.LogWarning("Body is null, unable to send webhook");
return;
}
_logger.LogDebug("SendAsync Body: {@Body}", body);
using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, option.WebhookUri);
foreach (var header in option.Headers)
{
if (string.IsNullOrEmpty(header.Key) || string.IsNullOrEmpty(header.Value))
{
continue;
}
httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
httpRequestMessage.Content = new FormUrlEncodedContent(dictionaryBody!);
using var response = await _httpClientFactory
.CreateClient(NamedClient.Default)
.SendAsync(httpRequestMessage)
.ConfigureAwait(false);
await response.LogIfFailedAsync(_logger).ConfigureAwait(false);
}
catch (HttpRequestException e)
{
_logger.LogWarning(e, "Error sending notification");
}
_logger.LogWarning(e, "Error sending notification");
}
}
}

View File

@ -1,29 +1,28 @@
using System;
namespace Jellyfin.Plugin.Webhook.Destinations.GenericForm
namespace Jellyfin.Plugin.Webhook.Destinations.GenericForm;
/// <summary>
/// Generic form webhook options.
/// </summary>
public class GenericFormOption : BaseOption
{
/// <summary>
/// Generic form webhook options.
/// Initializes a new instance of the <see cref="GenericFormOption"/> class.
/// </summary>
public class GenericFormOption : BaseOption
public GenericFormOption()
{
/// <summary>
/// Initializes a new instance of the <see cref="GenericFormOption"/> class.
/// </summary>
public GenericFormOption()
{
Headers = Array.Empty<GenericFormOptionValue>();
Fields = Array.Empty<GenericFormOptionValue>();
}
/// <summary>
/// Gets or sets the headers.
/// </summary>
public GenericFormOptionValue[] Headers { get; set; }
/// <summary>
/// Gets or sets the fields.
/// </summary>
public GenericFormOptionValue[] Fields { get; set; }
Headers = Array.Empty<GenericFormOptionValue>();
Fields = Array.Empty<GenericFormOptionValue>();
}
/// <summary>
/// Gets or sets the headers.
/// </summary>
public GenericFormOptionValue[] Headers { get; set; }
/// <summary>
/// Gets or sets the fields.
/// </summary>
public GenericFormOptionValue[] Fields { get; set; }
}

View File

@ -1,18 +1,17 @@
namespace Jellyfin.Plugin.Webhook.Destinations.GenericForm
namespace Jellyfin.Plugin.Webhook.Destinations.GenericForm;
/// <summary>
/// Generic form option value.
/// </summary>
public class GenericFormOptionValue
{
/// <summary>
/// Generic form option value.
/// Gets or sets the option key.
/// </summary>
public class GenericFormOptionValue
{
/// <summary>
/// Gets or sets the option key.
/// </summary>
public string Key { get; set; } = string.Empty;
public string Key { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the option value.
/// </summary>
public string Value { get; set; } = string.Empty;
}
/// <summary>
/// Gets or sets the option value.
/// </summary>
public string Value { get; set; } = string.Empty;
}

View File

@ -8,58 +8,57 @@ using Jellyfin.Plugin.Webhook.Extensions;
using MediaBrowser.Common.Net;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Webhook.Destinations.Gotify
namespace Jellyfin.Plugin.Webhook.Destinations.Gotify;
/// <summary>
/// Client for the <see cref="GotifyOption"/>.
/// </summary>
public class GotifyClient : BaseClient, IWebhookClient<GotifyOption>
{
private readonly ILogger<GotifyClient> _logger;
private readonly IHttpClientFactory _httpClientFactory;
/// <summary>
/// Client for the <see cref="GotifyOption"/>.
/// Initializes a new instance of the <see cref="GotifyClient"/> class.
/// </summary>
public class GotifyClient : BaseClient, IWebhookClient<GotifyOption>
/// <param name="logger">Instance of the <see cref="ILogger{GotifyDestination}"/> interface.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/>.</param>
public GotifyClient(ILogger<GotifyClient> logger, IHttpClientFactory httpClientFactory)
{
private readonly ILogger<GotifyClient> _logger;
private readonly IHttpClientFactory _httpClientFactory;
_logger = logger;
_httpClientFactory = httpClientFactory;
}
/// <summary>
/// Initializes a new instance of the <see cref="GotifyClient"/> class.
/// </summary>
/// <param name="logger">Instance of the <see cref="ILogger{GotifyDestination}"/> interface.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/>.</param>
public GotifyClient(ILogger<GotifyClient> logger, IHttpClientFactory httpClientFactory)
/// <inheritdoc />
public async Task SendAsync(GotifyOption option, Dictionary<string, object> data)
{
try
{
_logger = logger;
_httpClientFactory = httpClientFactory;
if (string.IsNullOrEmpty(option.WebhookUri))
{
throw new ArgumentException(nameof(option.WebhookUri));
}
if (!SendWebhook(_logger, option, data))
{
return;
}
// Add gotify specific properties.
data["Priority"] = option.Priority;
var body = option.GetMessageBody(data);
_logger.LogDebug("SendAsync Body: {@Body}", body);
using var content = new StringContent(body, Encoding.UTF8, MediaTypeNames.Application.Json);
using var response = await _httpClientFactory
.CreateClient(NamedClient.Default)
.PostAsync(new Uri(option.WebhookUri.TrimEnd() + $"/message?token={option.Token}"), content)
.ConfigureAwait(false);
await response.LogIfFailedAsync(_logger).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task SendAsync(GotifyOption option, Dictionary<string, object> data)
catch (HttpRequestException e)
{
try
{
if (string.IsNullOrEmpty(option.WebhookUri))
{
throw new ArgumentException(nameof(option.WebhookUri));
}
if (!SendWebhook(_logger, option, data))
{
return;
}
// Add gotify specific properties.
data["Priority"] = option.Priority;
var body = option.GetMessageBody(data);
_logger.LogDebug("SendAsync Body: {@Body}", body);
using var content = new StringContent(body, Encoding.UTF8, MediaTypeNames.Application.Json);
using var response = await _httpClientFactory
.CreateClient(NamedClient.Default)
.PostAsync(new Uri(option.WebhookUri.TrimEnd() + $"/message?token={option.Token}"), content)
.ConfigureAwait(false);
await response.LogIfFailedAsync(_logger).ConfigureAwait(false);
}
catch (HttpRequestException e)
{
_logger.LogWarning(e, "Error sending notification");
}
_logger.LogWarning(e, "Error sending notification");
}
}
}

View File

@ -1,18 +1,17 @@
namespace Jellyfin.Plugin.Webhook.Destinations.Gotify
namespace Jellyfin.Plugin.Webhook.Destinations.Gotify;
/// <summary>
/// Gotify specific options.
/// </summary>
public class GotifyOption : BaseOption
{
/// <summary>
/// Gotify specific options.
/// Gets or sets the authentication token.
/// </summary>
public class GotifyOption : BaseOption
{
/// <summary>
/// Gets or sets the authentication token.
/// </summary>
public string? Token { get; set; }
public string? Token { get; set; }
/// <summary>
/// Gets or sets the notification priority.
/// </summary>
public int Priority { get; set; }
}
}
/// <summary>
/// Gets or sets the notification priority.
/// </summary>
public int Priority { get; set; }
}

View File

@ -1,21 +1,20 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Jellyfin.Plugin.Webhook.Destinations
namespace Jellyfin.Plugin.Webhook.Destinations;
/// <summary>
/// Destination interface.
/// </summary>
/// <typeparam name="TDestinationOption">The type of options.</typeparam>
public interface IWebhookClient<in TDestinationOption>
where TDestinationOption : BaseOption
{
/// <summary>
/// Destination interface.
/// Send message to destination.
/// </summary>
/// <typeparam name="TDestinationOption">The type of options.</typeparam>
public interface IWebhookClient<in TDestinationOption>
where TDestinationOption : BaseOption
{
/// <summary>
/// Send message to destination.
/// </summary>
/// <param name="option">The destination option.</param>
/// <param name="data">The message to send.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task SendAsync(TDestinationOption option, Dictionary<string, object> data);
}
/// <param name="option">The destination option.</param>
/// <param name="data">The message to send.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task SendAsync(TDestinationOption option, Dictionary<string, object> data);
}

View File

@ -22,4 +22,4 @@ public interface IMqttClients
/// <param name="guid">guid of MqttOption.</param>
/// <returns>Instance of the <see cref="IManagedMqttClient"/> interface.</returns>
IManagedMqttClient? GetClient(Guid guid);
}
}

View File

@ -52,4 +52,4 @@ public class MqttClient : BaseClient, IWebhookClient<MqttOption>
_logger.LogDebug(e, "Error sending MQTT notification");
}
}
}
}

View File

@ -131,4 +131,4 @@ public class MqttClients : IMqttClients, IDisposable
client.Dispose();
}
}
}
}

View File

@ -70,4 +70,4 @@ public class MqttOption : BaseOption
/// </summary>
/// <returns>The compiled handlebars subject template.</returns>
public HandlebarsTemplate<object, string> GetCompiledTopicTemplate() => _compiledTopicTemplate ??= Handlebars.Compile(HandlebarsFunctionHelpers.Base64Decode(Topic));
}
}

View File

@ -1,128 +1,127 @@
namespace Jellyfin.Plugin.Webhook.Destinations
namespace Jellyfin.Plugin.Webhook.Destinations;
/// <summary>
/// The type of notification.
/// </summary>
public enum NotificationType
{
/// <summary>
/// The type of notification.
/// No notification type.
/// </summary>
public enum NotificationType
{
/// <summary>
/// No notification type.
/// </summary>
None = 0,
None = 0,
/// <summary>
/// Item added notification.
/// </summary>
ItemAdded = 1,
/// <summary>
/// Item added notification.
/// </summary>
ItemAdded = 1,
/// <summary>
/// Generic notification.
/// </summary>
Generic = 2,
/// <summary>
/// Generic notification.
/// </summary>
Generic = 2,
/// <summary>
/// Playback start notification.
/// </summary>
PlaybackStart = 3,
/// <summary>
/// Playback start notification.
/// </summary>
PlaybackStart = 3,
/// <summary>
/// Playback progress notification.
/// </summary>
PlaybackProgress = 4,
/// <summary>
/// Playback progress notification.
/// </summary>
PlaybackProgress = 4,
/// <summary>
/// Playback stop notification.
/// </summary>
PlaybackStop = 5,
/// <summary>
/// Playback stop notification.
/// </summary>
PlaybackStop = 5,
/// <summary>
/// Subtitle download failure.
/// </summary>
SubtitleDownloadFailure = 6,
/// <summary>
/// Subtitle download failure.
/// </summary>
SubtitleDownloadFailure = 6,
/// <summary>
/// Authentication failure.
/// </summary>
AuthenticationFailure = 7,
/// <summary>
/// Authentication failure.
/// </summary>
AuthenticationFailure = 7,
/// <summary>
/// Authentication success.
/// </summary>
AuthenticationSuccess = 8,
/// <summary>
/// Authentication success.
/// </summary>
AuthenticationSuccess = 8,
/// <summary>
/// Session started.
/// </summary>
SessionStart = 9,
/// <summary>
/// Session started.
/// </summary>
SessionStart = 9,
/// <summary>
/// Server pending restart.
/// </summary>
PendingRestart = 10,
/// <summary>
/// Server pending restart.
/// </summary>
PendingRestart = 10,
/// <summary>
/// Task completed.
/// </summary>
TaskCompleted = 11,
/// <summary>
/// Task completed.
/// </summary>
TaskCompleted = 11,
/// <summary>
/// Plugin installation cancelled.
/// </summary>
PluginInstallationCancelled = 12,
/// <summary>
/// Plugin installation cancelled.
/// </summary>
PluginInstallationCancelled = 12,
/// <summary>
/// Plugin installation failed.
/// </summary>
PluginInstallationFailed = 13,
/// <summary>
/// Plugin installation failed.
/// </summary>
PluginInstallationFailed = 13,
/// <summary>
/// Plugin installed.
/// </summary>
PluginInstalled = 14,
/// <summary>
/// Plugin installed.
/// </summary>
PluginInstalled = 14,
/// <summary>
/// Plugin installing.
/// </summary>
PluginInstalling = 15,
/// <summary>
/// Plugin installing.
/// </summary>
PluginInstalling = 15,
/// <summary>
/// Plugin uninstalled.
/// </summary>
PluginUninstalled = 16,
/// <summary>
/// Plugin uninstalled.
/// </summary>
PluginUninstalled = 16,
/// <summary>
/// Plugin updated.
/// </summary>
PluginUpdated = 17,
/// <summary>
/// Plugin updated.
/// </summary>
PluginUpdated = 17,
/// <summary>
/// User created.
/// </summary>
UserCreated = 18,
/// <summary>
/// User created.
/// </summary>
UserCreated = 18,
/// <summary>
/// User created.
/// </summary>
UserDeleted = 19,
/// <summary>
/// User created.
/// </summary>
UserDeleted = 19,
/// <summary>
/// User locked out.
/// </summary>
UserLockedOut = 20,
/// <summary>
/// User locked out.
/// </summary>
UserLockedOut = 20,
/// <summary>
/// User password changed.
/// </summary>
UserPasswordChanged = 21,
/// <summary>
/// User password changed.
/// </summary>
UserPasswordChanged = 21,
/// <summary>
/// User updated.
/// </summary>
UserUpdated = 22,
/// <summary>
/// User updated.
/// </summary>
UserUpdated = 22,
/// <summary>
/// User data saved.
/// </summary>
UserDataSaved = 23
}
/// <summary>
/// User data saved.
/// </summary>
UserDataSaved = 23
}

View File

@ -7,60 +7,59 @@ using Jellyfin.Plugin.Webhook.Extensions;
using MediaBrowser.Common.Net;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Webhook.Destinations.Pushbullet
namespace Jellyfin.Plugin.Webhook.Destinations.Pushbullet;
/// <summary>
/// Client for the <see cref="PushbulletOption"/>.
/// </summary>
public class PushbulletClient : BaseClient, IWebhookClient<PushbulletOption>
{
private readonly ILogger<PushbulletClient> _logger;
private readonly IHttpClientFactory _httpClientFactory;
/// <summary>
/// Client for the <see cref="PushbulletOption"/>.
/// Initializes a new instance of the <see cref="PushbulletClient"/> class.
/// </summary>
public class PushbulletClient : BaseClient, IWebhookClient<PushbulletOption>
/// <param name="logger">Instance of the <see cref="ILogger{PushbulletClient}"/> interface.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
public PushbulletClient(
ILogger<PushbulletClient> logger,
IHttpClientFactory httpClientFactory)
{
private readonly ILogger<PushbulletClient> _logger;
private readonly IHttpClientFactory _httpClientFactory;
_logger = logger;
_httpClientFactory = httpClientFactory;
}
/// <summary>
/// Initializes a new instance of the <see cref="PushbulletClient"/> class.
/// </summary>
/// <param name="logger">Instance of the <see cref="ILogger{PushbulletClient}"/> interface.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
public PushbulletClient(
ILogger<PushbulletClient> logger,
IHttpClientFactory httpClientFactory)
/// <inheritdoc />
public async Task SendAsync(PushbulletOption option, Dictionary<string, object> data)
{
try
{
_logger = logger;
_httpClientFactory = httpClientFactory;
if (!SendWebhook(_logger, option, data))
{
return;
}
data["PushbulletToken"] = option.Token;
data["PushbulletDeviceId"] = option.DeviceId;
data["PushbulletChannel"] = option.Channel;
var body = option.GetMessageBody(data);
_logger.LogDebug("SendAsync Body: {@Body}", body);
using var requestOptions = new HttpRequestMessage(HttpMethod.Post, string.IsNullOrEmpty(option.WebhookUri) ? PushbulletOption.ApiUrl : option.WebhookUri);
requestOptions.Headers.TryAddWithoutValidation("Access-Token", option.Token);
requestOptions.Content = new StringContent(body, Encoding.UTF8, MediaTypeNames.Application.Json);
using var response = await _httpClientFactory
.CreateClient(NamedClient.Default)
.SendAsync(requestOptions)
.ConfigureAwait(false);
await response.LogIfFailedAsync(_logger).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task SendAsync(PushbulletOption option, Dictionary<string, object> data)
catch (HttpRequestException e)
{
try
{
if (!SendWebhook(_logger, option, data))
{
return;
}
data["PushbulletToken"] = option.Token;
data["PushbulletDeviceId"] = option.DeviceId;
data["PushbulletChannel"] = option.Channel;
var body = option.GetMessageBody(data);
_logger.LogDebug("SendAsync Body: {@Body}", body);
using var requestOptions = new HttpRequestMessage(HttpMethod.Post, string.IsNullOrEmpty(option.WebhookUri) ? PushbulletOption.ApiUrl : option.WebhookUri);
requestOptions.Headers.TryAddWithoutValidation("Access-Token", option.Token);
requestOptions.Content = new StringContent(body, Encoding.UTF8, MediaTypeNames.Application.Json);
using var response = await _httpClientFactory
.CreateClient(NamedClient.Default)
.SendAsync(requestOptions)
.ConfigureAwait(false);
await response.LogIfFailedAsync(_logger).ConfigureAwait(false);
}
catch (HttpRequestException e)
{
_logger.LogWarning(e, "Error sending notification");
}
_logger.LogWarning(e, "Error sending notification");
}
}
}

View File

@ -1,31 +1,30 @@
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Webhook.Destinations.Pushbullet
namespace Jellyfin.Plugin.Webhook.Destinations.Pushbullet;
/// <summary>
/// Pushbullet specific option.
/// </summary>
public class PushbulletOption : BaseOption
{
/// <summary>
/// Pushbullet specific option.
/// The webhook endpoint.
/// </summary>
public class PushbulletOption : BaseOption
{
/// <summary>
/// The webhook endpoint.
/// </summary>
[JsonIgnore]
public const string ApiUrl = "https://api.pushbullet.com/v2/pushes";
[JsonIgnore]
public const string ApiUrl = "https://api.pushbullet.com/v2/pushes";
/// <summary>
/// Gets or sets the pushbullet token.
/// </summary>
public string Token { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the pushbullet token.
/// </summary>
public string Token { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the device id.
/// </summary>
public string DeviceId { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the device id.
/// </summary>
public string DeviceId { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the channel.
/// </summary>
public string Channel { get; set; } = string.Empty;
}
}
/// <summary>
/// Gets or sets the channel.
/// </summary>
public string Channel { get; set; } = string.Empty;
}

View File

@ -8,82 +8,81 @@ using Jellyfin.Plugin.Webhook.Extensions;
using MediaBrowser.Common.Net;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Webhook.Destinations.Pushover
namespace Jellyfin.Plugin.Webhook.Destinations.Pushover;
/// <summary>
/// Client for the <see cref="PushoverOption"/>.
/// </summary>
public class PushoverClient : BaseClient, IWebhookClient<PushoverOption>
{
private readonly ILogger<PushoverClient> _logger;
private readonly IHttpClientFactory _httpClientFactory;
/// <summary>
/// Client for the <see cref="PushoverOption"/>.
/// Initializes a new instance of the <see cref="PushoverClient"/> class.
/// </summary>
public class PushoverClient : BaseClient, IWebhookClient<PushoverOption>
/// <param name="logger">Instance of the <see cref="ILogger{PushoverClient}"/> interface.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/>.</param>
public PushoverClient(ILogger<PushoverClient> logger, IHttpClientFactory httpClientFactory)
{
private readonly ILogger<PushoverClient> _logger;
private readonly IHttpClientFactory _httpClientFactory;
_logger = logger;
_httpClientFactory = httpClientFactory;
}
/// <summary>
/// Initializes a new instance of the <see cref="PushoverClient"/> class.
/// </summary>
/// <param name="logger">Instance of the <see cref="ILogger{PushoverClient}"/> interface.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/>.</param>
public PushoverClient(ILogger<PushoverClient> logger, IHttpClientFactory httpClientFactory)
/// <inheritdoc />
public async Task SendAsync(PushoverOption option, Dictionary<string, object> data)
{
try
{
_logger = logger;
_httpClientFactory = httpClientFactory;
if (!SendWebhook(_logger, option, data))
{
return;
}
data["Token"] = option.Token;
data["UserToken"] = option.UserToken;
if (!string.IsNullOrEmpty(option.Device))
{
data["Device"] = option.Device;
}
if (!string.IsNullOrEmpty(option.Title))
{
data["Title"] = option.Title;
}
if (!string.IsNullOrEmpty(option.MessageUrl))
{
data["MessageUrl"] = option.MessageUrl;
}
if (!string.IsNullOrEmpty(option.MessageUrlTitle))
{
data["MessageUrlTitle"] = option.MessageUrlTitle;
}
if (option.MessagePriority is not null)
{
data["MessagePriority"] = option.MessagePriority;
}
if (!string.IsNullOrEmpty(option.NotificationSound))
{
data["NotificationSound"] = option.NotificationSound;
}
var body = option.GetMessageBody(data);
_logger.LogDebug("SendAsync Body: {@Body}", body);
using var content = new StringContent(body, Encoding.UTF8, MediaTypeNames.Application.Json);
using var response = await _httpClientFactory
.CreateClient(NamedClient.Default)
.PostAsync(string.IsNullOrEmpty(option.WebhookUri) ? PushoverOption.ApiUrl : new Uri(option.WebhookUri), content)
.ConfigureAwait(false);
await response.LogIfFailedAsync(_logger).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task SendAsync(PushoverOption option, Dictionary<string, object> data)
catch (HttpRequestException e)
{
try
{
if (!SendWebhook(_logger, option, data))
{
return;
}
data["Token"] = option.Token;
data["UserToken"] = option.UserToken;
if (!string.IsNullOrEmpty(option.Device))
{
data["Device"] = option.Device;
}
if (!string.IsNullOrEmpty(option.Title))
{
data["Title"] = option.Title;
}
if (!string.IsNullOrEmpty(option.MessageUrl))
{
data["MessageUrl"] = option.MessageUrl;
}
if (!string.IsNullOrEmpty(option.MessageUrlTitle))
{
data["MessageUrlTitle"] = option.MessageUrlTitle;
}
if (option.MessagePriority is not null)
{
data["MessagePriority"] = option.MessagePriority;
}
if (!string.IsNullOrEmpty(option.NotificationSound))
{
data["NotificationSound"] = option.NotificationSound;
}
var body = option.GetMessageBody(data);
_logger.LogDebug("SendAsync Body: {@Body}", body);
using var content = new StringContent(body, Encoding.UTF8, MediaTypeNames.Application.Json);
using var response = await _httpClientFactory
.CreateClient(NamedClient.Default)
.PostAsync(string.IsNullOrEmpty(option.WebhookUri) ? PushoverOption.ApiUrl : new Uri(option.WebhookUri), content)
.ConfigureAwait(false);
await response.LogIfFailedAsync(_logger).ConfigureAwait(false);
}
catch (HttpRequestException e)
{
_logger.LogWarning(e, "Error sending notification");
}
_logger.LogWarning(e, "Error sending notification");
}
}
}

View File

@ -1,57 +1,56 @@
using System;
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Webhook.Destinations.Pushover
namespace Jellyfin.Plugin.Webhook.Destinations.Pushover;
/// <summary>
/// Pushover specific options.
/// </summary>
public class PushoverOption : BaseOption
{
/// <summary>
/// Pushover specific options.
/// The webhook endpoint.
/// </summary>
public class PushoverOption : BaseOption
{
/// <summary>
/// The webhook endpoint.
/// </summary>
[JsonIgnore]
public static readonly Uri ApiUrl = new Uri("https://api.pushover.net/1/messages.json");
[JsonIgnore]
public static readonly Uri ApiUrl = new Uri("https://api.pushover.net/1/messages.json");
/// <summary>
/// Gets or sets the pushover token.
/// </summary>
public string Token { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the pushover token.
/// </summary>
public string Token { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the pushover user token.
/// </summary>
public string UserToken { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the pushover user token.
/// </summary>
public string UserToken { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the device.
/// </summary>
public string? Device { get; set; }
/// <summary>
/// Gets or sets the device.
/// </summary>
public string? Device { get; set; }
/// <summary>
/// Gets or sets the message title.
/// </summary>
public string? Title { get; set; }
/// <summary>
/// Gets or sets the message title.
/// </summary>
public string? Title { get; set; }
/// <summary>
/// Gets or sets the message url.
/// </summary>
public string? MessageUrl { get; set; }
/// <summary>
/// Gets or sets the message url.
/// </summary>
public string? MessageUrl { get; set; }
/// <summary>
/// Gets or sets the message url title.
/// </summary>
public string? MessageUrlTitle { get; set; }
/// <summary>
/// Gets or sets the message url title.
/// </summary>
public string? MessageUrlTitle { get; set; }
/// <summary>
/// Gets or sets the message priority.
/// </summary>
public int? MessagePriority { get; set; }
/// <summary>
/// Gets or sets the message priority.
/// </summary>
public int? MessagePriority { get; set; }
/// <summary>
/// Gets or sets the notification sound.
/// </summary>
public string? NotificationSound { get; set; }
}
}
/// <summary>
/// Gets or sets the notification sound.
/// </summary>
public string? NotificationSound { get; set; }
}

View File

@ -8,59 +8,58 @@ using Jellyfin.Plugin.Webhook.Extensions;
using MediaBrowser.Common.Net;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Webhook.Destinations.Slack
namespace Jellyfin.Plugin.Webhook.Destinations.Slack;
/// <summary>
/// Client for the <see cref="SlackOption"/>.
/// </summary>
public class SlackClient : BaseClient, IWebhookClient<SlackOption>
{
private readonly ILogger<SlackClient> _logger;
private readonly IHttpClientFactory _httpClientFactory;
/// <summary>
/// Client for the <see cref="SlackOption"/>.
/// Initializes a new instance of the <see cref="SlackClient"/> class.
/// </summary>
public class SlackClient : BaseClient, IWebhookClient<SlackOption>
/// <param name="logger">Instance of the <see cref="ILogger{SlackClient}"/> interface.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/>.</param>
public SlackClient(ILogger<SlackClient> logger, IHttpClientFactory httpClientFactory)
{
private readonly ILogger<SlackClient> _logger;
private readonly IHttpClientFactory _httpClientFactory;
_logger = logger;
_httpClientFactory = httpClientFactory;
}
/// <summary>
/// Initializes a new instance of the <see cref="SlackClient"/> class.
/// </summary>
/// <param name="logger">Instance of the <see cref="ILogger{SlackClient}"/> interface.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/>.</param>
public SlackClient(ILogger<SlackClient> logger, IHttpClientFactory httpClientFactory)
/// <inheritdoc />
public async Task SendAsync(SlackOption option, Dictionary<string, object> data)
{
try
{
_logger = logger;
_httpClientFactory = httpClientFactory;
if (string.IsNullOrEmpty(option.WebhookUri))
{
throw new ArgumentException(nameof(option.WebhookUri));
}
if (!SendWebhook(_logger, option, data))
{
return;
}
data["SlackUsername"] = option.Username;
data["BotUsername"] = option.Username;
data["SlackIconUrl"] = option.IconUrl;
var body = option.GetMessageBody(data);
_logger.LogDebug("SendAsync Body: {@Body}", body);
using var content = new StringContent(body, Encoding.UTF8, MediaTypeNames.Application.Json);
using var response = await _httpClientFactory
.CreateClient(NamedClient.Default)
.PostAsync(new Uri(option.WebhookUri), content)
.ConfigureAwait(false);
await response.LogIfFailedAsync(_logger).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task SendAsync(SlackOption option, Dictionary<string, object> data)
catch (HttpRequestException e)
{
try
{
if (string.IsNullOrEmpty(option.WebhookUri))
{
throw new ArgumentException(nameof(option.WebhookUri));
}
if (!SendWebhook(_logger, option, data))
{
return;
}
data["SlackUsername"] = option.Username;
data["BotUsername"] = option.Username;
data["SlackIconUrl"] = option.IconUrl;
var body = option.GetMessageBody(data);
_logger.LogDebug("SendAsync Body: {@Body}", body);
using var content = new StringContent(body, Encoding.UTF8, MediaTypeNames.Application.Json);
using var response = await _httpClientFactory
.CreateClient(NamedClient.Default)
.PostAsync(new Uri(option.WebhookUri), content)
.ConfigureAwait(false);
await response.LogIfFailedAsync(_logger).ConfigureAwait(false);
}
catch (HttpRequestException e)
{
_logger.LogWarning(e, "Error sending notification");
}
_logger.LogWarning(e, "Error sending notification");
}
}
}

View File

@ -1,18 +1,17 @@
namespace Jellyfin.Plugin.Webhook.Destinations.Slack
namespace Jellyfin.Plugin.Webhook.Destinations.Slack;
/// <summary>
/// Slack specific options.
/// </summary>
public class SlackOption : BaseOption
{
/// <summary>
/// Slack specific options.
/// Gets or sets the username.
/// </summary>
public class SlackOption : BaseOption
{
/// <summary>
/// Gets or sets the username.
/// </summary>
public string Username { get; set; } = string.Empty;
public string Username { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the icon url.
/// </summary>
public string IconUrl { get; set; } = string.Empty;
}
}
/// <summary>
/// Gets or sets the icon url.
/// </summary>
public string IconUrl { get; set; } = string.Empty;
}

View File

@ -4,60 +4,59 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using MimeKit;
namespace Jellyfin.Plugin.Webhook.Destinations.Smtp
namespace Jellyfin.Plugin.Webhook.Destinations.Smtp;
/// <summary>
/// Client for the <see cref="SmtpOption"/>.
/// </summary>
public class SmtpClient : BaseClient, IWebhookClient<SmtpOption>
{
private readonly ILogger<SmtpClient> _logger;
/// <summary>
/// Client for the <see cref="SmtpOption"/>.
/// Initializes a new instance of the <see cref="SmtpClient"/> class.
/// </summary>
public class SmtpClient : BaseClient, IWebhookClient<SmtpOption>
/// <param name="logger">Instance of the <see cref="ILogger{SmtpClient}"/> interface.</param>
public SmtpClient(ILogger<SmtpClient> logger)
{
private readonly ILogger<SmtpClient> _logger;
_logger = logger;
}
/// <summary>
/// Initializes a new instance of the <see cref="SmtpClient"/> class.
/// </summary>
/// <param name="logger">Instance of the <see cref="ILogger{SmtpClient}"/> interface.</param>
public SmtpClient(ILogger<SmtpClient> logger)
/// <inheritdoc />
public async Task SendAsync(SmtpOption option, Dictionary<string, object> data)
{
try
{
_logger = logger;
if (!SendWebhook(_logger, option, data))
{
return;
}
var message = new MimeMessage();
message.From.Add(new MailboxAddress(option.SenderAddress, option.SenderAddress));
message.To.Add(new MailboxAddress(option.ReceiverAddress, option.ReceiverAddress));
message.Subject = option.GetCompiledSubjectTemplate()(data);
message.Body = new TextPart(option.IsHtml ? "html" : "plain")
{
Text = option.GetMessageBody(data)
};
using var smtpClient = new MailKit.Net.Smtp.SmtpClient();
await smtpClient.ConnectAsync(option.SmtpServer, option.SmtpPort, option.UseSsl)
.ConfigureAwait(false);
if (option.UseCredentials)
{
await smtpClient.AuthenticateAsync(option.Username, option.Password)
.ConfigureAwait(false);
}
await smtpClient.SendAsync(message)
.ConfigureAwait(false);
}
/// <inheritdoc />
public async Task SendAsync(SmtpOption option, Dictionary<string, object> data)
catch (Exception e)
{
try
{
if (!SendWebhook(_logger, option, data))
{
return;
}
var message = new MimeMessage();
message.From.Add(new MailboxAddress(option.SenderAddress, option.SenderAddress));
message.To.Add(new MailboxAddress(option.ReceiverAddress, option.ReceiverAddress));
message.Subject = option.GetCompiledSubjectTemplate()(data);
message.Body = new TextPart(option.IsHtml ? "html" : "plain")
{
Text = option.GetMessageBody(data)
};
using var smtpClient = new MailKit.Net.Smtp.SmtpClient();
await smtpClient.ConnectAsync(option.SmtpServer, option.SmtpPort, option.UseSsl)
.ConfigureAwait(false);
if (option.UseCredentials)
{
await smtpClient.AuthenticateAsync(option.Username, option.Password)
.ConfigureAwait(false);
}
await smtpClient.SendAsync(message)
.ConfigureAwait(false);
}
catch (Exception e)
{
_logger.LogWarning(e, "Error sending email");
}
_logger.LogWarning(e, "Error sending email");
}
}
}

View File

@ -1,72 +1,71 @@
using HandlebarsDotNet;
using Jellyfin.Plugin.Webhook.Helpers;
namespace Jellyfin.Plugin.Webhook.Destinations.Smtp
namespace Jellyfin.Plugin.Webhook.Destinations.Smtp;
/// <summary>
/// Smtp specific option.
/// </summary>
public class SmtpOption : BaseOption
{
private HandlebarsTemplate<object, string>? _compiledSubjectTemplate;
/// <summary>
/// Smtp specific option.
/// Gets or sets the sender address.
/// </summary>
public class SmtpOption : BaseOption
public string SenderAddress { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the receiver address.
/// </summary>
public string ReceiverAddress { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the smtp server.
/// </summary>
public string SmtpServer { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the smtp port.
/// </summary>
public int SmtpPort { get; set; } = 25;
/// <summary>
/// Gets or sets a value indicating whether use credentials.
/// </summary>
public bool UseCredentials { get; set; }
/// <summary>
/// Gets or sets the username.
/// </summary>
public string Username { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the password.
/// </summary>
public string Password { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether to use ssl.
/// </summary>
public bool UseSsl { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the body is html.
/// </summary>
public bool IsHtml { get; set; }
/// <summary>
/// Gets or sets the email subject template.
/// </summary>
public string SubjectTemplate { get; set; } = string.Empty;
/// <summary>
/// Gets the compiled handlebars subject template.
/// </summary>
/// <returns>The compiled handlebars subject template.</returns>
public HandlebarsTemplate<object, string> GetCompiledSubjectTemplate()
{
private HandlebarsTemplate<object, string>? _compiledSubjectTemplate;
/// <summary>
/// Gets or sets the sender address.
/// </summary>
public string SenderAddress { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the receiver address.
/// </summary>
public string ReceiverAddress { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the smtp server.
/// </summary>
public string SmtpServer { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the smtp port.
/// </summary>
public int SmtpPort { get; set; } = 25;
/// <summary>
/// Gets or sets a value indicating whether use credentials.
/// </summary>
public bool UseCredentials { get; set; }
/// <summary>
/// Gets or sets the username.
/// </summary>
public string Username { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the password.
/// </summary>
public string Password { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether to use ssl.
/// </summary>
public bool UseSsl { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the body is html.
/// </summary>
public bool IsHtml { get; set; }
/// <summary>
/// Gets or sets the email subject template.
/// </summary>
public string SubjectTemplate { get; set; } = string.Empty;
/// <summary>
/// Gets the compiled handlebars subject template.
/// </summary>
/// <returns>The compiled handlebars subject template.</returns>
public HandlebarsTemplate<object, string> GetCompiledSubjectTemplate()
{
return _compiledSubjectTemplate ??= Handlebars.Compile(HandlebarsFunctionHelpers.Base64Decode(SubjectTemplate));
}
return _compiledSubjectTemplate ??= Handlebars.Compile(HandlebarsFunctionHelpers.Base64Decode(SubjectTemplate));
}
}
}

View File

@ -2,40 +2,39 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Webhook.Extensions
namespace Jellyfin.Plugin.Webhook.Extensions;
/// <summary>
/// Extension methods for <see cref="HttpResponseMessage"/>.
/// </summary>
public static class HttpResponseMessageExtensions
{
/// <summary>
/// Extension methods for <see cref="HttpResponseMessage"/>.
/// Log a warning message if the <paramref name="response"/> contains an error status code.
/// </summary>
public static class HttpResponseMessageExtensions
/// <param name="response">The HTTP response to log if failed.</param>
/// <param name="logger">The logger to use to log the warning.</param>
/// <returns>A task representing the async operation.</returns>
public static async Task LogIfFailedAsync(this HttpResponseMessage response, ILogger logger)
{
/// <summary>
/// Log a warning message if the <paramref name="response"/> contains an error status code.
/// </summary>
/// <param name="response">The HTTP response to log if failed.</param>
/// <param name="logger">The logger to use to log the warning.</param>
/// <returns>A task representing the async operation.</returns>
public static async Task LogIfFailedAsync(this HttpResponseMessage response, ILogger logger)
// Don't log anything for successful responses
if (response.IsSuccessStatusCode)
{
// Don't log anything for successful responses
if (response.IsSuccessStatusCode)
{
return;
}
// Log the request that caused the failed response, if available
var request = response.RequestMessage;
if (request is not null)
{
var requestStr = request.Content is not null
? await request.Content.ReadAsStringAsync().ConfigureAwait(false)
: "<empty request body>";
logger.LogWarning("Notification failed with {Method} request to {Url}: {Content}", request.Method, request.RequestUri, requestStr);
}
// Log the response
var responseStr = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
logger.LogWarning("Notification failed with response status code {StatusCode}: {Content}", response.StatusCode, responseStr);
return;
}
// Log the request that caused the failed response, if available
var request = response.RequestMessage;
if (request is not null)
{
var requestStr = request.Content is not null
? await request.Content.ReadAsStringAsync().ConfigureAwait(false)
: "<empty request body>";
logger.LogWarning("Notification failed with {Method} request to {Url}: {Content}", request.Method, request.RequestUri, requestStr);
}
// Log the response
var responseStr = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
logger.LogWarning("Notification failed with response status code {StatusCode}: {Content}", response.StatusCode, responseStr);
}
}

View File

@ -13,349 +13,348 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Updates;
namespace Jellyfin.Plugin.Webhook.Helpers
namespace Jellyfin.Plugin.Webhook.Helpers;
/// <summary>
/// Data object helpers.
/// </summary>
public static class DataObjectHelpers
{
/// <summary>
/// Data object helpers.
/// Gets the default data object.
/// </summary>
public static class DataObjectHelpers
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="notificationType">The notification type.</param>
/// <returns>The default data object.</returns>
public static Dictionary<string, object> GetBaseDataObject(IServerApplicationHost applicationHost, NotificationType notificationType)
{
/// <summary>
/// Gets the default data object.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="notificationType">The notification type.</param>
/// <returns>The default data object.</returns>
public static Dictionary<string, object> GetBaseDataObject(IServerApplicationHost applicationHost, NotificationType notificationType)
{
var dataObject = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
dataObject["ServerId"] = applicationHost.SystemId;
dataObject["ServerName"] = applicationHost.FriendlyName.Escape();
dataObject["ServerVersion"] = applicationHost.ApplicationVersionString;
dataObject["ServerUrl"] = WebhookPlugin.Instance?.Configuration.ServerUrl ?? "localhost:8096";
dataObject[nameof(NotificationType)] = notificationType.ToString();
var dataObject = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
dataObject["ServerId"] = applicationHost.SystemId;
dataObject["ServerName"] = applicationHost.FriendlyName.Escape();
dataObject["ServerVersion"] = applicationHost.ApplicationVersionString;
dataObject["ServerUrl"] = WebhookPlugin.Instance?.Configuration.ServerUrl ?? "localhost:8096";
dataObject[nameof(NotificationType)] = notificationType.ToString();
return dataObject;
}
/// <summary>
/// Get data object from <see cref="BaseItem"/>.
/// </summary>
/// <param name="dataObject">The existing data object.</param>
/// <param name="item">Instance of the <see cref="BaseItem"/>.</param>
/// <returns>The data object.</returns>
public static Dictionary<string, object> AddBaseItemData(this Dictionary<string, object> dataObject, BaseItem? item)
{
if (item is null)
{
return dataObject;
}
/// <summary>
/// Get data object from <see cref="BaseItem"/>.
/// </summary>
/// <param name="dataObject">The existing data object.</param>
/// <param name="item">Instance of the <see cref="BaseItem"/>.</param>
/// <returns>The data object.</returns>
public static Dictionary<string, object> AddBaseItemData(this Dictionary<string, object> dataObject, BaseItem? item)
dataObject["Timestamp"] = DateTime.Now;
dataObject["UtcTimestamp"] = DateTime.UtcNow;
dataObject["Name"] = item.Name.Escape();
dataObject["Overview"] = item.Overview.Escape();
dataObject["Tagline"] = item.Tagline.Escape();
dataObject["ItemId"] = item.Id;
dataObject["ItemType"] = item.GetType().Name.Escape();
dataObject["RunTimeTicks"] = item.RunTimeTicks ?? 0;
dataObject["RunTime"] = TimeSpan.FromTicks(item.RunTimeTicks ?? 0).ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture);
if (item.ProductionYear is not null)
{
if (item is null)
{
return dataObject;
}
dataObject["Year"] = item.ProductionYear;
}
dataObject["Timestamp"] = DateTime.Now;
dataObject["UtcTimestamp"] = DateTime.UtcNow;
dataObject["Name"] = item.Name.Escape();
dataObject["Overview"] = item.Overview.Escape();
dataObject["Tagline"] = item.Tagline.Escape();
dataObject["ItemId"] = item.Id;
dataObject["ItemType"] = item.GetType().Name.Escape();
dataObject["RunTimeTicks"] = item.RunTimeTicks ?? 0;
dataObject["RunTime"] = TimeSpan.FromTicks(item.RunTimeTicks ?? 0).ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture);
if (item.ProductionYear is not null)
{
dataObject["Year"] = item.ProductionYear;
}
switch (item)
{
case Season season:
if (!string.IsNullOrEmpty(season.Series?.Name))
{
dataObject["SeriesName"] = season.Series.Name.Escape();
}
if (season.Series?.ProductionYear is not null)
{
dataObject["Year"] = season.Series.ProductionYear;
}
if (season.IndexNumber is not null)
{
dataObject["SeasonNumber"] = season.IndexNumber;
dataObject["SeasonNumber00"] = season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
dataObject["SeasonNumber000"] = season.IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture);
}
break;
case Episode episode:
if (!string.IsNullOrEmpty(episode.Series?.Name))
{
dataObject["SeriesName"] = episode.Series.Name.Escape();
}
if (episode.Season?.IndexNumber is not null)
{
dataObject["SeasonNumber"] = episode.Season.IndexNumber;
dataObject["SeasonNumber00"] = episode.Season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
dataObject["SeasonNumber000"] = episode.Season.IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture);
}
if (episode.IndexNumber is not null)
{
dataObject["EpisodeNumber"] = episode.IndexNumber;
dataObject["EpisodeNumber00"] = episode.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
dataObject["EpisodeNumber000"] = episode.IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture);
}
if (episode.IndexNumberEnd is not null)
{
dataObject["EpisodeNumberEnd"] = episode.IndexNumberEnd;
dataObject["EpisodeNumberEnd00"] = episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
dataObject["EpisodeNumberEnd000"] = episode.IndexNumberEnd.Value.ToString("000", CultureInfo.InvariantCulture);
}
if (episode.Series?.ProductionYear is not null)
{
dataObject["Year"] = episode.Series.ProductionYear;
}
break;
case Audio audio:
if (!string.IsNullOrEmpty(audio.Album))
{
dataObject["Album"] = audio.Album;
}
if (audio.Artists.Count != 0)
{
// Should all artists be sent?
dataObject["Artist"] = audio.Artists[0];
}
if (audio.ProductionYear is not null)
{
dataObject["Year"] = audio.ProductionYear;
}
break;
case MusicAlbum album:
if (album.Artists.Count != 0)
{
// Should all artists be sent?
dataObject["Artist"] = album.Artists[0];
}
if (album.ProductionYear is not null)
{
dataObject["Year"] = album.ProductionYear;
}
break;
}
foreach (var (providerKey, providerValue) in item.ProviderIds)
{
dataObject[$"Provider_{providerKey.ToLowerInvariant()}"] = providerValue;
}
var itemMediaStreams = item.GetMediaStreams();
if (itemMediaStreams is not null)
{
var streamCounter = new Dictionary<MediaStreamType, int>();
foreach (var mediaStream in itemMediaStreams)
switch (item)
{
case Season season:
if (!string.IsNullOrEmpty(season.Series?.Name))
{
streamCounter.TryGetValue(mediaStream.Type, out var count);
streamCounter[mediaStream.Type] = count + 1;
var baseKey = $"{mediaStream.Type}_{count}";
dataObject["SeriesName"] = season.Series.Name.Escape();
}
switch (mediaStream.Type)
if (season.Series?.ProductionYear is not null)
{
dataObject["Year"] = season.Series.ProductionYear;
}
if (season.IndexNumber is not null)
{
dataObject["SeasonNumber"] = season.IndexNumber;
dataObject["SeasonNumber00"] = season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
dataObject["SeasonNumber000"] = season.IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture);
}
break;
case Episode episode:
if (!string.IsNullOrEmpty(episode.Series?.Name))
{
dataObject["SeriesName"] = episode.Series.Name.Escape();
}
if (episode.Season?.IndexNumber is not null)
{
dataObject["SeasonNumber"] = episode.Season.IndexNumber;
dataObject["SeasonNumber00"] = episode.Season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
dataObject["SeasonNumber000"] = episode.Season.IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture);
}
if (episode.IndexNumber is not null)
{
dataObject["EpisodeNumber"] = episode.IndexNumber;
dataObject["EpisodeNumber00"] = episode.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
dataObject["EpisodeNumber000"] = episode.IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture);
}
if (episode.IndexNumberEnd is not null)
{
dataObject["EpisodeNumberEnd"] = episode.IndexNumberEnd;
dataObject["EpisodeNumberEnd00"] = episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
dataObject["EpisodeNumberEnd000"] = episode.IndexNumberEnd.Value.ToString("000", CultureInfo.InvariantCulture);
}
if (episode.Series?.ProductionYear is not null)
{
dataObject["Year"] = episode.Series.ProductionYear;
}
break;
case Audio audio:
if (!string.IsNullOrEmpty(audio.Album))
{
dataObject["Album"] = audio.Album;
}
if (audio.Artists.Count != 0)
{
// Should all artists be sent?
dataObject["Artist"] = audio.Artists[0];
}
if (audio.ProductionYear is not null)
{
dataObject["Year"] = audio.ProductionYear;
}
break;
case MusicAlbum album:
if (album.Artists.Count != 0)
{
// Should all artists be sent?
dataObject["Artist"] = album.Artists[0];
}
if (album.ProductionYear is not null)
{
dataObject["Year"] = album.ProductionYear;
}
break;
}
foreach (var (providerKey, providerValue) in item.ProviderIds)
{
dataObject[$"Provider_{providerKey.ToLowerInvariant()}"] = providerValue;
}
var itemMediaStreams = item.GetMediaStreams();
if (itemMediaStreams is not null)
{
var streamCounter = new Dictionary<MediaStreamType, int>();
foreach (var mediaStream in itemMediaStreams)
{
streamCounter.TryGetValue(mediaStream.Type, out var count);
streamCounter[mediaStream.Type] = count + 1;
var baseKey = $"{mediaStream.Type}_{count}";
switch (mediaStream.Type)
{
case MediaStreamType.Audio:
{
case MediaStreamType.Audio:
{
dataObject[baseKey + "_Title"] = mediaStream.DisplayTitle;
dataObject[baseKey + "_Type"] = mediaStream.Type.ToString();
dataObject[baseKey + "_Language"] = mediaStream.Language;
dataObject[baseKey + "_Codec"] = mediaStream.Codec;
dataObject[baseKey + "_Channels"] = mediaStream.Channels ?? 0;
dataObject[baseKey + "_Bitrate"] = mediaStream.BitRate ?? 0;
dataObject[baseKey + "_SampleRate"] = mediaStream.SampleRate ?? 0;
dataObject[baseKey + "_Default"] = mediaStream.IsDefault;
break;
}
case MediaStreamType.Video:
{
dataObject[baseKey + "_Title"] = mediaStream.DisplayTitle;
dataObject[baseKey + "_Type"] = mediaStream.Type.ToString();
dataObject[baseKey + "_Codec"] = mediaStream.Codec;
dataObject[baseKey + "_Profile"] = mediaStream.Profile;
dataObject[baseKey + "_Level"] = mediaStream.Level ?? 0;
dataObject[baseKey + "_Height"] = mediaStream.Height ?? 0;
dataObject[baseKey + "_Width"] = mediaStream.Width ?? 0;
dataObject[baseKey + "_AspectRatio"] = mediaStream.AspectRatio;
dataObject[baseKey + "_Interlaced"] = mediaStream.IsInterlaced;
dataObject[baseKey + "_FrameRate"] = mediaStream.RealFrameRate ?? 0;
dataObject[baseKey + "_VideoRange"] = mediaStream.VideoRange;
dataObject[baseKey + "_ColorSpace"] = mediaStream.ColorSpace;
dataObject[baseKey + "_ColorTransfer"] = mediaStream.ColorTransfer;
dataObject[baseKey + "_ColorPrimaries"] = mediaStream.ColorPrimaries;
dataObject[baseKey + "_PixelFormat"] = mediaStream.PixelFormat;
dataObject[baseKey + "_RefFrames"] = mediaStream.RefFrames ?? 0;
break;
}
case MediaStreamType.Subtitle:
dataObject[baseKey + "_Title"] = mediaStream.DisplayTitle;
dataObject[baseKey + "_Type"] = mediaStream.Type.ToString();
dataObject[baseKey + "_Language"] = mediaStream.Language;
dataObject[baseKey + "_Codec"] = mediaStream.Codec;
dataObject[baseKey + "_Default"] = mediaStream.IsDefault;
dataObject[baseKey + "_Forced"] = mediaStream.IsForced;
dataObject[baseKey + "_External"] = mediaStream.IsExternal;
break;
dataObject[baseKey + "_Title"] = mediaStream.DisplayTitle;
dataObject[baseKey + "_Type"] = mediaStream.Type.ToString();
dataObject[baseKey + "_Language"] = mediaStream.Language;
dataObject[baseKey + "_Codec"] = mediaStream.Codec;
dataObject[baseKey + "_Channels"] = mediaStream.Channels ?? 0;
dataObject[baseKey + "_Bitrate"] = mediaStream.BitRate ?? 0;
dataObject[baseKey + "_SampleRate"] = mediaStream.SampleRate ?? 0;
dataObject[baseKey + "_Default"] = mediaStream.IsDefault;
break;
}
case MediaStreamType.Video:
{
dataObject[baseKey + "_Title"] = mediaStream.DisplayTitle;
dataObject[baseKey + "_Type"] = mediaStream.Type.ToString();
dataObject[baseKey + "_Codec"] = mediaStream.Codec;
dataObject[baseKey + "_Profile"] = mediaStream.Profile;
dataObject[baseKey + "_Level"] = mediaStream.Level ?? 0;
dataObject[baseKey + "_Height"] = mediaStream.Height ?? 0;
dataObject[baseKey + "_Width"] = mediaStream.Width ?? 0;
dataObject[baseKey + "_AspectRatio"] = mediaStream.AspectRatio;
dataObject[baseKey + "_Interlaced"] = mediaStream.IsInterlaced;
dataObject[baseKey + "_FrameRate"] = mediaStream.RealFrameRate ?? 0;
dataObject[baseKey + "_VideoRange"] = mediaStream.VideoRange;
dataObject[baseKey + "_ColorSpace"] = mediaStream.ColorSpace;
dataObject[baseKey + "_ColorTransfer"] = mediaStream.ColorTransfer;
dataObject[baseKey + "_ColorPrimaries"] = mediaStream.ColorPrimaries;
dataObject[baseKey + "_PixelFormat"] = mediaStream.PixelFormat;
dataObject[baseKey + "_RefFrames"] = mediaStream.RefFrames ?? 0;
break;
}
case MediaStreamType.Subtitle:
dataObject[baseKey + "_Title"] = mediaStream.DisplayTitle;
dataObject[baseKey + "_Type"] = mediaStream.Type.ToString();
dataObject[baseKey + "_Language"] = mediaStream.Language;
dataObject[baseKey + "_Codec"] = mediaStream.Codec;
dataObject[baseKey + "_Default"] = mediaStream.IsDefault;
dataObject[baseKey + "_Forced"] = mediaStream.IsForced;
dataObject[baseKey + "_External"] = mediaStream.IsExternal;
break;
}
}
return dataObject;
}
/// <summary>
/// Add playback progress data.
/// </summary>
/// <param name="dataObject">The data object.</param>
/// <param name="playbackProgressEventArgs">The playback progress event args.</param>
/// <returns>The modified data object.</returns>
public static Dictionary<string, object> AddPlaybackProgressData(this Dictionary<string, object> dataObject, PlaybackProgressEventArgs playbackProgressEventArgs)
{
dataObject[nameof(playbackProgressEventArgs.PlaybackPositionTicks)] = playbackProgressEventArgs.PlaybackPositionTicks ?? 0;
dataObject["PlaybackPosition"] = TimeSpan.FromTicks(playbackProgressEventArgs.PlaybackPositionTicks ?? 0).ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture);
dataObject[nameof(playbackProgressEventArgs.MediaSourceId)] = playbackProgressEventArgs.MediaSourceId;
dataObject[nameof(playbackProgressEventArgs.IsPaused)] = playbackProgressEventArgs.IsPaused;
dataObject[nameof(playbackProgressEventArgs.IsAutomated)] = playbackProgressEventArgs.IsAutomated;
dataObject[nameof(playbackProgressEventArgs.DeviceId)] = playbackProgressEventArgs.DeviceId;
dataObject[nameof(playbackProgressEventArgs.DeviceName)] = playbackProgressEventArgs.DeviceName;
dataObject[nameof(playbackProgressEventArgs.ClientName)] = playbackProgressEventArgs.ClientName;
return dataObject;
}
/// <summary>
/// Add user data.
/// </summary>
/// <param name="dataObject">The data object.</param>
/// <param name="user">The user to add.</param>
/// <returns>The modified data object.</returns>
public static Dictionary<string, object> AddUserData(this Dictionary<string, object> dataObject, UserDto user)
{
dataObject["NotificationUsername"] = user.Name.Escape();
dataObject["UserId"] = user.Id;
dataObject[nameof(user.LastLoginDate)] = user.LastLoginDate ?? DateTime.UtcNow;
dataObject[nameof(user.LastActivityDate)] = user.LastActivityDate ?? DateTime.MinValue;
return dataObject;
}
/// <summary>
/// Add user data.
/// </summary>
/// <param name="dataObject">The data object.</param>
/// <param name="user">The user to add.</param>
/// <returns>The modified data object.</returns>
public static Dictionary<string, object> AddUserData(this Dictionary<string, object> dataObject, User user)
{
dataObject["NotificationUsername"] = user.Username.Escape();
dataObject["UserId"] = user.Id;
dataObject[nameof(user.LastLoginDate)] = user.LastLoginDate ?? DateTime.UtcNow;
dataObject[nameof(user.LastActivityDate)] = user.LastActivityDate ?? DateTime.MinValue;
return dataObject;
}
/// <summary>
/// Add session info data.
/// </summary>
/// <param name="dataObject">The data object.</param>
/// <param name="sessionInfo">The session info to add.</param>
/// <returns>The modified data object.</returns>
public static Dictionary<string, object> AddSessionInfoData(this Dictionary<string, object> dataObject, SessionInfo sessionInfo)
{
dataObject[nameof(sessionInfo.Id)] = sessionInfo.Id;
dataObject[nameof(sessionInfo.UserId)] = sessionInfo.UserId;
dataObject["NotificationUsername"] = sessionInfo.UserName.Escape();
dataObject[nameof(sessionInfo.Client)] = sessionInfo.Client.Escape();
dataObject[nameof(sessionInfo.LastActivityDate)] = sessionInfo.LastActivityDate;
dataObject[nameof(sessionInfo.LastPlaybackCheckIn)] = sessionInfo.LastPlaybackCheckIn;
dataObject[nameof(sessionInfo.DeviceName)] = sessionInfo.DeviceName.Escape();
dataObject[nameof(sessionInfo.DeviceId)] = sessionInfo.DeviceId;
return dataObject;
}
/// <summary>
/// Add plugin installation info.
/// </summary>
/// <param name="dataObject">The data object.</param>
/// <param name="installationInfo">The plugin installation info to add.</param>
/// <returns>The modified data object.</returns>
public static Dictionary<string, object> AddPluginInstallationInfo(this Dictionary<string, object> dataObject, InstallationInfo installationInfo)
{
dataObject["PluginId"] = installationInfo.Id;
dataObject["PluginName"] = installationInfo.Name.Escape();
dataObject["PluginVersion"] = installationInfo.Version;
dataObject["PluginChangelog"] = installationInfo.Changelog.Escape();
dataObject["PluginChecksum"] = installationInfo.Checksum;
dataObject["PluginSourceUrl"] = installationInfo.SourceUrl;
return dataObject;
}
/// <summary>
/// Add exception info.
/// </summary>
/// <param name="dataObject">The data object.</param>
/// <param name="exception">The exception to add.</param>
/// <returns>The modified data object.</returns>
public static Dictionary<string, object> AddExceptionInfo(this Dictionary<string, object> dataObject, Exception exception)
{
dataObject["ExceptionMessage"] = exception.Message.Escape();
dataObject["ExceptionMessageInner"] = exception.InnerException?.Message ?? string.Empty;
return dataObject;
}
/// <summary>
/// Add user item data.
/// </summary>
/// <param name="dataObject">The data object.</param>
/// <param name="userItemData">The user item data.</param>
/// <returns>The modified data object.</returns>
public static Dictionary<string, object> AddUserItemData(this Dictionary<string, object> dataObject, UserItemData userItemData)
{
dataObject["Likes"] = userItemData.Likes ?? false;
dataObject["Rating"] = userItemData.Rating ?? 0;
dataObject["PlaybackPositionTicks"] = userItemData.PlaybackPositionTicks;
dataObject["PlaybackPosition"] = TimeSpan.FromTicks(userItemData.PlaybackPositionTicks).ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture);
dataObject["PlayCount"] = userItemData.PlayCount;
dataObject["Favorite"] = userItemData.IsFavorite;
dataObject["Played"] = userItemData.Played;
dataObject["AudioStreamIndex"] = userItemData.AudioStreamIndex ?? -1;
dataObject["SubtitleStreamIndex"] = userItemData.SubtitleStreamIndex ?? -1;
if (userItemData.LastPlayedDate.HasValue)
{
dataObject["LastPlayedDate"] = userItemData.LastPlayedDate;
}
return dataObject;
}
/// <summary>
/// Escape quotes for proper json.
/// </summary>
/// <param name="input">Input string.</param>
/// <returns>Escaped string.</returns>
private static string Escape(this string? input)
=> input?.Replace("\"", "\\\"", StringComparison.Ordinal) ?? string.Empty;
return dataObject;
}
/// <summary>
/// Add playback progress data.
/// </summary>
/// <param name="dataObject">The data object.</param>
/// <param name="playbackProgressEventArgs">The playback progress event args.</param>
/// <returns>The modified data object.</returns>
public static Dictionary<string, object> AddPlaybackProgressData(this Dictionary<string, object> dataObject, PlaybackProgressEventArgs playbackProgressEventArgs)
{
dataObject[nameof(playbackProgressEventArgs.PlaybackPositionTicks)] = playbackProgressEventArgs.PlaybackPositionTicks ?? 0;
dataObject["PlaybackPosition"] = TimeSpan.FromTicks(playbackProgressEventArgs.PlaybackPositionTicks ?? 0).ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture);
dataObject[nameof(playbackProgressEventArgs.MediaSourceId)] = playbackProgressEventArgs.MediaSourceId;
dataObject[nameof(playbackProgressEventArgs.IsPaused)] = playbackProgressEventArgs.IsPaused;
dataObject[nameof(playbackProgressEventArgs.IsAutomated)] = playbackProgressEventArgs.IsAutomated;
dataObject[nameof(playbackProgressEventArgs.DeviceId)] = playbackProgressEventArgs.DeviceId;
dataObject[nameof(playbackProgressEventArgs.DeviceName)] = playbackProgressEventArgs.DeviceName;
dataObject[nameof(playbackProgressEventArgs.ClientName)] = playbackProgressEventArgs.ClientName;
return dataObject;
}
/// <summary>
/// Add user data.
/// </summary>
/// <param name="dataObject">The data object.</param>
/// <param name="user">The user to add.</param>
/// <returns>The modified data object.</returns>
public static Dictionary<string, object> AddUserData(this Dictionary<string, object> dataObject, UserDto user)
{
dataObject["NotificationUsername"] = user.Name.Escape();
dataObject["UserId"] = user.Id;
dataObject[nameof(user.LastLoginDate)] = user.LastLoginDate ?? DateTime.UtcNow;
dataObject[nameof(user.LastActivityDate)] = user.LastActivityDate ?? DateTime.MinValue;
return dataObject;
}
/// <summary>
/// Add user data.
/// </summary>
/// <param name="dataObject">The data object.</param>
/// <param name="user">The user to add.</param>
/// <returns>The modified data object.</returns>
public static Dictionary<string, object> AddUserData(this Dictionary<string, object> dataObject, User user)
{
dataObject["NotificationUsername"] = user.Username.Escape();
dataObject["UserId"] = user.Id;
dataObject[nameof(user.LastLoginDate)] = user.LastLoginDate ?? DateTime.UtcNow;
dataObject[nameof(user.LastActivityDate)] = user.LastActivityDate ?? DateTime.MinValue;
return dataObject;
}
/// <summary>
/// Add session info data.
/// </summary>
/// <param name="dataObject">The data object.</param>
/// <param name="sessionInfo">The session info to add.</param>
/// <returns>The modified data object.</returns>
public static Dictionary<string, object> AddSessionInfoData(this Dictionary<string, object> dataObject, SessionInfo sessionInfo)
{
dataObject[nameof(sessionInfo.Id)] = sessionInfo.Id;
dataObject[nameof(sessionInfo.UserId)] = sessionInfo.UserId;
dataObject["NotificationUsername"] = sessionInfo.UserName.Escape();
dataObject[nameof(sessionInfo.Client)] = sessionInfo.Client.Escape();
dataObject[nameof(sessionInfo.LastActivityDate)] = sessionInfo.LastActivityDate;
dataObject[nameof(sessionInfo.LastPlaybackCheckIn)] = sessionInfo.LastPlaybackCheckIn;
dataObject[nameof(sessionInfo.DeviceName)] = sessionInfo.DeviceName.Escape();
dataObject[nameof(sessionInfo.DeviceId)] = sessionInfo.DeviceId;
return dataObject;
}
/// <summary>
/// Add plugin installation info.
/// </summary>
/// <param name="dataObject">The data object.</param>
/// <param name="installationInfo">The plugin installation info to add.</param>
/// <returns>The modified data object.</returns>
public static Dictionary<string, object> AddPluginInstallationInfo(this Dictionary<string, object> dataObject, InstallationInfo installationInfo)
{
dataObject["PluginId"] = installationInfo.Id;
dataObject["PluginName"] = installationInfo.Name.Escape();
dataObject["PluginVersion"] = installationInfo.Version;
dataObject["PluginChangelog"] = installationInfo.Changelog.Escape();
dataObject["PluginChecksum"] = installationInfo.Checksum;
dataObject["PluginSourceUrl"] = installationInfo.SourceUrl;
return dataObject;
}
/// <summary>
/// Add exception info.
/// </summary>
/// <param name="dataObject">The data object.</param>
/// <param name="exception">The exception to add.</param>
/// <returns>The modified data object.</returns>
public static Dictionary<string, object> AddExceptionInfo(this Dictionary<string, object> dataObject, Exception exception)
{
dataObject["ExceptionMessage"] = exception.Message.Escape();
dataObject["ExceptionMessageInner"] = exception.InnerException?.Message ?? string.Empty;
return dataObject;
}
/// <summary>
/// Add user item data.
/// </summary>
/// <param name="dataObject">The data object.</param>
/// <param name="userItemData">The user item data.</param>
/// <returns>The modified data object.</returns>
public static Dictionary<string, object> AddUserItemData(this Dictionary<string, object> dataObject, UserItemData userItemData)
{
dataObject["Likes"] = userItemData.Likes ?? false;
dataObject["Rating"] = userItemData.Rating ?? 0;
dataObject["PlaybackPositionTicks"] = userItemData.PlaybackPositionTicks;
dataObject["PlaybackPosition"] = TimeSpan.FromTicks(userItemData.PlaybackPositionTicks).ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture);
dataObject["PlayCount"] = userItemData.PlayCount;
dataObject["Favorite"] = userItemData.IsFavorite;
dataObject["Played"] = userItemData.Played;
dataObject["AudioStreamIndex"] = userItemData.AudioStreamIndex ?? -1;
dataObject["SubtitleStreamIndex"] = userItemData.SubtitleStreamIndex ?? -1;
if (userItemData.LastPlayedDate.HasValue)
{
dataObject["LastPlayedDate"] = userItemData.LastPlayedDate;
}
return dataObject;
}
/// <summary>
/// Escape quotes for proper json.
/// </summary>
/// <param name="input">Input string.</param>
/// <returns>Escaped string.</returns>
private static string Escape(this string? input)
=> input?.Replace("\"", "\\\"", StringComparison.Ordinal) ?? string.Empty;
}

View File

@ -2,88 +2,87 @@
using System.Globalization;
using HandlebarsDotNet;
namespace Jellyfin.Plugin.Webhook.Helpers
namespace Jellyfin.Plugin.Webhook.Helpers;
/// <summary>
/// Handlebar helpers.
/// </summary>
public static class HandlebarsFunctionHelpers
{
/// <summary>
/// Handlebar helpers.
/// </summary>
public static class HandlebarsFunctionHelpers
private static readonly HandlebarsBlockHelper StringEqualityHelper = (output, options, context, arguments) =>
{
private static readonly HandlebarsBlockHelper StringEqualityHelper = (output, options, context, arguments) =>
if (arguments.Length != 2)
{
if (arguments.Length != 2)
{
throw new HandlebarsException("{{if_equals}} helper must have exactly two arguments");
}
var left = GetStringValue(arguments[0]);
var right = GetStringValue(arguments[1]);
if (string.Equals(left, right, StringComparison.OrdinalIgnoreCase))
{
options.Template(output, context);
}
else
{
options.Inverse(output, context);
}
};
private static readonly HandlebarsBlockHelper StringExistHelper = (output, options, context, arguments) =>
{
if (arguments.Length != 1)
{
throw new HandlebarsException("{{if_exist}} helper must have exactly one argument");
}
var arg = GetStringValue(arguments[0]);
if (string.IsNullOrEmpty(arg))
{
options.Inverse(output, context);
}
else
{
options.Template(output, context);
}
};
/// <summary>
/// Register handlebars helpers.
/// </summary>
public static void RegisterHelpers()
{
Handlebars.RegisterHelper("if_equals", StringEqualityHelper);
Handlebars.RegisterHelper("if_exist", StringExistHelper);
Handlebars.RegisterHelper("link_to", (writer, context, parameters) =>
{
writer.WriteSafeString($"<a href='{parameters["url"]}'>{context["text"]}</a>");
});
throw new HandlebarsException("{{if_equals}} helper must have exactly two arguments");
}
/// <summary>
/// Base 64 decode.
/// </summary>
/// <remarks>
/// The template is stored as base64 in config.
/// </remarks>
/// <param name="base64EncodedData">The encoded data.</param>
/// <returns>The decoded string.</returns>
public static string Base64Decode(string? base64EncodedData)
var left = GetStringValue(arguments[0]);
var right = GetStringValue(arguments[1]);
if (string.Equals(left, right, StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(base64EncodedData))
{
return string.Empty;
}
options.Template(output, context);
}
else
{
options.Inverse(output, context);
}
};
var base64EncodedBytes = Convert.FromBase64String(base64EncodedData);
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
private static readonly HandlebarsBlockHelper StringExistHelper = (output, options, context, arguments) =>
{
if (arguments.Length != 1)
{
throw new HandlebarsException("{{if_exist}} helper must have exactly one argument");
}
private static string? GetStringValue(object? input)
var arg = GetStringValue(arguments[0]);
if (string.IsNullOrEmpty(arg))
{
// UndefinedBindingResult means the parameter was a part of the provided dataset.
return input is UndefinedBindingResult or null
? null
: Convert.ToString(input, CultureInfo.InvariantCulture);
options.Inverse(output, context);
}
else
{
options.Template(output, context);
}
};
/// <summary>
/// Register handlebars helpers.
/// </summary>
public static void RegisterHelpers()
{
Handlebars.RegisterHelper("if_equals", StringEqualityHelper);
Handlebars.RegisterHelper("if_exist", StringExistHelper);
Handlebars.RegisterHelper("link_to", (writer, context, parameters) =>
{
writer.WriteSafeString($"<a href='{parameters["url"]}'>{context["text"]}</a>");
});
}
/// <summary>
/// Base 64 decode.
/// </summary>
/// <remarks>
/// The template is stored as base64 in config.
/// </remarks>
/// <param name="base64EncodedData">The encoded data.</param>
/// <returns>The decoded string.</returns>
public static string Base64Decode(string? base64EncodedData)
{
if (string.IsNullOrEmpty(base64EncodedData))
{
return string.Empty;
}
var base64EncodedBytes = Convert.FromBase64String(base64EncodedData);
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
}
private static string? GetStringValue(object? input)
{
// UndefinedBindingResult means the parameter was a part of the provided dataset.
return input is UndefinedBindingResult or null
? null
: Convert.ToString(input, CultureInfo.InvariantCulture);
}
}

View File

@ -3,20 +3,19 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Jellyfin.Plugin.Webhook.Destinations;
namespace Jellyfin.Plugin.Webhook
namespace Jellyfin.Plugin.Webhook;
/// <summary>
/// Webhook sender interface.
/// </summary>
public interface IWebhookSender
{
/// <summary>
/// Webhook sender interface.
/// Send notification with item type.
/// </summary>
public interface IWebhookSender
{
/// <summary>
/// Send notification with item type.
/// </summary>
/// <param name="notificationType">The notification type.</param>
/// <param name="itemData">The item data.</param>
/// <param name="itemType">The item type. Default <c>null</c>.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task SendNotification(NotificationType notificationType, Dictionary<string, object> itemData, Type? itemType = null);
}
/// <param name="notificationType">The notification type.</param>
/// <param name="itemData">The item data.</param>
/// <param name="itemType">The item type. Default <c>null</c>.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task SendNotification(NotificationType notificationType, Dictionary<string, object> itemData, Type? itemType = null);
}

View File

@ -15,12 +15,12 @@
<PackageReference Include="MailKit" Version="3.4.1" />
<PackageReference Include="Handlebars.Net" Version="2.1.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.*" />
<PackageReference Include="MQTTnet.Extensions.ManagedClient" Version="4.1.0.247" />
<PackageReference Include="MQTTnet.Extensions.ManagedClient" Version="4.1.1.318" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>

View File

@ -1,30 +1,29 @@
using System;
namespace Jellyfin.Plugin.Webhook.Models
namespace Jellyfin.Plugin.Webhook.Models;
/// <summary>
/// Queued item container.
/// </summary>
public class QueuedItemContainer
{
/// <summary>
/// Queued item container.
/// Initializes a new instance of the <see cref="QueuedItemContainer"/> class.
/// </summary>
public class QueuedItemContainer
/// <param name="id">The item id.</param>
public QueuedItemContainer(Guid id)
{
/// <summary>
/// Initializes a new instance of the <see cref="QueuedItemContainer"/> class.
/// </summary>
/// <param name="id">The item id.</param>
public QueuedItemContainer(Guid id)
{
ItemId = id;
RetryCount = 0;
}
/// <summary>
/// Gets or sets the current retry count.
/// </summary>
public int RetryCount { get; set; }
/// <summary>
/// Gets or sets the current item id.
/// </summary>
public Guid ItemId { get; set; }
ItemId = id;
RetryCount = 0;
}
}
/// <summary>
/// Gets or sets the current retry count.
/// </summary>
public int RetryCount { get; set; }
/// <summary>
/// Gets or sets the current item id.
/// </summary>
public Guid ItemId { get; set; }
}

View File

@ -6,48 +6,47 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Session;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// Authentication failure notifier.
/// </summary>
public class AuthenticationFailureNotifier : IEventConsumer<GenericEventArgs<AuthenticationRequest>>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// Authentication failure notifier.
/// Initializes a new instance of the <see cref="AuthenticationFailureNotifier"/> class.
/// </summary>
public class AuthenticationFailureNotifier : IEventConsumer<GenericEventArgs<AuthenticationRequest>>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public AuthenticationFailureNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationFailureNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public AuthenticationFailureNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
/// <inheritdoc />
public async Task OnEvent(GenericEventArgs<AuthenticationRequest> eventArgs)
{
if (eventArgs.Argument is null)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
return;
}
/// <inheritdoc />
public async Task OnEvent(GenericEventArgs<AuthenticationRequest> eventArgs)
{
if (eventArgs.Argument is null)
{
return;
}
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.AuthenticationFailure);
dataObject[nameof(eventArgs.Argument.App)] = eventArgs.Argument.App;
dataObject[nameof(eventArgs.Argument.Username)] = eventArgs.Argument.Username;
dataObject[nameof(eventArgs.Argument.UserId)] = eventArgs.Argument.UserId;
dataObject[nameof(eventArgs.Argument.AppVersion)] = eventArgs.Argument.AppVersion;
dataObject[nameof(eventArgs.Argument.DeviceId)] = eventArgs.Argument.DeviceId;
dataObject[nameof(eventArgs.Argument.DeviceName)] = eventArgs.Argument.DeviceName;
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.AuthenticationFailure);
dataObject[nameof(eventArgs.Argument.App)] = eventArgs.Argument.App;
dataObject[nameof(eventArgs.Argument.Username)] = eventArgs.Argument.Username;
dataObject[nameof(eventArgs.Argument.UserId)] = eventArgs.Argument.UserId;
dataObject[nameof(eventArgs.Argument.AppVersion)] = eventArgs.Argument.AppVersion;
dataObject[nameof(eventArgs.Argument.DeviceId)] = eventArgs.Argument.DeviceId;
dataObject[nameof(eventArgs.Argument.DeviceName)] = eventArgs.Argument.DeviceName;
await _webhookSender.SendNotification(NotificationType.AuthenticationFailure, dataObject)
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.AuthenticationFailure, dataObject)
.ConfigureAwait(false);
}
}

View File

@ -6,43 +6,42 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Events;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// Authentication success notifier.
/// </summary>
public class AuthenticationSuccessNotifier : IEventConsumer<GenericEventArgs<AuthenticationResult>>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// Authentication success notifier.
/// Initializes a new instance of the <see cref="AuthenticationSuccessNotifier"/> class.
/// </summary>
public class AuthenticationSuccessNotifier : IEventConsumer<GenericEventArgs<AuthenticationResult>>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public AuthenticationSuccessNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationSuccessNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public AuthenticationSuccessNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
/// <inheritdoc />
public async Task OnEvent(GenericEventArgs<AuthenticationResult> eventArgs)
{
if (eventArgs.Argument is null)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
return;
}
/// <inheritdoc />
public async Task OnEvent(GenericEventArgs<AuthenticationResult> eventArgs)
{
if (eventArgs.Argument is null)
{
return;
}
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.AuthenticationSuccess)
.AddUserData(eventArgs.Argument.User);
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.AuthenticationSuccess)
.AddUserData(eventArgs.Argument.User);
await _webhookSender.SendNotification(NotificationType.AuthenticationSuccess, dataObject)
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.AuthenticationSuccess, dataObject)
.ConfigureAwait(false);
}
}

View File

@ -6,48 +6,47 @@ using Jellyfin.Plugin.Webhook.Helpers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Notifications;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// Generic notifier.
/// </summary>
public class GenericNotifier : INotificationService
{
private readonly IWebhookSender _webhookSender;
private readonly IServerApplicationHost _applicationHost;
/// <summary>
/// Generic notifier.
/// Initializes a new instance of the <see cref="GenericNotifier"/> class.
/// </summary>
public class GenericNotifier : INotificationService
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
public GenericNotifier(IWebhookSender webhookSender, IServerApplicationHost applicationHost)
{
private readonly IWebhookSender _webhookSender;
private readonly IServerApplicationHost _applicationHost;
/// <summary>
/// Initializes a new instance of the <see cref="GenericNotifier"/> class.
/// </summary>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
public GenericNotifier(IWebhookSender webhookSender, IServerApplicationHost applicationHost)
{
_webhookSender = webhookSender;
_applicationHost = applicationHost;
}
/// <inheritdoc />
public string Name => "Webhook: Generic Notifier";
/// <inheritdoc />
public async Task SendNotification(UserNotification request, CancellationToken cancellationToken)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.Generic);
dataObject[nameof(request.Name)] = request.Name;
dataObject[nameof(request.Description)] = request.Description;
dataObject[nameof(request.Date)] = request.Date;
dataObject[nameof(request.Level)] = request.Level;
dataObject[nameof(request.Url)] = request.Url;
dataObject[nameof(request.User.Username)] = request.User.Username;
dataObject["UserId"] = request.User.Id;
await _webhookSender.SendNotification(NotificationType.Generic, dataObject)
.ConfigureAwait(false);
}
/// <inheritdoc />
public bool IsEnabledForUser(User user) => true;
_webhookSender = webhookSender;
_applicationHost = applicationHost;
}
/// <inheritdoc />
public string Name => "Webhook: Generic Notifier";
/// <inheritdoc />
public async Task SendNotification(UserNotification request, CancellationToken cancellationToken)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.Generic);
dataObject[nameof(request.Name)] = request.Name;
dataObject[nameof(request.Description)] = request.Description;
dataObject[nameof(request.Date)] = request.Date;
dataObject[nameof(request.Level)] = request.Level;
dataObject[nameof(request.Url)] = request.Url;
dataObject[nameof(request.User.Username)] = request.User.Username;
dataObject["UserId"] = request.User.Id;
await _webhookSender.SendNotification(NotificationType.Generic, dataObject)
.ConfigureAwait(false);
}
/// <inheritdoc />
public bool IsEnabledForUser(User user) => true;
}

View File

@ -1,23 +1,22 @@
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
namespace Jellyfin.Plugin.Webhook.Notifiers.ItemAddedNotifier
namespace Jellyfin.Plugin.Webhook.Notifiers.ItemAddedNotifier;
/// <summary>
/// Item added manager interface.
/// </summary>
public interface IItemAddedManager
{
/// <summary>
/// Item added manager interface.
/// Process the current queue.
/// </summary>
public interface IItemAddedManager
{
/// <summary>
/// Process the current queue.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public Task ProcessItemsAsync();
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public Task ProcessItemsAsync();
/// <summary>
/// Add item to process queue.
/// </summary>
/// <param name="item">The added item.</param>
public void AddItem(BaseItem item);
}
}
/// <summary>
/// Add item to process queue.
/// </summary>
/// <param name="item">The added item.</param>
public void AddItem(BaseItem item);
}

View File

@ -9,86 +9,85 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Webhook.Notifiers.ItemAddedNotifier
namespace Jellyfin.Plugin.Webhook.Notifiers.ItemAddedNotifier;
/// <inheritdoc />
public class ItemAddedManager : IItemAddedManager
{
/// <inheritdoc />
public class ItemAddedManager : IItemAddedManager
private const int MaxRetries = 10;
private readonly ILogger<ItemAddedManager> _logger;
private readonly ILibraryManager _libraryManager;
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
private readonly ConcurrentDictionary<Guid, QueuedItemContainer> _itemProcessQueue;
/// <summary>
/// Initializes a new instance of the <see cref="ItemAddedManager"/> class.
/// </summary>
/// <param name="logger">Instance of the <see cref="ILogger{ItemAddedManager}"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public ItemAddedManager(
ILogger<ItemAddedManager> logger,
ILibraryManager libraryManager,
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private const int MaxRetries = 10;
private readonly ILogger<ItemAddedManager> _logger;
private readonly ILibraryManager _libraryManager;
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
private readonly ConcurrentDictionary<Guid, QueuedItemContainer> _itemProcessQueue;
_logger = logger;
_libraryManager = libraryManager;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
_itemProcessQueue = new ConcurrentDictionary<Guid, QueuedItemContainer>();
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemAddedManager"/> class.
/// </summary>
/// <param name="logger">Instance of the <see cref="ILogger{ItemAddedManager}"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public ItemAddedManager(
ILogger<ItemAddedManager> logger,
ILibraryManager libraryManager,
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
/// <inheritdoc />
public async Task ProcessItemsAsync()
{
_logger.LogDebug("ProcessItemsAsync");
// Attempt to process all items in queue.
var currentItems = _itemProcessQueue.ToArray();
foreach (var (key, container) in currentItems)
{
_logger = logger;
_libraryManager = libraryManager;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
_itemProcessQueue = new ConcurrentDictionary<Guid, QueuedItemContainer>();
}
/// <inheritdoc />
public async Task ProcessItemsAsync()
{
_logger.LogDebug("ProcessItemsAsync");
// Attempt to process all items in queue.
var currentItems = _itemProcessQueue.ToArray();
foreach (var (key, container) in currentItems)
var item = _libraryManager.GetItemById(key);
if (item is null)
{
var item = _libraryManager.GetItemById(key);
if (item is null)
{
// Remove item from queue.
_itemProcessQueue.TryRemove(key, out _);
return;
}
_logger.LogDebug("Item {ItemName}", item.Name);
// Metadata not refreshed yet and under retry limit.
if (item.ProviderIds.Keys.Count == 0 && container.RetryCount < MaxRetries)
{
_logger.LogDebug("Requeue {ItemName}, no provider ids", item.Name);
container.RetryCount++;
_itemProcessQueue.AddOrUpdate(key, container, (_, _) => container);
continue;
}
_logger.LogDebug("Notifying for {ItemName}", item.Name);
// Send notification to each configured destination.
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.ItemAdded)
.AddBaseItemData(item);
var itemType = item.GetType();
await _webhookSender.SendNotification(NotificationType.ItemAdded, dataObject, itemType)
.ConfigureAwait(false);
// Remove item from queue.
_itemProcessQueue.TryRemove(key, out _);
return;
}
}
/// <inheritdoc />
public void AddItem(BaseItem item)
{
_itemProcessQueue.TryAdd(item.Id, new QueuedItemContainer(item.Id));
_logger.LogDebug("Queued {ItemName} for notification", item.Name);
_logger.LogDebug("Item {ItemName}", item.Name);
// Metadata not refreshed yet and under retry limit.
if (item.ProviderIds.Keys.Count == 0 && container.RetryCount < MaxRetries)
{
_logger.LogDebug("Requeue {ItemName}, no provider ids", item.Name);
container.RetryCount++;
_itemProcessQueue.AddOrUpdate(key, container, (_, _) => container);
continue;
}
_logger.LogDebug("Notifying for {ItemName}", item.Name);
// Send notification to each configured destination.
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.ItemAdded)
.AddBaseItemData(item);
var itemType = item.GetType();
await _webhookSender.SendNotification(NotificationType.ItemAdded, dataObject, itemType)
.ConfigureAwait(false);
// Remove item from queue.
_itemProcessQueue.TryRemove(key, out _);
}
}
/// <inheritdoc />
public void AddItem(BaseItem item)
{
_itemProcessQueue.TryAdd(item.Id, new QueuedItemContainer(item.Id));
_logger.LogDebug("Queued {ItemName} for notification", item.Name);
}
}

View File

@ -4,65 +4,64 @@ using Jellyfin.Plugin.Webhook.Helpers;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;
namespace Jellyfin.Plugin.Webhook.Notifiers.ItemAddedNotifier
namespace Jellyfin.Plugin.Webhook.Notifiers.ItemAddedNotifier;
/// <summary>
/// Notifier when a library item is added.
/// </summary>
public class ItemAddedNotifierEntryPoint : IServerEntryPoint
{
private readonly IItemAddedManager _itemAddedManager;
private readonly ILibraryManager _libraryManager;
/// <summary>
/// Notifier when a library item is added.
/// Initializes a new instance of the <see cref="ItemAddedNotifierEntryPoint"/> class.
/// </summary>
public class ItemAddedNotifierEntryPoint : IServerEntryPoint
/// <param name="itemAddedManager">Instance of the <see cref="IItemAddedManager"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
public ItemAddedNotifierEntryPoint(
IItemAddedManager itemAddedManager,
ILibraryManager libraryManager)
{
private readonly IItemAddedManager _itemAddedManager;
private readonly ILibraryManager _libraryManager;
_itemAddedManager = itemAddedManager;
_libraryManager = libraryManager;
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemAddedNotifierEntryPoint"/> class.
/// </summary>
/// <param name="itemAddedManager">Instance of the <see cref="IItemAddedManager"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
public ItemAddedNotifierEntryPoint(
IItemAddedManager itemAddedManager,
ILibraryManager libraryManager)
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <inheritdoc />
public Task RunAsync()
{
_libraryManager.ItemAdded += ItemAddedHandler;
HandlebarsFunctionHelpers.RegisterHelpers();
return Task.CompletedTask;
}
/// <summary>
/// Dispose.
/// </summary>
/// <param name="disposing">Dispose all assets.</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_itemAddedManager = itemAddedManager;
_libraryManager = libraryManager;
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <inheritdoc />
public Task RunAsync()
{
_libraryManager.ItemAdded += ItemAddedHandler;
HandlebarsFunctionHelpers.RegisterHelpers();
return Task.CompletedTask;
}
/// <summary>
/// Dispose.
/// </summary>
/// <param name="disposing">Dispose all assets.</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_libraryManager.ItemAdded -= ItemAddedHandler;
}
}
private void ItemAddedHandler(object? sender, ItemChangeEventArgs itemChangeEventArgs)
{
// Never notify on virtual items.
if (itemChangeEventArgs.Item.IsVirtualItem)
{
return;
}
_itemAddedManager.AddItem(itemChangeEventArgs.Item);
_libraryManager.ItemAdded -= ItemAddedHandler;
}
}
}
private void ItemAddedHandler(object? sender, ItemChangeEventArgs itemChangeEventArgs)
{
// Never notify on virtual items.
if (itemChangeEventArgs.Item.IsVirtualItem)
{
return;
}
_itemAddedManager.AddItem(itemChangeEventArgs.Item);
}
}

View File

@ -5,68 +5,67 @@ using System.Threading.Tasks;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
namespace Jellyfin.Plugin.Webhook.Notifiers.ItemAddedNotifier
namespace Jellyfin.Plugin.Webhook.Notifiers.ItemAddedNotifier;
/// <summary>
/// Scheduled task that processes item added events.
/// </summary>
public class ItemAddedScheduledTask : IScheduledTask, IConfigurableScheduledTask
{
private const int RecheckIntervalSec = 30;
private readonly IItemAddedManager _itemAddedManager;
private readonly ILocalizationManager _localizationManager;
/// <summary>
/// Scheduled task that processes item added events.
/// Initializes a new instance of the <see cref="ItemAddedScheduledTask"/> class.
/// </summary>
public class ItemAddedScheduledTask : IScheduledTask, IConfigurableScheduledTask
/// <param name="itemAddedManager">Instance of the <see cref="IItemAddedManager"/> interface.</param>
/// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param>
public ItemAddedScheduledTask(
IItemAddedManager itemAddedManager,
ILocalizationManager localizationManager)
{
private const int RecheckIntervalSec = 30;
private readonly IItemAddedManager _itemAddedManager;
private readonly ILocalizationManager _localizationManager;
_itemAddedManager = itemAddedManager;
_localizationManager = localizationManager;
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemAddedScheduledTask"/> class.
/// </summary>
/// <param name="itemAddedManager">Instance of the <see cref="IItemAddedManager"/> interface.</param>
/// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param>
public ItemAddedScheduledTask(
IItemAddedManager itemAddedManager,
ILocalizationManager localizationManager)
/// <inheritdoc />
public string Name => "Webhook Item Added Notifier";
/// <inheritdoc />
public string Key => "WebhookItemAdded";
/// <inheritdoc />
public string Description => "Processes item added queue";
/// <inheritdoc />
public string Category => _localizationManager.GetLocalizedString("TasksLibraryCategory");
/// <inheritdoc />
public bool IsHidden => false;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => false;
/// <inheritdoc />
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
return _itemAddedManager.ProcessItemsAsync();
}
/// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[]
{
_itemAddedManager = itemAddedManager;
_localizationManager = localizationManager;
}
/// <inheritdoc />
public string Name => "Webhook Item Added Notifier";
/// <inheritdoc />
public string Key => "WebhookItemAdded";
/// <inheritdoc />
public string Description => "Processes item added queue";
/// <inheritdoc />
public string Category => _localizationManager.GetLocalizedString("TasksLibraryCategory");
/// <inheritdoc />
public bool IsHidden => false;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => false;
/// <inheritdoc />
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
return _itemAddedManager.ProcessItemsAsync();
}
/// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[]
new TaskTriggerInfo
{
new TaskTriggerInfo
{
Type = TaskTriggerInfo.TriggerInterval,
IntervalTicks = TimeSpan.FromSeconds(RecheckIntervalSec).Ticks
}
};
}
Type = TaskTriggerInfo.TriggerInterval,
IntervalTicks = TimeSpan.FromSeconds(RecheckIntervalSec).Ticks
}
};
}
}

View File

@ -5,37 +5,36 @@ using Jellyfin.Plugin.Webhook.Helpers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// Pending restart notifier.
/// </summary>
public class PendingRestartNotifier : IEventConsumer<PendingRestartEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// Pending restart notifier.
/// Initializes a new instance of the <see cref="PendingRestartNotifier"/> class.
/// </summary>
public class PendingRestartNotifier : IEventConsumer<PendingRestartEventArgs>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PendingRestartNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="PendingRestartNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PendingRestartNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <inheritdoc />
public async Task OnEvent(PendingRestartEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PendingRestart);
/// <inheritdoc />
public async Task OnEvent(PendingRestartEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PendingRestart);
await _webhookSender.SendNotification(NotificationType.PendingRestart, dataObject)
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.PendingRestart, dataObject)
.ConfigureAwait(false);
}
}

View File

@ -6,65 +6,64 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Library;
namespace Jellyfin.Plugin.Webhook.Notifiers
{
/// <summary>
/// Playback progress notifier.
/// </summary>
public class PlaybackProgressNotifier : IEventConsumer<PlaybackProgressEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// Initializes a new instance of the <see cref="PlaybackProgressNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PlaybackProgressNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
/// <summary>
/// Playback progress notifier.
/// </summary>
public class PlaybackProgressNotifier : IEventConsumer<PlaybackProgressEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// Initializes a new instance of the <see cref="PlaybackProgressNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PlaybackProgressNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <inheritdoc />
public async Task OnEvent(PlaybackProgressEventArgs eventArgs)
{
if (eventArgs.Item is null)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
return;
}
/// <inheritdoc />
public async Task OnEvent(PlaybackProgressEventArgs eventArgs)
if (eventArgs.Item.IsThemeMedia)
{
if (eventArgs.Item is null)
// Don't report theme song or local trailer playback.
return;
}
if (eventArgs.Users.Count == 0)
{
// No users in playback session.
return;
}
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PlaybackProgress)
.AddBaseItemData(eventArgs.Item)
.AddPlaybackProgressData(eventArgs);
foreach (var user in eventArgs.Users)
{
var userDataObject = new Dictionary<string, object>(dataObject)
{
return;
}
["NotificationUsername"] = user.Username,
["UserId"] = user.Id
};
if (eventArgs.Item.IsThemeMedia)
{
// Don't report theme song or local trailer playback.
return;
}
if (eventArgs.Users.Count == 0)
{
// No users in playback session.
return;
}
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PlaybackProgress)
.AddBaseItemData(eventArgs.Item)
.AddPlaybackProgressData(eventArgs);
foreach (var user in eventArgs.Users)
{
var userDataObject = new Dictionary<string, object>(dataObject)
{
["NotificationUsername"] = user.Username,
["UserId"] = user.Id
};
await _webhookSender.SendNotification(NotificationType.PlaybackProgress, userDataObject, eventArgs.Item.GetType())
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.PlaybackProgress, userDataObject, eventArgs.Item.GetType())
.ConfigureAwait(false);
}
}
}

View File

@ -6,65 +6,64 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Library;
namespace Jellyfin.Plugin.Webhook.Notifiers
{
/// <summary>
/// Playback start notifier.
/// </summary>
public class PlaybackStartNotifier : IEventConsumer<PlaybackStartEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// Initializes a new instance of the <see cref="PlaybackStartNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PlaybackStartNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
/// <summary>
/// Playback start notifier.
/// </summary>
public class PlaybackStartNotifier : IEventConsumer<PlaybackStartEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// Initializes a new instance of the <see cref="PlaybackStartNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PlaybackStartNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <inheritdoc />
public async Task OnEvent(PlaybackStartEventArgs eventArgs)
{
if (eventArgs.Item is null)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
return;
}
/// <inheritdoc />
public async Task OnEvent(PlaybackStartEventArgs eventArgs)
if (eventArgs.Item.IsThemeMedia)
{
if (eventArgs.Item is null)
// Don't report theme song or local trailer playback.
return;
}
if (eventArgs.Users.Count == 0)
{
// No users in playback session.
return;
}
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PlaybackStart)
.AddBaseItemData(eventArgs.Item)
.AddPlaybackProgressData(eventArgs);
foreach (var user in eventArgs.Users)
{
var userDataObject = new Dictionary<string, object>(dataObject)
{
return;
}
["NotificationUsername"] = user.Username,
["UserId"] = user.Id
};
if (eventArgs.Item.IsThemeMedia)
{
// Don't report theme song or local trailer playback.
return;
}
if (eventArgs.Users.Count == 0)
{
// No users in playback session.
return;
}
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PlaybackStart)
.AddBaseItemData(eventArgs.Item)
.AddPlaybackProgressData(eventArgs);
foreach (var user in eventArgs.Users)
{
var userDataObject = new Dictionary<string, object>(dataObject)
{
["NotificationUsername"] = user.Username,
["UserId"] = user.Id
};
await _webhookSender.SendNotification(NotificationType.PlaybackStart, userDataObject, eventArgs.Item.GetType())
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.PlaybackStart, userDataObject, eventArgs.Item.GetType())
.ConfigureAwait(false);
}
}
}

View File

@ -6,66 +6,65 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Library;
namespace Jellyfin.Plugin.Webhook.Notifiers
{
/// <summary>
/// Playback stop notifier.
/// </summary>
public class PlaybackStopNotifier : IEventConsumer<PlaybackStopEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// Initializes a new instance of the <see cref="PlaybackStopNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PlaybackStopNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
/// <summary>
/// Playback stop notifier.
/// </summary>
public class PlaybackStopNotifier : IEventConsumer<PlaybackStopEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// Initializes a new instance of the <see cref="PlaybackStopNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PlaybackStopNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <inheritdoc />
public async Task OnEvent(PlaybackStopEventArgs eventArgs)
{
if (eventArgs.Item is null)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
return;
}
/// <inheritdoc />
public async Task OnEvent(PlaybackStopEventArgs eventArgs)
if (eventArgs.Item.IsThemeMedia)
{
if (eventArgs.Item is null)
// Don't report theme song or local trailer playback.
return;
}
if (eventArgs.Users.Count == 0)
{
// No users in playback session.
return;
}
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PlaybackStop)
.AddBaseItemData(eventArgs.Item)
.AddPlaybackProgressData(eventArgs);
dataObject[nameof(eventArgs.PlayedToCompletion)] = eventArgs.PlayedToCompletion;
foreach (var user in eventArgs.Users)
{
var userDataObject = new Dictionary<string, object>(dataObject)
{
return;
}
["NotificationUsername"] = user.Username,
["UserId"] = user.Id
};
if (eventArgs.Item.IsThemeMedia)
{
// Don't report theme song or local trailer playback.
return;
}
if (eventArgs.Users.Count == 0)
{
// No users in playback session.
return;
}
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PlaybackStop)
.AddBaseItemData(eventArgs.Item)
.AddPlaybackProgressData(eventArgs);
dataObject[nameof(eventArgs.PlayedToCompletion)] = eventArgs.PlayedToCompletion;
foreach (var user in eventArgs.Users)
{
var userDataObject = new Dictionary<string, object>(dataObject)
{
["NotificationUsername"] = user.Username,
["UserId"] = user.Id
};
await _webhookSender.SendNotification(NotificationType.PlaybackStop, userDataObject, eventArgs.Item.GetType())
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.PlaybackStop, userDataObject, eventArgs.Item.GetType())
.ConfigureAwait(false);
}
}
}

View File

@ -5,38 +5,37 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Events.Updates;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// Plugin installation cancelled.
/// </summary>
public class PluginInstallationCancelledNotifier : IEventConsumer<PluginInstallationCancelledEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// Plugin installation cancelled.
/// Initializes a new instance of the <see cref="PluginInstallationCancelledNotifier"/> class.
/// </summary>
public class PluginInstallationCancelledNotifier : IEventConsumer<PluginInstallationCancelledEventArgs>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PluginInstallationCancelledNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="PluginInstallationCancelledNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PluginInstallationCancelledNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <inheritdoc />
public async Task OnEvent(PluginInstallationCancelledEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PluginInstallationCancelled)
.AddPluginInstallationInfo(eventArgs.Argument);
/// <inheritdoc />
public async Task OnEvent(PluginInstallationCancelledEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PluginInstallationCancelled)
.AddPluginInstallationInfo(eventArgs.Argument);
await _webhookSender.SendNotification(NotificationType.PluginInstallationCancelled, dataObject)
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.PluginInstallationCancelled, dataObject)
.ConfigureAwait(false);
}
}

View File

@ -5,39 +5,38 @@ using MediaBrowser.Common.Updates;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// Plugin installation failed notifier.
/// </summary>
public class PluginInstallationFailedNotifier : IEventConsumer<InstallationFailedEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// Plugin installation failed notifier.
/// Initializes a new instance of the <see cref="PluginInstallationFailedNotifier"/> class.
/// </summary>
public class PluginInstallationFailedNotifier : IEventConsumer<InstallationFailedEventArgs>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PluginInstallationFailedNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="PluginInstallationFailedNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PluginInstallationFailedNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <inheritdoc />
public async Task OnEvent(InstallationFailedEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PluginInstallationFailed)
.AddPluginInstallationInfo(eventArgs.InstallationInfo)
.AddExceptionInfo(eventArgs.Exception);
/// <inheritdoc />
public async Task OnEvent(InstallationFailedEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PluginInstallationFailed)
.AddPluginInstallationInfo(eventArgs.InstallationInfo)
.AddExceptionInfo(eventArgs.Exception);
await _webhookSender.SendNotification(NotificationType.PluginInstallationFailed, dataObject)
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.PluginInstallationFailed, dataObject)
.ConfigureAwait(false);
}
}

View File

@ -5,38 +5,37 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Events.Updates;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// Plugin installed notifier.
/// </summary>
public class PluginInstalledNotifier : IEventConsumer<PluginInstalledEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// Plugin installed notifier.
/// Initializes a new instance of the <see cref="PluginInstalledNotifier"/> class.
/// </summary>
public class PluginInstalledNotifier : IEventConsumer<PluginInstalledEventArgs>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PluginInstalledNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="PluginInstalledNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PluginInstalledNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <inheritdoc />
public async Task OnEvent(PluginInstalledEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PluginInstalled)
.AddPluginInstallationInfo(eventArgs.Argument);
/// <inheritdoc />
public async Task OnEvent(PluginInstalledEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PluginInstalled)
.AddPluginInstallationInfo(eventArgs.Argument);
await _webhookSender.SendNotification(NotificationType.PluginInstalled, dataObject)
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.PluginInstalled, dataObject)
.ConfigureAwait(false);
}
}

View File

@ -5,38 +5,37 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Events.Updates;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// Plugin installing notifier.
/// </summary>
public class PluginInstallingNotifier : IEventConsumer<PluginInstallingEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// Plugin installing notifier.
/// Initializes a new instance of the <see cref="PluginInstallingNotifier"/> class.
/// </summary>
public class PluginInstallingNotifier : IEventConsumer<PluginInstallingEventArgs>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PluginInstallingNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="PluginInstallingNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PluginInstallingNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <inheritdoc />
public async Task OnEvent(PluginInstallingEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PluginInstalling)
.AddPluginInstallationInfo(eventArgs.Argument);
/// <inheritdoc />
public async Task OnEvent(PluginInstallingEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PluginInstalling)
.AddPluginInstallationInfo(eventArgs.Argument);
await _webhookSender.SendNotification(NotificationType.PluginInstalling, dataObject)
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.PluginInstalling, dataObject)
.ConfigureAwait(false);
}
}

View File

@ -5,42 +5,41 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Events.Updates;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// Plugin uninstalled notifier.
/// </summary>
public class PluginUninstalledNotifier : IEventConsumer<PluginUninstalledEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// Plugin uninstalled notifier.
/// Initializes a new instance of the <see cref="PluginUninstalledNotifier"/> class.
/// </summary>
public class PluginUninstalledNotifier : IEventConsumer<PluginUninstalledEventArgs>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PluginUninstalledNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="PluginUninstalledNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PluginUninstalledNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <inheritdoc />
public async Task OnEvent(PluginUninstalledEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PluginUninstalled);
dataObject["PluginId"] = eventArgs.Argument.Id;
dataObject["PluginName"] = eventArgs.Argument.Name;
dataObject["PluginDescription"] = eventArgs.Argument.Description;
dataObject["PluginVersion"] = eventArgs.Argument.Version;
dataObject["PluginStatus"] = eventArgs.Argument.Status;
/// <inheritdoc />
public async Task OnEvent(PluginUninstalledEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PluginUninstalled);
dataObject["PluginId"] = eventArgs.Argument.Id;
dataObject["PluginName"] = eventArgs.Argument.Name;
dataObject["PluginDescription"] = eventArgs.Argument.Description;
dataObject["PluginVersion"] = eventArgs.Argument.Version;
dataObject["PluginStatus"] = eventArgs.Argument.Status;
await _webhookSender.SendNotification(NotificationType.PluginUninstalled, dataObject)
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.PluginUninstalled, dataObject)
.ConfigureAwait(false);
}
}

View File

@ -5,38 +5,37 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Events.Updates;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// Plugin updated notifier.
/// </summary>
public class PluginUpdatedNotifier : IEventConsumer<PluginUpdatedEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// Plugin updated notifier.
/// Initializes a new instance of the <see cref="PluginUpdatedNotifier"/> class.
/// </summary>
public class PluginUpdatedNotifier : IEventConsumer<PluginUpdatedEventArgs>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PluginUpdatedNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="PluginUpdatedNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public PluginUpdatedNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <inheritdoc />
public async Task OnEvent(PluginUpdatedEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PluginUpdated)
.AddPluginInstallationInfo(eventArgs.Argument);
/// <inheritdoc />
public async Task OnEvent(PluginUpdatedEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.PluginUpdated)
.AddPluginInstallationInfo(eventArgs.Argument);
await _webhookSender.SendNotification(NotificationType.PluginUpdated, dataObject)
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.PluginUpdated, dataObject)
.ConfigureAwait(false);
}
}

View File

@ -5,44 +5,43 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Events.Session;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// Session start notifier.
/// </summary>
public class SessionStartNotifier : IEventConsumer<SessionStartedEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// Session start notifier.
/// Initializes a new instance of the <see cref="SessionStartNotifier"/> class.
/// </summary>
public class SessionStartNotifier : IEventConsumer<SessionStartedEventArgs>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public SessionStartNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="SessionStartNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public SessionStartNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
/// <inheritdoc />
public async Task OnEvent(SessionStartedEventArgs eventArgs)
{
if (eventArgs.Argument is null)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
return;
}
/// <inheritdoc />
public async Task OnEvent(SessionStartedEventArgs eventArgs)
{
if (eventArgs.Argument is null)
{
return;
}
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.SessionStart)
.AddSessionInfoData(eventArgs.Argument)
.AddBaseItemData(eventArgs.Argument.FullNowPlayingItem);
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.SessionStart)
.AddSessionInfoData(eventArgs.Argument)
.AddBaseItemData(eventArgs.Argument.FullNowPlayingItem);
await _webhookSender.SendNotification(NotificationType.SessionStart, dataObject)
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.SessionStart, dataObject)
.ConfigureAwait(false);
}
}

View File

@ -5,42 +5,41 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Subtitles;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// Subtitle download failure notifier.
/// </summary>
public class SubtitleDownloadFailureNotifier : IEventConsumer<SubtitleDownloadFailureEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// Subtitle download failure notifier.
/// Initializes a new instance of the <see cref="SubtitleDownloadFailureNotifier"/> class.
/// </summary>
public class SubtitleDownloadFailureNotifier : IEventConsumer<SubtitleDownloadFailureEventArgs>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public SubtitleDownloadFailureNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="SubtitleDownloadFailureNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public SubtitleDownloadFailureNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
/// <inheritdoc />
public async Task OnEvent(SubtitleDownloadFailureEventArgs eventArgs)
{
if (eventArgs.Item is null)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
return;
}
/// <inheritdoc />
public async Task OnEvent(SubtitleDownloadFailureEventArgs eventArgs)
{
if (eventArgs.Item is null)
{
return;
}
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.SubtitleDownloadFailure)
.AddBaseItemData(eventArgs.Item);
await _webhookSender.SendNotification(NotificationType.SubtitleDownloadFailure, dataObject, eventArgs.Item.GetType())
.ConfigureAwait(false);
}
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.SubtitleDownloadFailure)
.AddBaseItemData(eventArgs.Item);
await _webhookSender.SendNotification(NotificationType.SubtitleDownloadFailure, dataObject, eventArgs.Item.GetType())
.ConfigureAwait(false);
}
}

View File

@ -5,50 +5,49 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
using MediaBrowser.Model.Tasks;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// Task completed notifier.
/// </summary>
public class TaskCompletedNotifier : IEventConsumer<TaskCompletionEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// Task completed notifier.
/// Initializes a new instance of the <see cref="TaskCompletedNotifier"/> class.
/// </summary>
public class TaskCompletedNotifier : IEventConsumer<TaskCompletionEventArgs>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public TaskCompletedNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="TaskCompletedNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public TaskCompletedNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <inheritdoc />
public async Task OnEvent(TaskCompletionEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.TaskCompleted);
dataObject["TaskId"] = eventArgs.Task.Id;
dataObject["TaskName"] = eventArgs.Task.Name;
dataObject["TaskDescription"] = eventArgs.Task.Description;
dataObject["TaskCategory"] = eventArgs.Task.Category;
dataObject["TaskState"] = eventArgs.Task.State.ToString();
dataObject["ResultId"] = eventArgs.Result.Id;
dataObject["ResultKey"] = eventArgs.Result.Key;
dataObject["ResultName"] = eventArgs.Result.Name;
dataObject["ResultStatus"] = eventArgs.Result.Status.ToString();
dataObject["StartTime"] = eventArgs.Result.StartTimeUtc;
dataObject["EndTime"] = eventArgs.Result.EndTimeUtc;
dataObject["ResultErrorMessage"] = eventArgs.Result.ErrorMessage;
dataObject["ResultLongErrorMessage"] = eventArgs.Result.LongErrorMessage;
/// <inheritdoc />
public async Task OnEvent(TaskCompletionEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.TaskCompleted);
dataObject["TaskId"] = eventArgs.Task.Id;
dataObject["TaskName"] = eventArgs.Task.Name;
dataObject["TaskDescription"] = eventArgs.Task.Description;
dataObject["TaskCategory"] = eventArgs.Task.Category;
dataObject["TaskState"] = eventArgs.Task.State.ToString();
dataObject["ResultId"] = eventArgs.Result.Id;
dataObject["ResultKey"] = eventArgs.Result.Key;
dataObject["ResultName"] = eventArgs.Result.Name;
dataObject["ResultStatus"] = eventArgs.Result.Status.ToString();
dataObject["StartTime"] = eventArgs.Result.StartTimeUtc;
dataObject["EndTime"] = eventArgs.Result.EndTimeUtc;
dataObject["ResultErrorMessage"] = eventArgs.Result.ErrorMessage;
dataObject["ResultLongErrorMessage"] = eventArgs.Result.LongErrorMessage;
await _webhookSender.SendNotification(NotificationType.TaskCompleted, dataObject)
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.TaskCompleted, dataObject)
.ConfigureAwait(false);
}
}

View File

@ -5,38 +5,37 @@ using Jellyfin.Plugin.Webhook.Helpers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// User created notifier.
/// </summary>
public class UserCreatedNotifier : IEventConsumer<UserCreatedEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// User created notifier.
/// Initializes a new instance of the <see cref="UserCreatedNotifier"/> class.
/// </summary>
public class UserCreatedNotifier : IEventConsumer<UserCreatedEventArgs>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public UserCreatedNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="UserCreatedNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public UserCreatedNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <inheritdoc />
public async Task OnEvent(UserCreatedEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.UserCreated)
.AddUserData(eventArgs.Argument);
/// <inheritdoc />
public async Task OnEvent(UserCreatedEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.UserCreated)
.AddUserData(eventArgs.Argument);
await _webhookSender.SendNotification(NotificationType.UserCreated, dataObject)
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.UserCreated, dataObject)
.ConfigureAwait(false);
}
}

View File

@ -7,104 +7,103 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Webhook.Notifiers.UserDataSavedNotifier
namespace Jellyfin.Plugin.Webhook.Notifiers.UserDataSavedNotifier;
/// <summary>
/// User data saved notifier.
/// </summary>
public class UserDataSavedNotifierEntryPoint : IServerEntryPoint
{
private readonly IWebhookSender _webhookSender;
private readonly IServerApplicationHost _applicationHost;
private readonly IUserDataManager _userDataManager;
private readonly IUserManager _userManager;
private readonly ILogger<UserDataSavedNotifierEntryPoint> _logger;
/// <summary>
/// User data saved notifier.
/// Initializes a new instance of the <see cref="UserDataSavedNotifierEntryPoint"/> class.
/// </summary>
public class UserDataSavedNotifierEntryPoint : IServerEntryPoint
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{UserDataChangedNotifierEntryPoint}"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
public UserDataSavedNotifierEntryPoint(
IWebhookSender webhookSender,
IServerApplicationHost applicationHost,
IUserDataManager userDataManager,
ILogger<UserDataSavedNotifierEntryPoint> logger,
IUserManager userManager)
{
private readonly IWebhookSender _webhookSender;
private readonly IServerApplicationHost _applicationHost;
private readonly IUserDataManager _userDataManager;
private readonly IUserManager _userManager;
private readonly ILogger<UserDataSavedNotifierEntryPoint> _logger;
_userDataManager = userDataManager;
_logger = logger;
_userManager = userManager;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="UserDataSavedNotifierEntryPoint"/> class.
/// </summary>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{UserDataChangedNotifierEntryPoint}"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
public UserDataSavedNotifierEntryPoint(
IWebhookSender webhookSender,
IServerApplicationHost applicationHost,
IUserDataManager userDataManager,
ILogger<UserDataSavedNotifierEntryPoint> logger,
IUserManager userManager)
/// <inheritdoc />
public Task RunAsync()
{
_userDataManager.UserDataSaved += UserDataSavedHandler;
return Task.CompletedTask;
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose.
/// </summary>
/// <param name="disposing">Dispose all assets.</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_userDataManager = userDataManager;
_logger = logger;
_userManager = userManager;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
_userDataManager.UserDataSaved -= UserDataSavedHandler;
}
}
/// <inheritdoc />
public Task RunAsync()
private async void UserDataSavedHandler(object? sender, UserDataSaveEventArgs eventArgs)
{
try
{
_userDataManager.UserDataSaved += UserDataSavedHandler;
return Task.CompletedTask;
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose.
/// </summary>
/// <param name="disposing">Dispose all assets.</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
if (eventArgs.Item is null)
{
_userDataManager.UserDataSaved -= UserDataSavedHandler;
return;
}
if (eventArgs.Item.IsThemeMedia)
{
// Don't report theme song or local trailer playback.
return;
}
var user = _userManager.GetUserById(eventArgs.UserId);
if (user is null)
{
return;
}
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.UserDataSaved)
.AddBaseItemData(eventArgs.Item)
.AddUserItemData(eventArgs.UserData);
dataObject["SaveReason"] = eventArgs.SaveReason.ToString();
dataObject["NotificationUsername"] = user.Username;
dataObject["UserId"] = user.Id;
await _webhookSender.SendNotification(NotificationType.UserDataSaved, dataObject, eventArgs.Item.GetType())
.ConfigureAwait(false);
}
private async void UserDataSavedHandler(object? sender, UserDataSaveEventArgs eventArgs)
catch (Exception ex)
{
try
{
if (eventArgs.Item is null)
{
return;
}
if (eventArgs.Item.IsThemeMedia)
{
// Don't report theme song or local trailer playback.
return;
}
var user = _userManager.GetUserById(eventArgs.UserId);
if (user is null)
{
return;
}
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.UserDataSaved)
.AddBaseItemData(eventArgs.Item)
.AddUserItemData(eventArgs.UserData);
dataObject["SaveReason"] = eventArgs.SaveReason.ToString();
dataObject["NotificationUsername"] = user.Username;
dataObject["UserId"] = user.Id;
await _webhookSender.SendNotification(NotificationType.UserDataSaved, dataObject, eventArgs.Item.GetType())
.ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Unable to send notification");
}
_logger.LogWarning(ex, "Unable to send notification");
}
}
}

View File

@ -5,38 +5,37 @@ using Jellyfin.Plugin.Webhook.Helpers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// User deleted notifier.
/// </summary>
public class UserDeletedNotifier : IEventConsumer<UserDeletedEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// User deleted notifier.
/// Initializes a new instance of the <see cref="UserDeletedNotifier"/> class.
/// </summary>
public class UserDeletedNotifier : IEventConsumer<UserDeletedEventArgs>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public UserDeletedNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="UserDeletedNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public UserDeletedNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <inheritdoc />
public async Task OnEvent(UserDeletedEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.UserDeleted)
.AddUserData(eventArgs.Argument);
/// <inheritdoc />
public async Task OnEvent(UserDeletedEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.UserDeleted)
.AddUserData(eventArgs.Argument);
await _webhookSender.SendNotification(NotificationType.UserDeleted, dataObject)
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.UserDeleted, dataObject)
.ConfigureAwait(false);
}
}

View File

@ -5,38 +5,37 @@ using Jellyfin.Plugin.Webhook.Helpers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// User locked out notifier.
/// </summary>
public class UserLockedOutNotifier : IEventConsumer<UserLockedOutEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// User locked out notifier.
/// Initializes a new instance of the <see cref="UserLockedOutNotifier"/> class.
/// </summary>
public class UserLockedOutNotifier : IEventConsumer<UserLockedOutEventArgs>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public UserLockedOutNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="UserLockedOutNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public UserLockedOutNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <inheritdoc />
public async Task OnEvent(UserLockedOutEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.UserLockedOut)
.AddUserData(eventArgs.Argument);
/// <inheritdoc />
public async Task OnEvent(UserLockedOutEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.UserLockedOut)
.AddUserData(eventArgs.Argument);
await _webhookSender.SendNotification(NotificationType.UserLockedOut, dataObject)
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.UserLockedOut, dataObject)
.ConfigureAwait(false);
}
}

View File

@ -5,38 +5,37 @@ using Jellyfin.Plugin.Webhook.Helpers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// User password changed notifier.
/// </summary>
public class UserPasswordChangedNotifier : IEventConsumer<UserPasswordChangedEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// User password changed notifier.
/// Initializes a new instance of the <see cref="UserPasswordChangedNotifier"/> class.
/// </summary>
public class UserPasswordChangedNotifier : IEventConsumer<UserPasswordChangedEventArgs>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public UserPasswordChangedNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="UserPasswordChangedNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public UserPasswordChangedNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <inheritdoc />
public async Task OnEvent(UserPasswordChangedEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.UserPasswordChanged)
.AddUserData(eventArgs.Argument);
/// <inheritdoc />
public async Task OnEvent(UserPasswordChangedEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.UserPasswordChanged)
.AddUserData(eventArgs.Argument);
await _webhookSender.SendNotification(NotificationType.UserPasswordChanged, dataObject)
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.UserPasswordChanged, dataObject)
.ConfigureAwait(false);
}
}

View File

@ -5,38 +5,37 @@ using Jellyfin.Plugin.Webhook.Helpers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Events;
namespace Jellyfin.Plugin.Webhook.Notifiers
namespace Jellyfin.Plugin.Webhook.Notifiers;
/// <summary>
/// User updated notifier.
/// </summary>
public class UserUpdatedNotifier : IEventConsumer<UserUpdatedEventArgs>
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
/// <summary>
/// User updated notifier.
/// Initializes a new instance of the <see cref="UserUpdatedNotifier"/> class.
/// </summary>
public class UserUpdatedNotifier : IEventConsumer<UserUpdatedEventArgs>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public UserUpdatedNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
private readonly IServerApplicationHost _applicationHost;
private readonly IWebhookSender _webhookSender;
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <summary>
/// Initializes a new instance of the <see cref="UserUpdatedNotifier"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="webhookSender">Instance of the <see cref="IWebhookSender"/> interface.</param>
public UserUpdatedNotifier(
IServerApplicationHost applicationHost,
IWebhookSender webhookSender)
{
_applicationHost = applicationHost;
_webhookSender = webhookSender;
}
/// <inheritdoc />
public async Task OnEvent(UserUpdatedEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.UserUpdated)
.AddUserData(eventArgs.Argument);
/// <inheritdoc />
public async Task OnEvent(UserUpdatedEventArgs eventArgs)
{
var dataObject = DataObjectHelpers
.GetBaseDataObject(_applicationHost, NotificationType.UserUpdated)
.AddUserData(eventArgs.Argument);
await _webhookSender.SendNotification(NotificationType.UserUpdated, dataObject)
.ConfigureAwait(false);
}
await _webhookSender.SendNotification(NotificationType.UserUpdated, dataObject)
.ConfigureAwait(false);
}
}

View File

@ -25,65 +25,64 @@ using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace Jellyfin.Plugin.Webhook
namespace Jellyfin.Plugin.Webhook;
/// <summary>
/// Register webhook services.
/// </summary>
public class PluginServiceRegistrator : IPluginServiceRegistrator
{
/// <summary>
/// Register webhook services.
/// </summary>
public class PluginServiceRegistrator : IPluginServiceRegistrator
/// <inheritdoc />
public void RegisterServices(IServiceCollection serviceCollection)
{
/// <inheritdoc />
public void RegisterServices(IServiceCollection serviceCollection)
{
serviceCollection.AddScoped<IWebhookClient<DiscordOption>, DiscordClient>();
serviceCollection.AddScoped<IWebhookClient<GenericOption>, GenericClient>();
serviceCollection.AddScoped<IWebhookClient<GenericFormOption>, GenericFormClient>();
serviceCollection.AddScoped<IWebhookClient<GotifyOption>, GotifyClient>();
serviceCollection.AddScoped<IWebhookClient<PushbulletOption>, PushbulletClient>();
serviceCollection.AddScoped<IWebhookClient<PushoverOption>, PushoverClient>();
serviceCollection.AddScoped<IWebhookClient<SlackOption>, SlackClient>();
serviceCollection.AddScoped<IWebhookClient<SmtpOption>, SmtpClient>();
serviceCollection.AddScoped<IWebhookClient<MqttOption>, MqttClient>();
serviceCollection.AddScoped<IWebhookClient<DiscordOption>, DiscordClient>();
serviceCollection.AddScoped<IWebhookClient<GenericOption>, GenericClient>();
serviceCollection.AddScoped<IWebhookClient<GenericFormOption>, GenericFormClient>();
serviceCollection.AddScoped<IWebhookClient<GotifyOption>, GotifyClient>();
serviceCollection.AddScoped<IWebhookClient<PushbulletOption>, PushbulletClient>();
serviceCollection.AddScoped<IWebhookClient<PushoverOption>, PushoverClient>();
serviceCollection.AddScoped<IWebhookClient<SlackOption>, SlackClient>();
serviceCollection.AddScoped<IWebhookClient<SmtpOption>, SmtpClient>();
serviceCollection.AddScoped<IWebhookClient<MqttOption>, MqttClient>();
// Register sender.
serviceCollection.AddScoped<IWebhookSender, WebhookSender>();
// Register sender.
serviceCollection.AddScoped<IWebhookSender, WebhookSender>();
// Register MqttClients
serviceCollection.AddSingleton<IMqttClients, MqttClients>();
// Register MqttClients
serviceCollection.AddSingleton<IMqttClients, MqttClients>();
/*-- Register event consumers. --*/
// Library consumers.
serviceCollection.AddScoped<IEventConsumer<SubtitleDownloadFailureEventArgs>, SubtitleDownloadFailureNotifier>();
serviceCollection.AddSingleton<IItemAddedManager, ItemAddedManager>();
/*-- Register event consumers. --*/
// Library consumers.
serviceCollection.AddScoped<IEventConsumer<SubtitleDownloadFailureEventArgs>, SubtitleDownloadFailureNotifier>();
serviceCollection.AddSingleton<IItemAddedManager, ItemAddedManager>();
// Security consumers.
serviceCollection.AddScoped<IEventConsumer<GenericEventArgs<AuthenticationRequest>>, AuthenticationFailureNotifier>();
serviceCollection.AddScoped<IEventConsumer<GenericEventArgs<AuthenticationResult>>, AuthenticationSuccessNotifier>();
// Security consumers.
serviceCollection.AddScoped<IEventConsumer<GenericEventArgs<AuthenticationRequest>>, AuthenticationFailureNotifier>();
serviceCollection.AddScoped<IEventConsumer<GenericEventArgs<AuthenticationResult>>, AuthenticationSuccessNotifier>();
// Session consumers.
serviceCollection.AddScoped<IEventConsumer<PlaybackStartEventArgs>, PlaybackStartNotifier>();
serviceCollection.AddScoped<IEventConsumer<PlaybackStopEventArgs>, PlaybackStopNotifier>();
serviceCollection.AddScoped<IEventConsumer<PlaybackProgressEventArgs>, PlaybackProgressNotifier>();
serviceCollection.AddScoped<IEventConsumer<SessionStartedEventArgs>, SessionStartNotifier>();
// Session consumers.
serviceCollection.AddScoped<IEventConsumer<PlaybackStartEventArgs>, PlaybackStartNotifier>();
serviceCollection.AddScoped<IEventConsumer<PlaybackStopEventArgs>, PlaybackStopNotifier>();
serviceCollection.AddScoped<IEventConsumer<PlaybackProgressEventArgs>, PlaybackProgressNotifier>();
serviceCollection.AddScoped<IEventConsumer<SessionStartedEventArgs>, SessionStartNotifier>();
// System consumers.
serviceCollection.AddScoped<IEventConsumer<PendingRestartEventArgs>, PendingRestartNotifier>();
serviceCollection.AddScoped<IEventConsumer<TaskCompletionEventArgs>, TaskCompletedNotifier>();
// System consumers.
serviceCollection.AddScoped<IEventConsumer<PendingRestartEventArgs>, PendingRestartNotifier>();
serviceCollection.AddScoped<IEventConsumer<TaskCompletionEventArgs>, TaskCompletedNotifier>();
// Update consumers.
serviceCollection.AddScoped<IEventConsumer<PluginInstallationCancelledEventArgs>, PluginInstallationCancelledNotifier>();
serviceCollection.AddScoped<IEventConsumer<InstallationFailedEventArgs>, PluginInstallationFailedNotifier>();
serviceCollection.AddScoped<IEventConsumer<PluginInstalledEventArgs>, PluginInstalledNotifier>();
serviceCollection.AddScoped<IEventConsumer<PluginInstallingEventArgs>, PluginInstallingNotifier>();
serviceCollection.AddScoped<IEventConsumer<PluginUninstalledEventArgs>, PluginUninstalledNotifier>();
serviceCollection.AddScoped<IEventConsumer<PluginUpdatedEventArgs>, PluginUpdatedNotifier>();
// Update consumers.
serviceCollection.AddScoped<IEventConsumer<PluginInstallationCancelledEventArgs>, PluginInstallationCancelledNotifier>();
serviceCollection.AddScoped<IEventConsumer<InstallationFailedEventArgs>, PluginInstallationFailedNotifier>();
serviceCollection.AddScoped<IEventConsumer<PluginInstalledEventArgs>, PluginInstalledNotifier>();
serviceCollection.AddScoped<IEventConsumer<PluginInstallingEventArgs>, PluginInstallingNotifier>();
serviceCollection.AddScoped<IEventConsumer<PluginUninstalledEventArgs>, PluginUninstalledNotifier>();
serviceCollection.AddScoped<IEventConsumer<PluginUpdatedEventArgs>, PluginUpdatedNotifier>();
// User consumers.
serviceCollection.AddScoped<IEventConsumer<UserCreatedEventArgs>, UserCreatedNotifier>();
serviceCollection.AddScoped<IEventConsumer<UserDeletedEventArgs>, UserDeletedNotifier>();
serviceCollection.AddScoped<IEventConsumer<UserLockedOutEventArgs>, UserLockedOutNotifier>();
serviceCollection.AddScoped<IEventConsumer<UserPasswordChangedEventArgs>, UserPasswordChangedNotifier>();
serviceCollection.AddScoped<IEventConsumer<UserUpdatedEventArgs>, UserUpdatedNotifier>();
}
// User consumers.
serviceCollection.AddScoped<IEventConsumer<UserCreatedEventArgs>, UserCreatedNotifier>();
serviceCollection.AddScoped<IEventConsumer<UserDeletedEventArgs>, UserDeletedNotifier>();
serviceCollection.AddScoped<IEventConsumer<UserLockedOutEventArgs>, UserLockedOutNotifier>();
serviceCollection.AddScoped<IEventConsumer<UserPasswordChangedEventArgs>, UserPasswordChangedNotifier>();
serviceCollection.AddScoped<IEventConsumer<UserUpdatedEventArgs>, UserUpdatedNotifier>();
}
}

View File

@ -6,55 +6,54 @@ using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
namespace Jellyfin.Plugin.Webhook
namespace Jellyfin.Plugin.Webhook;
/// <summary>
/// Plugin entrypoint.
/// </summary>
public class WebhookPlugin : BasePlugin<PluginConfiguration>, IHasWebPages
{
private readonly Guid _id = new("71552A5A-5C5C-4350-A2AE-EBE451A30173");
/// <summary>
/// Plugin entrypoint.
/// Initializes a new instance of the <see cref="WebhookPlugin"/> class.
/// </summary>
public class WebhookPlugin : BasePlugin<PluginConfiguration>, IHasWebPages
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
/// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
public WebhookPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
: base(applicationPaths, xmlSerializer)
{
private readonly Guid _id = new ("71552A5A-5C5C-4350-A2AE-EBE451A30173");
Instance = this;
}
/// <summary>
/// Initializes a new instance of the <see cref="WebhookPlugin"/> class.
/// </summary>
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
/// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
public WebhookPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
: base(applicationPaths, xmlSerializer)
/// <summary>
/// Gets current plugin instance.
/// </summary>
public static WebhookPlugin? Instance { get; private set; }
/// <inheritdoc />
public override Guid Id => _id;
/// <inheritdoc />
public override string Name => "Webhook";
/// <inheritdoc />
public override string Description => "Sends notifications to various services via webhooks.";
/// <inheritdoc />
public IEnumerable<PluginPageInfo> GetPages()
{
var prefix = GetType().Namespace;
yield return new PluginPageInfo
{
Instance = this;
}
Name = Name,
EmbeddedResourcePath = prefix + ".Configuration.Web.config.html"
};
/// <summary>
/// Gets current plugin instance.
/// </summary>
public static WebhookPlugin? Instance { get; private set; }
/// <inheritdoc />
public override Guid Id => _id;
/// <inheritdoc />
public override string Name => "Webhook";
/// <inheritdoc />
public override string Description => "Sends notifications to various services via webhooks.";
/// <inheritdoc />
public IEnumerable<PluginPageInfo> GetPages()
yield return new PluginPageInfo
{
var prefix = GetType().Namespace;
yield return new PluginPageInfo
{
Name = Name,
EmbeddedResourcePath = prefix + ".Configuration.Web.config.html"
};
yield return new PluginPageInfo
{
Name = $"{Name}.js",
EmbeddedResourcePath = prefix + ".Configuration.Web.config.js"
};
}
Name = $"{Name}.js",
EmbeddedResourcePath = prefix + ".Configuration.Web.config.js"
};
}
}

View File

@ -18,175 +18,174 @@ using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Webhook
namespace Jellyfin.Plugin.Webhook;
/// <inheritdoc />
public class WebhookSender : IWebhookSender
{
/// <inheritdoc />
public class WebhookSender : IWebhookSender
private readonly ILogger<WebhookSender> _logger;
private readonly IWebhookClient<DiscordOption> _discordClient;
private readonly IWebhookClient<GenericOption> _genericClient;
private readonly IWebhookClient<GenericFormOption> _genericFormClient;
private readonly IWebhookClient<GotifyOption> _gotifyClient;
private readonly IWebhookClient<PushbulletOption> _pushbulletClient;
private readonly IWebhookClient<PushoverOption> _pushoverClient;
private readonly IWebhookClient<SlackOption> _slackClient;
private readonly IWebhookClient<SmtpOption> _smtpClient;
private readonly IWebhookClient<MqttOption> _mqttClient;
/// <summary>
/// Initializes a new instance of the <see cref="WebhookSender"/> class.
/// </summary>
/// <param name="logger">Instance of the <see cref="ILogger{WebhookSender}"/> interface.</param>
/// <param name="discordClient">Instance of <see cref="IWebhookClient{DiscordOption}"/>.</param>
/// /// <param name="genericClient">Instance of the <see cref="IWebhookClient{GenericOption}"/>.</param>
/// <param name="genericFormClient">Instance of the <see cref="IWebhookClient{GenericFormOption}"/>.</param>
/// <param name="gotifyClient">Instance of <see cref="IWebhookClient{GotifyOption}"/>.</param>
/// <param name="pushbulletClient">Instance of the <see cref="IWebhookClient{PushbulletOption}"/>.</param>
/// <param name="pushoverClient">Instance of the <see cref="IWebhookClient{PushoverOption}"/>.</param>
/// <param name="slackClient">Instance of the <see cref="IWebhookClient{SlackOption}"/>.</param>
/// <param name="smtpClient">Instance of the <see cref="IWebhookClient{SmtpOption}"/>.</param>
/// <param name="mqttClient">Instance of the <see cref="IWebhookClient{mqttClient}"/>.</param>
public WebhookSender(
ILogger<WebhookSender> logger,
IWebhookClient<DiscordOption> discordClient,
IWebhookClient<GenericOption> genericClient,
IWebhookClient<GenericFormOption> genericFormClient,
IWebhookClient<GotifyOption> gotifyClient,
IWebhookClient<PushbulletOption> pushbulletClient,
IWebhookClient<PushoverOption> pushoverClient,
IWebhookClient<SlackOption> slackClient,
IWebhookClient<SmtpOption> smtpClient,
IWebhookClient<MqttOption> mqttClient)
{
private readonly ILogger<WebhookSender> _logger;
private readonly IWebhookClient<DiscordOption> _discordClient;
private readonly IWebhookClient<GenericOption> _genericClient;
private readonly IWebhookClient<GenericFormOption> _genericFormClient;
private readonly IWebhookClient<GotifyOption> _gotifyClient;
private readonly IWebhookClient<PushbulletOption> _pushbulletClient;
private readonly IWebhookClient<PushoverOption> _pushoverClient;
private readonly IWebhookClient<SlackOption> _slackClient;
private readonly IWebhookClient<SmtpOption> _smtpClient;
private readonly IWebhookClient<MqttOption> _mqttClient;
_logger = logger;
_discordClient = discordClient;
_genericClient = genericClient;
_genericFormClient = genericFormClient;
_gotifyClient = gotifyClient;
_pushbulletClient = pushbulletClient;
_pushoverClient = pushoverClient;
_slackClient = slackClient;
_smtpClient = smtpClient;
_mqttClient = mqttClient;
}
/// <summary>
/// Initializes a new instance of the <see cref="WebhookSender"/> class.
/// </summary>
/// <param name="logger">Instance of the <see cref="ILogger{WebhookSender}"/> interface.</param>
/// <param name="discordClient">Instance of <see cref="IWebhookClient{DiscordOption}"/>.</param>
/// /// <param name="genericClient">Instance of the <see cref="IWebhookClient{GenericOption}"/>.</param>
/// <param name="genericFormClient">Instance of the <see cref="IWebhookClient{GenericFormOption}"/>.</param>
/// <param name="gotifyClient">Instance of <see cref="IWebhookClient{GotifyOption}"/>.</param>
/// <param name="pushbulletClient">Instance of the <see cref="IWebhookClient{PushbulletOption}"/>.</param>
/// <param name="pushoverClient">Instance of the <see cref="IWebhookClient{PushoverOption}"/>.</param>
/// <param name="slackClient">Instance of the <see cref="IWebhookClient{SlackOption}"/>.</param>
/// <param name="smtpClient">Instance of the <see cref="IWebhookClient{SmtpOption}"/>.</param>
/// <param name="mqttClient">Instance of the <see cref="IWebhookClient{mqttClient}"/>.</param>
public WebhookSender(
ILogger<WebhookSender> logger,
IWebhookClient<DiscordOption> discordClient,
IWebhookClient<GenericOption> genericClient,
IWebhookClient<GenericFormOption> genericFormClient,
IWebhookClient<GotifyOption> gotifyClient,
IWebhookClient<PushbulletOption> pushbulletClient,
IWebhookClient<PushoverOption> pushoverClient,
IWebhookClient<SlackOption> slackClient,
IWebhookClient<SmtpOption> smtpClient,
IWebhookClient<MqttOption> mqttClient)
private static PluginConfiguration Configuration =>
WebhookPlugin.Instance!.Configuration;
/// <inheritdoc />
public async Task SendNotification(NotificationType notificationType, Dictionary<string, object> itemData, Type? itemType = null)
{
foreach (var option in Configuration.DiscordOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
_logger = logger;
_discordClient = discordClient;
_genericClient = genericClient;
_genericFormClient = genericFormClient;
_gotifyClient = gotifyClient;
_pushbulletClient = pushbulletClient;
_pushoverClient = pushoverClient;
_slackClient = slackClient;
_smtpClient = smtpClient;
_mqttClient = mqttClient;
await SendNotification(_discordClient, option, itemData, itemType)
.ConfigureAwait(false);
}
private static PluginConfiguration Configuration =>
WebhookPlugin.Instance!.Configuration;
/// <inheritdoc />
public async Task SendNotification(NotificationType notificationType, Dictionary<string, object> itemData, Type? itemType = null)
foreach (var option in Configuration.GenericOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
foreach (var option in Configuration.DiscordOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
await SendNotification(_discordClient, option, itemData, itemType)
.ConfigureAwait(false);
}
foreach (var option in Configuration.GenericOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
await SendNotification(_genericClient, option, itemData, itemType)
.ConfigureAwait(false);
}
foreach (var option in Configuration.GenericFormOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
await SendNotification(_genericFormClient, option, itemData, itemType)
.ConfigureAwait(false);
}
foreach (var option in Configuration.GotifyOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
await SendNotification(_gotifyClient, option, itemData, itemType)
.ConfigureAwait(false);
}
foreach (var option in Configuration.PushbulletOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
await SendNotification(_pushbulletClient, option, itemData, itemType)
.ConfigureAwait(false);
}
foreach (var option in Configuration.PushoverOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
await SendNotification(_pushoverClient, option, itemData, itemType)
.ConfigureAwait(false);
}
foreach (var option in Configuration.SlackOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
await SendNotification(_slackClient, option, itemData, itemType)
.ConfigureAwait(false);
}
foreach (var option in Configuration.SmtpOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
await SendNotification(_smtpClient, option, itemData, itemType)
.ConfigureAwait(false);
}
foreach (var option in Configuration.MqttOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
await SendNotification(_mqttClient, option, itemData, itemType)
.ConfigureAwait(false);
}
await SendNotification(_genericClient, option, itemData, itemType)
.ConfigureAwait(false);
}
private static bool NotifyOnItem<T>(T baseOptions, Type? itemType)
where T : BaseOption
foreach (var option in Configuration.GenericFormOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
if (itemType is null)
{
return true;
}
if (baseOptions.EnableAlbums && itemType == typeof(MusicAlbum))
{
return true;
}
if (baseOptions.EnableMovies && itemType == typeof(Movie))
{
return true;
}
if (baseOptions.EnableEpisodes && itemType == typeof(Episode))
{
return true;
}
if (baseOptions.EnableSeries && itemType == typeof(Series))
{
return true;
}
if (baseOptions.EnableSeasons && itemType == typeof(Season))
{
return true;
}
if (baseOptions.EnableSongs && itemType == typeof(Audio))
{
return true;
}
return false;
await SendNotification(_genericFormClient, option, itemData, itemType)
.ConfigureAwait(false);
}
private async Task SendNotification<T>(IWebhookClient<T> webhookClient, T option, Dictionary<string, object> itemData, Type? itemType)
where T : BaseOption
foreach (var option in Configuration.GotifyOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
if (NotifyOnItem(option, itemType))
await SendNotification(_gotifyClient, option, itemData, itemType)
.ConfigureAwait(false);
}
foreach (var option in Configuration.PushbulletOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
await SendNotification(_pushbulletClient, option, itemData, itemType)
.ConfigureAwait(false);
}
foreach (var option in Configuration.PushoverOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
await SendNotification(_pushoverClient, option, itemData, itemType)
.ConfigureAwait(false);
}
foreach (var option in Configuration.SlackOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
await SendNotification(_slackClient, option, itemData, itemType)
.ConfigureAwait(false);
}
foreach (var option in Configuration.SmtpOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
await SendNotification(_smtpClient, option, itemData, itemType)
.ConfigureAwait(false);
}
foreach (var option in Configuration.MqttOptions.Where(o => o.NotificationTypes.Contains(notificationType)))
{
await SendNotification(_mqttClient, option, itemData, itemType)
.ConfigureAwait(false);
}
}
private static bool NotifyOnItem<T>(T baseOptions, Type? itemType)
where T : BaseOption
{
if (itemType is null)
{
return true;
}
if (baseOptions.EnableAlbums && itemType == typeof(MusicAlbum))
{
return true;
}
if (baseOptions.EnableMovies && itemType == typeof(Movie))
{
return true;
}
if (baseOptions.EnableEpisodes && itemType == typeof(Episode))
{
return true;
}
if (baseOptions.EnableSeries && itemType == typeof(Series))
{
return true;
}
if (baseOptions.EnableSeasons && itemType == typeof(Season))
{
return true;
}
if (baseOptions.EnableSongs && itemType == typeof(Audio))
{
return true;
}
return false;
}
private async Task SendNotification<T>(IWebhookClient<T> webhookClient, T option, Dictionary<string, object> itemData, Type? itemType)
where T : BaseOption
{
if (NotifyOnItem(option, itemType))
{
try
{
try
{
await webhookClient.SendAsync(option, itemData)
.ConfigureAwait(false);
}
catch (Exception e)
{
_logger.LogError(e, "Unable to send notification");
}
await webhookClient.SendAsync(option, itemData)
.ConfigureAwait(false);
}
catch (Exception e)
{
_logger.LogError(e, "Unable to send notification");
}
}
}

View File

@ -58,4 +58,4 @@ public class WebhookServerEntryPoint : IServerEntryPoint
{
await _mqttClients.UpdateClients(Configuration.MqttOptions).ConfigureAwait(false);
}
}
}