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

Feature/v4 whatsapp
pull/3400/head
Jamie 5 years ago committed by GitHub
commit 0df6a734de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Ombi.Api.Twilio
{
public interface IWhatsAppApi
{
Task<string> SendMessage(WhatsAppModel message, string accountSid, string authToken);
}
}

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

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

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

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

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

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

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

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

@ -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…
Cancel
Save