diff --git a/src/Ombi.Api.Pushbullet/IPushbulletApi.cs b/src/Ombi.Api.Pushbullet/IPushbulletApi.cs new file mode 100644 index 000000000..cd1fd8dac --- /dev/null +++ b/src/Ombi.Api.Pushbullet/IPushbulletApi.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Ombi.Api.Pushbullet +{ + public interface IPushbulletApi + { + Task Push(string accessToken, string subject, string body, string channelTag); + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Pushbullet/Ombi.Api.Pushbullet.csproj b/src/Ombi.Api.Pushbullet/Ombi.Api.Pushbullet.csproj new file mode 100644 index 000000000..a8c3e7a4c --- /dev/null +++ b/src/Ombi.Api.Pushbullet/Ombi.Api.Pushbullet.csproj @@ -0,0 +1,11 @@ + + + + netstandard1.6 + + + + + + + \ No newline at end of file diff --git a/src/Ombi.Api.Pushbullet/PushbulletApi.cs b/src/Ombi.Api.Pushbullet/PushbulletApi.cs new file mode 100644 index 000000000..74d2dbb70 --- /dev/null +++ b/src/Ombi.Api.Pushbullet/PushbulletApi.cs @@ -0,0 +1,36 @@ +using System.Net.Http; +using System.Threading.Tasks; + +namespace Ombi.Api.Pushbullet +{ + public class PushbulletApi : IPushbulletApi + { + public PushbulletApi(IApi api) + { + Api = api; + } + + private IApi Api { get; } + private const string BaseUrl = "https://api.pushbullet.com/v2"; + public async Task Push(string accessToken, string subject, string body, string channelTag) + { + var request = new Request("/pushes", BaseUrl, HttpMethod.Post); + + request.AddHeader("Access-Token", accessToken); + request.ApplicationJsonContentType(); + + + var jsonBody = new + { + type = "note", + body = body, + title = subject, + channel_tag = channelTag + }; + + request.AddJsonBody(jsonBody); + + await Api.Request(request); + } + } +} diff --git a/src/Ombi.Api/Request.cs b/src/Ombi.Api/Request.cs index 56ff100eb..67453faf3 100644 --- a/src/Ombi.Api/Request.cs +++ b/src/Ombi.Api/Request.cs @@ -78,6 +78,11 @@ namespace Ombi.Api ContentHeaders.Add(new KeyValuePair(key, value)); } + public void ApplicationJsonContentType() + { + AddContentHeader("Content-Type", "application/json"); + } + public void AddQueryString(string key, string value) { if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) return; diff --git a/src/Ombi.Core/Models/UI/PushbulletNotificationViewModel.cs b/src/Ombi.Core/Models/UI/PushbulletNotificationViewModel.cs new file mode 100644 index 000000000..f4a83f58a --- /dev/null +++ b/src/Ombi.Core/Models/UI/PushbulletNotificationViewModel.cs @@ -0,0 +1,23 @@ + +using System.Collections.Generic; +using Ombi.Settings.Settings.Models.Notifications; +using Ombi.Store.Entities; + +namespace Ombi.Core.Models.UI +{ + /// + /// The view model for the notification settings page + /// + /// + public class PushbulletNotificationViewModel : PushbulletSettings + { + /// + /// Gets or sets the notification templates. + /// + /// + /// The notification templates. + /// + public List NotificationTemplates { get; set; } + + } +} diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index 7f94f8705..ba7a80e67 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -29,6 +29,7 @@ using Ombi.Notifications.Agents; using Ombi.Schedule.Jobs.Radarr; using Ombi.Api; using Ombi.Api.FanartTv; +using Ombi.Api.Pushbullet; using Ombi.Api.Service; using Ombi.Core.Rule.Interfaces; using Ombi.Core.Senders; @@ -72,6 +73,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); } @@ -82,7 +84,6 @@ namespace Ombi.DependencyInjection services.AddScoped(); services.AddTransient(); - //services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -91,7 +92,6 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); - //services.AddTransient(); services.AddTransient(typeof(ISettingsService<>), typeof(SettingsService<>)); } public static void RegisterServices(this IServiceCollection services) @@ -104,6 +104,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); + services.AddTransient(); } public static void RegisterJobs(this IServiceCollection services) diff --git a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj index f130ec11b..2e5eab0af 100644 --- a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj +++ b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Ombi.Mapping/Profiles/SettingsProfile.cs b/src/Ombi.Mapping/Profiles/SettingsProfile.cs index 1633ea35b..3268f39e1 100644 --- a/src/Ombi.Mapping/Profiles/SettingsProfile.cs +++ b/src/Ombi.Mapping/Profiles/SettingsProfile.cs @@ -10,6 +10,7 @@ namespace Ombi.Mapping.Profiles { CreateMap().ReverseMap(); CreateMap().ReverseMap(); + CreateMap().ReverseMap(); } } } \ No newline at end of file diff --git a/src/Ombi.Notifications/Agents/IPushbulletNotification.cs b/src/Ombi.Notifications/Agents/IPushbulletNotification.cs new file mode 100644 index 000000000..607cf7fb5 --- /dev/null +++ b/src/Ombi.Notifications/Agents/IPushbulletNotification.cs @@ -0,0 +1,6 @@ +namespace Ombi.Notifications.Agents +{ + public interface IPushbulletNotification : INotification + { + } +} \ No newline at end of file diff --git a/src/Ombi.Notifications/Agents/PushbulletNotification.cs b/src/Ombi.Notifications/Agents/PushbulletNotification.cs new file mode 100644 index 000000000..1295e664f --- /dev/null +++ b/src/Ombi.Notifications/Agents/PushbulletNotification.cs @@ -0,0 +1,144 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Ombi.Api.Pushbullet; +using Ombi.Core.Settings; +using Ombi.Helpers; +using Ombi.Notifications.Interfaces; +using Ombi.Notifications.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 PushbulletNotification : BaseNotification, IPushbulletNotification + { + public PushbulletNotification(IPushbulletApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t) : base(sn, r, m, t) + { + Api = api; + Logger = log; + } + + public override string NotificationName => "PushbulletNotification"; + + private IPushbulletApi Api { get; } + private ILogger Logger { get; } + + protected override bool ValidateConfiguration(PushbulletSettings settings) + { + if (!settings.Enabled) + { + return false; + } + if (string.IsNullOrEmpty(settings.AccessToken)) + { + return false; + } + + return true; + } + + protected override async Task NewRequest(NotificationOptions model, PushbulletSettings settings) + { + var parsed = await LoadTemplate(NotificationAgent.Discord, NotificationType.NewRequest, model); + + var notification = new NotificationMessage + { + Message = parsed.Message, + }; + + await Send(notification, settings); + } + + protected override async Task Issue(NotificationOptions model, PushbulletSettings settings) + { + var parsed = await LoadTemplate(NotificationAgent.Discord, NotificationType.Issue, model); + + var notification = new NotificationMessage + { + Message = parsed.Message, + }; + await Send(notification, settings); + } + + protected override async Task AddedToRequestQueue(NotificationOptions model, PushbulletSettings settings) + { + string user; + string title; + if (model.RequestType == RequestType.Movie) + { + user = MovieRequest.RequestedUser.UserAlias; + title = MovieRequest.Title; + } + else + { + user = TvRequest.RequestedUser.UserAlias; + title = TvRequest.ParentRequest.Title; + } + var message = $"Hello! The user '{user}' has requested {title} but it could not be added. This has been added into the requests queue and will keep retrying"; + var notification = new NotificationMessage + { + Message = message + }; + await Send(notification, settings); + } + + protected override async Task RequestDeclined(NotificationOptions model, PushbulletSettings settings) + { + var parsed = await LoadTemplate(NotificationAgent.Discord, NotificationType.RequestDeclined, model); + + var notification = new NotificationMessage + { + Message = parsed.Message, + }; + await Send(notification, settings); + } + + protected override async Task RequestApproved(NotificationOptions model, PushbulletSettings settings) + { + var parsed = await LoadTemplate(NotificationAgent.Discord, NotificationType.RequestApproved, model); + + var notification = new NotificationMessage + { + Message = parsed.Message, + }; + + await Send(notification, settings); + } + + protected override async Task AvailableRequest(NotificationOptions model, PushbulletSettings settings) + { + var parsed = await LoadTemplate(NotificationAgent.Discord, NotificationType.RequestAvailable, model); + + var notification = new NotificationMessage + { + Message = parsed.Message, + }; + await Send(notification, settings); + } + + protected override async Task Send(NotificationMessage model, PushbulletSettings settings) + { + try + { + await Api.Push(settings.AccessToken, model.Subject, model.Message, settings.ChannelTag); + } + catch (Exception e) + { + Logger.LogError(LoggingEvents.DiscordNotification, e, "Failed to send Pushbullet Notification"); + } + } + + protected override async Task Test(NotificationOptions model, PushbulletSettings 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); + } + } +} diff --git a/src/Ombi.Notifications/Ombi.Notifications.csproj b/src/Ombi.Notifications/Ombi.Notifications.csproj index 675b772b6..a3bc8acfa 100644 --- a/src/Ombi.Notifications/Ombi.Notifications.csproj +++ b/src/Ombi.Notifications/Ombi.Notifications.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Ombi.Settings/Settings/Models/Notifications/PushbulletSettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/PushbulletSettings.cs new file mode 100644 index 000000000..c1c51e870 --- /dev/null +++ b/src/Ombi.Settings/Settings/Models/Notifications/PushbulletSettings.cs @@ -0,0 +1,12 @@ +using System; +using Newtonsoft.Json; + +namespace Ombi.Settings.Settings.Models.Notifications +{ + public class PushbulletSettings : Settings + { + public bool Enabled { get; set; } + public string AccessToken { get; set; } + public string ChannelTag { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.sln b/src/Ombi.sln index 4866b2be0..41c80454f 100644 --- a/src/Ombi.sln +++ b/src/Ombi.sln @@ -67,7 +67,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Notifications.Tests", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Service", "Ombi.Api.Service\Ombi.Api.Service.csproj", "{A0892896-F5BD-47E2-823E-DFCE82514EEC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.FanartTv", "Ombi.Api.FanartTv\Ombi.Api.FanartTv.csproj", "{FD947E63-A0D2-4878-8378-2005D5E9AB8A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.FanartTv", "Ombi.Api.FanartTv\Ombi.Api.FanartTv.csproj", "{FD947E63-A0D2-4878-8378-2005D5E9AB8A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Pushbullet", "Ombi.Api.Pushbullet\Ombi.Api.Pushbullet.csproj", "{E237CDF6-D044-437D-B157-E9A3CC0BCF53}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -171,6 +173,10 @@ Global {FD947E63-A0D2-4878-8378-2005D5E9AB8A}.Debug|Any CPU.Build.0 = Debug|Any CPU {FD947E63-A0D2-4878-8378-2005D5E9AB8A}.Release|Any CPU.ActiveCfg = Release|Any CPU {FD947E63-A0D2-4878-8378-2005D5E9AB8A}.Release|Any CPU.Build.0 = Release|Any CPU + {E237CDF6-D044-437D-B157-E9A3CC0BCF53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E237CDF6-D044-437D-B157-E9A3CC0BCF53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E237CDF6-D044-437D-B157-E9A3CC0BCF53}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E237CDF6-D044-437D-B157-E9A3CC0BCF53}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -194,5 +200,6 @@ Global {2C7836E7-B120-40A6-B641-DDAA02FBAE23} = {6F42AB98-9196-44C4-B888-D5E409F415A1} {A0892896-F5BD-47E2-823E-DFCE82514EEC} = {9293CA11-360A-4C20-A674-B9E794431BF5} {FD947E63-A0D2-4878-8378-2005D5E9AB8A} = {9293CA11-360A-4C20-A674-B9E794431BF5} + {E237CDF6-D044-437D-B157-E9A3CC0BCF53} = {9293CA11-360A-4C20-A674-B9E794431BF5} EndGlobalSection EndGlobal diff --git a/src/Ombi/ClientApp/app/interfaces/INotifcationSettings.ts b/src/Ombi/ClientApp/app/interfaces/INotifcationSettings.ts index 8bd1ec507..8037bf5d8 100644 --- a/src/Ombi/ClientApp/app/interfaces/INotifcationSettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/INotifcationSettings.ts @@ -48,4 +48,10 @@ export interface IDiscordNotifcationSettings extends INotificationSettings{ webhookUrl : string, username: string, notificationTemplates: INotificationTemplates[], +} + +export interface IPushbulletNotificationSettings extends INotificationSettings { + accessToken: string, + notificationTemplates: INotificationTemplates[], + channelTag: string; } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/services/applications/tester.service.ts b/src/Ombi/ClientApp/app/services/applications/tester.service.ts index 1345f72c7..d1dcfe7f9 100644 --- a/src/Ombi/ClientApp/app/services/applications/tester.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/tester.service.ts @@ -4,7 +4,7 @@ import { Observable } from 'rxjs/Rx'; import { ServiceAuthHelpers } from '../service.helpers'; -import { IDiscordNotifcationSettings, IEmailNotificationSettings } from '../../interfaces/INotifcationSettings' +import { IDiscordNotifcationSettings, IEmailNotificationSettings, IPushbulletNotificationSettings } from '../../interfaces/INotifcationSettings' @Injectable() @@ -17,6 +17,10 @@ export class TesterService extends ServiceAuthHelpers { return this.http.post(`${this.url}discord`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData); } + pushbulletTest(settings: IPushbulletNotificationSettings): Observable { + return this.http.post(`${this.url}pushbullet`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData); + } + emailTest(settings: IEmailNotificationSettings): Observable { return this.http.post(`${this.url}email`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData); } diff --git a/src/Ombi/ClientApp/app/services/settings.service.ts b/src/Ombi/ClientApp/app/services/settings.service.ts index bdaf20e40..e53dac55f 100644 --- a/src/Ombi/ClientApp/app/services/settings.service.ts +++ b/src/Ombi/ClientApp/app/services/settings.service.ts @@ -13,7 +13,7 @@ import { ICustomizationSettings, IRadarrSettings } from '../interfaces/ISettings'; -import { IEmailNotificationSettings, IDiscordNotifcationSettings } from '../interfaces/INotifcationSettings'; +import { IEmailNotificationSettings, IDiscordNotifcationSettings, IPushbulletNotificationSettings } from '../interfaces/INotifcationSettings'; @Injectable() export class SettingsService extends ServiceAuthHelpers { @@ -96,4 +96,11 @@ export class SettingsService extends ServiceAuthHelpers { saveDiscordNotificationSettings(settings: IDiscordNotifcationSettings): Observable { return this.httpAuth.post(`${this.url}/notifications/discord`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData).catch(this.handleError) } + getPushbulletNotificationSettings(): Observable { + return this.httpAuth.get(`${this.url}/notifications/pushbullet`).map(this.extractData).catch(this.handleError) + } + + savePushbulletNotificationSettings(settings: IPushbulletNotificationSettings): Observable { + return this.httpAuth.post(`${this.url}/notifications/pushbullet`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData).catch(this.handleError) + } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/settings/notifications/pushbullet.component.html b/src/Ombi/ClientApp/app/settings/notifications/pushbullet.component.html new file mode 100644 index 000000000..4f4d0f62b --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/notifications/pushbullet.component.html @@ -0,0 +1,60 @@ + + +
+
+ Pushbyllet Notifications +
+
+ +
+
+ + +
+
+ +
+
The Access Token is required
+
+ You can find this here: https://www.pushbullet.com/#settings/account +
+ +
+ +
+
+ +
+ +
+ +
+
+ + + +
+
+ +
+
+ + + +
+
+ +
+
+
+
+ + +
+ +
+
+
\ No newline at end of file diff --git a/src/Ombi/ClientApp/app/settings/notifications/pushbullet.component.ts b/src/Ombi/ClientApp/app/settings/notifications/pushbullet.component.ts new file mode 100644 index 000000000..7e09865c1 --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/notifications/pushbullet.component.ts @@ -0,0 +1,69 @@ +import { Component, OnInit } from '@angular/core'; +import { FormGroup, Validators, FormBuilder } from '@angular/forms'; + +import { INotificationTemplates, IPushbulletNotificationSettings, NotificationType } from '../../interfaces/INotifcationSettings'; +import { SettingsService } from '../../services/settings.service'; +import { NotificationService } from "../../services/notification.service"; +import { TesterService } from "../../services/applications/tester.service"; + +@Component({ + templateUrl: './pushbullet.component.html', +}) +export class PushbulletComponent implements OnInit { + constructor(private settingsService: SettingsService, + private notificationService: NotificationService, + private fb: FormBuilder, + private testerService : TesterService) { } + + NotificationType = NotificationType; + templates: INotificationTemplates[]; + + form: FormGroup; + + ngOnInit(): void { + this.settingsService.getPushbulletNotificationSettings().subscribe(x => { + this.templates = x.notificationTemplates; + + this.form = this.fb.group({ + enabled: [x.enabled], + channelTag: [x.channelTag], + accessToken: [x.accessToken, [Validators.required]], + }); + }); + } + + onSubmit(form: FormGroup) { + if (form.invalid) { + this.notificationService.error("Validation", "Please check your entered values"); + return + } + + var settings = form.value; + settings.notificationTemplates = this.templates; + + this.settingsService.savePushbulletNotificationSettings(settings).subscribe(x => { + if (x) { + this.notificationService.success("Settings Saved", "Successfully saved the Pushbullet settings"); + } else { + this.notificationService.success("Settings Saved", "There was an error when saving the Pushbullet settings"); + } + }); + + } + + test(form: FormGroup) { + if (form.invalid) { + this.notificationService.error("Validation", "Please check your entered values"); + return + } + + this.testerService.pushbulletTest(form.value).subscribe(x => { + if (x) { + this.notificationService.success("Successful", "Successfully sent a Pushbullet message, please check the discord channel"); + } else { + this.notificationService.success("Error", "There was an error when sending the Pushbullet message. Please check your settings"); + } + }) + + } +} \ No newline at end of file diff --git a/src/Ombi/Controllers/External/TesterController.cs b/src/Ombi/Controllers/External/TesterController.cs index 39ee39c13..b210de871 100644 --- a/src/Ombi/Controllers/External/TesterController.cs +++ b/src/Ombi/Controllers/External/TesterController.cs @@ -24,16 +24,20 @@ namespace Ombi.Controllers.External /// The service. /// The notification. /// The notification. - public TesterController(INotificationService service, IDiscordNotification notification, IEmailNotification emailN) + public TesterController(INotificationService service, IDiscordNotification notification, IEmailNotification emailN, + IPushbulletNotification pushbullet) { Service = service; DiscordNotification = notification; EmailNotification = emailN; + PushbulletNotification = pushbullet; } private INotificationService Service { get; } private IDiscordNotification DiscordNotification { get; } private IEmailNotification EmailNotification { get; } + private IPushbulletNotification PushbulletNotification { get; } + /// /// Sends a test message to discord using the provided settings @@ -45,7 +49,22 @@ namespace Ombi.Controllers.External { settings.Enabled = true; DiscordNotification.NotifyAsync( - new NotificationOptions {NotificationType = NotificationType.Test, RequestId = -1}, settings); + new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings); + + return true; + } + + /// + /// Sends a test message to discord using the provided settings + /// + /// The settings. + /// + [HttpPost("pushbullet")] + public bool Pushbullet([FromBody] PushbulletSettings settings) + { + settings.Enabled = true; + PushbulletNotification.NotifyAsync( + new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings); return true; } diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 026fcf3ca..bbe56360c 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -261,6 +261,40 @@ namespace Ombi.Controllers return model; } + /// + /// Saves the pushbullet notification settings. + /// + /// The model. + /// + [HttpPost("notifications/pushbullet")] + public async Task PushbulletNotificationSettings([FromBody] PushbulletNotificationViewModel model) + { + // Save the email settings + var settings = Mapper.Map(model); + var result = await Save(settings); + + // Save the templates + await TemplateRepository.UpdateRange(model.NotificationTemplates); + + return result; + } + + /// + /// Gets the pushbullet Notification Settings. + /// + /// + [HttpGet("notifications/pushbullet")] + public async Task PushbulletNotificationSettings() + { + var settings = await Get(); + var model = Mapper.Map(settings); + + // Lookup to see if we have any templates saved + model.NotificationTemplates = await BuildTemplates(NotificationAgent.Pushbullet); + + return model; + } + private async Task> BuildTemplates(NotificationAgent agent) { var templates = await TemplateRepository.GetAllTemplates(agent);