Added Discord notification #865

pull/1425/head
Jamie.Rees 7 years ago
parent 4c797733ca
commit 43dbe854a6

@ -0,0 +1,33 @@
using System.Net.Http;
using System.Threading.Tasks;
using Ombi.Api.Discord.Models;
namespace Ombi.Api.Discord
{
public class DiscordApi : IDiscordApi
{
public DiscordApi()
{
Api = new Api();
}
private string Endpoint => "https://discordapp.com/api/"; //webhooks/270828242636636161/lLysOMhJ96AFO1kvev0bSqP-WCZxKUh1UwfubhIcLkpS0DtM3cg4Pgeraw3waoTXbZii
private Api Api { get; }
public async Task SendMessage(string message, string webhookId, string webhookToken, string username = null)
{
var request = new Request(Endpoint, $"webhooks/{webhookId}/{webhookToken}", HttpMethod.Post);
var body = new DiscordWebhookBody
{
content = message,
username = username
};
request.AddJsonBody(body);
request.AddHeader("Content-Type", "application/json");
await Api.Request(request);
}
}
}

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Ombi.Api.Discord
{
public interface IDiscordApi
{
Task SendMessage(string message, string webhookId, string webhookToken, string username = null);
}
}

@ -0,0 +1,8 @@
namespace Ombi.Api.Discord.Models
{
public class DiscordWebhookBody
{
public string content { get; set; }
public string username { get; set; }
}
}

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
</ItemGroup>
</Project>

@ -82,7 +82,7 @@ namespace Ombi.Api.Radarr
try
{
var response = await Api.Request(request);
var response = await Api.RequestContent(request);
if (response.Contains("\"message\":"))
{
var error = JsonConvert.DeserializeObject<RadarrError>(response);

@ -60,7 +60,7 @@ namespace Ombi.Api
}
}
public async Task<string> Request(Request request)
public async Task<string> RequestContent(Request request)
{
using (var httpClient = new HttpClient())
{
@ -93,5 +93,34 @@ namespace Ombi.Api
}
}
}
public async Task Request(Request request)
{
using (var httpClient = new HttpClient())
{
using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri))
{
// Add the Json Body
if (request.JsonBody != null)
{
httpRequestMessage.Content = new JsonContent(request.JsonBody);
}
// Add headers
foreach (var header in request.Headers)
{
httpRequestMessage.Headers.Add(header.Key, header.Value);
}
using (var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage))
{
if (!httpResponseMessage.IsSuccessStatusCode)
{
// Logging
}
}
}
}
}
}
}

@ -50,7 +50,7 @@ namespace Ombi.Api
public List<KeyValuePair<string, string>> Headers { get; } = new List<KeyValuePair<string, string>>();
public List<KeyValuePair<string, string>> ContentHeaders { get; } = new List<KeyValuePair<string, string>>();
public object JsonBody { get; set; }
public object JsonBody { get; private set; }
public bool IsValidUrl
{

@ -2,6 +2,8 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Ombi.Api.Discord;
using Ombi.Api.Emby;
using Ombi.Api.Plex;
using Ombi.Api.Radarr;
@ -60,6 +62,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<ITvMazeApi, TvMazeApi>();
services.AddTransient<ITraktApi, TraktApi>();
services.AddTransient<IRadarrApi, RadarrApi>();
services.AddTransient<IDiscordApi, DiscordApi>();
}
public static void RegisterStore(this IServiceCollection services)

@ -6,8 +6,13 @@ namespace Ombi.Helpers
{
public static EventId ApiException => new EventId(1000);
public static EventId RadarrApiException => new EventId(1001);
public static EventId CacherException => new EventId(2000);
public static EventId RadarrCacherException => new EventId(2001);
public static EventId MovieSender => new EventId(3000);
public static EventId Notification => new EventId(4000);
public static EventId DiscordNotification => new EventId(4001);
}
}

@ -0,0 +1,131 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Api.Discord;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Notifications;
using Ombi.Notifications.Models;
using Ombi.Settings.Settings.Models.Notifications;
namespace Ombi.Notification.Discord
{
public class DiscordNotification : BaseNotification<DiscordNotificationSettings>
{
public DiscordNotification(IDiscordApi api, ISettingsService<DiscordNotificationSettings> sn, ILogger<DiscordNotification> log) : base(sn)
{
Api = api;
Logger = log;
}
public override string NotificationName => "DiscordNotification";
private IDiscordApi Api { get; }
private ILogger<DiscordNotification> Logger { get; }
protected override bool ValidateConfiguration(DiscordNotificationSettings settings)
{
if (!settings.Enabled)
{
return false;
}
if (string.IsNullOrEmpty(settings.WebhookUrl))
{
return false;
}
try
{
var a = settings.Token;
var b = settings.WebookId;
}
catch (IndexOutOfRangeException)
{
return false;
}
return true;
}
protected override async Task NewRequest(NotificationModel model, DiscordNotificationSettings settings)
{
var message = $"{model.Title} has been requested by user: {model.User}";
var notification = new NotificationMessage
{
Message = message,
};
await Send(notification, settings);
}
protected override async Task Issue(NotificationModel model, DiscordNotificationSettings settings)
{
var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
var notification = new NotificationMessage
{
Message = message,
};
await Send(notification, settings);
}
protected override async Task AddedToRequestQueue(NotificationModel model, DiscordNotificationSettings settings)
{
var message = $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying";
var notification = new NotificationMessage
{
Message = message,
};
await Send(notification, settings);
}
protected override async Task RequestDeclined(NotificationModel model, DiscordNotificationSettings settings)
{
var message = $"Hello! Your request for {model.Title} has been declined, Sorry!";
var notification = new NotificationMessage
{
Message = message,
};
await Send(notification, settings);
}
protected override async Task RequestApproved(NotificationModel model, DiscordNotificationSettings settings)
{
var message = $"Hello! The request for {model.Title} has now been approved!";
var notification = new NotificationMessage
{
Message = message,
};
await Send(notification, settings);
}
protected override async Task AvailableRequest(NotificationModel model, DiscordNotificationSettings settings)
{
var message = $"Hello! The request for {model.Title} is now available!";
var notification = new NotificationMessage
{
Message = message,
};
await Send(notification, settings);
}
protected override async Task Send(NotificationMessage model, DiscordNotificationSettings settings)
{
try
{
await Api.SendMessage(model.Message, settings.WebookId, settings.Token, settings.Username);
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.DiscordNotification, e, "Failed to send Discord Notification");
}
}
protected override async Task Test(NotificationModel model, DiscordNotificationSettings settings)
{
var message = $"This is a test from Ombi, if you can see this then we have successfully pushed a notification!";
var notification = new NotificationMessage
{
Message = message,
};
await Send(notification, settings);
}
}
}

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api.Discord\Ombi.Api.Discord.csproj" />
<ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" />
</ItemGroup>
</Project>

@ -4,9 +4,9 @@ using MailKit.Net.Smtp;
using MimeKit;
using Ombi.Core.Models.Requests;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.Notifications;
using Ombi.Notifications.Models;
using Ombi.Notifications.Templates;
using Ombi.Settings.Settings.Models.Notifications;
namespace Ombi.Notifications.Email
{
@ -20,6 +20,10 @@ namespace Ombi.Notifications.Email
protected override bool ValidateConfiguration(EmailNotificationSettings settings)
{
if (!settings.Enabled)
{
return false;
}
if (settings.Authentication)
{
if (string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword))

@ -25,7 +25,7 @@ namespace Ombi.Notifications
public async Task NotifyAsync(NotificationModel model, Settings.Settings.Models.Settings settings)
{
if (settings == null) await NotifyAsync(model);
var notificationSettings = (T)settings;
if (!ValidateConfiguration(notificationSettings))

@ -7,12 +7,8 @@ namespace Ombi.Notifications
{
public interface INotificationService
{
ConcurrentDictionary<string, INotification> Observers { get; }
Task Publish(NotificationModel model);
Task Publish(NotificationModel model, Settings.Settings.Models.Settings settings);
Task PublishTest(NotificationModel model, Settings.Settings.Models.Settings settings, INotification type);
void Subscribe(INotification notification);
void UnSubscribe(INotification notification);
}
}

@ -1,15 +1,47 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Ombi.Core.Settings.Models;
using Microsoft.Extensions.Logging;
using Ombi.Helpers;
using Ombi.Notifications.Models;
namespace Ombi.Notifications
{
public class NotificationService : INotificationService
{
public ConcurrentDictionary<string, INotification> Observers { get; } = new ConcurrentDictionary<string, INotification>();
public NotificationService(IServiceProvider provider, ILogger<NotificationService> log)
{
Log = log;
NotificationAgents = new List<INotification>();
var baseSearchType = typeof(BaseNotification<>).FullName;
var ass = typeof(NotificationService).GetTypeInfo().Assembly;
foreach (var ti in ass.DefinedTypes)
{
if (ti?.BaseType?.FullName == baseSearchType)
{
var type = ti?.AsType();
var ctors = type.GetConstructors();
var ctor = ctors.FirstOrDefault();
var services = new List<object>();
foreach (var param in ctor.GetParameters())
{
services.Add(provider.GetService(param.ParameterType));
}
var item = Activator.CreateInstance(type, services.ToArray());
NotificationAgents.Add((INotification)item);
}
}
}
private List<INotification> NotificationAgents { get; }
private ILogger<NotificationService> Log { get; }
/// <summary>
/// Sends a notification to the user. This one is used in normal notification scenarios
@ -18,7 +50,7 @@ namespace Ombi.Notifications
/// <returns></returns>
public async Task Publish(NotificationModel model)
{
var notificationTasks = Observers.Values.Select(notification => NotifyAsync(notification, model));
var notificationTasks = NotificationAgents.Select(notification => NotifyAsync(notification, model));
await Task.WhenAll(notificationTasks).ConfigureAwait(false);
}
@ -31,21 +63,12 @@ namespace Ombi.Notifications
/// <returns></returns>
public async Task Publish(NotificationModel model, Settings.Settings.Models.Settings settings)
{
var notificationTasks = Observers.Values.Select(notification => NotifyAsync(notification, model, settings));
var notificationTasks = NotificationAgents.Select(notification => NotifyAsync(notification, model, settings));
await Task.WhenAll(notificationTasks).ConfigureAwait(false);
}
public void Subscribe(INotification notification)
{
Observers.TryAdd(notification.NotificationName, notification);
}
public void UnSubscribe(INotification notification)
{
Observers.TryRemove(notification.NotificationName, out notification);
}
private async Task NotifyAsync(INotification notification, NotificationModel model)
{
try
@ -54,6 +77,7 @@ namespace Ombi.Notifications
}
catch (Exception ex)
{
Log.LogError(LoggingEvents.Notification, ex, "Failed to notify for notification: {@notification}", notification);
}
}

@ -0,0 +1,31 @@
using System;
using Newtonsoft.Json;
namespace Ombi.Settings.Settings.Models.Notifications
{
public class DiscordNotificationSettings : Settings
{
public bool Enabled { get; set; }
public string WebhookUrl { get; set; }
public string Username { get; set; }
[JsonIgnore]
public string WebookId => SplitWebUrl(4);
[JsonIgnore]
public string Token => SplitWebUrl(5);
private string SplitWebUrl(int index)
{
if (!WebhookUrl.StartsWith("http", StringComparison.CurrentCulture))
{
WebhookUrl = "https://" + WebhookUrl;
}
var split = WebhookUrl.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
return split.Length < index
? string.Empty
: split[index];
}
}
}

@ -1,6 +1,6 @@
namespace Ombi.Core.Settings.Models.Notifications
namespace Ombi.Settings.Settings.Models.Notifications
{
public sealed class EmailNotificationSettings : Ombi.Settings.Settings.Models.Settings
public sealed class EmailNotificationSettings : Settings
{
public bool Enabled { get; set; }
public string EmailHost { get; set; }

@ -61,6 +61,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Core.Tests", "Ombi.Cor
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Radarr", "Ombi.Api.Radarr\Ombi.Api.Radarr.csproj", "{94D04C1F-E35A-499C-B0A0-9FADEBDF8336}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Discord", "Ombi.Api.Discord\Ombi.Api.Discord.csproj", "{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Notification.Discord", "Ombi.Notification.Discord\Ombi.Notification.Discord.csproj", "{D7B05CF2-E6B3-4BE5-8A02-6698CC0DCE9A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -147,6 +151,14 @@ Global
{94D04C1F-E35A-499C-B0A0-9FADEBDF8336}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94D04C1F-E35A-499C-B0A0-9FADEBDF8336}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94D04C1F-E35A-499C-B0A0-9FADEBDF8336}.Release|Any CPU.Build.0 = Release|Any CPU
{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Release|Any CPU.Build.0 = Release|Any CPU
{D7B05CF2-E6B3-4BE5-8A02-6698CC0DCE9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7B05CF2-E6B3-4BE5-8A02-6698CC0DCE9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7B05CF2-E6B3-4BE5-8A02-6698CC0DCE9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7B05CF2-E6B3-4BE5-8A02-6698CC0DCE9A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -166,5 +178,7 @@ Global
{3880375C-1A7E-4D75-96EC-63B954C42FEA} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{FC6A8F7C-9722-4AE4-960D-277ACB0E81CB} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
{94D04C1F-E35A-499C-B0A0-9FADEBDF8336} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{D7B05CF2-E6B3-4BE5-8A02-6698CC0DCE9A} = {EA30DD15-6280-4687-B370-2956EC2E54E5}
EndGlobalSection
EndGlobal

Loading…
Cancel
Save