diff --git a/Ombi.Api.Interfaces/IMattermostApi.cs b/Ombi.Api.Interfaces/IMattermostApi.cs new file mode 100644 index 000000000..281902277 --- /dev/null +++ b/Ombi.Api.Interfaces/IMattermostApi.cs @@ -0,0 +1,37 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: IMattermostApi.cs +// Created By: Michel Zaleski +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System.Threading.Tasks; +using Ombi.Api.Models.Notifications; + +namespace Ombi.Api.Interfaces +{ + public interface IMattermostApi + { + Task PushAsync(string webhook, MattermostNotificationBody message); + } +} \ No newline at end of file diff --git a/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj b/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj index cf50e513d..ca7ad260c 100644 --- a/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj +++ b/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj @@ -65,6 +65,7 @@ + diff --git a/Ombi.Api.Models/Notifications/MattermostNotificationBody.cs b/Ombi.Api.Models/Notifications/MattermostNotificationBody.cs new file mode 100644 index 000000000..0c5b17685 --- /dev/null +++ b/Ombi.Api.Models/Notifications/MattermostNotificationBody.cs @@ -0,0 +1,45 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: MattermostNotificationBody.cs +// Created By: Michel Zaleski +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using Newtonsoft.Json; + +namespace Ombi.Api.Models.Notifications +{ + public class MattermostNotificationBody + { + [JsonConstructor] + public MattermostNotificationBody() + { + username = "Ombi"; + } + + public string username { get; set; } = "Ombi"; + public string channel { get; set; } + public string text { get; set; } + public string icon_url { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Ombi.Api.Models.csproj b/Ombi.Api.Models/Ombi.Api.Models.csproj index e13506d5b..b35217e12 100644 --- a/Ombi.Api.Models/Ombi.Api.Models.csproj +++ b/Ombi.Api.Models/Ombi.Api.Models.csproj @@ -96,6 +96,7 @@ + diff --git a/Ombi.Api/MattermostApi.cs b/Ombi.Api/MattermostApi.cs new file mode 100644 index 000000000..9edbef7b6 --- /dev/null +++ b/Ombi.Api/MattermostApi.cs @@ -0,0 +1,58 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexApi.cs +// Created By: Michel Zaleski +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Threading.Tasks; +using Ombi.Api.Interfaces; +using Ombi.Api.Models.Notifications; +using RestSharp; + +namespace Ombi.Api +{ + public class MattermostApi : IMattermostApi + { + public async Task PushAsync(string webhook, MattermostNotificationBody message) + { + var request = new RestRequest + { + Method = Method.POST, + Resource = "/" + }; + + request.AddJsonBody(message); + + var api = new ApiRequest(); + return await Task.Run( + () => + { + var result = api.Execute(request, new Uri(webhook)); + return result.Content; + }); + } + } +} + diff --git a/Ombi.Api/Ombi.Api.csproj b/Ombi.Api/Ombi.Api.csproj index d004e1c3f..7c628391e 100644 --- a/Ombi.Api/Ombi.Api.csproj +++ b/Ombi.Api/Ombi.Api.csproj @@ -85,6 +85,7 @@ + diff --git a/Ombi.Core.Migration/Migrations/Version2200.cs b/Ombi.Core.Migration/Migrations/Version2200.cs index d52e1654b..588d305ed 100644 --- a/Ombi.Core.Migration/Migrations/Version2200.cs +++ b/Ombi.Core.Migration/Migrations/Version2200.cs @@ -94,7 +94,7 @@ namespace Ombi.Core.Migration.Migrations content.Add(new RecentlyAddedLog { AddedAt = DateTime.UtcNow, - ProviderId = ep.ProviderId + ProviderId = ep.RatingKey }); } diff --git a/Ombi.Core.Migration/Migrations/Version2210.cs b/Ombi.Core.Migration/Migrations/Version2210.cs new file mode 100644 index 000000000..94592174a --- /dev/null +++ b/Ombi.Core.Migration/Migrations/Version2210.cs @@ -0,0 +1,134 @@ +#region Copyright + +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: Version1100.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ + +#endregion + +using System; +using System.Data; +using NLog; +using Ombi.Core.SettingModels; +using Ombi.Store; +using Ombi.Store.Models; +using Ombi.Store.Models.Emby; +using Ombi.Store.Models.Plex; +using Ombi.Store.Repository; +using Quartz.Collection; + +namespace Ombi.Core.Migration.Migrations +{ + [Migration(22100, "v2.21.0.0")] + public class Version2210 : BaseMigration, IMigration + { + public Version2210(IRepository log, + IRepository content, IRepository plexEp, IRepository embyContent, IRepository embyEp) + { + Log = log; + PlexContent = content; + PlexEpisodes = plexEp; + EmbyContent = embyContent; + EmbyEpisodes = embyEp; + } + + public int Version => 22100; + private IRepository Log { get; } + private IRepository PlexContent { get; } + private IRepository PlexEpisodes { get; } + private IRepository EmbyContent { get; } + private IRepository EmbyEpisodes { get; } + + public void Start(IDbConnection con) + { + UpdateRecentlyAdded(con); + UpdateSchema(con, Version); + + } + + private void UpdateRecentlyAdded(IDbConnection con) + { + + //Delete the recently added table, lets start again + Log.DeleteAll("RecentlyAddedLog"); + + + + // Plex + var plexAllContent = PlexContent.GetAll(); + var content = new HashSet(); + foreach (var plexContent in plexAllContent) + { + if(plexContent.Type == PlexMediaType.Artist) continue; + content.Add(new RecentlyAddedLog + { + AddedAt = DateTime.UtcNow, + ProviderId = plexContent.ProviderId + }); + } + Log.BatchInsert(content, "RecentlyAddedLog"); + + var plexEpisodeses = PlexEpisodes.GetAll(); + content.Clear(); + foreach (var ep in plexEpisodeses) + { + content.Add(new RecentlyAddedLog + { + AddedAt = DateTime.UtcNow, + ProviderId = ep.RatingKey + }); + } + Log.BatchInsert(content, "RecentlyAddedLog"); + + // Emby + content.Clear(); + var embyContent = EmbyContent.GetAll(); + foreach (var plexContent in embyContent) + { + content.Add(new RecentlyAddedLog + { + AddedAt = DateTime.UtcNow, + ProviderId = plexContent.EmbyId + }); + } + Log.BatchInsert(content, "RecentlyAddedLog"); + + var embyEpisodes = EmbyEpisodes.GetAll(); + content.Clear(); + foreach (var ep in embyEpisodes) + { + content.Add(new RecentlyAddedLog + { + AddedAt = DateTime.UtcNow, + ProviderId = ep.EmbyId + }); + } + Log.BatchInsert(content, "RecentlyAddedLog"); + + + } + + + } +} diff --git a/Ombi.Core.Migration/Ombi.Core.Migration.csproj b/Ombi.Core.Migration/Ombi.Core.Migration.csproj index 06dfda84d..4c2272a03 100644 --- a/Ombi.Core.Migration/Ombi.Core.Migration.csproj +++ b/Ombi.Core.Migration/Ombi.Core.Migration.csproj @@ -69,6 +69,7 @@ + diff --git a/Ombi.Core/Notification/NotificationMessageResolver.cs b/Ombi.Core/Notification/NotificationMessageResolver.cs index f5f0b90f9..fee5eeb00 100644 --- a/Ombi.Core/Notification/NotificationMessageResolver.cs +++ b/Ombi.Core/Notification/NotificationMessageResolver.cs @@ -70,6 +70,9 @@ namespace Ombi.Core.Notification case TransportType.Slack: content = notification.SlackNotification; break; + case TransportType.Mattermost: + content = notification.MattermostNotification; + break; default: throw new ArgumentOutOfRangeException(nameof(transportType), transportType, null); } diff --git a/Ombi.Core/Notification/TransportType.cs b/Ombi.Core/Notification/TransportType.cs index f37ab4404..fbed9b3b0 100644 --- a/Ombi.Core/Notification/TransportType.cs +++ b/Ombi.Core/Notification/TransportType.cs @@ -31,6 +31,7 @@ namespace Ombi.Core.Notification Email, Pushbullet, Pushover, - Slack + Slack, + Mattermost } } \ No newline at end of file diff --git a/Ombi.Core/Ombi.Core.csproj b/Ombi.Core/Ombi.Core.csproj index 3ad547420..9c2660df3 100644 --- a/Ombi.Core/Ombi.Core.csproj +++ b/Ombi.Core/Ombi.Core.csproj @@ -138,6 +138,7 @@ + diff --git a/Ombi.Core/SettingModels/MattermostNotificationSettings.cs b/Ombi.Core/SettingModels/MattermostNotificationSettings.cs new file mode 100644 index 000000000..03ae5af3e --- /dev/null +++ b/Ombi.Core/SettingModels/MattermostNotificationSettings.cs @@ -0,0 +1,13 @@ +using System; +using Newtonsoft.Json; + +namespace Ombi.Core.SettingModels +{ + public sealed class MattermostNotificationSettings : NotificationSettings + { + public string WebhookUrl { get; set; } + public string Channel { get; set; } + public string Username { get; set; } + public string IconUrl { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Core/SettingModels/NotificationSettingsV2.cs b/Ombi.Core/SettingModels/NotificationSettingsV2.cs index 2380d70f1..cf0209692 100644 --- a/Ombi.Core/SettingModels/NotificationSettingsV2.cs +++ b/Ombi.Core/SettingModels/NotificationSettingsV2.cs @@ -51,11 +51,13 @@ namespace Ombi.Core.SettingModels } }; SlackNotification = new List(); + MattermostNotification = new List(); PushoverNotification = new List(); PushbulletNotification = new List(); } public List EmailNotification { get; set; } public List SlackNotification { get; set; } + public List MattermostNotification { get; set; } public List PushbulletNotification { get; set; } public List PushoverNotification { get; set; } } diff --git a/Ombi.Services/Jobs/PlexContentCacher.cs b/Ombi.Services/Jobs/PlexContentCacher.cs index 5b6dc55d4..d4f872fc2 100644 --- a/Ombi.Services/Jobs/PlexContentCacher.cs +++ b/Ombi.Services/Jobs/PlexContentCacher.cs @@ -276,7 +276,8 @@ namespace Ombi.Services.Jobs Title = m.Title, Type = Store.Models.Plex.PlexMediaType.Movie, Url = m.Url, - ItemId = m.ItemId + ItemId = m.ItemId, + AddedAt = DateTime.UtcNow, }); } } @@ -318,7 +319,8 @@ namespace Ombi.Services.Jobs Type = Store.Models.Plex.PlexMediaType.Show, Url = t.Url, Seasons = ByteConverterHelper.ReturnBytes(t.Seasons), - ItemId = t.ItemId + ItemId = t.ItemId, + AddedAt = DateTime.UtcNow, }); } } @@ -360,7 +362,8 @@ namespace Ombi.Services.Jobs Title = a.Title, Type = Store.Models.Plex.PlexMediaType.Artist, Url = a.Url, - ItemId = "album" + ItemId = "album", + AddedAt = DateTime.UtcNow, }); } } diff --git a/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs b/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs index c334b17cd..158d77f0d 100644 --- a/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs +++ b/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs @@ -115,12 +115,12 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter var firstRun = !recentlyAdded.Any(); - var filteredMovies = movie.Where(m => recentlyAdded.All(x => x.ProviderId != m.ProviderId)).ToList(); - var filteredEp = episodes.Where(m => recentlyAdded.All(x => x.ProviderId != m.ProviderId)).ToList(); - var filteredSeries = series.Where(m => recentlyAdded.All(x => x.ProviderId != m.ProviderId)).ToList(); + var filteredMovies = movie.Where(m => recentlyAdded.All(x => x.ProviderId != m.EmbyId)).ToList(); + var filteredEp = episodes.Where(m => recentlyAdded.All(x => x.ProviderId != m.EmbyId)).ToList(); + var filteredSeries = series.Where(m => recentlyAdded.All(x => x.ProviderId != m.EmbyId)).ToList(); var info = new List(); - foreach (var m in filteredMovies) + foreach (var m in filteredMovies.OrderByDescending(x => x.AddedAt)) { var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception thrown when processing an emby movie for the newsletter, Retrying {0}", timespan)); @@ -144,6 +144,11 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter // Check if there are any epiosdes, then get the series info. // Otherwise then just add the series to the newsletter + if (test && !filteredEp.Any() && episodes.Any()) + { + // if this is a test make sure we show something + filteredEp = episodes.Take(5).ToList(); + } if (filteredEp.Any()) { var recentlyAddedModel = new List(); @@ -205,7 +210,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter } else { - foreach (var t in filteredSeries) + foreach (var t in filteredSeries.OrderByDescending(x => x.AddedAt)) { @@ -239,7 +244,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter { RecentlyAddedLog.Insert(new RecentlyAddedLog { - ProviderId = a.ProviderId, + ProviderId = a.EmbyId, AddedAt = DateTime.UtcNow }); } @@ -247,7 +252,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter { RecentlyAddedLog.Insert(new RecentlyAddedLog { - ProviderId = a.ProviderId, + ProviderId = a.EmbyId, AddedAt = DateTime.UtcNow }); } @@ -255,7 +260,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter { RecentlyAddedLog.Insert(new RecentlyAddedLog { - ProviderId = s.ProviderId, + ProviderId = s.EmbyId, AddedAt = DateTime.UtcNow }); } diff --git a/Ombi.Services/Jobs/RecentlyAddedNewsletter/PlexRecentlyAddedNewsletter.cs b/Ombi.Services/Jobs/RecentlyAddedNewsletter/PlexRecentlyAddedNewsletter.cs index 1f9601b07..ca88b4c2b 100644 --- a/Ombi.Services/Jobs/RecentlyAddedNewsletter/PlexRecentlyAddedNewsletter.cs +++ b/Ombi.Services/Jobs/RecentlyAddedNewsletter/PlexRecentlyAddedNewsletter.cs @@ -117,7 +117,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter var firstRun = !recentlyAdded.Any(); var filteredMovies = movie.Where(m => recentlyAdded.All(x => x.ProviderId != m.ProviderId)).ToList(); - var filteredEp = episodes.Where(m => recentlyAdded.All(x => x.ProviderId != m.ProviderId)).ToList(); + var filteredEp = episodes.Where(m => recentlyAdded.All(x => x.ProviderId != m.RatingKey)).ToList(); var filteredSeries = series.Where(x => recentlyAdded.All(c => c.ProviderId != x.ProviderId)).ToList(); var info = new List(); @@ -127,7 +127,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter // if this is a test make sure we show something filteredMovies = movie.Take(5).ToList(); } - foreach (var m in filteredMovies) + foreach (var m in filteredMovies.OrderByDescending(x => x.AddedAt)) { var i = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, m.ItemId); if (i.Video == null) @@ -144,6 +144,11 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter newsletter.MovieCount = info.Count; info.Clear(); + if (test && !filteredEp.Any() && episodes.Any()) + { + // if this is a test make sure we show something + filteredEp = episodes.Take(5).ToList(); + } if (filteredEp.Any()) { var recentlyAddedModel = new List(); @@ -189,7 +194,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter // if this is a test make sure we show something filteredSeries = series.Take(5).ToList(); } - foreach (var t in filteredSeries) + foreach (var t in filteredSeries.OrderByDescending(x => x.AddedAt)) { var i = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, t.ItemId); if (i.Directory == null) @@ -226,7 +231,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter { RecentlyAddedLog.Insert(new RecentlyAddedLog { - ProviderId = a.ProviderId, + ProviderId = a.RatingKey, AddedAt = DateTime.UtcNow }); } @@ -335,7 +340,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter try { - var info = TvApi.ShowLookupByTheTvDbId(int.Parse(PlexHelper.GetProviderIdFromPlexGuid(t.Metadata.Directory.Guid))); + var info = TvApi.ShowLookupByTheTvDbId(int.Parse(PlexHelper.GetProviderIdFromPlexGuid(t?.Metadata?.Directory?.Guid ?? string.Empty))); var banner = info.image?.original; if (!string.IsNullOrEmpty(banner)) @@ -370,7 +375,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter for (var i = 0; i < orderedEpisodes.Count; i++) { var ep = orderedEpisodes[i]; - if (i <= orderedEpisodes.Count - 1) + if (i < orderedEpisodes.Count - 1) { epSb.Append($"{ep.Video.FirstOrDefault().Index},"); } diff --git a/Ombi.Services/Notification/MattermostNotification.cs b/Ombi.Services/Notification/MattermostNotification.cs new file mode 100644 index 000000000..671974bf5 --- /dev/null +++ b/Ombi.Services/Notification/MattermostNotification.cs @@ -0,0 +1,156 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: MattermostNotification.cs +// Created By: Michel Zaleski +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Threading.Tasks; +using NLog; +using Ombi.Api.Interfaces; +using Ombi.Api.Models.Notifications; +using Ombi.Core; +using Ombi.Core.Models; +using Ombi.Core.SettingModels; +using Ombi.Services.Interfaces; + +namespace Ombi.Services.Notification +{ + public class MattermostNotification : INotification + { + public MattermostNotification(IMattermostApi api, ISettingsService sn) + { + Api = api; + Settings = sn; + } + + public string NotificationName => "MattermostNotification"; + + private IMattermostApi Api { get; } + private ISettingsService Settings { get; } + private static Logger Log = LogManager.GetCurrentClassLogger(); + + + public async Task NotifyAsync(NotificationModel model) + { + var settings = Settings.GetSettings(); + + await NotifyAsync(model, settings); + } + + public async Task NotifyAsync(NotificationModel model, Settings settings) + { + if (settings == null) await NotifyAsync(model); + + var pushSettings = (MattermostNotificationSettings)settings; + if (!ValidateConfiguration(pushSettings)) + { + Log.Error("Settings for Mattermost was not correct, we cannot push a notification"); + return; + } + + switch (model.NotificationType) + { + case NotificationType.NewRequest: + await PushNewRequestAsync(model, pushSettings); + break; + case NotificationType.Issue: + await PushIssueAsync(model, pushSettings); + break; + case NotificationType.RequestAvailable: + break; + case NotificationType.RequestApproved: + break; + case NotificationType.AdminNote: + break; + case NotificationType.Test: + await PushTest(pushSettings); + break; + case NotificationType.RequestDeclined: + break; + case NotificationType.ItemAddedToFaultQueue: + await PushFaultQueue(model, pushSettings); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private async Task PushNewRequestAsync(NotificationModel model, MattermostNotificationSettings settings) + { + var message = $"{model.Title} has been requested by user: {model.User}"; + await Push(settings, message); + } + + private async Task PushIssueAsync(NotificationModel model, MattermostNotificationSettings settings) + { + var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}"; + await Push(settings, message); + } + + private async Task PushTest(MattermostNotificationSettings settings) + { + var message = $"This is a test from Ombi, if you can see this then we have successfully pushed a notification!"; + await Push(settings, message); + } + + private async Task PushFaultQueue(NotificationModel model, MattermostNotificationSettings settings) + { + var message = $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying"; + await Push(settings, message); + } + + private async Task Push(MattermostNotificationSettings config, string message) + { + try + { + var notification = new MattermostNotificationBody { username = config.Username, channel = config.Channel ?? string.Empty, icon_url = config.IconUrl ?? string.Empty, text = message }; + + var result = await Api.PushAsync(config.WebhookUrl, notification); + if (!result.Equals("ok")) + { + Log.Error("Mattermost returned a message that was not 'ok', the notification did not get pushed"); + Log.Error($"Message that mattermost returned: {result}"); + } + } + catch (Exception e) + { + Log.Error(e); + } + } + + private bool ValidateConfiguration(MattermostNotificationSettings settings) + { + if (!settings.Enabled) + { + return false; + } + if (string.IsNullOrEmpty(settings.WebhookUrl)) + { + return false; + } + return true; + } + } +} \ No newline at end of file diff --git a/Ombi.Services/Ombi.Services.csproj b/Ombi.Services/Ombi.Services.csproj index d3ca25f5d..0eca0f558 100644 --- a/Ombi.Services/Ombi.Services.csproj +++ b/Ombi.Services/Ombi.Services.csproj @@ -157,6 +157,7 @@ + diff --git a/Ombi.Store/Models/RecenetlyAddedLog.cs b/Ombi.Store/Models/RecentlyAddedLog.cs similarity index 94% rename from Ombi.Store/Models/RecenetlyAddedLog.cs rename to Ombi.Store/Models/RecentlyAddedLog.cs index 4f7a75aba..f39df89bd 100644 --- a/Ombi.Store/Models/RecenetlyAddedLog.cs +++ b/Ombi.Store/Models/RecentlyAddedLog.cs @@ -34,6 +34,9 @@ namespace Ombi.Store.Models [Table("RecentlyAddedLog")] public class RecentlyAddedLog : Entity { + /// + /// This is actually a unique id for that content... + /// public string ProviderId { get; set; } public DateTime AddedAt { get; set; } } diff --git a/Ombi.Store/Ombi.Store.csproj b/Ombi.Store/Ombi.Store.csproj index 06a0b59fc..976e7fd59 100644 --- a/Ombi.Store/Ombi.Store.csproj +++ b/Ombi.Store/Ombi.Store.csproj @@ -68,7 +68,7 @@ - + diff --git a/Ombi.UI.Tests/AdminModuleTests.cs b/Ombi.UI.Tests/AdminModuleTests.cs index 30da77591..5935f9c26 100644 --- a/Ombi.UI.Tests/AdminModuleTests.cs +++ b/Ombi.UI.Tests/AdminModuleTests.cs @@ -73,8 +73,10 @@ namespace Ombi.UI.Tests private Mock Cache { get; set; } private Mock> Log { get; set; } private Mock> SlackSettings { get; set; } + private Mock> MattermostSettings { get; set; } private Mock> LandingPageSettings { get; set; } private Mock SlackApi { get; set; } + private Mock MattermostApi { get; set; } private Mock Analytics { get; set; } private Mock> NotifyV2 { get; set; } private Mock RecentlyAdded { get; set; } @@ -114,6 +116,8 @@ namespace Ombi.UI.Tests Log = new Mock>(); SlackApi = new Mock(); SlackSettings = new Mock>(); + MattermostApi = new Mock(); + MattermostSettings = new Mock>(); LandingPageSettings = new Mock>(); ScheduledJobsSettingsMock = new Mock>(); RecorderMock = new Mock(); @@ -147,8 +151,10 @@ namespace Ombi.UI.Tests with.Dependency(Cache.Object); with.Dependency(Log.Object); with.Dependency(SlackApi.Object); + with.Dependency(MattermostApi.Object); with.Dependency(LandingPageSettings.Object); with.Dependency(SlackSettings.Object); + with.Dependency(MattermostSettings.Object); with.Dependency(ScheduledJobsSettingsMock.Object); with.Dependency(RecorderMock.Object); with.Dependency(RecentlyAdded.Object); diff --git a/Ombi.UI/Modules/Admin/AdminModule.cs b/Ombi.UI/Modules/Admin/AdminModule.cs index f3c8acb8a..60de03907 100644 --- a/Ombi.UI/Modules/Admin/AdminModule.cs +++ b/Ombi.UI/Modules/Admin/AdminModule.cs @@ -87,9 +87,11 @@ namespace Ombi.UI.Modules.Admin private INotificationService NotificationService { get; } private ICacheProvider Cache { get; } private ISettingsService SlackSettings { get; } + private ISettingsService MattermostSettings { get; } private ISettingsService LandingSettings { get; } private ISettingsService ScheduledJobSettings { get; } private ISlackApi SlackApi { get; } + private IMattermostApi MattermostApi { get; } private IJobRecord JobRecorder { get; } private IAnalytics Analytics { get; } private IRecentlyAdded RecentlyAdded { get; } @@ -123,7 +125,8 @@ namespace Ombi.UI.Modules.Admin ISettingsService headphones, ISettingsService logs, ICacheProvider cache, ISettingsService slackSettings, - ISlackApi slackApi, ISettingsService lp, + ISlackApi slackApi, ISettingsService mattermostSettings, + IMattermostApi mattermostApi, ISettingsService lp, ISettingsService scheduler, IJobRecord rec, IAnalytics analytics, ISettingsService notifyService, IRecentlyAdded recentlyAdded, IMassEmail massEmail, ISettingsService watcherSettings, @@ -154,6 +157,8 @@ namespace Ombi.UI.Modules.Admin Cache = cache; SlackSettings = slackSettings; SlackApi = slackApi; + MattermostSettings = mattermostSettings; + MattermostApi = mattermostApi; LandingSettings = lp; ScheduledJobSettings = scheduler; JobRecorder = rec; @@ -239,6 +244,10 @@ namespace Ombi.UI.Modules.Admin Get["/slacknotification"] = _ => SlackNotifications(); Post["/slacknotification"] = _ => SaveSlackNotifications(); + Post["/testmattermostnotification", true] = async (x, ct) => await TestMattermostNotification(); + Get["/mattermostnotification"] = _ => MattermostNotifications(); + Post["/mattermostnotification"] = _ => SaveMattermostNotifications(); + Post["/testdiscordnotification", true] = async (x, ct) => await TestDiscordNotification(); Get["/discordnotification", true] = async (x, ct) => await DiscordNotification(); Post["/discordnotification", true] = async (x, ct) => await SaveDiscordNotifications(); @@ -1051,6 +1060,71 @@ namespace Ombi.UI.Modules.Admin : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); } + private async Task TestMattermostNotification() + { + var settings = this.BindAndValidate(); + if (!ModelValidationResult.IsValid) + { + return Response.AsJson(ModelValidationResult.SendJsonError()); + } + var notificationModel = new NotificationModel + { + NotificationType = NotificationType.Test, + DateTime = DateTime.Now + }; + + var currentMattermostSettings = await MattermostSettings.GetSettingsAsync(); + try + { + NotificationService.Subscribe(new MattermostNotification(MattermostApi, MattermostSettings)); + settings.Enabled = true; + await NotificationService.Publish(notificationModel, settings); + Log.Info("Sent mattermost notification test"); + } + catch (Exception e) + { + Log.Error(e, "Failed to subscribe and publish test Mattermost Notification"); + } + finally + { + if (!currentMattermostSettings.Enabled) + { + NotificationService.UnSubscribe(new MattermostNotification(MattermostApi, MattermostSettings)); + } + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Mattermost Notification! If you do not receive it please check the logs." }); + } + + private Negotiator MattermostNotifications() + { + var settings = MattermostSettings.GetSettings(); + return View["MattermostNotifications", settings]; + } + + private Response SaveMattermostNotifications() + { + var settings = this.BindAndValidate(); + if (!ModelValidationResult.IsValid) + { + return Response.AsJson(ModelValidationResult.SendJsonError()); + } + + var result = MattermostSettings.SaveSettings(settings); + if (settings.Enabled) + { + NotificationService.Subscribe(new MattermostNotification(MattermostApi, MattermostSettings)); + } + else + { + NotificationService.UnSubscribe(new MattermostNotification(MattermostApi, MattermostSettings)); + } + + Log.Info("Saved mattermost settings, result: {0}", result); + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Mattermost Notifications!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + private async Task DiscordNotification() { var settings = await DiscordSettings.GetSettingsAsync(); diff --git a/Ombi.UI/NinjectModules/ApiModule.cs b/Ombi.UI/NinjectModules/ApiModule.cs index d8b40cb33..b7346cc44 100644 --- a/Ombi.UI/NinjectModules/ApiModule.cs +++ b/Ombi.UI/NinjectModules/ApiModule.cs @@ -44,6 +44,7 @@ namespace Ombi.UI.NinjectModules Bind().To(); Bind().To(); Bind().To(); + Bind().To(); Bind().To(); Bind().To(); Bind().To(); diff --git a/Ombi.UI/Ombi.UI.csproj b/Ombi.UI/Ombi.UI.csproj index 1118a196f..0c7273d8a 100644 --- a/Ombi.UI/Ombi.UI.csproj +++ b/Ombi.UI/Ombi.UI.csproj @@ -299,6 +299,7 @@ + @@ -834,6 +835,9 @@ Always + + Always + Always diff --git a/Ombi.UI/Startup.cs b/Ombi.UI/Startup.cs index 73ef53250..1ed5db99c 100644 --- a/Ombi.UI/Startup.cs +++ b/Ombi.UI/Startup.cs @@ -127,6 +127,10 @@ namespace Ombi.UI var slackSettings = slackService.GetSettings(); SubScribeOvserver(slackSettings, notificationService, new SlackNotification(container.Get(), slackService)); + var mattermostService = container.Get>(); + var mattermostSettings = mattermostService.GetSettings(); + SubScribeOvserver(mattermostSettings, notificationService, new MattermostNotification(container.Get(), mattermostService)); + var discordSettings = container.Get>(); var discordService = discordSettings.GetSettings(); SubScribeOvserver(discordService, notificationService, new DiscordNotification(container.Get(), discordSettings)); diff --git a/Ombi.UI/Validators/MattermostSettingsValidator.cs b/Ombi.UI/Validators/MattermostSettingsValidator.cs new file mode 100644 index 000000000..333762d79 --- /dev/null +++ b/Ombi.UI/Validators/MattermostSettingsValidator.cs @@ -0,0 +1,40 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: MattermostSettingsValidator.cs +// Created By: Michel Zaleski +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using FluentValidation; +using Ombi.Core.SettingModels; + +namespace Ombi.UI.Validators +{ + public class MattermostSettingsValidator : AbstractValidator + { + public MattermostSettingsValidator() + { + RuleFor(request => request.WebhookUrl).NotEmpty().WithMessage("You must specify a Webhook Url"); + } + } +} \ No newline at end of file diff --git a/Ombi.UI/Views/About/About.cshtml b/Ombi.UI/Views/About/About.cshtml index 18a5aeb40..ad65912c0 100644 --- a/Ombi.UI/Views/About/About.cshtml +++ b/Ombi.UI/Views/About/About.cshtml @@ -78,6 +78,14 @@ https://github.com/tidusjar/Ombi + + + Forums + + + https://forums.ombi.io/ + + Wiki diff --git a/Ombi.UI/Views/Admin/MattermostNotifications.cshtml b/Ombi.UI/Views/Admin/MattermostNotifications.cshtml new file mode 100644 index 000000000..b48256319 --- /dev/null +++ b/Ombi.UI/Views/Admin/MattermostNotifications.cshtml @@ -0,0 +1,127 @@ +@using Ombi.UI.Helpers +@Html.Partial("Shared/Partial/_Sidebar") + +
+
+
+ Mattermost Notifications + +
+
+ + @if (Model.Enabled) + { + + } + else + { + + } + +
+
+ +
+ + This is the full webhook url. + Mattermost > Integrations > Incoming Webhook > Add Incoming Webhook. You will then have a Webhook Url +
+ +
+
+ +
+ + You can override the default channel here +
+ +
+
+ +
+ + You can override the default username (Ombi) here +
+ +
+
+ +
+ + You can override the default icon here +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + \ No newline at end of file diff --git a/Ombi.UI/Views/Admin/NewsletterSettings.cshtml b/Ombi.UI/Views/Admin/NewsletterSettings.cshtml index 32bc10dce..6a96144eb 100644 --- a/Ombi.UI/Views/Admin/NewsletterSettings.cshtml +++ b/Ombi.UI/Views/Admin/NewsletterSettings.cshtml @@ -33,7 +33,7 @@
-
diff --git a/Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml b/Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml index 03d533097..bbf3abaa4 100644 --- a/Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml +++ b/Ombi.UI/Views/Shared/Partial/_Sidebar.cshtml @@ -33,6 +33,7 @@ @Html.GetSidebarUrl(Context, "/admin/pushbulletnotification", "Pushbullet Notifications","fa fa-bell-o") @Html.GetSidebarUrl(Context, "/admin/pushovernotification", "Pushover Notifications", "fa fa-bell-o") @Html.GetSidebarUrl(Context, "/admin/slacknotification", "Slack Notifications", "fa fa-slack") + @Html.GetSidebarUrl(Context, "/admin/mattermostnotification", "Mattermost Notifications", "fa fa-bell-o") @Html.GetSidebarUrl(Context, "/admin/discordnotification", "Discord Notifications", "fa fa-bell-o") diff --git a/README.md b/README.md index b3e348137..7bbc0640b 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,10 @@ ____ |----------|:---------------------------:|:----------------------------:|:----------------------------:| | AppVeyor | [![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn/branch/master?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/master) | [![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn/branch/eap?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/eap) | [![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn/branch/dev?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/dev) | Download |[![Download](http://i.imgur.com/odToka3.png)](https://github.com/tidusjar/Ombi/releases) | [![Download](http://i.imgur.com/odToka3.png)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/eap/artifacts) | [![Download](http://i.imgur.com/odToka3.png)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/dev/artifacts) | + +We now have a forums! +Check it out: [https://forums.ombi.io/](https://forums.ombi.io) + # Features Here some of the features Ombi has: * All your users to Request Movies, TV Shows (Whole series, whole seaons or even single episodes!) and Albums diff --git a/appveyor.yml b/appveyor.yml index 2357e7eaa..dec0cba4d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,9 +3,9 @@ configuration: Release assembly_info: patch: true file: '**\AssemblyInfo.*' - assembly_version: '2.2.0' + assembly_version: '2.2.1' assembly_file_version: '{version}' - assembly_informational_version: '2.2.0' + assembly_informational_version: '2.2.1' before_build: - cmd: appveyor-retry nuget restore build: