pull/3400/head
Jamie Rees 4 years ago
commit ac51126437

@ -74,6 +74,7 @@ Supported notifications:
* Pushover
* Mattermost
* Telegram
* Webhook
### The difference between Version 3 and 2

@ -0,0 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Ombi.Api.Webhook
{
public interface IWebhookApi
{
Task PushAsync(string endpoint, string accessToken, IDictionary<string, string> parameters);
}
}

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>
<PackageVersion></PackageVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,40 @@
using Newtonsoft.Json.Serialization;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace Ombi.Api.Webhook
{
public class WebhookApi : IWebhookApi
{
private static readonly CamelCasePropertyNamesContractResolver _nameResolver = new CamelCasePropertyNamesContractResolver();
public WebhookApi(IApi api)
{
_api = api;
}
private readonly IApi _api;
public async Task PushAsync(string baseUrl, string accessToken, IDictionary<string, string> parameters)
{
var request = new Request("/", baseUrl, HttpMethod.Post);
if (!string.IsNullOrWhiteSpace(accessToken))
{
request.AddHeader("Access-Token", accessToken);
}
var body = parameters.ToDictionary(
x => _nameResolver.GetResolvedPropertyName(x.Key),
x => x.Value
);
request.ApplicationJsonContentType();
request.AddJsonBody(body);
await _api.Request(request);
}
}
}

@ -7,6 +7,8 @@ namespace Ombi.Core.Models.Search
{
public int Id { get; set; }
public bool Approved { get; set; }
public bool? Denied { get; set; }
public string DeniedReason { get; set; }
public bool Requested { get; set; }
public int RequestId { get; set; }
public bool Available { get; set; }

@ -0,0 +1,15 @@

using System.Collections.Generic;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.UI
{
/// <summary>
/// The view model for the notification settings page
/// </summary>
/// <seealso cref="WebhookSettings" />
public class WebhookNotificationViewModel : WebhookSettings
{
}
}

@ -34,6 +34,8 @@ namespace Ombi.Core.Rule.Rules.Search
obj.Requested = true;
obj.RequestId = movieRequests.Id;
obj.Approved = movieRequests.Approved;
obj.Denied = movieRequests.Denied ?? false;
obj.DeniedReason = movieRequests.DeniedReason;
obj.Available = movieRequests.Available;
return Success();
@ -49,6 +51,7 @@ namespace Ombi.Core.Rule.Rules.Search
request.RequestId = tvRequests.Id;
request.Requested = true;
request.Approved = tvRequests.ChildRequests.Any(x => x.Approved);
request.Denied = tvRequests.ChildRequests.Any(x => x.Denied ?? false);
// Let's modify the seasonsrequested to reflect what we have requested...
foreach (var season in request.SeasonRequests)
@ -96,7 +99,9 @@ namespace Ombi.Core.Rule.Rules.Search
if (albumRequest != null) // Do we already have a request for this?
{
obj.Requested = true;
obj.RequestId = albumRequest.Id;
obj.RequestId = albumRequest.Id;
obj.Denied = albumRequest.Denied;
obj.DeniedReason = albumRequest.DeniedReason;
obj.Approved = albumRequest.Approved;
obj.Available = albumRequest.Available;

@ -34,6 +34,7 @@ using Ombi.Api.FanartTv;
using Ombi.Api.Github;
using Ombi.Api.Gotify;
using Ombi.Api.GroupMe;
using Ombi.Api.Webhook;
using Ombi.Api.Lidarr;
using Ombi.Api.Mattermost;
using Ombi.Api.Notifications;
@ -137,6 +138,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IFanartTvApi, FanartTvApi>();
services.AddTransient<IPushoverApi, PushoverApi>();
services.AddTransient<IGotifyApi, GotifyApi>();
services.AddTransient<IWebhookApi, WebhookApi>();
services.AddTransient<IMattermostApi, MattermostApi>();
services.AddTransient<ICouchPotatoApi, CouchPotatoApi>();
services.AddTransient<IDogNzbApi, DogNzbApi>();
@ -192,6 +194,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IMattermostNotification, MattermostNotification>();
services.AddTransient<IPushoverNotification, PushoverNotification>();
services.AddTransient<IGotifyNotification, GotifyNotification>();
services.AddTransient<IWebhookNotification, WebhookNotification>();
services.AddTransient<ITelegramNotification, TelegramNotification>();
services.AddTransient<IMobileNotification, MobileNotification>();
services.AddTransient<IChangeLogProcessor, ChangeLogProcessor>();

@ -38,6 +38,7 @@
<ProjectReference Include="..\Ombi.Api.Trakt\Ombi.Api.Trakt.csproj" />
<ProjectReference Include="..\Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj" />
<ProjectReference Include="..\Ombi.Api.Twilio\Ombi.Api.Twilio.csproj" />
<ProjectReference Include="..\Ombi.Api.Webhook\Ombi.Api.Webhook.csproj" />
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj" />
<ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" />
<ProjectReference Include="..\Ombi.Schedule\Ombi.Schedule.csproj" />

@ -34,6 +34,7 @@ namespace Ombi.Helpers
public static EventId TelegramNotifcation => new EventId(4006);
public static EventId GotifyNotification => new EventId(4007);
public static EventId WhatsApp => new EventId(4008);
public static EventId WebhookNotification => new EventId(4009);
public static EventId TvSender => new EventId(5000);
public static EventId SonarrSender => new EventId(5001);

@ -11,6 +11,7 @@
Mattermost = 6,
Mobile = 7,
Gotify = 8,
WhatsApp = 9
Webhook = 9,
WhatsApp = 10
}
}

@ -22,6 +22,7 @@ namespace Ombi.Mapping.Profiles
CreateMap<GotifyNotificationViewModel, GotifySettings>().ReverseMap();
CreateMap<WhatsAppSettingsViewModel, WhatsAppSettings>().ReverseMap();
CreateMap<TwilioSettingsViewModel, TwilioSettings>().ReverseMap();
CreateMap<WebhookNotificationViewModel, WebhookSettings>().ReverseMap();
}
}
}

@ -0,0 +1,6 @@
namespace Ombi.Notifications.Agents
{
public interface IWebhookNotification : INotification
{
}
}

@ -0,0 +1,123 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Api.Webhook;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Notifications.Models;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
namespace Ombi.Notifications.Agents
{
public class WebhookNotification : BaseNotification<WebhookSettings>, IWebhookNotification
{
public WebhookNotification(IWebhookApi api, ISettingsService<WebhookSettings> sn, ILogger<WebhookNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref)
{
Api = api;
Logger = log;
}
public override string NotificationName => "WebhookNotification";
private IWebhookApi Api { get; }
private ILogger<WebhookNotification> Logger { get; }
protected override bool ValidateConfiguration(WebhookSettings settings)
{
return settings.Enabled && !string.IsNullOrEmpty(settings.WebhookUrl);
}
protected override async Task NewRequest(NotificationOptions model, WebhookSettings settings)
{
await Run(model, settings, NotificationType.NewRequest);
}
protected override async Task NewIssue(NotificationOptions model, WebhookSettings settings)
{
await Run(model, settings, NotificationType.Issue);
}
protected override async Task IssueComment(NotificationOptions model, WebhookSettings settings)
{
await Run(model, settings, NotificationType.IssueComment);
}
protected override async Task IssueResolved(NotificationOptions model, WebhookSettings settings)
{
await Run(model, settings, NotificationType.IssueResolved);
}
protected override async Task AddedToRequestQueue(NotificationOptions model, WebhookSettings settings)
{
await Run(model, settings, NotificationType.ItemAddedToFaultQueue);
}
protected override async Task RequestDeclined(NotificationOptions model, WebhookSettings settings)
{
await Run(model, settings, NotificationType.RequestDeclined);
}
protected override async Task RequestApproved(NotificationOptions model, WebhookSettings settings)
{
await Run(model, settings, NotificationType.RequestApproved);
}
protected override async Task AvailableRequest(NotificationOptions model, WebhookSettings settings)
{
await Run(model, settings, NotificationType.RequestAvailable);
}
protected override async Task Send(NotificationMessage model, WebhookSettings settings)
{
try
{
await Api.PushAsync(settings.WebhookUrl, settings.ApplicationToken, model.Data);
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.WebhookNotification, e, "Failed to send webhook notification");
}
}
protected override async Task Test(NotificationOptions model, WebhookSettings settings)
{
var c = new NotificationMessageCurlys();
var testData = c.Curlys.ToDictionary(x => x.Key, x => x.Value);
testData[nameof(NotificationType)] = NotificationType.Test.ToString();
var notification = new NotificationMessage
{
Data = testData,
};
await Send(notification, settings);
}
private async Task Run(NotificationOptions model, WebhookSettings settings, NotificationType type)
{
var parsed = await LoadTemplate(NotificationAgent.Webhook, type, model);
if (parsed.Disabled)
{
Logger.LogInformation($"Template {type} is disabled for {NotificationAgent.Webhook}");
return;
}
var notificationData = parsed.Data.ToDictionary(x => x.Key, x => x.Value);
notificationData[nameof(NotificationType)] = type.ToString();
var notification = new NotificationMessage
{
Data = notificationData,
};
await Send(notification, settings);
}
}
}

@ -9,5 +9,6 @@ namespace Ombi.Notifications.Models
public string To { get; set; }
public Dictionary<string, string> Other { get; set; } = new Dictionary<string, string>();
public IDictionary<string, string> Data { get; set; }
}
}

@ -1,4 +1,6 @@
namespace Ombi.Notifications
using System.Collections.Generic;
namespace Ombi.Notifications
{
public class NotificationMessageContent
{
@ -6,5 +8,6 @@
public string Subject { get; set; }
public string Message { get; set; }
public string Image { get; set; }
public IReadOnlyDictionary<string, string> Data { get; set; }
}
}

@ -17,7 +17,9 @@ namespace Ombi.Notifications
public void Setup(NotificationOptions opts, FullBaseRequest req, CustomizationSettings s, UserNotificationPreferences pref)
{
LoadIssues(opts);
RequestId = req.Id.ToString();
RequestId = req?.Id.ToString();
string title;
if (req == null)
{
@ -68,7 +70,8 @@ namespace Ombi.Notifications
{
LoadIssues(opts);
RequestId = req.Id.ToString();
RequestId = req?.Id.ToString();
string title;
if (req == null)
{
@ -218,6 +221,7 @@ namespace Ombi.Notifications
}
// User Defined
public string RequestId { get; set; }
public string RequestedUser { get; set; }
public string UserName { get; set; }
public string IssueUser => UserName;
@ -241,7 +245,6 @@ namespace Ombi.Notifications
public string UserPreference { get; set; }
public string DenyReason { get; set; }
public string AvailableDate { get; set; }
public string RequestId { get; set; }
// System Defined
private string LongDate => DateTime.Now.ToString("D");
@ -251,6 +254,7 @@ namespace Ombi.Notifications
public Dictionary<string, string> Curlys => new Dictionary<string, string>
{
{nameof(RequestId), RequestId },
{nameof(RequestedUser), RequestedUser },
{nameof(Title), Title },
{nameof(RequestedDate), RequestedDate },

@ -47,7 +47,7 @@ namespace Ombi.Notifications
body = ReplaceFields(bodyFields, parameters, body);
subject = ReplaceFields(subjectFields, parameters, subject);
return new NotificationMessageContent { Message = body ?? string.Empty, Subject = subject ?? string.Empty};
return new NotificationMessageContent { Message = body ?? string.Empty, Subject = subject ?? string.Empty, Data = parameters };
}
public IEnumerable<string> ProcessConditions(IEnumerable<string> conditionalFields, IReadOnlyDictionary<string, string> parameters)

@ -16,6 +16,7 @@
<ItemGroup>
<ProjectReference Include="..\Ombi.Api.Discord\Ombi.Api.Discord.csproj" />
<ProjectReference Include="..\Ombi.Api.Gotify\Ombi.Api.Gotify.csproj" />
<ProjectReference Include="..\Ombi.Api.Webhook\Ombi.Api.Webhook.csproj" />
<ProjectReference Include="..\Ombi.Api.Mattermost\Ombi.Api.Mattermost.csproj" />
<ProjectReference Include="..\Ombi.Api.Notifications\Ombi.Api.Notifications.csproj" />
<ProjectReference Include="..\Ombi.Api.Pushbullet\Ombi.Api.Pushbullet.csproj" />

@ -0,0 +1,10 @@
using Ombi.Store.Entities;
namespace Ombi.Schedule.Jobs.Plex.Models
{
public class AvailabilityModel
{
public int Id { get; set; }
public string RequestedUser { get; set; }
}
}

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
@ -8,6 +9,7 @@ using Ombi.Core;
using Ombi.Helpers;
using Ombi.Hubs;
using Ombi.Notifications.Models;
using Ombi.Schedule.Jobs.Plex.Models;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
@ -60,13 +62,13 @@ namespace Ombi.Schedule.Jobs.Plex
private Task ProcessTv()
{
var tv = _tvRepo.GetChild().Where(x => !x.Available);
var tv = _tvRepo.GetChild().Where(x => !x.Available).AsNoTracking();
return ProcessTv(tv);
}
private async Task ProcessTv(IQueryable<ChildRequests> tv)
{
var plexEpisodes = _repo.GetAllEpisodes().Include(x => x.Series);
var plexEpisodes = _repo.GetAllEpisodes().Include(x => x.Series).AsNoTracking();
foreach (var child in tv)
{
@ -108,6 +110,7 @@ namespace Ombi.Schedule.Jobs.Plex
}
var availableEpisode = new List<AvailabilityModel>();
foreach (var season in child.SeasonRequests)
{
foreach (var episode in season.Episodes)
@ -122,20 +125,28 @@ namespace Ombi.Schedule.Jobs.Plex
if (foundEp != null)
{
availableEpisode.Add(new AvailabilityModel
{
Id = episode.Id
});
episode.Available = true;
}
}
}
//TODO Partial avilability notifications here
foreach(var c in availableEpisode)
{
await _tvRepo.MarkEpisodeAsAvailable(c.Id);
}
// Check to see if all of the episodes in all seasons are available for this request
var allAvailable = child.SeasonRequests.All(x => x.Episodes.All(c => c.Available));
if (allAvailable)
{
_log.LogInformation("[PAC] - Child request {0} is now available, sending notification", $"{child.Title} - {child.Id}");
// We have ful-fulled this request!
child.Available = true;
child.MarkedAsAvailable = DateTime.Now;
await _tvRepo.MarkChildAsAvailable(child.Id);
await _notificationService.Notify(new NotificationOptions
{
DateTime = DateTime.Now,
@ -153,10 +164,16 @@ namespace Ombi.Schedule.Jobs.Plex
private async Task ProcessMovies()
{
// Get all non available
var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available);
var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available).AsNoTracking();
var itemsForAvailbility = new List<AvailabilityModel>();
foreach (var movie in movies)
{
if (movie.Available)
{
return;
}
PlexServerContent item = null;
if (movie.ImdbId.HasValue())
{
@ -174,24 +191,29 @@ namespace Ombi.Schedule.Jobs.Plex
// We don't yet have this
continue;
}
_log.LogInformation("[PAC] - Movie request {0} is now available, sending notification", $"{movie.Title} - {movie.Id}");
itemsForAvailbility.Add(new AvailabilityModel
{
Id = movie.Id,
RequestedUser = movie.RequestedUser != null ? movie.RequestedUser.Email : string.Empty
});
}
movie.Available = true;
movie.MarkedAsAvailable = DateTime.Now;
item.RequestId = movie.Id;
foreach (var i in itemsForAvailbility)
{
await _movieRepo.MarkAsAvailable(i.Id);
_log.LogInformation("[PAC] - Movie request {0} is now available, sending notification", $"{movie.Title} - {movie.Id}");
await _notificationService.Notify(new NotificationOptions
{
DateTime = DateTime.Now,
NotificationType = NotificationType.RequestAvailable,
RequestId = movie.Id,
RequestId = i.Id,
RequestType = RequestType.Movie,
Recipient = movie.RequestedUser != null ? movie.RequestedUser.Email : string.Empty
Recipient = i.RequestedUser
});
}
await _movieRepo.Save();
await _repo.SaveChangesAsync();
}

@ -235,4 +235,4 @@ namespace Ombi.Schedule.Jobs.Plex
GC.SuppressFinalize(this);
}
}
}
}

@ -0,0 +1,9 @@
namespace Ombi.Settings.Settings.Models.Notifications
{
public class WebhookSettings : Settings
{
public bool Enabled { get; set; }
public string WebhookUrl { get; set; }
public string ApplicationToken { get; set; }
}
}

@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository.Requests;
namespace Ombi.Store.Context
{
@ -35,6 +36,7 @@ namespace Ombi.Store.Context
public DbSet<AlbumRequest> AlbumRequests { get; set; }
public DbSet<TvRequests> TvRequests { get; set; }
public DbSet<ChildRequests> ChildRequests { get; set; }
public DbSet<EpisodeRequests> EpisodeRequests { get; set; }
public DbSet<Issues> Issues { get; set; }
public DbSet<IssueCategory> IssueCategories { get; set; }

@ -10,6 +10,7 @@ namespace Ombi.Store.Repository.Requests
MovieRequests GetRequest(int theMovieDbId);
Task Update(MovieRequests request);
Task Save();
Task MarkAsAvailable(int id);
IQueryable<MovieRequests> GetWithUser();
IQueryable<MovieRequests> GetWithUser(string userId);
IQueryable<MovieRequests> GetAll(string userId);

@ -23,6 +23,8 @@ namespace Ombi.Store.Repository.Requests
Task UpdateChild(ChildRequests request);
IQueryable<ChildRequests> GetChild();
IQueryable<ChildRequests> GetChild(string userId);
Task MarkEpisodeAsAvailable(int id);
Task MarkChildAsAvailable(int id);
Task Save();
Task DeleteChildRange(IEnumerable<ChildRequests> request);
}

@ -54,6 +54,14 @@ namespace Ombi.Store.Repository.Requests
.AsQueryable();
}
public async Task MarkAsAvailable(int id)
{
var movieRequest = new MovieRequests{ Id = id, Available = true, MarkedAsAvailable = DateTime.UtcNow};
var attached = Db.MovieRequests.Attach(movieRequest);
attached.Property(x => x.Available).IsModified = true;
attached.Property(x => x.MarkedAsAvailable).IsModified = true;
await Db.SaveChangesAsync();
}
public IQueryable<MovieRequests> GetWithUser(string userId)
{

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
@ -100,6 +101,23 @@ namespace Ombi.Store.Repository.Requests
.AsQueryable();
}
public async Task MarkChildAsAvailable(int id)
{
var request = new ChildRequests { Id = id, Available = true, MarkedAsAvailable = DateTime.UtcNow };
var attached = Db.ChildRequests.Attach(request);
attached.Property(x => x.Available).IsModified = true;
attached.Property(x => x.MarkedAsAvailable).IsModified = true;
await Db.SaveChangesAsync();
}
public async Task MarkEpisodeAsAvailable(int id)
{
var request = new EpisodeRequests { Id = id, Available = true };
var attached = Db.EpisodeRequests.Attach(request);
attached.Property(x => x.Available).IsModified = true;
await Db.SaveChangesAsync();
}
public async Task Save()
{
await InternalSaveChanges();
@ -128,10 +146,10 @@ namespace Ombi.Store.Repository.Requests
public async Task Update(TvRequests request)
{
Db.Update(request);
await InternalSaveChanges();
}
public async Task UpdateChild(ChildRequests request)
{
Db.Update(request);

@ -112,6 +112,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.MusicBrainz", "Omb
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Twilio", "Ombi.Api.Twilio\Ombi.Api.Twilio.csproj", "{34E5DD1A-6A90-448B-9E71-64D1ACD6C1A3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Webhook", "Ombi.Api.Webhook\Ombi.Api.Webhook.csproj", "{E2186FDA-D827-4781-8663-130AC382F12C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -298,6 +300,10 @@ Global
{34E5DD1A-6A90-448B-9E71-64D1ACD6C1A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{34E5DD1A-6A90-448B-9E71-64D1ACD6C1A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{34E5DD1A-6A90-448B-9E71-64D1ACD6C1A3}.Release|Any CPU.Build.0 = Release|Any CPU
{E2186FDA-D827-4781-8663-130AC382F12C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E2186FDA-D827-4781-8663-130AC382F12C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E2186FDA-D827-4781-8663-130AC382F12C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E2186FDA-D827-4781-8663-130AC382F12C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -336,6 +342,7 @@ Global
{4FA21A20-92F4-462C-B929-2C517A88CC56} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
{105EA346-766E-45B8-928B-DE6991DCB7EB} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{E2186FDA-D827-4781-8663-130AC382F12C} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{F3969B69-3B07-4884-A7AB-0BAB8B84DF94} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
{27111E7C-748E-4996-BD71-2117027C6460} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
{9266403C-B04D-4C0F-AC39-82F12C781949} = {9293CA11-360A-4C20-A674-B9E794431BF5}

@ -0,0 +1,49 @@

<settings-menu></settings-menu>
<div *ngIf="form">
<fieldset>
<legend>Webhook Notifications</legend>
<div class="col-md-6">
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="enable" formControlName="enabled">
<label for="enable">Enabled</label>
</div>
</div>
<div class="form-group">
<label for="baseUrl" class="control-label">Base URL</label>
<input type="text" class="form-control form-control-custom " id="webhookUrl" name="webhookUrl" [ngClass]="{'form-error': form.get('webhookUrl').hasError('required')}" formControlName="webhookUrl" pTooltip="Enter the URL of your webhook server.">
<small *ngIf="form.get('webhookUrl').hasError('required')" class="error-text">The Webhook URL is required</small>
</div>
<div class="form-group">
<label for="applicationToken" class="control-label">Application Token
<i class="fa fa-question-circle" pTooltip="Optional authentication token. Will be sent as 'Access-Token' header."></i>
</label>
<input type="text" class="form-control form-control-custom " id="applicationToken" name="applicationToken" [ngClass]="{'form-error': form.get('applicationToken').hasError('required')}" formControlName="applicationToken" pTooltip="Enter your Application token from Webhook.">
<small *ngIf="form.get('applicationToken').hasError('required')" class="error-text">The Application Token is required</small>
</div>
<div class="form-group">
<div>
<button [disabled]="form.invalid" type="button" (click)="test(form)" class="btn btn-primary-outline">
Test
<div id="spinner"></div>
</button>
</div>
</div>
<div class="form-group">
<div>
<button [disabled]="form.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
</div>
</div>
</form>
</div>
</fieldset>
</div>

@ -0,0 +1,64 @@
import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { INotificationTemplates, IWebhookNotificationSettings, NotificationType } from "../../interfaces";
import { TesterService } from "../../services";
import { NotificationService } from "../../services";
import { SettingsService } from "../../services";
@Component({
templateUrl: "./webhook.component.html",
})
export class WebhookComponent implements OnInit {
public NotificationType = NotificationType;
public templates: INotificationTemplates[];
public form: FormGroup;
constructor(private settingsService: SettingsService,
private notificationService: NotificationService,
private fb: FormBuilder,
private testerService: TesterService) { }
public ngOnInit() {
this.settingsService.getWebhookNotificationSettings().subscribe(x => {
this.form = this.fb.group({
enabled: [x.enabled],
webhookUrl: [x.webhookUrl, [Validators.required]],
applicationToken: [x.applicationToken],
});
});
}
public onSubmit(form: FormGroup) {
if (form.invalid) {
this.notificationService.error("Please check your entered values");
return;
}
const settings = <IWebhookNotificationSettings> form.value;
this.settingsService.saveWebhookNotificationSettings(settings).subscribe(x => {
if (x) {
this.notificationService.success("Successfully saved the Webhook settings");
} else {
this.notificationService.success("There was an error when saving the Webhook settings");
}
});
}
public test(form: FormGroup) {
if (form.invalid) {
this.notificationService.error("Please check your entered values");
return;
}
this.testerService.webhookTest(form.value).subscribe(x => {
if (x) {
this.notificationService.success("Successfully sent a Webhook message");
} else {
this.notificationService.error("There was an error when sending the Webhook message. Please check your settings");
}
});
}
}

@ -119,6 +119,11 @@ export interface IGotifyNotificationSettings extends INotificationSettings {
priority: number;
}
export interface IWebhookNotificationSettings extends INotificationSettings {
webhookUrl: string;
applicationToken: string;
}
export interface IMattermostNotifcationSettings extends INotificationSettings {
webhookUrl: string;
username: string;

@ -24,6 +24,7 @@ import {
ISlackNotificationSettings,
ISonarrSettings,
ITelegramNotifcationSettings,
IWebhookNotificationSettings,
IWhatsAppSettings,
} from "../../interfaces";
@ -49,6 +50,10 @@ export class TesterService extends ServiceHelpers {
return this.http.post<boolean>(`${this.url}gotify`, JSON.stringify(settings), { headers: this.headers });
}
public webhookTest(settings: IWebhookNotificationSettings): Observable<boolean> {
return this.http.post<boolean>(`${this.url}webhook`, JSON.stringify(settings), { headers: this.headers });
}
public mattermostTest(settings: IMattermostNotifcationSettings): Observable<boolean> {
return this.http.post<boolean>(`${this.url}mattermost`, JSON.stringify(settings), {headers: this.headers});
}

@ -37,6 +37,7 @@ import {
IUserManagementSettings,
IVoteSettings,
ITwilioSettings,
IWebhookNotificationSettings,
} from "../interfaces";
import { ServiceHelpers } from "./service.helpers";
@ -193,6 +194,14 @@ export class SettingsService extends ServiceHelpers {
.post<boolean>(`${this.url}/notifications/gotify`, JSON.stringify(settings), { headers: this.headers });
}
public getWebhookNotificationSettings(): Observable<IWebhookNotificationSettings> {
return this.http.get<IWebhookNotificationSettings>(`${this.url}/notifications/webhook`, { headers: this.headers });
}
public saveWebhookNotificationSettings(settings: IWebhookNotificationSettings): Observable<boolean> {
return this.http
.post<boolean>(`${this.url}/notifications/webhook`, JSON.stringify(settings), { headers: this.headers });
}
public getSlackNotificationSettings(): Observable<ISlackNotificationSettings> {
return this.http.get<ISlackNotificationSettings>(`${this.url}/notifications/slack`, {headers: this.headers});
}

@ -37,6 +37,7 @@ import { PushbulletComponent } from "./notifications/pushbullet.component";
import { PushoverComponent } from "./notifications/pushover.component";
import { SlackComponent } from "./notifications/slack.component";
import { TelegramComponent } from "./notifications/telegram.component";
import { WebhookComponent } from "./notifications/webhook.component";
import { OmbiComponent } from "./ombi/ombi.component";
import { PlexComponent } from "./plex/plex.component";
import { RadarrComponent } from "./radarr/radarr.component";
@ -73,6 +74,7 @@ const routes: Routes = [
{ path: "Pushover", component: PushoverComponent, canActivate: [AuthGuard] },
{ path: "Pushbullet", component: PushbulletComponent, canActivate: [AuthGuard] },
{ path: "Gotify", component: GotifyComponent, canActivate: [AuthGuard] },
{ path: "Webhook", component: WebhookComponent, canActivate: [AuthGuard] },
{ path: "Mattermost", component: MattermostComponent, canActivate: [AuthGuard] },
{ path: "Twilio", component: TwilioComponent, canActivate: [AuthGuard] },
{ path: "UserManagement", component: UserManagementComponent, canActivate: [AuthGuard] },
@ -134,6 +136,7 @@ const routes: Routes = [
MattermostComponent,
PushbulletComponent,
GotifyComponent,
WebhookComponent,
UserManagementComponent,
UpdateComponent,
AboutComponent,

@ -44,7 +44,7 @@ namespace Ombi.Controllers.V1.External
IPushbulletNotification pushbullet, ISlackNotification slack, IPushoverNotification po, IMattermostNotification mm,
IPlexApi plex, IEmbyApi emby, IRadarrApi radarr, ISonarrApi sonarr, ILogger<TesterController> log, IEmailProvider provider,
ICouchPotatoApi cpApi, ITelegramNotification telegram, ISickRageApi srApi, INewsletterJob newsletter, IMobileNotification mobileNotification,
ILidarrApi lidarrApi, IGotifyNotification gotifyNotification, IWhatsAppApi whatsAppApi, OmbiUserManager um)
ILidarrApi lidarrApi, IGotifyNotification gotifyNotification, IWhatsAppApi whatsAppApi, OmbiUserManager um, IWebhookNotification webhookNotification)
{
Service = service;
DiscordNotification = notification;
@ -68,6 +68,7 @@ namespace Ombi.Controllers.V1.External
GotifyNotification = gotifyNotification;
WhatsAppApi = whatsAppApi;
UserManager = um;
WebhookNotification = webhookNotification;
}
private INotificationService Service { get; }
@ -77,6 +78,7 @@ namespace Ombi.Controllers.V1.External
private ISlackNotification SlackNotification { get; }
private IPushoverNotification PushoverNotification { get; }
private IGotifyNotification GotifyNotification { get; }
private IWebhookNotification WebhookNotification { get; }
private IMattermostNotification MattermostNotification { get; }
private IPlexApi PlexApi { get; }
private IRadarrApi RadarrApi { get; }
@ -188,6 +190,30 @@ namespace Ombi.Controllers.V1.External
}
/// <summary>
/// Sends a test message to configured webhook using the provided settings
/// </summary>
/// <param name="settings">The settings.</param>
/// <returns></returns>
[HttpPost("webhook")]
public bool Webhook([FromBody] WebhookSettings settings)
{
try
{
settings.Enabled = true;
WebhookNotification.NotifyAsync(
new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings);
return true;
}
catch (Exception e)
{
Log.LogError(LoggingEvents.Api, e, "Could not test your webhook");
return false;
}
}
/// <summary>
/// Sends a test message to mattermost using the provided settings
/// </summary>

@ -1069,6 +1069,33 @@ namespace Ombi.Controllers.V1
return model;
}
/// <summary>
/// Saves the webhook notification settings.
/// </summary>
/// <param name="model">The model.</param>
/// <returns></returns>
[HttpPost("notifications/webhook")]
public async Task<bool> WebhookNotificationSettings([FromBody] WebhookNotificationViewModel model)
{
var settings = Mapper.Map<WebhookSettings>(model);
var result = await Save(settings);
return result;
}
/// <summary>
/// Gets the webhook notification settings.
/// </summary>
/// <returns></returns>
[HttpGet("notifications/webhook")]
public async Task<WebhookNotificationViewModel> WebhookNotificationSettings()
{
var settings = await Get<WebhookSettings>();
var model = Mapper.Map<WebhookNotificationViewModel>(settings);
return model;
}
/// <summary>
/// Saves the Newsletter notification settings.
/// </summary>

@ -75,7 +75,7 @@
"RequestAdded": "Kérés sikeresen leadva erre: {{title}}",
"Similar": "Hasonló",
"Refine": "Finomítás",
"SearchBarPlaceholder": "Type Here to Search",
"SearchBarPlaceholder": "A kereséshez írj be valamit",
"Movies": {
"PopularMovies": "Népszerű filmek",
"UpcomingMovies": "Közelgő filmek",

@ -3,7 +3,7 @@
"SignInButton": "Войти",
"UsernamePlaceholder": "Имя пользователя",
"PasswordPlaceholder": "Пароль",
"RememberMe": "Запомнить Меня",
"RememberMe": "Запомнить меня",
"ForgottenPassword": "Забыли пароль?",
"Errors": {
"IncorrectCredentials": "Неверное имя пользователя или пароль"

Loading…
Cancel
Save