mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-20 05:13:18 -07:00
Merge branch 'feature/v4' of https://github.com/tidusjar/Ombi into feature/v4
This commit is contained in:
commit
0e7cbe5c6b
40 changed files with 684 additions and 145 deletions
|
@ -9,7 +9,7 @@
|
||||||
trigger:
|
trigger:
|
||||||
branches:
|
branches:
|
||||||
include:
|
include:
|
||||||
- feature/*
|
- feature/v4
|
||||||
exclude:
|
exclude:
|
||||||
- develop
|
- develop
|
||||||
- master
|
- master
|
||||||
|
|
9
src/Ombi.Api.Twilio/IWhatsAppApi.cs
Normal file
9
src/Ombi.Api.Twilio/IWhatsAppApi.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Twilio
|
||||||
|
{
|
||||||
|
public interface IWhatsAppApi
|
||||||
|
{
|
||||||
|
Task<string> SendMessage(WhatsAppModel message, string accountSid, string authToken);
|
||||||
|
}
|
||||||
|
}
|
11
src/Ombi.Api.Twilio/Ombi.Api.Twilio.csproj
Normal file
11
src/Ombi.Api.Twilio/Ombi.Api.Twilio.csproj
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Twilio" Version="5.37.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
24
src/Ombi.Api.Twilio/WhatsAppApi.cs
Normal file
24
src/Ombi.Api.Twilio/WhatsAppApi.cs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Twilio;
|
||||||
|
using Twilio.Rest.Api.V2010.Account;
|
||||||
|
using Twilio.Types;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Twilio
|
||||||
|
{
|
||||||
|
public class WhatsAppApi : IWhatsAppApi
|
||||||
|
{
|
||||||
|
public async Task<string> SendMessage(WhatsAppModel message, string accountSid, string authToken)
|
||||||
|
{
|
||||||
|
TwilioClient.Init(accountSid, authToken);
|
||||||
|
|
||||||
|
var response =await MessageResource.CreateAsync(
|
||||||
|
body: message.Message,
|
||||||
|
from: new PhoneNumber($"whatsapp:{message.From}"),
|
||||||
|
to: new PhoneNumber($"whatsapp:{message.To}")
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.Sid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
src/Ombi.Api.Twilio/WhatsAppModel.cs
Normal file
9
src/Ombi.Api.Twilio/WhatsAppModel.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Ombi.Api.Twilio
|
||||||
|
{
|
||||||
|
public class WhatsAppModel
|
||||||
|
{
|
||||||
|
public string Message { get; set; }
|
||||||
|
public string To { get; set; }
|
||||||
|
public string From { get; set; }
|
||||||
|
}
|
||||||
|
}
|
27
src/Ombi.Core/Models/UI/TwilioSettingsViewModel.cs
Normal file
27
src/Ombi.Core/Models/UI/TwilioSettingsViewModel.cs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
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="TwilioSettingsViewModel" />
|
||||||
|
public class TwilioSettingsViewModel
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public WhatsAppSettingsViewModel WhatsAppSettings { get; set; } = new WhatsAppSettingsViewModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WhatsAppSettingsViewModel : WhatsAppSettings
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the notification templates.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The notification templates.
|
||||||
|
/// </value>
|
||||||
|
public List<NotificationTemplates> NotificationTemplates { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,6 +64,7 @@ using Ombi.Schedule.Processor;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
using Quartz.Spi;
|
using Quartz.Spi;
|
||||||
using Ombi.Api.MusicBrainz;
|
using Ombi.Api.MusicBrainz;
|
||||||
|
using Ombi.Api.Twilio;
|
||||||
|
|
||||||
namespace Ombi.DependencyInjection
|
namespace Ombi.DependencyInjection
|
||||||
{
|
{
|
||||||
|
@ -147,6 +148,7 @@ namespace Ombi.DependencyInjection
|
||||||
services.AddTransient<ILidarrApi, LidarrApi>();
|
services.AddTransient<ILidarrApi, LidarrApi>();
|
||||||
services.AddTransient<IGroupMeApi, GroupMeApi>();
|
services.AddTransient<IGroupMeApi, GroupMeApi>();
|
||||||
services.AddTransient<IMusicBrainzApi, MusicBrainzApi>();
|
services.AddTransient<IMusicBrainzApi, MusicBrainzApi>();
|
||||||
|
services.AddTransient<IWhatsAppApi, WhatsAppApi>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RegisterStore(this IServiceCollection services) {
|
public static void RegisterStore(this IServiceCollection services) {
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
<ProjectReference Include="..\Ombi.Api.Telegram\Ombi.Api.Telegram.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Telegram\Ombi.Api.Telegram.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.Trakt\Ombi.Api.Trakt.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Trakt\Ombi.Api.Trakt.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj" />
|
<ProjectReference Include="..\Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj" />
|
||||||
|
<ProjectReference Include="..\Ombi.Api.Twilio\Ombi.Api.Twilio.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj" />
|
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" />
|
<ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Schedule\Ombi.Schedule.csproj" />
|
<ProjectReference Include="..\Ombi.Schedule\Ombi.Schedule.csproj" />
|
||||||
|
|
|
@ -33,6 +33,7 @@ namespace Ombi.Helpers
|
||||||
public static EventId PushoverNotification => new EventId(4005);
|
public static EventId PushoverNotification => new EventId(4005);
|
||||||
public static EventId TelegramNotifcation => new EventId(4006);
|
public static EventId TelegramNotifcation => new EventId(4006);
|
||||||
public static EventId GotifyNotification => new EventId(4007);
|
public static EventId GotifyNotification => new EventId(4007);
|
||||||
|
public static EventId WhatsApp => new EventId(4008);
|
||||||
|
|
||||||
public static EventId TvSender => new EventId(5000);
|
public static EventId TvSender => new EventId(5000);
|
||||||
public static EventId SonarrSender => new EventId(5001);
|
public static EventId SonarrSender => new EventId(5001);
|
||||||
|
|
|
@ -11,5 +11,6 @@
|
||||||
Mattermost = 6,
|
Mattermost = 6,
|
||||||
Mobile = 7,
|
Mobile = 7,
|
||||||
Gotify = 8,
|
Gotify = 8,
|
||||||
|
WhatsApp = 9
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -20,6 +20,8 @@ namespace Ombi.Mapping.Profiles
|
||||||
CreateMap<MobileNotificationsViewModel, MobileNotificationSettings>().ReverseMap();
|
CreateMap<MobileNotificationsViewModel, MobileNotificationSettings>().ReverseMap();
|
||||||
CreateMap<NewsletterNotificationViewModel, NewsletterSettings>().ReverseMap();
|
CreateMap<NewsletterNotificationViewModel, NewsletterSettings>().ReverseMap();
|
||||||
CreateMap<GotifyNotificationViewModel, GotifySettings>().ReverseMap();
|
CreateMap<GotifyNotificationViewModel, GotifySettings>().ReverseMap();
|
||||||
|
CreateMap<WhatsAppSettingsViewModel, WhatsAppSettings>().ReverseMap();
|
||||||
|
CreateMap<TwilioSettingsViewModel, TwilioSettings>().ReverseMap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
125
src/Ombi.Notifications/Agents/WhatsAppNotification.cs
Normal file
125
src/Ombi.Notifications/Agents/WhatsAppNotification.cs
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
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;
|
||||||
|
using Ombi.Api.Twilio;
|
||||||
|
|
||||||
|
namespace Ombi.Notifications.Agents
|
||||||
|
{
|
||||||
|
public class WhatsAppNotification : BaseNotification<TwilioSettings>
|
||||||
|
{
|
||||||
|
public WhatsAppNotification(IWhatsAppApi api, ISettingsService<TwilioSettings> sn, ILogger<WhatsAppNotification> 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 => "WhatsAppNotification";
|
||||||
|
|
||||||
|
private IWhatsAppApi Api { get; }
|
||||||
|
private ILogger Logger { get; }
|
||||||
|
|
||||||
|
protected override bool ValidateConfiguration(TwilioSettings settings)
|
||||||
|
{
|
||||||
|
if (!settings.WhatsAppSettings?.Enabled ?? false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !settings.WhatsAppSettings.AccountSid.IsNullOrEmpty() && !settings.WhatsAppSettings.AuthToken.IsNullOrEmpty() && !settings.WhatsAppSettings.From.IsNullOrEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task NewRequest(NotificationOptions model, TwilioSettings settings)
|
||||||
|
{
|
||||||
|
await Run(model, settings, NotificationType.NewRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task NewIssue(NotificationOptions model, TwilioSettings settings)
|
||||||
|
{
|
||||||
|
await Run(model, settings, NotificationType.Issue);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task IssueComment(NotificationOptions model, TwilioSettings settings)
|
||||||
|
{
|
||||||
|
await Run(model, settings, NotificationType.IssueComment);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task IssueResolved(NotificationOptions model, TwilioSettings settings)
|
||||||
|
{
|
||||||
|
await Run(model, settings, NotificationType.IssueResolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task AddedToRequestQueue(NotificationOptions model, TwilioSettings settings)
|
||||||
|
{
|
||||||
|
await Run(model, settings, NotificationType.ItemAddedToFaultQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task RequestDeclined(NotificationOptions model, TwilioSettings settings)
|
||||||
|
{
|
||||||
|
await Run(model, settings, NotificationType.RequestDeclined);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task RequestApproved(NotificationOptions model, TwilioSettings settings)
|
||||||
|
{
|
||||||
|
await Run(model, settings, NotificationType.RequestApproved);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task AvailableRequest(NotificationOptions model, TwilioSettings settings)
|
||||||
|
{
|
||||||
|
await Run(model, settings, NotificationType.RequestAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task Send(NotificationMessage model, TwilioSettings settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var whatsApp = new WhatsAppModel
|
||||||
|
{
|
||||||
|
Message = model.Message,
|
||||||
|
From = settings.WhatsAppSettings.From,
|
||||||
|
To = ""// TODO
|
||||||
|
};
|
||||||
|
await Api.SendMessage(whatsApp, settings.WhatsAppSettings.AccountSid, settings.WhatsAppSettings.AuthToken);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError(LoggingEvents.WhatsApp, e, "Failed to send WhatsApp Notification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task Test(NotificationOptions model, TwilioSettings 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Run(NotificationOptions model, TwilioSettings settings, NotificationType type)
|
||||||
|
{
|
||||||
|
var parsed = await LoadTemplate(NotificationAgent.WhatsApp, type, model);
|
||||||
|
if (parsed.Disabled)
|
||||||
|
{
|
||||||
|
Logger.LogInformation($"Template {type} is disabled for {NotificationAgent.WhatsApp}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var notification = new NotificationMessage
|
||||||
|
{
|
||||||
|
Message = parsed.Message,
|
||||||
|
};
|
||||||
|
await Send(notification, settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@
|
||||||
<ProjectReference Include="..\Ombi.Api.Pushover\Ombi.Api.Pushover.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Pushover\Ombi.Api.Pushover.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.Slack\Ombi.Api.Slack.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Slack\Ombi.Api.Slack.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.Telegram\Ombi.Api.Telegram.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Telegram\Ombi.Api.Telegram.csproj" />
|
||||||
|
<ProjectReference Include="..\Ombi.Api.Twilio\Ombi.Api.Twilio.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Notifications.Templates\Ombi.Notifications.Templates.csproj" />
|
<ProjectReference Include="..\Ombi.Notifications.Templates\Ombi.Notifications.Templates.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" />
|
<ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Store\Ombi.Store.csproj" />
|
<ProjectReference Include="..\Ombi.Store\Ombi.Store.csproj" />
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
namespace Ombi.Settings.Settings.Models.Notifications
|
||||||
|
{
|
||||||
|
public class TwilioSettings : Settings
|
||||||
|
{
|
||||||
|
public WhatsAppSettings WhatsAppSettings { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WhatsAppSettings
|
||||||
|
{
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
public string From { get; set; }
|
||||||
|
public string AccountSid { get; set; }
|
||||||
|
public string AuthToken { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -108,7 +108,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Hubs", "Ombi.Hubs\Ombi
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.GroupMe", "Ombi.Api.GroupMe\Ombi.Api.GroupMe.csproj", "{9266403C-B04D-4C0F-AC39-82F12C781949}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.GroupMe", "Ombi.Api.GroupMe\Ombi.Api.GroupMe.csproj", "{9266403C-B04D-4C0F-AC39-82F12C781949}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.MusicBrainz", "Ombi.Api.MusicBrainz\Ombi.Api.MusicBrainz.csproj", "{C5C1769B-4197-4410-A160-0EEF39EDDC98}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.MusicBrainz", "Ombi.Api.MusicBrainz\Ombi.Api.MusicBrainz.csproj", "{C5C1769B-4197-4410-A160-0EEF39EDDC98}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Twilio", "Ombi.Api.Twilio\Ombi.Api.Twilio.csproj", "{34E5DD1A-6A90-448B-9E71-64D1ACD6C1A3}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
@ -292,6 +294,10 @@ Global
|
||||||
{C5C1769B-4197-4410-A160-0EEF39EDDC98}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C5C1769B-4197-4410-A160-0EEF39EDDC98}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{C5C1769B-4197-4410-A160-0EEF39EDDC98}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{C5C1769B-4197-4410-A160-0EEF39EDDC98}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{C5C1769B-4197-4410-A160-0EEF39EDDC98}.Release|Any CPU.Build.0 = Release|Any CPU
|
{C5C1769B-4197-4410-A160-0EEF39EDDC98}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{34E5DD1A-6A90-448B-9E71-64D1ACD6C1A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{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
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -334,6 +340,7 @@ Global
|
||||||
{27111E7C-748E-4996-BD71-2117027C6460} = {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}
|
{9266403C-B04D-4C0F-AC39-82F12C781949} = {9293CA11-360A-4C20-A674-B9E794431BF5}
|
||||||
{C5C1769B-4197-4410-A160-0EEF39EDDC98} = {9293CA11-360A-4C20-A674-B9E794431BF5}
|
{C5C1769B-4197-4410-A160-0EEF39EDDC98} = {9293CA11-360A-4C20-A674-B9E794431BF5}
|
||||||
|
{34E5DD1A-6A90-448B-9E71-64D1ACD6C1A3} = {9293CA11-360A-4C20-A674-B9E794431BF5}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {192E9BF8-00B4-45E4-BCCC-4C215725C869}
|
SolutionGuid = {192E9BF8-00B4-45E4-BCCC-4C215725C869}
|
||||||
|
|
|
@ -27,11 +27,16 @@ export interface INotificationTemplates {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NotificationAgent {
|
export enum NotificationAgent {
|
||||||
Email,
|
Email = 0,
|
||||||
Discord,
|
Discord = 1,
|
||||||
Pushbullet,
|
Pushbullet = 2,
|
||||||
Pushover,
|
Pushover = 3,
|
||||||
Telegram,
|
Telegram = 4,
|
||||||
|
Slack = 5,
|
||||||
|
Mattermost = 6,
|
||||||
|
Mobile = 7,
|
||||||
|
Gotify = 8,
|
||||||
|
WhatsApp = 9
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NotificationType {
|
export enum NotificationType {
|
||||||
|
@ -47,6 +52,7 @@ export enum NotificationType {
|
||||||
IssueResolved = 9,
|
IssueResolved = 9,
|
||||||
IssueComment = 10,
|
IssueComment = 10,
|
||||||
Newsletter = 11,
|
Newsletter = 11,
|
||||||
|
WhatsApp = 12,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDiscordNotifcationSettings extends INotificationSettings {
|
export interface IDiscordNotifcationSettings extends INotificationSettings {
|
||||||
|
@ -85,6 +91,18 @@ export interface IPushbulletNotificationSettings extends INotificationSettings {
|
||||||
channelTag: string;
|
channelTag: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ITwilioSettings extends ISettings {
|
||||||
|
whatsAppSettings: IWhatsAppSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWhatsAppSettings {
|
||||||
|
enabled: number;
|
||||||
|
from: string;
|
||||||
|
accountSid: string;
|
||||||
|
authToken: string;
|
||||||
|
notificationTemplates: INotificationTemplates[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface IPushoverNotificationSettings extends INotificationSettings {
|
export interface IPushoverNotificationSettings extends INotificationSettings {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
notificationTemplates: INotificationTemplates[];
|
notificationTemplates: INotificationTemplates[];
|
||||||
|
|
|
@ -91,6 +91,7 @@ export interface INotificationPreferences {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum INotificationAgent {
|
export enum INotificationAgent {
|
||||||
|
|
||||||
Email = 0,
|
Email = 0,
|
||||||
Discord = 1,
|
Discord = 1,
|
||||||
Pushbullet = 2,
|
Pushbullet = 2,
|
||||||
|
@ -99,4 +100,6 @@ export enum INotificationAgent {
|
||||||
Slack = 5,
|
Slack = 5,
|
||||||
Mattermost = 6,
|
Mattermost = 6,
|
||||||
Mobile = 7,
|
Mobile = 7,
|
||||||
|
Gotify = 8,
|
||||||
|
WhatsApp = 9
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<div *ngIf="movie && radarrEnabled">
|
<div *ngIf="movie && radarrEnabled" class="text-center">
|
||||||
<button mat-raised-button color="warn" class="text-center" (click)="openAdvancedOptions();">Advanced Options</button>
|
<button mat-raised-button color="warn" class="text-center" (click)="openAdvancedOptions();">Advanced Options</button>
|
||||||
</div>
|
</div>
|
|
@ -41,11 +41,11 @@
|
||||||
<td mat-cell *matCellDef="let element"> {{element.requestStatus | translate}} </td>
|
<td mat-cell *matCellDef="let element"> {{element.requestStatus | translate}} </td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="actions">
|
<ng-container matColumnDef="actions" >
|
||||||
<th mat-header-cell *matHeaderCellDef> </th>
|
<th mat-header-cell *matHeaderCellDef> </th>
|
||||||
<td mat-cell *matCellDef="let element">
|
<td mat-cell *matCellDef="let element" >
|
||||||
<button mat-raised-button color="accent" [routerLink]="'/details/movie/' + element.theMovieDbId">Details</button>
|
<button mat-raised-button color="accent" [routerLink]="'/details/movie/' + element.theMovieDbId">Details</button>
|
||||||
<button mat-raised-button color="warn" (click)="openOptions(element)">Options</button>
|
<button mat-raised-button color="warn" (click)="openOptions(element)" *ngIf="isAdmin">Options</button>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { Component, AfterViewInit, ViewChild, EventEmitter, Output } from "@angular/core";
|
import { Component, AfterViewInit, ViewChild, EventEmitter, Output, ChangeDetectorRef } from "@angular/core";
|
||||||
import { IMovieRequests, IRequestsViewModel } from "../../../interfaces";
|
import { IMovieRequests, IRequestsViewModel } from "../../../interfaces";
|
||||||
import { MatPaginator, MatSort } from "@angular/material";
|
import { MatPaginator, MatSort } from "@angular/material";
|
||||||
import { merge, Observable, of as observableOf } from 'rxjs';
|
import { merge, Observable, of as observableOf } from 'rxjs';
|
||||||
import { catchError, map, startWith, switchMap } from 'rxjs/operators';
|
import { catchError, map, startWith, switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { RequestServiceV2 } from "../../../services/requestV2.service";
|
import { RequestServiceV2 } from "../../../services/requestV2.service";
|
||||||
|
import { AuthService } from "../../../auth/auth.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "./movies-grid.component.html",
|
templateUrl: "./movies-grid.component.html",
|
||||||
|
@ -18,13 +19,15 @@ export class MoviesGridComponent implements AfterViewInit {
|
||||||
public displayedColumns: string[] = ['requestedUser.requestedBy', 'title', 'requestedDate', 'status', 'requestStatus', 'actions'];
|
public displayedColumns: string[] = ['requestedUser.requestedBy', 'title', 'requestedDate', 'status', 'requestStatus', 'actions'];
|
||||||
public gridCount: string = "15";
|
public gridCount: string = "15";
|
||||||
public showUnavailableRequests: boolean;
|
public showUnavailableRequests: boolean;
|
||||||
|
public isAdmin: boolean;
|
||||||
@Output() public onOpenOptions = new EventEmitter<{request: any, filter: any}>();
|
|
||||||
|
@Output() public onOpenOptions = new EventEmitter<{ request: any, filter: any, onChange: any }>();
|
||||||
|
|
||||||
@ViewChild(MatPaginator, { static: false }) paginator: MatPaginator;
|
@ViewChild(MatPaginator, { static: false }) paginator: MatPaginator;
|
||||||
@ViewChild(MatSort, { static: false }) sort: MatSort;
|
@ViewChild(MatSort, { static: false }) sort: MatSort;
|
||||||
|
|
||||||
constructor(private requestService: RequestServiceV2) {
|
constructor(private requestService: RequestServiceV2, private ref: ChangeDetectorRef,
|
||||||
|
private auth: AuthService) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +37,8 @@ export class MoviesGridComponent implements AfterViewInit {
|
||||||
// this.dataSource = results.collection;
|
// this.dataSource = results.collection;
|
||||||
// this.resultsLength = results.total;
|
// this.resultsLength = results.total;
|
||||||
|
|
||||||
|
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||||
|
|
||||||
// If the user changes the sort order, reset back to the first page.
|
// If the user changes the sort order, reset back to the first page.
|
||||||
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
|
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
|
||||||
|
|
||||||
|
@ -69,10 +74,16 @@ export class MoviesGridComponent implements AfterViewInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
public openOptions(request: IMovieRequests) {
|
public openOptions(request: IMovieRequests) {
|
||||||
const filter = () => { this.dataSource = this.dataSource.filter((req) => {
|
const filter = () => {
|
||||||
|
this.dataSource = this.dataSource.filter((req) => {
|
||||||
return req.id !== request.id;
|
return req.id !== request.id;
|
||||||
})};
|
})
|
||||||
|
};
|
||||||
|
|
||||||
this.onOpenOptions.emit({request: request, filter: filter});
|
const onChange = () => {
|
||||||
|
this.ref.detectChanges();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onOpenOptions.emit({ request: request, filter: filter, onChange: onChange });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,7 @@
|
||||||
<a (click)="delete()" mat-list-item>
|
<a (click)="delete()" mat-list-item>
|
||||||
<span mat-line>Delete Request</span>
|
<span mat-line>Delete Request</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a *ngIf="data.canApprove" (click)="approve()" mat-list-item>
|
||||||
|
<span mat-line>Approve Request</span>
|
||||||
|
</a>
|
||||||
</mat-nav-list>
|
</mat-nav-list>
|
|
@ -1,7 +1,8 @@
|
||||||
import {Component, Inject} from '@angular/core';
|
import { Component, Inject } from '@angular/core';
|
||||||
import {MAT_BOTTOM_SHEET_DATA, MatBottomSheetRef} from '@angular/material/bottom-sheet';
|
import { MAT_BOTTOM_SHEET_DATA, MatBottomSheetRef } from '@angular/material/bottom-sheet';
|
||||||
import { RequestService } from '../../../services';
|
import { RequestService } from '../../../services';
|
||||||
import { RequestType } from '../../../interfaces';
|
import { RequestType } from '../../../interfaces';
|
||||||
|
import { UpdateType } from '../../models/UpdateType';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'request-options',
|
selector: 'request-options',
|
||||||
|
@ -9,17 +10,29 @@ import { RequestType } from '../../../interfaces';
|
||||||
})
|
})
|
||||||
export class RequestOptionsComponent {
|
export class RequestOptionsComponent {
|
||||||
constructor(@Inject(MAT_BOTTOM_SHEET_DATA) public data: any,
|
constructor(@Inject(MAT_BOTTOM_SHEET_DATA) public data: any,
|
||||||
private requestService: RequestService, private bottomSheetRef: MatBottomSheetRef<RequestOptionsComponent>) { }
|
private requestService: RequestService, private bottomSheetRef: MatBottomSheetRef<RequestOptionsComponent>) { }
|
||||||
|
|
||||||
public async delete() {
|
public async delete() {
|
||||||
if (this.data.type === RequestType.movie) {
|
if (this.data.type === RequestType.movie) {
|
||||||
await this.requestService.removeMovieRequestAsync(this.data.id);
|
await this.requestService.removeMovieRequestAsync(this.data.id);
|
||||||
}
|
}
|
||||||
if(this.data.type === RequestType.tvShow) {
|
if (this.data.type === RequestType.tvShow) {
|
||||||
await this.requestService.deleteChild(this.data.id).toPromise();
|
await this.requestService.deleteChild(this.data.id).toPromise();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bottomSheetRef.dismiss(true);
|
this.bottomSheetRef.dismiss({type: UpdateType.Delete});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async approve() {
|
||||||
|
if (this.data.type === RequestType.movie) {
|
||||||
|
await this.requestService.approveMovie({id: this.data.id}).toPromise();
|
||||||
|
}
|
||||||
|
if (this.data.type === RequestType.tvShow) {
|
||||||
|
await this.requestService.approveChild({id: this.data.id}).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bottomSheetRef.dismiss({type: UpdateType.Approve});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
import { Component } from "@angular/core";
|
import { Component, ViewChild } from "@angular/core";
|
||||||
import { MatBottomSheet } from "@angular/material";
|
import { MatBottomSheet } from "@angular/material";
|
||||||
import { RequestOptionsComponent } from "./options/request-options.component";
|
import { RequestOptionsComponent } from "./options/request-options.component";
|
||||||
|
import { UpdateType } from "../models/UpdateType";
|
||||||
|
import { MoviesGridComponent } from "./movies-grid/movies-grid.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "./requests-list.component.html",
|
templateUrl: "./requests-list.component.html",
|
||||||
|
@ -9,15 +11,24 @@ import { RequestOptionsComponent } from "./options/request-options.component";
|
||||||
export class RequestsListComponent {
|
export class RequestsListComponent {
|
||||||
|
|
||||||
constructor(private bottomSheet: MatBottomSheet) { }
|
constructor(private bottomSheet: MatBottomSheet) { }
|
||||||
|
|
||||||
public onOpenOptions(event: {request: any, filter: any}) {
|
public onOpenOptions(event: { request: any, filter: any, onChange: any }) {
|
||||||
const ref = this.bottomSheet.open(RequestOptionsComponent, { data: { id: event.request.id, type: event.request.requestType } });
|
const ref = this.bottomSheet.open(RequestOptionsComponent, { data: { id: event.request.id, type: event.request.requestType, canApprove: event.request.canApprove } });
|
||||||
|
|
||||||
ref.afterDismissed().subscribe((result) => {
|
ref.afterDismissed().subscribe((result) => {
|
||||||
if (!result) {
|
if(!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.type == UpdateType.Delete) {
|
||||||
|
event.filter();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.type == UpdateType.Approve) {
|
||||||
|
// Need to do this here, as the status is calculated on the server
|
||||||
|
event.request.requestStatus = 'Common.ProcessingRequest';
|
||||||
|
event.onChange();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.filter();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
<div class="mat-elevation-z8">
|
<div class="mat-elevation-z8">
|
||||||
|
|
||||||
<grid-spinner [loading]="isLoadingResults"></grid-spinner>
|
<grid-spinner [loading]="isLoadingResults"></grid-spinner>
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select placeholder="Requests to Display" [(value)]="gridCount" (selectionChange)="ngAfterViewInit()">
|
<mat-select placeholder="Requests to Display" [(value)]="gridCount" (selectionChange)="ngAfterViewInit()">
|
||||||
<mat-option value="10">10</mat-option>
|
<mat-option value="10">10</mat-option>
|
||||||
<mat-option value="15">15</mat-option>
|
<mat-option value="15">15</mat-option>
|
||||||
<mat-option value="30">30</mat-option>
|
<mat-option value="30">30</mat-option>
|
||||||
<mat-option value="100">100</mat-option>
|
<mat-option value="100">100</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<table mat-table [dataSource]="dataSource" class="table" matSort matSortActive="title"
|
<table mat-table [dataSource]="dataSource" class="table" matSort matSortActive="title" matSortDisableClear
|
||||||
matSortDisableClear matSortDirection="desc">
|
matSortDirection="desc">
|
||||||
|
|
||||||
|
|
||||||
<ng-container matColumnDef="series">
|
<ng-container matColumnDef="series">
|
||||||
|
@ -21,44 +21,42 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="requestedBy">
|
<ng-container matColumnDef="requestedBy">
|
||||||
<th mat-header-cell *matHeaderCellDef > Requested By </th>
|
<th mat-header-cell *matHeaderCellDef> Requested By </th>
|
||||||
<td mat-cell *matCellDef="let element"> {{element.requestedUser.userAlias}} </td>
|
<td mat-cell *matCellDef="let element"> {{element.requestedUser.userAlias}} </td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ng-container matColumnDef="status">
|
|
||||||
<th mat-header-cell *matHeaderCellDef > Status </th>
|
|
||||||
<td mat-cell *matCellDef="let element">
|
|
||||||
{{element.parentRequest.status}}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="requestedDate">
|
<ng-container matColumnDef="status">
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Requested Date </th>
|
<th mat-header-cell *matHeaderCellDef> Status </th>
|
||||||
<td mat-cell *matCellDef="let element">
|
<td mat-cell *matCellDef="let element">
|
||||||
{{element.requestedDate | amLocal | amDateFormat: 'LL'}}
|
{{element.parentRequest.status}}
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="requestStatus">
|
<ng-container matColumnDef="requestedDate">
|
||||||
<th mat-header-cell *matHeaderCellDef > Request Status </th>
|
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Requested Date </th>
|
||||||
<td mat-cell *matCellDef="let element">
|
<td mat-cell *matCellDef="let element">
|
||||||
<div *ngIf="element.approved && !element.available">{{'Common.ProcessingRequest' | translate}}</div>
|
{{element.requestedDate | amLocal | amDateFormat: 'LL'}}
|
||||||
<div *ngIf="element.requested && !element.approved && !element.available">{{'Common.PendingApproval' |
|
</td>
|
||||||
translate}}
|
</ng-container>
|
||||||
</div>
|
|
||||||
<div *ngIf="!element.requested && !element.available && !element.approved">{{'Common.NotRequested' |
|
<ng-container matColumnDef="requestStatus">
|
||||||
translate}}
|
<th mat-header-cell *matHeaderCellDef> Request Status </th>
|
||||||
</div>
|
<td mat-cell *matCellDef="let element">
|
||||||
</td>
|
<div *ngIf="element.approved && !element.available">{{'Common.ProcessingRequest' | translate}}</div>
|
||||||
</ng-container>
|
<div *ngIf="!element.approved && !element.available">{{'Common.PendingApproval' |translate}}</div>
|
||||||
|
<div *ngIf="element.available">{{'Common.Available' | translate}}</div>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="actions">
|
<ng-container matColumnDef="actions">
|
||||||
<th mat-header-cell *matHeaderCellDef> </th>
|
<th mat-header-cell *matHeaderCellDef> </th>
|
||||||
<td mat-cell *matCellDef="let element">
|
<td mat-cell *matCellDef="let element">
|
||||||
<button mat-raised-button color="accent" [routerLink]="'/details/tv/' + element.parentRequest.tvDbId">Details</button>
|
<button mat-raised-button color="accent"
|
||||||
<button mat-raised-button color="warn" (click)="openOptions(element)">Options</button>
|
[routerLink]="'/details/tv/' + element.parentRequest.tvDbId">Details</button>
|
||||||
|
<button mat-raised-button color="warn" (click)="openOptions(element)" *ngIf="isAdmin">Options</button>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
@ -67,4 +65,4 @@
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<mat-paginator [length]="resultsLength" [pageSize]="gridCount"></mat-paginator>
|
<mat-paginator [length]="resultsLength" [pageSize]="gridCount"></mat-paginator>
|
||||||
</div>
|
</div>
|
|
@ -1,10 +1,11 @@
|
||||||
import { Component, AfterViewInit, ViewChild, Output, EventEmitter } from "@angular/core";
|
import { Component, AfterViewInit, ViewChild, Output, EventEmitter, ChangeDetectorRef } from "@angular/core";
|
||||||
import { IRequestsViewModel, IChildRequests } from "../../../interfaces";
|
import { IRequestsViewModel, IChildRequests } from "../../../interfaces";
|
||||||
import { MatPaginator, MatSort } from "@angular/material";
|
import { MatPaginator, MatSort } from "@angular/material";
|
||||||
import { merge, of as observableOf, Observable } from 'rxjs';
|
import { merge, of as observableOf, Observable } from 'rxjs';
|
||||||
import { catchError, map, startWith, switchMap } from 'rxjs/operators';
|
import { catchError, map, startWith, switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { RequestServiceV2 } from "../../../services/requestV2.service";
|
import { RequestServiceV2 } from "../../../services/requestV2.service";
|
||||||
|
import { AuthService } from "../../../auth/auth.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "./tv-grid.component.html",
|
templateUrl: "./tv-grid.component.html",
|
||||||
|
@ -18,18 +19,21 @@ export class TvGridComponent implements AfterViewInit {
|
||||||
public displayedColumns: string[] = ['series', 'requestedBy', 'status', 'requestStatus', 'requestedDate','actions'];
|
public displayedColumns: string[] = ['series', 'requestedBy', 'status', 'requestStatus', 'requestedDate','actions'];
|
||||||
public gridCount: string = "15";
|
public gridCount: string = "15";
|
||||||
public showUnavailableRequests: boolean;
|
public showUnavailableRequests: boolean;
|
||||||
|
public isAdmin: boolean;
|
||||||
|
|
||||||
@Output() public onOpenOptions = new EventEmitter<{request: any, filter: any}>();
|
@Output() public onOpenOptions = new EventEmitter<{request: any, filter: any, onChange: any}>();
|
||||||
|
|
||||||
@ViewChild(MatPaginator, {static: false}) paginator: MatPaginator;
|
@ViewChild(MatPaginator, {static: false}) paginator: MatPaginator;
|
||||||
@ViewChild(MatSort, {static: false}) sort: MatSort;
|
@ViewChild(MatSort, {static: false}) sort: MatSort;
|
||||||
|
|
||||||
constructor(private requestService: RequestServiceV2) {
|
constructor(private requestService: RequestServiceV2, private auth: AuthService,
|
||||||
|
private ref: ChangeDetectorRef) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ngAfterViewInit() {
|
public async ngAfterViewInit() {
|
||||||
|
|
||||||
|
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||||
// If the user changes the sort order, reset back to the first page.
|
// If the user changes the sort order, reset back to the first page.
|
||||||
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
|
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
|
||||||
|
|
||||||
|
@ -58,8 +62,12 @@ export class TvGridComponent implements AfterViewInit {
|
||||||
const filter = () => { this.dataSource = this.dataSource.filter((req) => {
|
const filter = () => { this.dataSource = this.dataSource.filter((req) => {
|
||||||
return req.id !== request.id;
|
return req.id !== request.id;
|
||||||
})};
|
})};
|
||||||
|
|
||||||
|
const onChange = () => {
|
||||||
|
this.ref.detectChanges();
|
||||||
|
};
|
||||||
|
|
||||||
this.onOpenOptions.emit({request: request, filter: filter});
|
this.onOpenOptions.emit({request: request, filter: filter, onChange});
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadData(): Observable<IRequestsViewModel<IChildRequests>> {
|
private loadData(): Observable<IRequestsViewModel<IChildRequests>> {
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
export enum UpdateType {
|
||||||
|
Delete,
|
||||||
|
Approve
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import {
|
||||||
ISlackNotificationSettings,
|
ISlackNotificationSettings,
|
||||||
ISonarrSettings,
|
ISonarrSettings,
|
||||||
ITelegramNotifcationSettings,
|
ITelegramNotifcationSettings,
|
||||||
|
IWhatsAppSettings,
|
||||||
} from "../../interfaces";
|
} from "../../interfaces";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -52,6 +53,10 @@ export class TesterService extends ServiceHelpers {
|
||||||
return this.http.post<boolean>(`${this.url}mattermost`, JSON.stringify(settings), {headers: this.headers});
|
return this.http.post<boolean>(`${this.url}mattermost`, JSON.stringify(settings), {headers: this.headers});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public whatsAppTest(settings: IWhatsAppSettings): Observable<boolean> {
|
||||||
|
return this.http.post<boolean>(`${this.url}whatsapp`, JSON.stringify(settings), {headers: this.headers});
|
||||||
|
}
|
||||||
|
|
||||||
public slackTest(settings: ISlackNotificationSettings): Observable<boolean> {
|
public slackTest(settings: ISlackNotificationSettings): Observable<boolean> {
|
||||||
return this.http.post<boolean>(`${this.url}slack`, JSON.stringify(settings), {headers: this.headers});
|
return this.http.post<boolean>(`${this.url}slack`, JSON.stringify(settings), {headers: this.headers});
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import {
|
||||||
IUpdateSettings,
|
IUpdateSettings,
|
||||||
IUserManagementSettings,
|
IUserManagementSettings,
|
||||||
IVoteSettings,
|
IVoteSettings,
|
||||||
|
ITwilioSettings,
|
||||||
} from "../interfaces";
|
} from "../interfaces";
|
||||||
|
|
||||||
import { ServiceHelpers } from "./service.helpers";
|
import { ServiceHelpers } from "./service.helpers";
|
||||||
|
@ -254,6 +255,15 @@ export class SettingsService extends ServiceHelpers {
|
||||||
.post<boolean>(`${this.url}/notifications/telegram`, JSON.stringify(settings), {headers: this.headers});
|
.post<boolean>(`${this.url}/notifications/telegram`, JSON.stringify(settings), {headers: this.headers});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getTwilioSettings(): Observable<ITwilioSettings> {
|
||||||
|
return this.http.get<ITwilioSettings>(`${this.url}/notifications/twilio`, {headers: this.headers});
|
||||||
|
}
|
||||||
|
|
||||||
|
public saveTwilioSettings(settings: ITwilioSettings): Observable<boolean> {
|
||||||
|
return this.http
|
||||||
|
.post<boolean>(`${this.url}/notifications/twilio`, JSON.stringify(settings), {headers: this.headers});
|
||||||
|
}
|
||||||
|
|
||||||
public getJobSettings(): Observable<IJobSettings> {
|
public getJobSettings(): Observable<IJobSettings> {
|
||||||
return this.http.get<IJobSettings>(`${this.url}/jobs`, {headers: this.headers});
|
return this.http.get<IJobSettings>(`${this.url}/jobs`, {headers: this.headers});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,26 @@
|
||||||
|
<wiki [url]="'https://github.com/tidusjar/Ombi/wiki/Notification-Template-Variables'" [text]="'Notification Variables'">
|
||||||
<wiki [url]="'https://github.com/tidusjar/Ombi/wiki/Notification-Template-Variables'" [text]="'Notification Variables'"></wiki>
|
</wiki>
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
|
<mat-accordion>
|
||||||
|
<mat-expansion-panel *ngFor="let template of templates">
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
{{NotificationType[template.notificationType] | humanize}}
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
|
||||||
<ngb-accordion [closeOthers]="true" activeIds="0-header">
|
<div>
|
||||||
<ngb-panel *ngFor="let template of templates" id="{{template.notificationType}}" title="{{NotificationType[template.notificationType] | humanize}}">
|
<mat-slide-toggle [(ngModel)]="template.enabled">Enable</mat-slide-toggle>
|
||||||
<ng-template ngbPanelContent>
|
</div>
|
||||||
<div class="panel panel-default a">
|
|
||||||
<div class="panel-body">
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="checkbox">
|
|
||||||
<input type="checkbox" id="enabled" [(ngModel)]="template.enabled" ng-checked="template.enabled"><label for="enabled">Enable</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" *ngIf="showSubject">
|
|
||||||
<label class="control-label">Subject</label>
|
|
||||||
<div>
|
|
||||||
<input type="text" class="form-control form-control-custom" [(ngModel)]="template.subject" value="{{template.subject}}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<mat-form-field *ngIf="showSubject">
|
||||||
<label class="control-label">Message</label>
|
<input matInput placeholder="Subject" [(ngModel)]="template.subject">
|
||||||
<div>
|
</mat-form-field>
|
||||||
<textarea type="text" class="form-control form-control-custom" [(ngModel)]="template.message" value="{{template.message}}"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</ng-template>
|
<mat-form-field>
|
||||||
</ngb-panel>
|
<textarea matInput placeholder="Message" [(ngModel)]="template.message"></textarea>
|
||||||
</ngb-accordion>
|
</mat-form-field>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
</mat-accordion>
|
|
@ -0,0 +1,22 @@
|
||||||
|
<settings-menu>
|
||||||
|
</settings-menu>
|
||||||
|
<div *ngIf="form" class="container">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Twilio</legend>
|
||||||
|
<span>Below are the supported integrations with Twilio</span>
|
||||||
|
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
|
||||||
|
|
||||||
|
<mat-tab-group>
|
||||||
|
<mat-tab label="WhatsApp">
|
||||||
|
<app-whatsapp [form]="form" [templates]="templates"></app-whatsapp>
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
|
||||||
|
<div class=" md-form-field ">
|
||||||
|
<div>
|
||||||
|
<button mat-raised-button type="submit " color="primary" [disabled]="form.invalid ">Submit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||||
|
|
||||||
|
import { INotificationTemplates, ITwilioSettings, NotificationType } from "../../../interfaces";
|
||||||
|
import { TesterService } from "../../../services";
|
||||||
|
import { NotificationService } from "../../../services";
|
||||||
|
import { SettingsService } from "../../../services";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: "./twilio.component.html",
|
||||||
|
})
|
||||||
|
export class TwilioComponent 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.getTwilioSettings().subscribe(x => {
|
||||||
|
this.templates = x.whatsAppSettings.notificationTemplates;
|
||||||
|
|
||||||
|
this.form = this.fb.group({
|
||||||
|
whatsAppSettings: this.fb.group({
|
||||||
|
enabled: [x.whatsAppSettings.enabled],
|
||||||
|
accountSid: [x.whatsAppSettings.accountSid],
|
||||||
|
authToken: [x.whatsAppSettings.authToken],
|
||||||
|
from: [x.whatsAppSettings.from],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public onSubmit(form: FormGroup) {
|
||||||
|
if (form.invalid) {
|
||||||
|
this.notificationService.error("Please check your entered values");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = <ITwilioSettings> form.value;
|
||||||
|
settings.whatsAppSettings.notificationTemplates = this.templates;
|
||||||
|
|
||||||
|
this.settingsService.saveTwilioSettings(settings).subscribe(x => {
|
||||||
|
if (x) {
|
||||||
|
this.notificationService.success("Successfully saved the Twilio settings");
|
||||||
|
} else {
|
||||||
|
this.notificationService.success("There was an error when saving the Twilio settings");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
<div class="container" style="padding-top: 3%;">
|
||||||
|
<div [formGroup]="form" class="col">
|
||||||
|
<div formGroupName="whatsAppSettings" class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div>
|
||||||
|
<mat-slide-toggle formControlName="enabled">Enable</mat-slide-toggle>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="md-form-field">
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput placeholder="From Number" formControlName="from" matTooltip="The mobile number that the WhatsApp message is from, with the international prefix">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="md-form-field">
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput placeholder="Account SID" formControlName="accountSid" matTooltip="The Account SID that you can find from the Programmable SMS Dashboard">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="md-form-field">
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput placeholder="Authentication Token" formControlName="authToken" matTooltip="The Auth Token that you can find from the Programmable SMS Dashboard">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="md-form-field">
|
||||||
|
<div>
|
||||||
|
<button mat-raised-button type="button" color="accent" (click)="test(form)">Test</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<notification-templates [templates]="templates" [showSubject]="false"></notification-templates>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { Component, Input } from "@angular/core";
|
||||||
|
import { FormGroup } from "@angular/forms";
|
||||||
|
import { TesterService, NotificationService } from "../../../services";
|
||||||
|
import { INotificationTemplates, NotificationType } from "../../../interfaces";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: "./whatsapp.component.html",
|
||||||
|
selector: "app-whatsapp"
|
||||||
|
})
|
||||||
|
export class WhatsAppComponent {
|
||||||
|
|
||||||
|
public NotificationType = NotificationType;
|
||||||
|
@Input() public templates: INotificationTemplates[];
|
||||||
|
@Input() public form: FormGroup;
|
||||||
|
|
||||||
|
constructor(private testerService: TesterService,
|
||||||
|
private notificationService: NotificationService) { }
|
||||||
|
|
||||||
|
|
||||||
|
public test(form: FormGroup) {
|
||||||
|
if (form.invalid) {
|
||||||
|
this.notificationService.error("Please check your entered values");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.testerService.whatsAppTest(form.get("whatsAppSettings").value).subscribe(x => {
|
||||||
|
if (x) {
|
||||||
|
this.notificationService.success( "Successfully sent a WhatsApp message, please check the appropriate channel");
|
||||||
|
} else {
|
||||||
|
this.notificationService.error("There was an error when sending the WhatsApp message. Please check your settings");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,6 +55,8 @@ import { MatMenuModule} from "@angular/material";
|
||||||
import { SharedModule } from "../shared/shared.module";
|
import { SharedModule } from "../shared/shared.module";
|
||||||
import { HubService } from "../services/hub.service";
|
import { HubService } from "../services/hub.service";
|
||||||
import { LogsComponent } from "./logs/logs.component";
|
import { LogsComponent } from "./logs/logs.component";
|
||||||
|
import { TwilioComponent } from "./notifications/twilio/twilio.component";
|
||||||
|
import { WhatsAppComponent } from "./notifications/twilio/whatsapp.component";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: "Ombi", component: OmbiComponent, canActivate: [AuthGuard] },
|
{ path: "Ombi", component: OmbiComponent, canActivate: [AuthGuard] },
|
||||||
|
@ -72,6 +74,7 @@ const routes: Routes = [
|
||||||
{ path: "Pushbullet", component: PushbulletComponent, canActivate: [AuthGuard] },
|
{ path: "Pushbullet", component: PushbulletComponent, canActivate: [AuthGuard] },
|
||||||
{ path: "Gotify", component: GotifyComponent, canActivate: [AuthGuard] },
|
{ path: "Gotify", component: GotifyComponent, canActivate: [AuthGuard] },
|
||||||
{ path: "Mattermost", component: MattermostComponent, canActivate: [AuthGuard] },
|
{ path: "Mattermost", component: MattermostComponent, canActivate: [AuthGuard] },
|
||||||
|
{ path: "Twilio", component: TwilioComponent, canActivate: [AuthGuard] },
|
||||||
{ path: "UserManagement", component: UserManagementComponent, canActivate: [AuthGuard] },
|
{ path: "UserManagement", component: UserManagementComponent, canActivate: [AuthGuard] },
|
||||||
{ path: "Update", component: UpdateComponent, canActivate: [AuthGuard] },
|
{ path: "Update", component: UpdateComponent, canActivate: [AuthGuard] },
|
||||||
{ path: "CouchPotato", component: CouchPotatoComponent, canActivate: [AuthGuard] },
|
{ path: "CouchPotato", component: CouchPotatoComponent, canActivate: [AuthGuard] },
|
||||||
|
@ -149,6 +152,8 @@ const routes: Routes = [
|
||||||
TheMovieDbComponent,
|
TheMovieDbComponent,
|
||||||
FailedRequestsComponent,
|
FailedRequestsComponent,
|
||||||
LogsComponent,
|
LogsComponent,
|
||||||
|
TwilioComponent,
|
||||||
|
WhatsAppComponent
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
RouterModule,
|
RouterModule,
|
||||||
|
|
|
@ -2,62 +2,63 @@
|
||||||
|
|
||||||
<button mat-button [matMenuTriggerFor]="configurationmenu"><i class="fa fa-cogs" aria-hidden="true"></i> Configuration</button>
|
<button mat-button [matMenuTriggerFor]="configurationmenu"><i class="fa fa-cogs" aria-hidden="true"></i> Configuration</button>
|
||||||
<mat-menu #configurationmenu="matMenu">
|
<mat-menu #configurationmenu="matMenu">
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Customization']">Customization</button>
|
<button mat-menu-item [routerLink]="['/Settings/Customization']">Customization</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/LandingPage']">Landing Page</button>
|
<button mat-menu-item [routerLink]="['/Settings/LandingPage']">Landing Page</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Issues']">Issues</button>
|
<button mat-menu-item [routerLink]="['/Settings/Issues']">Issues</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/UserManagement']">User Management</button>
|
<button mat-menu-item [routerLink]="['/Settings/UserManagement']">User Management</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Authentication']">Authentication</button>
|
<button mat-menu-item [routerLink]="['/Settings/Authentication']">Authentication</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Vote']">Vote</button>
|
<button mat-menu-item [routerLink]="['/Settings/Vote']">Vote</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/TheMovieDb']">The Movie Database</button>
|
<button mat-menu-item [routerLink]="['/Settings/TheMovieDb']">The Movie Database</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<button mat-button [matMenuTriggerFor]="mediaservermenu"><i class="fa fa-server" aria-hidden="true"></i> Media Server</button>
|
<button mat-button [matMenuTriggerFor]="mediaservermenu"><i class="fa fa-server" aria-hidden="true"></i> Media Server</button>
|
||||||
<mat-menu #mediaservermenu="matMenu">
|
<mat-menu #mediaservermenu="matMenu">
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Plex']">Plex</button>
|
<button mat-menu-item [routerLink]="['/Settings/Plex']">Plex</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Emby']">Emby/Jellyfin</button>
|
<button mat-menu-item [routerLink]="['/Settings/Emby']">Emby/Jellyfin</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<button mat-button [matMenuTriggerFor]="tvmenu"><i class="fa fa-television" aria-hidden="true"></i> TV</button>
|
<button mat-button [matMenuTriggerFor]="tvmenu"><i class="fa fa-television" aria-hidden="true"></i> TV</button>
|
||||||
<mat-menu #tvmenu="matMenu">
|
<mat-menu #tvmenu="matMenu">
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Sonarr']">Sonarr</button>
|
<button mat-menu-item [routerLink]="['/Settings/Sonarr']">Sonarr</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/DogNzb']">DogNzb</button>
|
<button mat-menu-item [routerLink]="['/Settings/DogNzb']">DogNzb</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/SickRage']">SickRage</button>
|
<button mat-menu-item [routerLink]="['/Settings/SickRage']">SickRage</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<button mat-button [matMenuTriggerFor]="movieMenu"><i class="fa fa-film" aria-hidden="true"></i> Movies</button>
|
<button mat-button [matMenuTriggerFor]="movieMenu"><i class="fa fa-film" aria-hidden="true"></i> Movies</button>
|
||||||
<mat-menu #movieMenu="matMenu">
|
<mat-menu #movieMenu="matMenu">
|
||||||
<button mat-menu-item [routerLink]="['/Settings/CouchPotato']">CouchPotato</button>
|
<button mat-menu-item [routerLink]="['/Settings/CouchPotato']">CouchPotato</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/DogNzb']">DogNzb</button>
|
<button mat-menu-item [routerLink]="['/Settings/DogNzb']">DogNzb</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Radarr']">Radarr</button>
|
<button mat-menu-item [routerLink]="['/Settings/Radarr']">Radarr</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<button mat-button [matMenuTriggerFor]="musicMenu"><i class="fa fa-music" aria-hidden="true"></i> Music</button>
|
<button mat-button [matMenuTriggerFor]="musicMenu"><i class="fa fa-music" aria-hidden="true"></i> Music</button>
|
||||||
<mat-menu #musicMenu="matMenu">
|
<mat-menu #musicMenu="matMenu">
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Lidarr']">Lidarr</button>
|
<button mat-menu-item [routerLink]="['/Settings/Lidarr']">Lidarr</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<button mat-button [matMenuTriggerFor]="notificationMenu"><i class="fa fa-bell-o" aria-hidden="true"></i> Notifications</button>
|
<button mat-button [matMenuTriggerFor]="notificationMenu"><i class="fa fa-bell-o" aria-hidden="true"></i> Notifications</button>
|
||||||
<mat-menu #notificationMenu="matMenu">
|
<mat-menu #notificationMenu="matMenu">
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Mobile']">Mobile</button>
|
<button mat-menu-item [routerLink]="['/Settings/Mobile']">Mobile</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Email']">Email</button>
|
<button mat-menu-item [routerLink]="['/Settings/Email']">Email</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/MassEmail']">MassEmail</button>
|
<button mat-menu-item [routerLink]="['/Settings/MassEmail']">MassEmail</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Newsletter']">Newsletter</button>
|
<button mat-menu-item [routerLink]="['/Settings/Newsletter']">Newsletter</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Discord']">Discord</button>
|
<button mat-menu-item [routerLink]="['/Settings/Discord']">Discord</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Slack']">Slack</button>
|
<button mat-menu-item [routerLink]="['/Settings/Slack']">Slack</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Pushbullet']">Pushbullet</button>
|
<button mat-menu-item [routerLink]="['/Settings/Pushbullet']">Pushbullet</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Pushover']">Pushover</button>
|
<button mat-menu-item [routerLink]="['/Settings/Pushover']">Pushover</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Mattermost']">Mattermost</button>
|
<button mat-menu-item [routerLink]="['/Settings/Mattermost']">Mattermost</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Telegram']">Telegram</button>
|
<button mat-menu-item [routerLink]="['/Settings/Telegram']">Telegram</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Gotify']">Gotify</button>
|
<button mat-menu-item [routerLink]="['/Settings/Gotify']">Gotify</button>
|
||||||
|
<button mat-menu-item [routerLink]="['/Settings/Twilio']">Twilio</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<button mat-button [matMenuTriggerFor]="systemMenu"><i class="fa fa-tachometer" aria-hidden="true"></i> System</button>
|
<button mat-button [matMenuTriggerFor]="systemMenu"><i class="fa fa-tachometer" aria-hidden="true"></i> System</button>
|
||||||
<mat-menu #systemMenu="matMenu">
|
<mat-menu #systemMenu="matMenu">
|
||||||
<button mat-menu-item [routerLink]="['/Settings/About']">About</button>
|
<button mat-menu-item [routerLink]="['/Settings/About']">About</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/FailedRequests']">Failed Requests</button>
|
<button mat-menu-item [routerLink]="['/Settings/FailedRequests']">Failed Requests</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Update']">Update</button>
|
<button mat-menu-item [routerLink]="['/Settings/Update']">Update</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Jobs']">Scheduled Tasks</button>
|
<button mat-menu-item [routerLink]="['/Settings/Jobs']">Scheduled Tasks</button>
|
||||||
<button mat-menu-item [routerLink]="['/Settings/Logs']">Logs</button>
|
<button mat-menu-item [routerLink]="['/Settings/Logs']">Logs</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="col-md-12">
|
<div class="col">
|
||||||
<div *ngIf="!text" class="col-md-1 offset-md-11">
|
<div *ngIf="!text" class="col-md-4 ml-auto ">
|
||||||
<a href="{{url}}" target="_blank">
|
<a href="{{url}}" target="_blank">
|
||||||
<button mat-raised-button color="accent">
|
<button mat-raised-button color="accent">
|
||||||
<span>Wiki</span>
|
<span>Wiki</span>
|
||||||
|
@ -7,7 +7,7 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="text" class="col-md-1 offset-md-9">
|
<div *ngIf="text" class="col-md-4 ml-auto ">
|
||||||
<a href="{{url}}" target="_blank">
|
<a href="{{url}}" target="_blank">
|
||||||
<button mat-raised-button color="accent">
|
<button mat-raised-button color="accent">
|
||||||
<span>{{text}}</span>
|
<span>{{text}}</span>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Ombi.Api.CouchPotato;
|
using Ombi.Api.CouchPotato;
|
||||||
using Ombi.Api.Emby;
|
using Ombi.Api.Emby;
|
||||||
|
@ -10,7 +11,9 @@ using Ombi.Api.Plex;
|
||||||
using Ombi.Api.Radarr;
|
using Ombi.Api.Radarr;
|
||||||
using Ombi.Api.SickRage;
|
using Ombi.Api.SickRage;
|
||||||
using Ombi.Api.Sonarr;
|
using Ombi.Api.Sonarr;
|
||||||
|
using Ombi.Api.Twilio;
|
||||||
using Ombi.Attributes;
|
using Ombi.Attributes;
|
||||||
|
using Ombi.Core.Authentication;
|
||||||
using Ombi.Core.Models.UI;
|
using Ombi.Core.Models.UI;
|
||||||
using Ombi.Core.Notifications;
|
using Ombi.Core.Notifications;
|
||||||
using Ombi.Core.Settings.Models.External;
|
using Ombi.Core.Settings.Models.External;
|
||||||
|
@ -22,6 +25,7 @@ using Ombi.Notifications.Models;
|
||||||
using Ombi.Schedule.Jobs.Ombi;
|
using Ombi.Schedule.Jobs.Ombi;
|
||||||
using Ombi.Settings.Settings.Models.External;
|
using Ombi.Settings.Settings.Models.External;
|
||||||
using Ombi.Settings.Settings.Models.Notifications;
|
using Ombi.Settings.Settings.Models.Notifications;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
|
||||||
namespace Ombi.Controllers.V1.External
|
namespace Ombi.Controllers.V1.External
|
||||||
{
|
{
|
||||||
|
@ -40,7 +44,7 @@ namespace Ombi.Controllers.V1.External
|
||||||
IPushbulletNotification pushbullet, ISlackNotification slack, IPushoverNotification po, IMattermostNotification mm,
|
IPushbulletNotification pushbullet, ISlackNotification slack, IPushoverNotification po, IMattermostNotification mm,
|
||||||
IPlexApi plex, IEmbyApi emby, IRadarrApi radarr, ISonarrApi sonarr, ILogger<TesterController> log, IEmailProvider provider,
|
IPlexApi plex, IEmbyApi emby, IRadarrApi radarr, ISonarrApi sonarr, ILogger<TesterController> log, IEmailProvider provider,
|
||||||
ICouchPotatoApi cpApi, ITelegramNotification telegram, ISickRageApi srApi, INewsletterJob newsletter, IMobileNotification mobileNotification,
|
ICouchPotatoApi cpApi, ITelegramNotification telegram, ISickRageApi srApi, INewsletterJob newsletter, IMobileNotification mobileNotification,
|
||||||
ILidarrApi lidarrApi, IGotifyNotification gotifyNotification)
|
ILidarrApi lidarrApi, IGotifyNotification gotifyNotification, IWhatsAppApi whatsAppApi, OmbiUserManager um)
|
||||||
{
|
{
|
||||||
Service = service;
|
Service = service;
|
||||||
DiscordNotification = notification;
|
DiscordNotification = notification;
|
||||||
|
@ -62,6 +66,8 @@ namespace Ombi.Controllers.V1.External
|
||||||
MobileNotification = mobileNotification;
|
MobileNotification = mobileNotification;
|
||||||
LidarrApi = lidarrApi;
|
LidarrApi = lidarrApi;
|
||||||
GotifyNotification = gotifyNotification;
|
GotifyNotification = gotifyNotification;
|
||||||
|
WhatsAppApi = whatsAppApi;
|
||||||
|
UserManager = um;
|
||||||
}
|
}
|
||||||
|
|
||||||
private INotificationService Service { get; }
|
private INotificationService Service { get; }
|
||||||
|
@ -84,7 +90,8 @@ namespace Ombi.Controllers.V1.External
|
||||||
private INewsletterJob Newsletter { get; }
|
private INewsletterJob Newsletter { get; }
|
||||||
private IMobileNotification MobileNotification { get; }
|
private IMobileNotification MobileNotification { get; }
|
||||||
private ILidarrApi LidarrApi { get; }
|
private ILidarrApi LidarrApi { get; }
|
||||||
|
private IWhatsAppApi WhatsAppApi { get; }
|
||||||
|
private OmbiUserManager UserManager {get;}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends a test message to discord using the provided settings
|
/// Sends a test message to discord using the provided settings
|
||||||
|
@ -459,5 +466,35 @@ namespace Ombi.Controllers.V1.External
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("whatsapp")]
|
||||||
|
public async Task<bool> WhatsAppTest([FromBody] WhatsAppSettingsViewModel settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
var user = await UserManager.Users.Include(x => x.UserNotificationPreferences).FirstOrDefaultAsync(x => x.UserName == HttpContext.User.Identity.Name);
|
||||||
|
|
||||||
|
|
||||||
|
var status = await WhatsAppApi.SendMessage(new WhatsAppModel {
|
||||||
|
From = settings.From,
|
||||||
|
Message = "This is a test from Ombi!",
|
||||||
|
To = user.UserNotificationPreferences.FirstOrDefault(x => x.Agent == NotificationAgent.WhatsApp).Value
|
||||||
|
}, settings.AccountSid, settings.AuthToken);
|
||||||
|
if (status.HasValue())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.LogError(LoggingEvents.Api, e, "Could not test Lidarr");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -963,6 +963,44 @@ namespace Ombi.Controllers.V1
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Twilio Notification Settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet("notifications/twilio")]
|
||||||
|
public async Task<TwilioSettingsViewModel> TwilioNotificationSettings()
|
||||||
|
{
|
||||||
|
var settings = await Get<TwilioSettings>();
|
||||||
|
var model = Mapper.Map<TwilioSettingsViewModel>(settings);
|
||||||
|
|
||||||
|
// Lookup to see if we have any templates saved
|
||||||
|
if(model.WhatsAppSettings == null)
|
||||||
|
{
|
||||||
|
model.WhatsAppSettings = new WhatsAppSettingsViewModel();
|
||||||
|
}
|
||||||
|
model.WhatsAppSettings.NotificationTemplates = BuildTemplates(NotificationAgent.WhatsApp);
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the Mattermost notification settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">The model.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost("notifications/twilio")]
|
||||||
|
public async Task<bool> TwilioNotificationSettings([FromBody] TwilioSettingsViewModel model)
|
||||||
|
{
|
||||||
|
// Save the email settings
|
||||||
|
var settings = Mapper.Map<TwilioSettings>(model);
|
||||||
|
var result = await Save(settings);
|
||||||
|
|
||||||
|
// Save the templates
|
||||||
|
await TemplateRepository.UpdateRange(model.WhatsAppSettings.NotificationTemplates);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Saves the Mobile notification settings.
|
/// Saves the Mobile notification settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -81,6 +81,7 @@
|
||||||
<ProjectReference Include="..\Ombi.Api.Github\Ombi.Api.Github.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Github\Ombi.Api.Github.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.SickRage\Ombi.Api.SickRage.csproj" />
|
<ProjectReference Include="..\Ombi.Api.SickRage\Ombi.Api.SickRage.csproj" />
|
||||||
|
<ProjectReference Include="..\Ombi.Api.Twilio\Ombi.Api.Twilio.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj" />
|
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.DependencyInjection\Ombi.DependencyInjection.csproj" />
|
<ProjectReference Include="..\Ombi.DependencyInjection\Ombi.DependencyInjection.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Hubs\Ombi.Hubs.csproj" />
|
<ProjectReference Include="..\Ombi.Hubs\Ombi.Hubs.csproj" />
|
||||||
|
|
|
@ -2,12 +2,10 @@
|
||||||
using AutoMapper.EquivalencyExpression;
|
using AutoMapper.EquivalencyExpression;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.StaticFiles;
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
@ -24,7 +22,6 @@ using Ombi.Store.Context;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using SQLitePCL;
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.AspNetCore.StaticFiles.Infrastructure;
|
using Microsoft.AspNetCore.StaticFiles.Infrastructure;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue