Merge pull request #3373 from tidusjar/feature/v4-whatsapp

Feature/v4 whatsapp
This commit is contained in:
Jamie 2020-02-01 23:25:51 +00:00 committed by GitHub
commit 0df6a734de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 562 additions and 74 deletions

View 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);
}
}

View 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>

View 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;
}
}
}

View 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; }
}
}

View 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; }
}
}

View file

@ -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) {

View file

@ -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" />

View file

@ -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);

View file

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

View file

@ -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();
} }
} }
} }

View 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);
}
}
}

View file

@ -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" />

View file

@ -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; }
}
}

View file

@ -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}

View file

@ -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[];

View file

@ -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
} }

View file

@ -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});
} }

View file

@ -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});
} }

View file

@ -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">
<ngb-panel *ngFor="let template of templates" id="{{template.notificationType}}" title="{{NotificationType[template.notificationType] | humanize}}">
<ng-template ngbPanelContent>
<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> <div>
<input type="text" class="form-control form-control-custom" [(ngModel)]="template.subject" value="{{template.subject}}"> <mat-slide-toggle [(ngModel)]="template.enabled">Enable</mat-slide-toggle>
</div>
</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>

View file

@ -0,0 +1,21 @@
<settings-menu>
</settings-menu>
<div *ngIf="form" class="container">
<fieldset>
<legend>Twilio</legend>
<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>

View file

@ -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");
}
});
}
}

View file

@ -0,0 +1,35 @@
<div [formGroup]="form" class="col">
<div formGroupName="whatsAppSettings">
<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">
</mat-form-field>
</div>
<div class="md-form-field">
<mat-form-field>
<input matInput placeholder="Account SID" formControlName="accountSid">
</mat-form-field>
</div>
<div class="md-form-field">
<mat-form-field>
<input matInput placeholder="Authentication Token" formControlName="authToken">
</mat-form-field>
</div>
</div>
<div class="col">
<notification-templates [templates]="templates" [showSubject]="false"></notification-templates>
</div>
<div class="md-form-field">
<div>
<button mat-raised-button type="button" color="primary" (click)="test(form)">Test</button>
</div>
</div>
</div>
</div>

View file

@ -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");
}
});
}
}

View file

@ -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,

View file

@ -49,6 +49,7 @@
<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>

View file

@ -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;
}
}
} }
} }

View file

@ -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>

View file

@ -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" />

View file

@ -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;