From a834da3990dd3cf8d0de61c39cdc52caeb937f00 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Thu, 22 Sep 2016 21:44:51 +0100 Subject: [PATCH 01/16] Added different sonarr search commands. #515 --- PlexRequests.Api.Interfaces/ISonarrApi.cs | 2 + .../PlexRequests.Api.Models.csproj | 3 + .../Sonarr/SonarrAddEpisodeBody.cs | 2 +- .../Sonarr/SonarrSearchCommand.cs | 38 +++++++++++++ .../Sonarr/SonarrSeasonSearchResult.cs | 55 ++++++++++++++++++ .../Sonarr/SonarrSeriesSearchResult.cs | 56 +++++++++++++++++++ PlexRequests.Api/SonarrApi.cs | 53 ++++++++++++++++++ PlexRequests.UI/Helpers/TvSender.cs | 16 ++++++ 8 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 PlexRequests.Api.Models/Sonarr/SonarrSearchCommand.cs create mode 100644 PlexRequests.Api.Models/Sonarr/SonarrSeasonSearchResult.cs create mode 100644 PlexRequests.Api.Models/Sonarr/SonarrSeriesSearchResult.cs diff --git a/PlexRequests.Api.Interfaces/ISonarrApi.cs b/PlexRequests.Api.Interfaces/ISonarrApi.cs index 5c5594ad8..c0a40449c 100644 --- a/PlexRequests.Api.Interfaces/ISonarrApi.cs +++ b/PlexRequests.Api.Interfaces/ISonarrApi.cs @@ -48,5 +48,7 @@ namespace PlexRequests.Api.Interfaces SonarrEpisode UpdateEpisode(SonarrEpisode episodeInfo, string apiKey, Uri baseUrl); SonarrAddEpisodeResult SearchForEpisodes(int[] episodeIds, string apiKey, Uri baseUrl); Series UpdateSeries(Series series, string apiKey, Uri baseUrl); + SonarrSeasonSearchResult SearchForSeason(int seriesId, int seasonNumber, string apiKey, Uri baseUrl); + SonarrSeriesSearchResult SearchForSeries(int seriesId, string apiKey, Uri baseUrl); } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj b/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj index 20363d996..6426f8050 100644 --- a/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj +++ b/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj @@ -89,6 +89,9 @@ + + + diff --git a/PlexRequests.Api.Models/Sonarr/SonarrAddEpisodeBody.cs b/PlexRequests.Api.Models/Sonarr/SonarrAddEpisodeBody.cs index 265d881d1..6cf970241 100644 --- a/PlexRequests.Api.Models/Sonarr/SonarrAddEpisodeBody.cs +++ b/PlexRequests.Api.Models/Sonarr/SonarrAddEpisodeBody.cs @@ -29,6 +29,6 @@ namespace PlexRequests.Api.Models.Sonarr public class SonarrAddEpisodeBody { public string name { get; set; } - public int[] episodeIds { get; set; } + public int[] episodeIds { get; set; } } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/Sonarr/SonarrSearchCommand.cs b/PlexRequests.Api.Models/Sonarr/SonarrSearchCommand.cs new file mode 100644 index 000000000..f2649cd94 --- /dev/null +++ b/PlexRequests.Api.Models/Sonarr/SonarrSearchCommand.cs @@ -0,0 +1,38 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SonarrSearchCommand.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.Xml.Linq; + +namespace PlexRequests.Api.Models.Sonarr +{ + public class SonarrSearchCommand + { + public int seriesId { get; set; } + public int seasonNumber { get; set; } + public string name { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.Api.Models/Sonarr/SonarrSeasonSearchResult.cs b/PlexRequests.Api.Models/Sonarr/SonarrSeasonSearchResult.cs new file mode 100644 index 000000000..a0fe8bb29 --- /dev/null +++ b/PlexRequests.Api.Models/Sonarr/SonarrSeasonSearchResult.cs @@ -0,0 +1,55 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SonarrSeasonSearchResult.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 +namespace PlexRequests.Api.Models.Sonarr +{ + public class SeasonBody + { + public int seriesId { get; set; } + public int seasonNumber { get; set; } + public bool sendUpdatesToClient { get; set; } + public bool updateScheduledTask { get; set; } + public string completionMessage { get; set; } + public string name { get; set; } + public string trigger { get; set; } + } + + public class SonarrSeasonSearchResult + { + public string name { get; set; } + public SeasonBody body { get; set; } + public string priority { get; set; } + public string status { get; set; } + public string queued { get; set; } + public string trigger { get; set; } + public string state { get; set; } + public bool manual { get; set; } + public string startedOn { get; set; } + public bool sendUpdatesToClient { get; set; } + public bool updateScheduledTask { get; set; } + public int id { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.Api.Models/Sonarr/SonarrSeriesSearchResult.cs b/PlexRequests.Api.Models/Sonarr/SonarrSeriesSearchResult.cs new file mode 100644 index 000000000..76e5fc9ad --- /dev/null +++ b/PlexRequests.Api.Models/Sonarr/SonarrSeriesSearchResult.cs @@ -0,0 +1,56 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SonarrSeriesSearchResult.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 +namespace PlexRequests.Api.Models.Sonarr +{ + public class SeriesBody + { + public int seriesId { get; set; } + public bool sendUpdatesToClient { get; set; } + public bool updateScheduledTask { get; set; } + public string completionMessage { get; set; } + public string name { get; set; } + public string trigger { get; set; } + } + + public class SonarrSeriesSearchResult + { + public string name { get; set; } + public SeriesBody body { get; set; } + public string priority { get; set; } + public string status { get; set; } + public string queued { get; set; } + public string started { get; set; } + public string trigger { get; set; } + public string state { get; set; } + public bool manual { get; set; } + public string startedOn { get; set; } + public string stateChangeTime { get; set; } + public bool sendUpdatesToClient { get; set; } + public bool updateScheduledTask { get; set; } + public int id { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.Api/SonarrApi.cs b/PlexRequests.Api/SonarrApi.cs index 6cc8019f3..6cc82fefa 100644 --- a/PlexRequests.Api/SonarrApi.cs +++ b/PlexRequests.Api/SonarrApi.cs @@ -327,5 +327,58 @@ namespace PlexRequests.Api return null; } } + + public SonarrSeasonSearchResult SearchForSeason(int seriesId, int seasonNumber, string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/Command", Method = Method.POST }; + request.AddHeader("X-Api-Key", apiKey); + + var body = new SonarrSearchCommand + { + name = "SeasonSearch", + seriesId = seriesId, + seasonNumber = seasonNumber + }; + request.AddJsonBody(body); + + try + { + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => + Log.Error(exception, "Exception when calling SearchForSeason for Sonarr, Retrying {0}", timespan)); + + return policy.Execute(() => Api.ExecuteJson(request, baseUrl)); + } + catch (Exception e) + { + Log.Error(e, "There has been an API exception when put the Sonarr SearchForSeason"); + return null; + } + } + + public SonarrSeriesSearchResult SearchForSeries(int seriesId, string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/Command", Method = Method.POST }; + request.AddHeader("X-Api-Key", apiKey); + + var body = new SonarrSearchCommand + { + name = "SeriesSearch", + seriesId = seriesId + }; + request.AddJsonBody(body); + + try + { + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => + Log.Error(exception, "Exception when calling SearchForSeries for Sonarr, Retrying {0}", timespan)); + + return policy.Execute(() => Api.ExecuteJson(request, baseUrl)); + } + catch (Exception e) + { + Log.Error(e, "There has been an API exception when put the Sonarr SearchForSeries"); + return null; + } + } } } \ No newline at end of file diff --git a/PlexRequests.UI/Helpers/TvSender.cs b/PlexRequests.UI/Helpers/TvSender.cs index fe1572044..705ceecb1 100644 --- a/PlexRequests.UI/Helpers/TvSender.cs +++ b/PlexRequests.UI/Helpers/TvSender.cs @@ -112,6 +112,7 @@ namespace PlexRequests.UI.Helpers return addResult; } + // Series exists, don't need to add it if (series != null) { var requestAll = model.SeasonsRequested.Equals("All", StringComparison.CurrentCultureIgnoreCase); @@ -127,7 +128,10 @@ namespace PlexRequests.UI.Helpers { season.monitored = true; } + SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri); + SonarrApi.SearchForSeason(series.id, season.seasonNumber, sonarrSettings.ApiKey, sonarrSettings.FullUri); } + return new SonarrAddSeries { title = series.title }; // We have updated it } if (requestAll) @@ -137,18 +141,30 @@ namespace PlexRequests.UI.Helpers { season.monitored = true; } + + SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri); + SonarrApi.SearchForSeries(series.id, sonarrSettings.ApiKey, sonarrSettings.FullUri); // Search For all episodes!" + return new SonarrAddSeries { title = series.title }; // We have updated it } if (first) { var firstSeries = series?.seasons?.OrderBy(x => x.seasonNumber)?.FirstOrDefault() ?? new Season(); firstSeries.monitored = true; + SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri); + SonarrApi.SearchForSeason(series.id, firstSeries.seasonNumber, sonarrSettings.ApiKey, + sonarrSettings.FullUri); + return new SonarrAddSeries { title = series.title }; // We have updated it } if (latest) { var lastSeries = series?.seasons?.OrderByDescending(x => x.seasonNumber)?.FirstOrDefault() ?? new Season(); lastSeries.monitored = true; + SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri); + SonarrApi.SearchForSeason(series.id, lastSeries.seasonNumber, sonarrSettings.ApiKey, + sonarrSettings.FullUri); + return new SonarrAddSeries { title = series.title }; // We have updated it } From 5816ddef98ed640c6f82172e4663b7970e221f9d Mon Sep 17 00:00:00 2001 From: tidusjar Date: Fri, 23 Sep 2016 20:40:24 +0100 Subject: [PATCH 02/16] #515 --- PlexRequests.UI.Tests/TvSenderTests.cs | 26 ++---- PlexRequests.UI/Helpers/TvSender.cs | 109 ++++++++++++------------ PlexRequests.UI/Modules/SearchModule.cs | 1 - 3 files changed, 61 insertions(+), 75 deletions(-) diff --git a/PlexRequests.UI.Tests/TvSenderTests.cs b/PlexRequests.UI.Tests/TvSenderTests.cs index eb49f8b45..b9763627f 100644 --- a/PlexRequests.UI.Tests/TvSenderTests.cs +++ b/PlexRequests.UI.Tests/TvSenderTests.cs @@ -63,30 +63,18 @@ namespace PlexRequests.UI.Tests } [Test] - public async Task HappyPathSendSeriesToSonarr() + public async Task HappyPathSendSeriesToSonarrAllSeason() { - var seriesResult = new SonarrAddSeries() { monitored = true }; - SonarrMock.Setup(x => x.GetSeries(It.IsAny(), It.IsAny())).Returns(new List()); - SonarrMock.Setup( - x => - x.AddSeries( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), It.IsAny())).Returns(seriesResult); + var seriesResult = new SonarrAddSeries() { title = "ABC"}; + SonarrMock.Setup(x => x.GetSeries(It.IsAny(), It.IsAny())).Returns(F.Build().With(x => x.tvdbId, 1).With(x => x.title, "ABC").CreateMany().ToList()); + Sender = new TvSender(SonarrMock.Object, SickrageMock.Object); - var request = new RequestedModel(); + var request = new RequestedModel {SeasonsRequested = "All", ProviderId = 1, Title = "ABC"}; var result = await Sender.SendToSonarr(GetSonarrSettings(), request); - Assert.That(result, Is.EqualTo(seriesResult)); + Assert.That(result.title, Is.EqualTo("ABC")); SonarrMock.Verify(x => x.AddSeries(It.IsAny(), It.IsAny(), It.IsAny(), @@ -96,7 +84,7 @@ namespace PlexRequests.UI.Tests It.IsAny(), It.IsAny(), It.IsAny(), - true, It.IsAny()), Times.Once); + true, It.IsAny()), Times.Never); } [Test] diff --git a/PlexRequests.UI/Helpers/TvSender.cs b/PlexRequests.UI/Helpers/TvSender.cs index 705ceecb1..958af1159 100644 --- a/PlexRequests.UI/Helpers/TvSender.cs +++ b/PlexRequests.UI/Helpers/TvSender.cs @@ -74,6 +74,11 @@ namespace PlexRequests.UI.Helpers var series = await GetSonarrSeries(sonarrSettings, model.ProviderId); + var requestAll = model.SeasonsRequested?.Equals("All", StringComparison.CurrentCultureIgnoreCase); + var first = model.SeasonsRequested?.Equals("First", StringComparison.CurrentCultureIgnoreCase); + var latest = model.SeasonsRequested?.Equals("Latest", StringComparison.CurrentCultureIgnoreCase); + var specificSeasonRequest = model.SeasonList?.Any(); + if (episodeRequest) { // Does series exist? @@ -113,73 +118,67 @@ namespace PlexRequests.UI.Helpers } // Series exists, don't need to add it - if (series != null) + if (series == null) { - var requestAll = model.SeasonsRequested.Equals("All", StringComparison.CurrentCultureIgnoreCase); - var first = model.SeasonsRequested.Equals("First", StringComparison.CurrentCultureIgnoreCase); - var latest = model.SeasonsRequested.Equals("Latest", StringComparison.CurrentCultureIgnoreCase); + // Set the series as monitored with a season count as 0 so it doesn't search for anything + SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile, + sonarrSettings.SeasonFolders, sonarrSettings.RootPath, 0, model.SeasonList, sonarrSettings.ApiKey, + sonarrSettings.FullUri); - if (model.SeasonList.Any()) + await Task.Delay(TimeSpan.FromSeconds(1)); + + series = await GetSonarrSeries(sonarrSettings, model.ProviderId); + } + + if (requestAll ?? false) + { + // Monitor all seasons + foreach (var season in series.seasons) { - // Monitor the seasons that we have chosen - foreach (var season in series.seasons) - { - if (model.SeasonList.Contains(season.seasonNumber)) - { - season.monitored = true; - } - SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri); - SonarrApi.SearchForSeason(series.id, season.seasonNumber, sonarrSettings.ApiKey, sonarrSettings.FullUri); - } - return new SonarrAddSeries { title = series.title }; // We have updated it + season.monitored = true; } - if (requestAll) - { - // Monitor all seasons - foreach (var season in series.seasons) - { - season.monitored = true; - } + SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri); + SonarrApi.SearchForSeries(series.id, sonarrSettings.ApiKey, sonarrSettings.FullUri); // Search For all episodes!" + return new SonarrAddSeries { title = series.title }; // We have updated it + } - SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri); - SonarrApi.SearchForSeries(series.id, sonarrSettings.ApiKey, sonarrSettings.FullUri); // Search For all episodes!" - return new SonarrAddSeries { title = series.title }; // We have updated it - } + if (first ?? false) + { + var firstSeries = (series?.seasons?.OrderBy(x => x.seasonNumber)).FirstOrDefault(x => x.seasonNumber > 0) ?? new Season(); + firstSeries.monitored = true; + SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri); + SonarrApi.SearchForSeason(series.id, firstSeries.seasonNumber, sonarrSettings.ApiKey, + sonarrSettings.FullUri); + return new SonarrAddSeries { title = series.title }; // We have updated it + } - if (first) - { - var firstSeries = series?.seasons?.OrderBy(x => x.seasonNumber)?.FirstOrDefault() ?? new Season(); - firstSeries.monitored = true; - SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri); - SonarrApi.SearchForSeason(series.id, firstSeries.seasonNumber, sonarrSettings.ApiKey, - sonarrSettings.FullUri); - return new SonarrAddSeries { title = series.title }; // We have updated it - } + if (latest ?? false) + { + var lastSeries = series?.seasons?.OrderByDescending(x => x.seasonNumber)?.FirstOrDefault() ?? new Season(); + lastSeries.monitored = true; + SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri); + SonarrApi.SearchForSeason(series.id, lastSeries.seasonNumber, sonarrSettings.ApiKey, + sonarrSettings.FullUri); + return new SonarrAddSeries { title = series.title }; // We have updated it + } - if (latest) + if (specificSeasonRequest ?? false) + { + // Monitor the seasons that we have chosen + foreach (var season in series.seasons) { - var lastSeries = series?.seasons?.OrderByDescending(x => x.seasonNumber)?.FirstOrDefault() ?? new Season(); - lastSeries.monitored = true; - SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri); - SonarrApi.SearchForSeason(series.id, lastSeries.seasonNumber, sonarrSettings.ApiKey, - sonarrSettings.FullUri); - return new SonarrAddSeries { title = series.title }; // We have updated it + if (model.SeasonList.Contains(season.seasonNumber)) + { + season.monitored = true; + SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri); + SonarrApi.SearchForSeason(series.id, season.seasonNumber, sonarrSettings.ApiKey, sonarrSettings.FullUri); + } } - - - // Update the series in sonarr with the new monitored status - SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri); - await RequestAllEpisodesInASeasonWithExistingSeries(model, series, sonarrSettings); return new SonarrAddSeries { title = series.title }; // We have updated it } - - var result = SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile, - sonarrSettings.SeasonFolders, sonarrSettings.RootPath, model.SeasonCount, model.SeasonList, sonarrSettings.ApiKey, - sonarrSettings.FullUri, true, true); - - return result; + return null; } public SickRageTvAdd SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model) @@ -278,7 +277,7 @@ namespace PlexRequests.UI.Helpers var tasks = new List(); var requestedEpisodes = model.Episodes; - + foreach (var r in episodes) { if (r.hasFile) // If it already has the file, there is no point in updating it diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index 2a0bbe4f6..ca728613c 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -362,7 +362,6 @@ namespace PlexRequests.UI.Modules viewT.Requested = true; viewT.Episodes = dbt.Episodes.ToList(); viewT.Approved = dbt.Approved; - viewT.Available = dbt.Available; } if (sonarrCached.Contains(tvdbid) || sickRageCache.Contains(tvdbid)) // compare to the sonarr/sickrage db { From 9c789363f6e5face81be657fb7c231a251691734 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Sun, 25 Sep 2016 01:03:23 +0100 Subject: [PATCH 03/16] notification improvements --- .../NotificationMessageResolverTests.cs | 294 +++++++++--------- .../NotificationMessageResolver.cs | 32 +- .../Templates/BasicRequestTemplate.html | 189 +++++++++++ .../Templates/EmailBasicTemplate.cs | 70 +++++ .../Templates/IEmailBasicTemplate.cs | 37 +++ .../Notification/TransportType.cs | 36 +++ PlexRequests.Core/PlexRequests.Core.csproj | 11 +- .../EmailNotificationSettings.cs | 2 +- .../SettingModels/NotificationSettingsV2.cs | 62 ++++ .../Notification/EmailMessageNotification.cs | 48 ++- .../Notification/NotificationEngine.cs | 13 +- .../Notification/NotificationModel.cs | 1 + PlexRequests.UI.Tests/AdminModuleTests.cs | 3 + PlexRequests.UI/Modules/AdminModule.cs | 23 +- PlexRequests.UI/PlexRequests.UI.csproj | 3 + .../Views/Admin/NotificationSettings.cshtml | 84 +++++ PlexRequests.sln | 2 +- 17 files changed, 731 insertions(+), 179 deletions(-) create mode 100644 PlexRequests.Core/Notification/Templates/BasicRequestTemplate.html create mode 100644 PlexRequests.Core/Notification/Templates/EmailBasicTemplate.cs create mode 100644 PlexRequests.Core/Notification/Templates/IEmailBasicTemplate.cs create mode 100644 PlexRequests.Core/Notification/TransportType.cs create mode 100644 PlexRequests.Core/SettingModels/NotificationSettingsV2.cs create mode 100644 PlexRequests.UI/Views/Admin/NotificationSettings.cshtml diff --git a/PlexRequests.Core.Tests/NotificationMessageResolverTests.cs b/PlexRequests.Core.Tests/NotificationMessageResolverTests.cs index 58cfb06e5..8b89a6e2f 100644 --- a/PlexRequests.Core.Tests/NotificationMessageResolverTests.cs +++ b/PlexRequests.Core.Tests/NotificationMessageResolverTests.cs @@ -1,147 +1,147 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: AuthenticationSettingsTests.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.Collections.Generic; - -using NUnit.Framework; - -using PlexRequests.Core.Models; -using PlexRequests.Core.Notification; -using PlexRequests.Core.SettingModels; - -namespace PlexRequests.Core.Tests -{ - [TestFixture] - public class NotificationMessageResolverTests - { - [TestCaseSource(nameof(MessageBodyResolver))] - public string ResolveBody(string body, NotificationMessageCurlys param) - { - var n = new NotificationMessageResolver(); - var s = new NotificationSettings - { - Message = new List { new Notification.NotificationMessage { NotificationType = NotificationType.NewRequest, Body = body } } - }; - - var result = n.ParseMessage(s, NotificationType.NewRequest, param); - return result.Body; - } - - [TestCaseSource(nameof(MessageSubjectResolver))] - public string ResolveSubject(string subject, NotificationMessageCurlys param) - { - var n = new NotificationMessageResolver(); - var s = new NotificationSettings - { - Message = new List { new Notification.NotificationMessage { NotificationType = NotificationType.NewRequest, Subject = subject }} - }; - - var result = n.ParseMessage(s, NotificationType.NewRequest, param); - return result.Subject; - } - - private static IEnumerable MessageSubjectResolver - { - get - { - yield return new TestCaseData( - "{Username} has requested a {Type}", - new NotificationMessageCurlys("Jamie", "Finding Dory", DateTime.Now.ToString(), "Movie", string.Empty)) - .Returns("Jamie has requested a Movie").SetName("Subject Curlys"); - - yield return new TestCaseData( - null, - new NotificationMessageCurlys("Jamie", "Finding Dory", DateTime.Now.ToString(), "Movie", string.Empty)) - .Returns(string.Empty).SetName("Empty Subject"); - - yield return new TestCaseData( - "New Request Incoming!", - new NotificationMessageCurlys("Jamie", "Finding Dory", DateTime.Now.ToString(), "Movie", string.Empty)) - .Returns("New Request Incoming!").SetName("No curlys"); - - yield return new TestCaseData( - "%$R£%$£^%$&{Username}@{}:§", - new NotificationMessageCurlys("Jamie", "Finding Dory", DateTime.Now.ToString(), "Movie", string.Empty)) - .Returns("%$R£%$£^%$&Jamie@{}:§").SetName("Special Chars"); - } - } - private static IEnumerable MessageBodyResolver - { - get - { - yield return new TestCaseData( - "There has been a new request from {Username}, Title: {Title} for {Type}", - new NotificationMessageCurlys("Jamie", "Finding Dory", DateTime.Now.ToString(), "Movie", string.Empty)) - .Returns("There has been a new request from Jamie, Title: Finding Dory for Movie").SetName("FindingDory"); - - yield return new TestCaseData( - null, - new NotificationMessageCurlys(string.Empty, string.Empty, string.Empty, string.Empty, string.Empty)) - .Returns(string.Empty) - .SetName("Empty Message"); - - yield return new TestCaseData( - "{{Wowwzer}} Damn}{{Username}}}}", - new NotificationMessageCurlys("HEY!", string.Empty, string.Empty, string.Empty, string.Empty)) - .Returns("{{Wowwzer}} Damn}{HEY!}}}") - .SetName("Multiple Curlys"); - - - yield return new TestCaseData( - "This is a message with no curlys", - new NotificationMessageCurlys("Jamie", "Finding Dory", DateTime.Now.ToString(), "Movie", string.Empty)) - .Returns("This is a message with no curlys") - .SetName("No Curlys"); - - yield return new TestCaseData( - new string(')', 5000), - new NotificationMessageCurlys(string.Empty, string.Empty, string.Empty, string.Empty, string.Empty)) - .Returns(new string(')', 5000)) - .SetName("Long String"); - - - yield return new TestCaseData( - "This is a {Username} and {Username} Because {Issue}{Issue}", - new NotificationMessageCurlys("HEY!", string.Empty, string.Empty, string.Empty, "Bob")) - .Returns("This is a HEY! and HEY! Because BobBob") - .SetName("Double Curly"); - - yield return new TestCaseData( - "This is a {username} and {username} Because {Issue}{Issue}", - new NotificationMessageCurlys("HEY!", string.Empty, string.Empty, string.Empty, "Bob")) - .Returns("This is a {username} and {username} Because BobBob") - .SetName("Case sensitive"); - - yield return new TestCaseData( - "{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}", - new NotificationMessageCurlys("HEY!", string.Empty, "b", string.Empty, "Bob")) - .Returns("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") - .SetName("Lots of curlys"); - } - } - } -} \ No newline at end of file +//#region Copyright +//// /************************************************************************ +//// Copyright (c) 2016 Jamie Rees +//// File: AuthenticationSettingsTests.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.Collections.Generic; + +//using NUnit.Framework; + +//using PlexRequests.Core.Models; +//using PlexRequests.Core.Notification; +//using PlexRequests.Core.SettingModels; + +//namespace PlexRequests.Core.Tests +//{ +// [TestFixture] +// public class NotificationMessageResolverTests +// { +// [TestCaseSource(nameof(MessageBodyResolver))] +// public string ResolveBody(string body, NotificationMessageCurlys param) +// { +// var n = new NotificationMessageResolver(); +// var s = new NotificationSettings +// { +// Message = new List { new Notification.NotificationMessage { NotificationType = NotificationType.NewRequest, Body = body } } +// }; + +// var result = n.ParseMessage(s, NotificationType.NewRequest, param, TransportType.Email); +// return result.Body; +// } + +// [TestCaseSource(nameof(MessageSubjectResolver))] +// public string ResolveSubject(string subject, NotificationMessageCurlys param) +// { +// var n = new NotificationMessageResolver(); +// var s = new NotificationSettings +// { +// Message = new List { new Notification.NotificationMessage { NotificationType = NotificationType.NewRequest, Subject = subject }} +// }; + +// var result = n.ParseMessage(s, NotificationType.NewRequest, param, TransportType.Email); +// return result.Subject; +// } + +// private static IEnumerable MessageSubjectResolver +// { +// get +// { +// yield return new TestCaseData( +// "{Username} has requested a {Type}", +// new NotificationMessageCurlys("Jamie", "Finding Dory", DateTime.Now.ToString(), "Movie", string.Empty)) +// .Returns("Jamie has requested a Movie").SetName("Subject Curlys"); + +// yield return new TestCaseData( +// null, +// new NotificationMessageCurlys("Jamie", "Finding Dory", DateTime.Now.ToString(), "Movie", string.Empty)) +// .Returns(string.Empty).SetName("Empty Subject"); + +// yield return new TestCaseData( +// "New Request Incoming!", +// new NotificationMessageCurlys("Jamie", "Finding Dory", DateTime.Now.ToString(), "Movie", string.Empty)) +// .Returns("New Request Incoming!").SetName("No curlys"); + +// yield return new TestCaseData( +// "%$R£%$£^%$&{Username}@{}:§", +// new NotificationMessageCurlys("Jamie", "Finding Dory", DateTime.Now.ToString(), "Movie", string.Empty)) +// .Returns("%$R£%$£^%$&Jamie@{}:§").SetName("Special Chars"); +// } +// } +// private static IEnumerable MessageBodyResolver +// { +// get +// { +// yield return new TestCaseData( +// "There has been a new request from {Username}, Title: {Title} for {Type}", +// new NotificationMessageCurlys("Jamie", "Finding Dory", DateTime.Now.ToString(), "Movie", string.Empty)) +// .Returns("There has been a new request from Jamie, Title: Finding Dory for Movie").SetName("FindingDory"); + +// yield return new TestCaseData( +// null, +// new NotificationMessageCurlys(string.Empty, string.Empty, string.Empty, string.Empty, string.Empty)) +// .Returns(string.Empty) +// .SetName("Empty Message"); + +// yield return new TestCaseData( +// "{{Wowwzer}} Damn}{{Username}}}}", +// new NotificationMessageCurlys("HEY!", string.Empty, string.Empty, string.Empty, string.Empty)) +// .Returns("{{Wowwzer}} Damn}{HEY!}}}") +// .SetName("Multiple Curlys"); + + +// yield return new TestCaseData( +// "This is a message with no curlys", +// new NotificationMessageCurlys("Jamie", "Finding Dory", DateTime.Now.ToString(), "Movie", string.Empty)) +// .Returns("This is a message with no curlys") +// .SetName("No Curlys"); + +// yield return new TestCaseData( +// new string(')', 5000), +// new NotificationMessageCurlys(string.Empty, string.Empty, string.Empty, string.Empty, string.Empty)) +// .Returns(new string(')', 5000)) +// .SetName("Long String"); + + +// yield return new TestCaseData( +// "This is a {Username} and {Username} Because {Issue}{Issue}", +// new NotificationMessageCurlys("HEY!", string.Empty, string.Empty, string.Empty, "Bob")) +// .Returns("This is a HEY! and HEY! Because BobBob") +// .SetName("Double Curly"); + +// yield return new TestCaseData( +// "This is a {username} and {username} Because {Issue}{Issue}", +// new NotificationMessageCurlys("HEY!", string.Empty, string.Empty, string.Empty, "Bob")) +// .Returns("This is a {username} and {username} Because BobBob") +// .SetName("Case sensitive"); + +// yield return new TestCaseData( +// "{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}{Date}", +// new NotificationMessageCurlys("HEY!", string.Empty, "b", string.Empty, "Bob")) +// .Returns("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") +// .SetName("Lots of curlys"); +// } +// } +// } +//} \ No newline at end of file diff --git a/PlexRequests.Core/Notification/NotificationMessageResolver.cs b/PlexRequests.Core/Notification/NotificationMessageResolver.cs index ed90bbf12..f9c7b1b82 100644 --- a/PlexRequests.Core/Notification/NotificationMessageResolver.cs +++ b/PlexRequests.Core/Notification/NotificationMessageResolver.cs @@ -24,6 +24,8 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion + +using System; using System.Collections.Generic; using System.Linq; @@ -50,16 +52,37 @@ namespace PlexRequests.Core.Notification /// The notification. /// The type. /// The c. + /// Type of the transport. /// - public NotificationMessageContent ParseMessage(T notification, NotificationType type, NotificationMessageCurlys c) where T : NotificationSettings + public NotificationMessageContent ParseMessage(NotificationSettingsV2 notification, NotificationType type, NotificationMessageCurlys c, TransportType transportType) { - var content = notification.Message.FirstOrDefault(x => x.NotificationType == type); + IEnumerable content = null; + switch (transportType) + { + case TransportType.Email: + content = notification.EmailNotification; + break; + case TransportType.Pushbullet: + content = notification.PushbulletNotification; + break; + case TransportType.Pushover: + content = notification.PushoverNotification; + break; + case TransportType.Slack: + content = notification.SlackNotification; + break; + default: + throw new ArgumentOutOfRangeException(nameof(transportType), transportType, null); + } + if (content == null) { return new NotificationMessageContent(); } - return Resolve(content.Body, content.Subject, c.Curlys); + var message = content.FirstOrDefault(x => x.NotificationType == type) ?? new NotificationMessage(); + + return Resolve(message.Body, message.Subject, c.Curlys); } /// @@ -78,7 +101,7 @@ namespace PlexRequests.Core.Notification body = ReplaceFields(bodyFields, parameters, body); subject = ReplaceFields(subjectFields, parameters, subject); - return new NotificationMessageContent { Body = body ?? string.Empty, Subject = subject ?? string.Empty }; + return new NotificationMessageContent {Body = body ?? string.Empty, Subject = subject ?? string.Empty}; } /// @@ -123,7 +146,6 @@ namespace PlexRequests.Core.Notification { currentWord += c.ToString(); // Add the character onto the word. } - } return fields; diff --git a/PlexRequests.Core/Notification/Templates/BasicRequestTemplate.html b/PlexRequests.Core/Notification/Templates/BasicRequestTemplate.html new file mode 100644 index 000000000..c7d7e6c9d --- /dev/null +++ b/PlexRequests.Core/Notification/Templates/BasicRequestTemplate.html @@ -0,0 +1,189 @@ + + + + + + Plex Requests .Net + + + + + + + + + +
  +
+ + + + + + + + + + + +
+ + + + + + + + + + +
+ +
+

Hi there!

+

{@SUBJECT}

+

{@BODY}

+ +
+ +
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/PlexRequests.Core/Notification/Templates/EmailBasicTemplate.cs b/PlexRequests.Core/Notification/Templates/EmailBasicTemplate.cs new file mode 100644 index 000000000..8ce53afb6 --- /dev/null +++ b/PlexRequests.Core/Notification/Templates/EmailBasicTemplate.cs @@ -0,0 +1,70 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: EmailBasicTemplate.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.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using NLog; +using PlexRequests.Core.Models; +using PlexRequests.Core.SettingModels; + +namespace PlexRequests.Core.Notification.Templates +{ + public class EmailBasicTemplate : IEmailBasicTemplate + { + public string TemplateLocation => Path.Combine(Path.GetDirectoryName(Application.ExecutablePath) ?? string.Empty, "Notification", "Templates", "BasicRequestTemplate.html"); + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + + private const string SubjectKey = "{@SUBJECT}"; + private const string BodyKey = "{@BODY}"; + private const string ImgSrc = "{@IMGSRC}"; + private const string DateKey = "{@DATENOW}"; + + public string LoadTemplate(string subject, string body, string imgSrc) + { + try + { + var sb = new StringBuilder(File.ReadAllText(TemplateLocation)); + sb.Replace(SubjectKey, subject); + sb.Replace(BodyKey, body); + sb.Replace(ImgSrc, imgSrc); + sb.Replace(DateKey, DateTime.Now.ToString("f")); + + return sb.ToString(); + } + catch (Exception e) + { + Log.Error(e); + return string.Empty; + } + } + } +} \ No newline at end of file diff --git a/PlexRequests.Core/Notification/Templates/IEmailBasicTemplate.cs b/PlexRequests.Core/Notification/Templates/IEmailBasicTemplate.cs new file mode 100644 index 000000000..e72645722 --- /dev/null +++ b/PlexRequests.Core/Notification/Templates/IEmailBasicTemplate.cs @@ -0,0 +1,37 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: IEmailBasicTemplate.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.Threading.Tasks; + +namespace PlexRequests.Core.Notification.Templates +{ + public interface IEmailBasicTemplate + { + string LoadTemplate(string subject, string body, string imgSrc); + string TemplateLocation { get; } + } +} \ No newline at end of file diff --git a/PlexRequests.Core/Notification/TransportType.cs b/PlexRequests.Core/Notification/TransportType.cs new file mode 100644 index 000000000..6244b2f99 --- /dev/null +++ b/PlexRequests.Core/Notification/TransportType.cs @@ -0,0 +1,36 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: TransportType.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 +namespace PlexRequests.Core.Notification +{ + public enum TransportType + { + Email, + Pushbullet, + Pushover, + Slack + } +} \ No newline at end of file diff --git a/PlexRequests.Core/PlexRequests.Core.csproj b/PlexRequests.Core/PlexRequests.Core.csproj index 2e2d1b3a1..d3cd82a7b 100644 --- a/PlexRequests.Core/PlexRequests.Core.csproj +++ b/PlexRequests.Core/PlexRequests.Core.csproj @@ -44,6 +44,7 @@ + @@ -81,11 +82,15 @@ + + + + @@ -134,7 +139,11 @@ PlexRequests.Store - + + + PreserveNewest + + +
+
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ } +
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/PlexRequests.sln b/PlexRequests.sln index f806ea2f7..ec6287fad 100644 --- a/PlexRequests.sln +++ b/PlexRequests.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.UI", "PlexRequests.UI\PlexRequests.UI.csproj", "{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}" EndProject From 99d391983a550c6f3dabfe458bc53f0f1cf24d41 Mon Sep 17 00:00:00 2001 From: Michael Reber Date: Sat, 1 Oct 2016 14:21:08 +0200 Subject: [PATCH 04/16] Correction of the German translation --- PlexRequests.UI/Resources/UI.de.resx | 58 ++++++++++++++-------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/PlexRequests.UI/Resources/UI.de.resx b/PlexRequests.UI/Resources/UI.de.resx index f3607f5fa..08590a8ef 100644 --- a/PlexRequests.UI/Resources/UI.de.resx +++ b/PlexRequests.UI/Resources/UI.de.resx @@ -121,7 +121,7 @@ Anmelden - Möchten Sie einen Film oder eine Serie sehen, welche/r nicht auf Plex ist? Loggen Sie sich unten mit Ihrem Plex.tv Benutzernamen und Passwort ein! + Möchten Sie einen Film oder eine Serie schauen, die momentan noch nicht auf Plex ist? Dann loggen Sie sich unten ein und fordern Sie das Material an! Ihre Login-Daten werden nur zur Authorisierung Ihres Plex-Konto verwendet. @@ -172,7 +172,7 @@ Ausloggen - Es ist ein neues Update verfügbar! Klicken + Es ist ein neues Update verfügbar! Hier Klicken Englisch @@ -211,7 +211,7 @@ Alben - Möchten Sie etwas zu sehen, das derzeit nicht auf Plex ist ?! Kein Problem! Suchen Sie einfach unten danach und fragen Sie es an! + Möchten Sie etwas schauen, das derzeit nicht auf Plex ist?! Kein Problem! Suchen Sie unten einfach danach und fragen Sie es an! Suche @@ -220,13 +220,13 @@ Vorschläge - Demnächst + Demnächst verfügbar - In den Kinos + Momentan im Kino - Senden Sie mir eine Benachrichtigung, wenn Gegenstände, die ich angefordert habe hinzugefügt wurden. + Sende mir eine Benachrichtigung, wenn die Serien oder die Filme, die ich angefordert habe hinzugefügt wurden. Speichern @@ -235,13 +235,13 @@ Verfügbar - angefragt + angefordert - Angefordert + Anfordern - alle Staffeln + Alle Staffeln Erste Staffel @@ -253,10 +253,10 @@ Auswählen - Melde Problem + Problem melden - Falsche Audio + Falscher Ton Keine Untertitel @@ -268,7 +268,7 @@ Wiedergabe-Probleme - Sonstige + Sonstiges Track-Count @@ -280,7 +280,7 @@ Staffeln - Schließen + Schliessen Fügen Sie ein Problem hinzu @@ -298,7 +298,7 @@ Anfragen - Im Folgenden finden Sie Ihre und alle anderen Anfragen, sowie deren Download- und Genehmigungsstatus angezeigt. + Unten befinden sich alle Anfragen aller Benutzer. Hier ist auch der aktuelle Status des beantragten Mediums ersichtlich. Filme @@ -349,7 +349,7 @@ Nicht veröffentlicht - Sortieren nach + Sortieren nach Filter @@ -376,7 +376,7 @@ Beantragt von - angefragt + Angefragt vor Toggle Dropdown @@ -388,10 +388,10 @@ Entfernen - Markeiren als "Nicht verfügbar" + Als "Nicht verfügbar" markieren - Markieren als "Verfügbar" + Als "Verfügbar" markieren Genehmigt @@ -409,31 +409,31 @@ wurde bereits angefragt! - Wir konnten nicht prüfen ob {0} in Plex ist. Bist du sicher dass es richtig installiert ist? + Wir konnten nicht prüfen ob {0} auf Plex ist. Bist du sicher dass es richtig installiert ist? - Etwas ging schief beim hinzufügen des Filmes zu CouchPotato! Bitte überprüfe deine Einstellungen. + Etwas ging etwas schief beim hinzufügen des Filmes zu CouchPotato! Bitte überprüfe deine Einstellungen. - Du hast deine wöchentliche Maximalanfragenanzahl für Filme erreicht. Bitte kontaktiere deinen Admin. + Du hast deine wöchentliche Maximalanfragenanzahl für neue Filme erreicht. Bitte kontaktiere deinen Admin. - ist bereits in Plex! + ist bereits auf Plex! - Etwas ging schief beim hinzufügen des Filmes zu SickRage! Bitte überprüfe deine Einstellungen. + Etwas ging etwas schief beim hinzufügen des Filmes zu SickRage! Bitte überprüfe deine Einstellungen. - The Anfrage für Serien ist nicht richtig installiert. Bitte kontaktiere deinen Admin. + The Anfrage für Serien ist momentan nicht richtig installiert. Bitte kontaktiere deinen Admin. - Du hast deine wöchentliche Maximalanfragenanzahl für Alben erreicht. Bitte kontaktiere deinen Admin. + Du hast deine wöchentliche Maximalanfragenanzahl für neue Alben erreicht. Bitte kontaktiere deinen Admin. - Wir konnten den Interpreten auf MusicBrainz nicht finden. Bitte versuche es später erneut oder kontaktiere deinen Admin. + Wir konnten den Interpreten auf MusicBrainz leider nicht finden. Bitte versuche es später erneut oder kontaktiere deinen Admin. - Du hast deine wöchentliche Maximalanfragenanzahl für Serien erreicht. Bitte kontaktiere deinen Admin. + Du hast deine wöchentliche Maximalanfragenanzahl für neue Serien erreicht. Bitte kontaktiere deinen Admin. Entschuldige, aber diese Funktion ist momentan nur für Benutzer mit Plex-Accounts freigeschalten. @@ -442,7 +442,7 @@ Entschuldige, aber dein Admin hat diese Funktion noch nicht freigeschalten. - Wir konnten diese Meldung nicht entfernen, weil du es nie hattest. + Wir konnten diese Meldung nicht entfernen. Speichern fehlgeschlagen. Bitte versuche es erneut. @@ -459,4 +459,4 @@ Es gibt keine Informationen für die Release-Termin - \ No newline at end of file + From 2748a745e8d49915afab51423c5b165f27b1b891 Mon Sep 17 00:00:00 2001 From: Michael Reber Date: Sat, 1 Oct 2016 14:28:49 +0200 Subject: [PATCH 05/16] forgot to correct two incorrect translations --- PlexRequests.UI/Resources/UI.de.resx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PlexRequests.UI/Resources/UI.de.resx b/PlexRequests.UI/Resources/UI.de.resx index 08590a8ef..e38eb63b0 100644 --- a/PlexRequests.UI/Resources/UI.de.resx +++ b/PlexRequests.UI/Resources/UI.de.resx @@ -451,12 +451,12 @@ Französisch - Wählen Sie Episode + Wählen Sie ihre Episode Falsche Benutzer oder Passwort - Es gibt keine Informationen für die Release-Termin + Es gibt noch keine Informationen für diesen Release-Termin From eb42458b1343b01f7df1a9fbfb77cac94e523924 Mon Sep 17 00:00:00 2001 From: Michael Reber Date: Sun, 2 Oct 2016 11:57:56 +0200 Subject: [PATCH 06/16] Last correction.. Now the translation is ready to be used --- PlexRequests.UI/Resources/UI.de.resx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/PlexRequests.UI/Resources/UI.de.resx b/PlexRequests.UI/Resources/UI.de.resx index e38eb63b0..039e49e46 100644 --- a/PlexRequests.UI/Resources/UI.de.resx +++ b/PlexRequests.UI/Resources/UI.de.resx @@ -1,4 +1,4 @@ - + @Html.LoadAnalytics() From f32276151add415f5c8084a1c1009c81c73d7af9 Mon Sep 17 00:00:00 2001 From: Matt McHughes Date: Thu, 6 Oct 2016 14:52:39 -0500 Subject: [PATCH 10/16] added properties to disable tv requests for specific episodes or seasons and wired up to admin settings --- .../SettingModels/PlexRequestSettings.cs | 2 ++ PlexRequests.UI/Views/Admin/Settings.cshtml | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/PlexRequests.Core/SettingModels/PlexRequestSettings.cs b/PlexRequests.Core/SettingModels/PlexRequestSettings.cs index 8def76efd..6c1d95595 100644 --- a/PlexRequests.Core/SettingModels/PlexRequestSettings.cs +++ b/PlexRequests.Core/SettingModels/PlexRequestSettings.cs @@ -56,6 +56,8 @@ namespace PlexRequests.Core.SettingModels public bool CollectAnalyticData { get; set; } public bool IgnoreNotifyForAutoApprovedRequests { get; set; } public bool Wizard { get; set; } + public bool DisableTvRequestsByEpisode { get; set; } + public bool DisableTvRequestsBySeason { get; set; } /// /// The CSS name of the theme we want diff --git a/PlexRequests.UI/Views/Admin/Settings.cshtml b/PlexRequests.UI/Views/Admin/Settings.cshtml index 79aa9eb47..e9c3c7241 100644 --- a/PlexRequests.UI/Views/Admin/Settings.cshtml +++ b/PlexRequests.UI/Views/Admin/Settings.cshtml @@ -193,6 +193,36 @@ } + +
+
+ + @if (Model.DisableTvRequestsByEpisode) + { + + + } + else + { + + } +
+
+ +
+
+ + @if (Model.DisableTvRequestsBySeason) + { + + + } + else + { + + } +
+
From 75599aed784b1d4c2c2ad7baeac2e79a968da6d6 Mon Sep 17 00:00:00 2001 From: Matt McHughes Date: Thu, 6 Oct 2016 15:02:13 -0500 Subject: [PATCH 11/16] WIP hide tv request options based on admin settings --- PlexRequests.UI/Content/search.js | 4 +++- PlexRequests.UI/Views/Search/Index.cshtml | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/PlexRequests.UI/Content/search.js b/PlexRequests.UI/Content/search.js index cab9171e1..ea22b5ce5 100644 --- a/PlexRequests.UI/Content/search.js +++ b/PlexRequests.UI/Content/search.js @@ -468,7 +468,9 @@ $(function () { episodes: result.episodes, tvFullyAvailable: result.tvFullyAvailable, url: result.plexUrl, - tvPartialAvailable : result.tvPartialAvailable + tvPartialAvailable: result.tvPartialAvailable, + disableTvRequestsByEpisode: result.disableTvRequestsByEpisode, + disableTvRequestsBySeason: result.disableTvRequestsBySeason }; return context; } diff --git a/PlexRequests.UI/Views/Search/Index.cshtml b/PlexRequests.UI/Views/Search/Index.cshtml index f49dddf6a..5d29eb483 100644 --- a/PlexRequests.UI/Views/Search/Index.cshtml +++ b/PlexRequests.UI/Views/Search/Index.cshtml @@ -213,10 +213,14 @@
{{#if available}} From 88d7104defeb42809634403fab757344c4260ff5 Mon Sep 17 00:00:00 2001 From: Matt McHughes Date: Fri, 7 Oct 2016 11:23:53 -0500 Subject: [PATCH 12/16] finished wiring tv request settings to tv search --- PlexRequests.UI/Content/search.js | 1 + PlexRequests.UI/Models/SearchTvShowViewModel.cs | 2 ++ PlexRequests.UI/Modules/SearchModule.cs | 6 +++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/PlexRequests.UI/Content/search.js b/PlexRequests.UI/Content/search.js index ea22b5ce5..e6772061f 100644 --- a/PlexRequests.UI/Content/search.js +++ b/PlexRequests.UI/Content/search.js @@ -472,6 +472,7 @@ $(function () { disableTvRequestsByEpisode: result.disableTvRequestsByEpisode, disableTvRequestsBySeason: result.disableTvRequestsBySeason }; + return context; } diff --git a/PlexRequests.UI/Models/SearchTvShowViewModel.cs b/PlexRequests.UI/Models/SearchTvShowViewModel.cs index e552873f7..45bd0c8fd 100644 --- a/PlexRequests.UI/Models/SearchTvShowViewModel.cs +++ b/PlexRequests.UI/Models/SearchTvShowViewModel.cs @@ -55,5 +55,7 @@ namespace PlexRequests.UI.Models public int SiteRating { get; set; } public List Episodes { get; set; } public bool TvFullyAvailable { get; set; } + public bool DisableTvRequestsByEpisode { get; set; } + public bool DisableTvRequestsBySeason { get; set; } } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index ca728613c..d03339fa0 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -290,8 +290,10 @@ namespace PlexRequests.UI.Modules private async Task SearchTvShow(string searchTerm) { + Analytics.TrackEventAsync(Category.Search, Action.TvShow, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies)); var plexSettings = await PlexService.GetSettingsAsync(); + var prSettings = await PrService.GetSettingsAsync(); var providerId = string.Empty; var apiTv = new List(); @@ -336,7 +338,9 @@ namespace PlexRequests.UI.Modules Runtime = t.show.runtime.ToString(), SeriesId = t.show.id, SeriesName = t.show.name, - Status = t.show.status + Status = t.show.status, + DisableTvRequestsByEpisode = prSettings.DisableTvRequestsByEpisode, + DisableTvRequestsBySeason = prSettings.DisableTvRequestsBySeason }; From f753766585f5f2a73dcc7271fc0f15652c10e5fa Mon Sep 17 00:00:00 2001 From: Matt McHughes Date: Fri, 7 Oct 2016 11:25:23 -0500 Subject: [PATCH 13/16] fixed case typo --- PlexRequests.UI/Views/Admin/Settings.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlexRequests.UI/Views/Admin/Settings.cshtml b/PlexRequests.UI/Views/Admin/Settings.cshtml index e9c3c7241..2dfe5c861 100644 --- a/PlexRequests.UI/Views/Admin/Settings.cshtml +++ b/PlexRequests.UI/Views/Admin/Settings.cshtml @@ -219,7 +219,7 @@ } else { - + }
From 6e3e29035902f6e0b427cd2abbe7334b7a18c396 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Sun, 9 Oct 2016 01:13:52 +0100 Subject: [PATCH 14/16] #569 --- PlexRequests.Api.Interfaces/IPlexApi.cs | 1 + PlexRequests.Api.Models/Plex/RecentlyAdded.cs | 84 ++++++ .../PlexRequests.Api.Models.csproj | 1 + PlexRequests.Api/PlexApi.cs | 33 +++ PlexRequests.Api/TheMovieDbApi.cs | 6 + .../Templates/BasicRequestTemplate.html | 2 +- .../SettingModels/PlexRequestSettings.cs | 1 + .../SettingModels/ScheduledJobsSettings.cs | 2 + .../Interfaces/ISonarrCacher.cs | 19 +- PlexRequests.Services/Jobs/JobNames.cs | 1 + PlexRequests.Services/Jobs/RecentlyAdded.cs | 239 ++++++++++++++++++ PlexRequests.Services/Jobs/SonarrCacher.cs | 24 +- .../Jobs/Templates/RecentlyAddedTemplate.cs | 58 +++++ .../Jobs/Templates/RecentlyAddedTemplate.html | 187 ++++++++++++++ .../Models/SonarrCachedResult.cs | 47 ++++ .../Notification/EmailMessageNotification.cs | 6 +- .../PlexRequests.Services.csproj | 12 + PlexRequests.UI/Helpers/TvSender.cs | 2 +- PlexRequests.UI/Jobs/Scheduler.cs | 11 +- PlexRequests.UI/Modules/SearchModule.cs | 4 +- .../Views/Admin/SchedulerSettings.cshtml | 29 ++- PlexRequests.UI/Views/Admin/Settings.cshtml | 31 ++- 22 files changed, 766 insertions(+), 34 deletions(-) create mode 100644 PlexRequests.Api.Models/Plex/RecentlyAdded.cs create mode 100644 PlexRequests.Services/Jobs/RecentlyAdded.cs create mode 100644 PlexRequests.Services/Jobs/Templates/RecentlyAddedTemplate.cs create mode 100644 PlexRequests.Services/Jobs/Templates/RecentlyAddedTemplate.html create mode 100644 PlexRequests.Services/Models/SonarrCachedResult.cs diff --git a/PlexRequests.Api.Interfaces/IPlexApi.cs b/PlexRequests.Api.Interfaces/IPlexApi.cs index 7bd6c8095..cb2a52049 100644 --- a/PlexRequests.Api.Interfaces/IPlexApi.cs +++ b/PlexRequests.Api.Interfaces/IPlexApi.cs @@ -44,5 +44,6 @@ namespace PlexRequests.Api.Interfaces PlexSearch GetAllEpisodes(string authToken, Uri host, string section, int startPage, int returnCount); PlexServer GetServer(string authToken); PlexSeasonMetadata GetSeasons(string authToken, Uri plexFullHost, string ratingKey); + RecentlyAdded RecentlyAdded(string authToken, Uri plexFullHost); } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/Plex/RecentlyAdded.cs b/PlexRequests.Api.Models/Plex/RecentlyAdded.cs new file mode 100644 index 000000000..b5536625a --- /dev/null +++ b/PlexRequests.Api.Models/Plex/RecentlyAdded.cs @@ -0,0 +1,84 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: RecentlyAdded.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.Collections.Generic; + +namespace PlexRequests.Api.Models.Plex +{ + public class RecentlyAddedChild + { + public string _elementType { get; set; } + public string allowSync { get; set; } + public string librarySectionID { get; set; } + public string librarySectionTitle { get; set; } + public string librarySectionUUID { get; set; } + public int ratingKey { get; set; } + public string key { get; set; } + public int parentRatingKey { get; set; } + public string type { get; set; } + public string title { get; set; } + public string parentKey { get; set; } + public string parentTitle { get; set; } + public string parentSummary { get; set; } + public string summary { get; set; } + public int index { get; set; } + public int parentIndex { get; set; } + public string thumb { get; set; } + public string art { get; set; } + public string parentThumb { get; set; } + public int leafCount { get; set; } + public int viewedLeafCount { get; set; } + public int addedAt { get; set; } + public int updatedAt { get; set; } + public List _children { get; set; } + public string studio { get; set; } + public string contentRating { get; set; } + public string rating { get; set; } + public int? viewCount { get; set; } + public int? lastViewedAt { get; set; } + public int? year { get; set; } + public int? duration { get; set; } + public string originallyAvailableAt { get; set; } + public string chapterSource { get; set; } + public string parentTheme { get; set; } + public string titleSort { get; set; } + public string tagline { get; set; } + public int? viewOffset { get; set; } + public string originalTitle { get; set; } + } + + public class RecentlyAdded + { + public string _elementType { get; set; } + public string allowSync { get; set; } + public string identifier { get; set; } + public string mediaTagPrefix { get; set; } + public string mediaTagVersion { get; set; } + public string mixedParents { get; set; } + public List _children { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj b/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj index 6426f8050..10d5aa1e8 100644 --- a/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj +++ b/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj @@ -73,6 +73,7 @@ + diff --git a/PlexRequests.Api/PlexApi.cs b/PlexRequests.Api/PlexApi.cs index 233bb66ff..83e06d7cd 100644 --- a/PlexRequests.Api/PlexApi.cs +++ b/PlexRequests.Api/PlexApi.cs @@ -347,6 +347,39 @@ namespace PlexRequests.Api return servers; } + public RecentlyAdded RecentlyAdded(string authToken, Uri plexFullHost) + { + var request = new RestRequest + { + Method = Method.GET, + Resource = "library/recentlyAdded" + }; + + request.AddHeader("X-Plex-Token", authToken); + request.AddHeader("X-Plex-Client-Identifier", $"PlexRequests.Net{Version}"); + request.AddHeader("X-Plex-Product", "Plex Requests .Net"); + request.AddHeader("X-Plex-Version", Version); + request.AddHeader("Content-Type", "application/json"); + request.AddHeader("Accept", "application/json"); + + try + { + var lib = RetryHandler.Execute(() => Api.ExecuteJson(request, plexFullHost), + (exception, timespan) => Log.Error(exception, "Exception when calling RecentlyAdded for Plex, Retrying {0}", timespan), new[] { + TimeSpan.FromSeconds (5), + TimeSpan.FromSeconds(10), + TimeSpan.FromSeconds(30) + }); + + return lib; + } + catch (Exception e) + { + Log.Error(e, "There has been a API Exception when attempting to get the Plex RecentlyAdded"); + return new RecentlyAdded(); + } + } + private void AddHeaders(ref RestRequest request, string authToken) { request.AddHeader("X-Plex-Token", authToken); diff --git a/PlexRequests.Api/TheMovieDbApi.cs b/PlexRequests.Api/TheMovieDbApi.cs index 8137ad05f..0e860d58a 100644 --- a/PlexRequests.Api/TheMovieDbApi.cs +++ b/PlexRequests.Api/TheMovieDbApi.cs @@ -74,6 +74,12 @@ namespace PlexRequests.Api return movies; } + public async Task GetMovieInformation(string imdbId) + { + var movies = await Client.GetMovie(imdbId); + return movies; + } + [Obsolete("Should use TvMaze for TV")] public async Task GetTvShowInformation(int tmdbId) { diff --git a/PlexRequests.Core/Notification/Templates/BasicRequestTemplate.html b/PlexRequests.Core/Notification/Templates/BasicRequestTemplate.html index c7d7e6c9d..3d005619e 100644 --- a/PlexRequests.Core/Notification/Templates/BasicRequestTemplate.html +++ b/PlexRequests.Core/Notification/Templates/BasicRequestTemplate.html @@ -135,7 +135,7 @@
- + diff --git a/PlexRequests.Core/SettingModels/PlexRequestSettings.cs b/PlexRequests.Core/SettingModels/PlexRequestSettings.cs index 6c1d95595..078798b3e 100644 --- a/PlexRequests.Core/SettingModels/PlexRequestSettings.cs +++ b/PlexRequests.Core/SettingModels/PlexRequestSettings.cs @@ -58,6 +58,7 @@ namespace PlexRequests.Core.SettingModels public bool Wizard { get; set; } public bool DisableTvRequestsByEpisode { get; set; } public bool DisableTvRequestsBySeason { get; set; } + public bool SendRecentlyAddedEmail { get; set; } /// /// The CSS name of the theme we want diff --git a/PlexRequests.Core/SettingModels/ScheduledJobsSettings.cs b/PlexRequests.Core/SettingModels/ScheduledJobsSettings.cs index 15e83d2b2..c881e8565 100644 --- a/PlexRequests.Core/SettingModels/ScheduledJobsSettings.cs +++ b/PlexRequests.Core/SettingModels/ScheduledJobsSettings.cs @@ -38,6 +38,7 @@ namespace PlexRequests.Core.SettingModels StoreCleanup = 24; UserRequestLimitResetter = 12; PlexEpisodeCacher = 12; + RecentlyAdded = 168; } public int PlexAvailabilityChecker { get; set; } @@ -48,5 +49,6 @@ namespace PlexRequests.Core.SettingModels public int StoreCleanup { get; set; } public int UserRequestLimitResetter { get; set; } public int PlexEpisodeCacher { get; set; } + public int RecentlyAdded { get; set; } } } \ No newline at end of file diff --git a/PlexRequests.Services/Interfaces/ISonarrCacher.cs b/PlexRequests.Services/Interfaces/ISonarrCacher.cs index a7cf8f9fa..291216d98 100644 --- a/PlexRequests.Services/Interfaces/ISonarrCacher.cs +++ b/PlexRequests.Services/Interfaces/ISonarrCacher.cs @@ -1,8 +1,11 @@ -namespace PlexRequests.Services.Interfaces -{ - public interface ISonarrCacher - { - void Queued(); - int[] QueuedIds(); - } -} +using System.Collections.Generic; +using PlexRequests.Services.Models; + +namespace PlexRequests.Services.Interfaces +{ + public interface ISonarrCacher + { + void Queued(); + IEnumerable QueuedIds(); + } +} diff --git a/PlexRequests.Services/Jobs/JobNames.cs b/PlexRequests.Services/Jobs/JobNames.cs index 29418ee96..77f177713 100644 --- a/PlexRequests.Services/Jobs/JobNames.cs +++ b/PlexRequests.Services/Jobs/JobNames.cs @@ -36,5 +36,6 @@ namespace PlexRequests.Services.Jobs public const string StoreCleanup = "Database Cleanup"; public const string RequestLimitReset = "Request Limit Reset"; public const string EpisodeCacher = "Plex Episode Cacher"; + public const string RecentlyAddedEmail = "Recently Added Email Notification"; } } \ No newline at end of file diff --git a/PlexRequests.Services/Jobs/RecentlyAdded.cs b/PlexRequests.Services/Jobs/RecentlyAdded.cs new file mode 100644 index 000000000..60fe31d88 --- /dev/null +++ b/PlexRequests.Services/Jobs/RecentlyAdded.cs @@ -0,0 +1,239 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: RecentlyAdded.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.Collections.Generic; +using System.Linq; +using System.Text; +using MailKit.Net.Smtp; +using MimeKit; +using NLog; +using PlexRequests.Api; +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Plex; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Services.Interfaces; +using PlexRequests.Services.Jobs.Templates; +using Quartz; + + +namespace PlexRequests.Services.Jobs +{ + public class RecentlyAdded : IJob + { + public RecentlyAdded(IPlexApi api, ISettingsService plexSettings, ISettingsService email, + ISettingsService scheduledService, IJobRecord rec) + { + JobRecord = rec; + Api = api; + PlexSettings = plexSettings; + EmailSettings = email; + ScheduledJobsSettings = scheduledService; + } + + private IPlexApi Api { get; } + private TvMazeApi TvApi = new TvMazeApi(); + private readonly TheMovieDbApi _movieApi = new TheMovieDbApi(); + private ISettingsService PlexSettings { get; } + private ISettingsService EmailSettings { get; } + private ISettingsService ScheduledJobsSettings { get; } + private IJobRecord JobRecord { get; } + + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + + public void Execute(IJobExecutionContext context) + { + try + { + var jobs = JobRecord.GetJobs(); + var thisJob = + jobs.FirstOrDefault( + x => x.Name.Equals(JobNames.RecentlyAddedEmail, StringComparison.CurrentCultureIgnoreCase)); + + var settings = ScheduledJobsSettings.GetSettings(); + + if (thisJob?.LastRun > DateTime.Now.AddHours(-settings.RecentlyAdded)) + { + return; + } + + Start(); + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + JobRecord.Record(JobNames.RecentlyAddedEmail); + } + } + + private void Start() + { + var sb = new StringBuilder(); + var plexSettings = PlexSettings.GetSettings(); + var recentlyAdded = Api.RecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri); + + var movies = + recentlyAdded._children.Where(x => x.type.Equals("Movie", StringComparison.CurrentCultureIgnoreCase)); + var tv = + recentlyAdded._children.Where( + x => x.type.Equals("season", StringComparison.CurrentCultureIgnoreCase)) + .GroupBy(x => x.parentTitle) + .Select(x => x.FirstOrDefault()); + + GenerateMovieHtml(movies, plexSettings, ref sb); + GenerateTvHtml(tv, plexSettings, ref sb); + + var template = new RecentlyAddedTemplate(); + var html = template.LoadTemplate(sb.ToString()); + + Send(html, plexSettings); + } + + private void GenerateMovieHtml(IEnumerable movies, PlexSettings plexSettings, ref StringBuilder sb) + { + sb.Append("

New Movies:



"); + sb.Append("
"); + foreach (var movie in movies) + { + var metaData = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, + movie.ratingKey.ToString()); + + var imdbId = PlexHelper.GetProviderIdFromPlexGuid(metaData.Video.Guid); + var info = _movieApi.GetMovieInformation(imdbId).Result; + + sb.Append(""); + sb.Append(""); + sb.Append(""); + sb.Append(""); + sb.Append("
"); + sb.AppendFormat("", info.BackdropPath); + sb.Append("
"); + + sb.AppendFormat("

{1} {2}

", + info.ImdbId, info.Title, info.ReleaseDate?.ToString("yyyy") ?? string.Empty); + + sb.AppendFormat("

Genre: {0}

", string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())); + sb.AppendFormat("

{0}

", info.Overview); + + sb.Append(""); + sb.Append("
"); + sb.Append("
"); + sb.Append("
"); + + } + sb.Append("



"); + } + + private void GenerateTvHtml(IEnumerable tv, PlexSettings plexSettings, ref StringBuilder sb) + { + // TV + sb.Append("

New Episodes:



"); + sb.Append(""); + foreach (var t in tv) + { + var parentMetaData = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, + t.parentRatingKey.ToString()); + + var info = TvApi.ShowLookupByTheTvDbId(int.Parse(PlexHelper.GetProviderIdFromPlexGuid(parentMetaData.Directory.Guid))); + var banner = info.image?.original; + if (!string.IsNullOrEmpty(banner)) + { + banner = banner.Replace("http", "https"); // Always use the Https banners + } + sb.Append(""); + sb.Append(""); + sb.Append(""); + sb.Append(""); + sb.Append("
"); + sb.AppendFormat("", banner); + sb.Append("
"); + + sb.AppendFormat("

{1} {2}

", + info.externals.imdb, info.name, info.premiered.Substring(0, 4)); // Only the year + + sb.AppendFormat("

Genre: {0}

", string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())); + sb.AppendFormat("

{0}

", + string.IsNullOrEmpty(parentMetaData.Directory.Summary) ? info.summary : parentMetaData.Directory.Summary); // Episode Summary + + sb.Append(""); + sb.Append("
"); + sb.Append("
"); + sb.Append("
"); + } + sb.Append("



"); + } + + private void Send(string html, PlexSettings plexSettings) + { + var users = Api.GetUsers(plexSettings.PlexAuthToken); + var settings = EmailSettings.GetSettings(); + var body = new BodyBuilder { HtmlBody = html, TextBody = "This email is only available on devices that support HTML." }; + var message = new MimeMessage + { + Body = body.ToMessageBody(), + Subject = "New Content on Plex!", + }; + + foreach (var user in users.User) + { + message.Bcc.Add(new MailboxAddress(user.Username, user.Email)); + } + + message.From.Add(new MailboxAddress(settings.EmailUsername, settings.EmailSender)); + try + { + using (var client = new SmtpClient()) + { + client.Connect(settings.EmailHost, settings.EmailPort); // Let MailKit figure out the correct SecureSocketOptions. + + // Note: since we don't have an OAuth2 token, disable + // the XOAUTH2 authentication mechanism. + client.AuthenticationMechanisms.Remove("XOAUTH2"); + + if (settings.Authentication) + { + client.Authenticate(settings.EmailUsername, settings.EmailPassword); + } + Log.Info("sending message to {0} \r\n from: {1}\r\n Are we authenticated: {2}", message.To, message.From, client.IsAuthenticated); + client.Send(message); + client.Disconnect(true); + } + } + catch (Exception e) + { + Log.Error(e); + } + } + } +} \ No newline at end of file diff --git a/PlexRequests.Services/Jobs/SonarrCacher.cs b/PlexRequests.Services/Jobs/SonarrCacher.cs index 26e2b36cb..d94561604 100644 --- a/PlexRequests.Services/Jobs/SonarrCacher.cs +++ b/PlexRequests.Services/Jobs/SonarrCacher.cs @@ -35,6 +35,7 @@ using PlexRequests.Core; using PlexRequests.Core.SettingModels; using PlexRequests.Helpers; using PlexRequests.Services.Interfaces; +using PlexRequests.Services.Models; using PlexRequests.Store.Models; using PlexRequests.Store.Repository; @@ -84,10 +85,29 @@ namespace PlexRequests.Services.Jobs } // we do not want to set here... - public int[] QueuedIds() + public IEnumerable QueuedIds() { + var result = new List(); + var series = Cache.Get>(CacheKeys.SonarrQueued); - return series?.Select(x => x.tvdbId).ToArray() ?? new int[] { }; + if (series != null) + { + foreach (var s in series) + { + var cached = new SonarrCachedResult {TvdbId = s.tvdbId}; + foreach (var season in s.seasons) + { + cached.Seasons.Add(new SonarrSeasons + { + SeasonNumber = season.seasonNumber, + Monitored = season.monitored + }); + } + + result.Add(cached); + } + } + return result; } public void Execute(IJobExecutionContext context) diff --git a/PlexRequests.Services/Jobs/Templates/RecentlyAddedTemplate.cs b/PlexRequests.Services/Jobs/Templates/RecentlyAddedTemplate.cs new file mode 100644 index 000000000..c375e04f2 --- /dev/null +++ b/PlexRequests.Services/Jobs/Templates/RecentlyAddedTemplate.cs @@ -0,0 +1,58 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: RecentlyAddedTemplate.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.IO; +using System.Text; +using System.Windows.Forms; +using NLog; + +namespace PlexRequests.Services.Jobs.Templates +{ + public class RecentlyAddedTemplate + { + public string TemplateLocation => Path.Combine(Path.GetDirectoryName(Application.ExecutablePath) ?? string.Empty, "Jobs", "Templates", "RecentlyAddedTemplate.html"); + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + + private const string RecentlyAddedKey = "{@RECENTLYADDED}"; + + public string LoadTemplate(string html) + { + try + { + var sb = new StringBuilder(File.ReadAllText(TemplateLocation)); + sb.Replace(RecentlyAddedKey, html); + return sb.ToString(); + } + catch (Exception e) + { + Log.Error(e); + return string.Empty; + } + } + } +} \ No newline at end of file diff --git a/PlexRequests.Services/Jobs/Templates/RecentlyAddedTemplate.html b/PlexRequests.Services/Jobs/Templates/RecentlyAddedTemplate.html new file mode 100644 index 000000000..0ab085dce --- /dev/null +++ b/PlexRequests.Services/Jobs/Templates/RecentlyAddedTemplate.html @@ -0,0 +1,187 @@ + + + + + + Plex Requests .Net + + + + + + + + + +
  +
+ + + + + + + + + + + +
+ + + + + + + +
+ +
+
+
+

Here is a list of Movies and TV Shows that have recently been added to Plex!

+ +
+ + {@RECENTLYADDED} + +
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/PlexRequests.Services/Models/SonarrCachedResult.cs b/PlexRequests.Services/Models/SonarrCachedResult.cs new file mode 100644 index 000000000..1398a7ce4 --- /dev/null +++ b/PlexRequests.Services/Models/SonarrCachedResult.cs @@ -0,0 +1,47 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SonarrCachedResult.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.Collections.Generic; + +namespace PlexRequests.Services.Models +{ + public class SonarrCachedResult + { + public SonarrCachedResult() + { + Seasons = new List( ); + } + public List Seasons { get; set; } + public int TvdbId { get; set; } + } + + public class SonarrSeasons + { + public int SeasonNumber { get; set; } + public bool Monitored { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.Services/Notification/EmailMessageNotification.cs b/PlexRequests.Services/Notification/EmailMessageNotification.cs index 5dd48b59d..3f6bf9c91 100644 --- a/PlexRequests.Services/Notification/EmailMessageNotification.cs +++ b/PlexRequests.Services/Notification/EmailMessageNotification.cs @@ -129,7 +129,7 @@ namespace PlexRequests.Services.Notification $"Plex Requests: New {model.RequestType.GetString()?.ToLower()} request for {model.Title}!", $"Hello! The user '{model.User}' has requested the {model.RequestType.GetString()?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}", model.ImgSrc); - var body = new BodyBuilder { HtmlBody = html, }; + var body = new BodyBuilder { HtmlBody = html, TextBody = "This email is only available on devices that support HTML." }; var message = new MimeMessage { @@ -150,7 +150,7 @@ namespace PlexRequests.Services.Notification $"Plex Requests: New issue for {model.Title}!", $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!", model.ImgSrc); - var body = new BodyBuilder { HtmlBody = html, }; + var body = new BodyBuilder { HtmlBody = html, TextBody = "This email is only available on devices that support HTML." }; var message = new MimeMessage { @@ -175,7 +175,7 @@ namespace PlexRequests.Services.Notification $"Plex Requests: {model.Title} is now available!", $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)", model.ImgSrc); - var body = new BodyBuilder { HtmlBody = html, }; + var body = new BodyBuilder { HtmlBody = html, TextBody = "This email is only available on devices that support HTML." }; var message = new MimeMessage { diff --git a/PlexRequests.Services/PlexRequests.Services.csproj b/PlexRequests.Services/PlexRequests.Services.csproj index 6aed947bc..9819e387f 100644 --- a/PlexRequests.Services/PlexRequests.Services.csproj +++ b/PlexRequests.Services/PlexRequests.Services.csproj @@ -72,6 +72,10 @@ ..\packages\Quartz.2.3.3\lib\net40\Quartz.dll + + False + ..\packages\TMDbLib.0.9.0.0-alpha\lib\net45\TMDbLib.dll + @@ -79,12 +83,14 @@ + + @@ -97,6 +103,7 @@ + @@ -136,6 +143,11 @@ PlexRequests.Store + + + PreserveNewest + +