diff --git a/PlexRequests.Core/UserMapper.cs b/PlexRequests.Core/UserMapper.cs index 3c15d548e..8a4e491a5 100644 --- a/PlexRequests.Core/UserMapper.cs +++ b/PlexRequests.Core/UserMapper.cs @@ -151,7 +151,7 @@ namespace PlexRequests.Core var passwordMatch = PasswordHasher.VerifyPassword(oldPassword, userToChange.Salt, userToChange.Hash); if (!passwordMatch) { - throw new SecurityException("Password does not match"); + throw new SecurityException("Incorrect password."); } var newSalt = PasswordHasher.GenerateSalt(); diff --git a/PlexRequests.Helpers.Tests/StringHasherTests.cs b/PlexRequests.Helpers.Tests/StringHasherTests.cs index e09fea347..a51482f00 100644 --- a/PlexRequests.Helpers.Tests/StringHasherTests.cs +++ b/PlexRequests.Helpers.Tests/StringHasherTests.cs @@ -46,6 +46,7 @@ namespace PlexRequests.Helpers.Tests { yield return new TestCaseData("hello!").Returns("5a8dd3ad0756a93ded72b823b19dd877").SetName("Hello"); yield return new TestCaseData("0111111").Returns("9549d400a68633435918290085f06293").SetName("Number"); + yield return new TestCaseData("tidusjar").Returns("f47201e0103fa37ca82ce5243cebb9c7").SetName("tidusjar"); yield return new TestCaseData("hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!").Returns("26d0d126918fbecfc0fa4a63070d314c") .SetName("Long string"); } diff --git a/PlexRequests.Helpers.Tests/TypeHelperTests.cs b/PlexRequests.Helpers.Tests/TypeHelperTests.cs index 1d93c375f..7aff9b29c 100644 --- a/PlexRequests.Helpers.Tests/TypeHelperTests.cs +++ b/PlexRequests.Helpers.Tests/TypeHelperTests.cs @@ -65,7 +65,7 @@ namespace PlexRequests.Helpers.Tests "ProviderId", "ImdbId", "TvDbId", "Overview", "Title", "PosterPath", "ReleaseDate", "Type", "Status", "Approved", "RequestedBy", "RequestedDate", "Available", "Issues", "OtherMessage", "AdminNote", "SeasonList", "SeasonCount", "SeasonsRequested", "MusicBrainzId", "RequestedUsers","ArtistName", - "ArtistId","IssueId","Episodes","AllUsers","CanApprove","Id" + "ArtistId","IssueId","Episodes", "Denied", "DeniedReason", "AllUsers","CanApprove","Id", }).SetName("Requested Model"); } } diff --git a/PlexRequests.Helpers/StringHasher.cs b/PlexRequests.Helpers/StringHasher.cs index 41477b7b7..d18083186 100644 --- a/PlexRequests.Helpers/StringHasher.cs +++ b/PlexRequests.Helpers/StringHasher.cs @@ -35,7 +35,7 @@ namespace PlexRequests.Helpers { using (var md5 = MD5.Create()) { - var inputBytes = Encoding.ASCII.GetBytes(input); + var inputBytes = Encoding.UTF8.GetBytes(input); var hash = md5.ComputeHash(inputBytes); var sb = new StringBuilder(); diff --git a/PlexRequests.Services/Jobs/CouchPotatoCacher.cs b/PlexRequests.Services/Jobs/CouchPotatoCacher.cs index be36d004f..5ad3d99d8 100644 --- a/PlexRequests.Services/Jobs/CouchPotatoCacher.cs +++ b/PlexRequests.Services/Jobs/CouchPotatoCacher.cs @@ -92,8 +92,12 @@ namespace PlexRequests.Services.Jobs { var movies = Cache.Get(CacheKeys.CouchPotatoQueued); - var items = movies?.movies?.Select(x => x.info?.tmdb_id).Cast().ToArray(); - return items ?? new int[] { }; + var items = movies?.movies?.Select(x => x.info?.tmdb_id); + if(items != null) + { + return items.Cast().ToArray(); + } + return new int[] { }; } catch (Exception e) { @@ -107,4 +111,4 @@ namespace PlexRequests.Services.Jobs Queued(); } } -} \ No newline at end of file +} diff --git a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs index c8b46b32f..701a5fb50 100644 --- a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs +++ b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs @@ -195,9 +195,18 @@ namespace PlexRequests.Services.Jobs public PlexMovie GetMovie(PlexMovie[] plexMovies, string title, string year, string providerId = null) { + if (plexMovies.Length == 0) + { + return null; + } var advanced = !string.IsNullOrEmpty(providerId); foreach (var movie in plexMovies) { + if (string.IsNullOrEmpty(movie.Title) || string.IsNullOrEmpty(movie.ReleaseYear)) + { + continue; + } + if (advanced) { if (!string.IsNullOrEmpty(movie.ProviderId) && diff --git a/PlexRequests.Store/RequestedModel.cs b/PlexRequests.Store/RequestedModel.cs index 212f52203..1bba98a18 100644 --- a/PlexRequests.Store/RequestedModel.cs +++ b/PlexRequests.Store/RequestedModel.cs @@ -27,7 +27,7 @@ namespace PlexRequests.Store public string Status { get; set; } public bool Approved { get; set; } - [Obsolete("Use RequestedUsers")] + [Obsolete("Use RequestedUsers")] //TODO remove this obsolete property public string RequestedBy { get; set; } public DateTime RequestedDate { get; set; } @@ -44,6 +44,8 @@ namespace PlexRequests.Store public string ArtistId { get; set; } public int IssueId { get; set; } public List Episodes { get; set; } + public bool Denied { get; set; } + public string DeniedReason { get; set; } [JsonIgnore] public List AllUsers diff --git a/PlexRequests.UI/Content/requests.js b/PlexRequests.UI/Content/requests.js index 678fa60fd..838039910 100644 --- a/PlexRequests.UI/Content/requests.js +++ b/PlexRequests.UI/Content/requests.js @@ -373,6 +373,17 @@ $('#noteModal').on('show.bs.modal', function (event) { requestField.val(id); // Add ID to the hidden field }); +// Update deny reason modal +$('#denyReasonModal').on('show.bs.modal', function (event) { + var button = $(event.relatedTarget); // Button that triggered the modal + var id = button.data('identifier'); // Extract info from data-* attributes + + var modal = $(this); + modal.find('.denySaveReason').val(id); // Add ID to the button + var requestField = modal.find('input'); + requestField.val(id); // Add ID to the hidden field +}); + // Delete $(document).on("click", ".delete", function (e) { e.preventDefault(); @@ -403,28 +414,85 @@ $(document).on("click", ".delete", function (e) { // Approve single request $(document).on("click", ".approve", function (e) { e.preventDefault(); - var $this = $(this); - var $form = $this.parents('form').first(); + var $self = $(this); + var $form = $self.parents('form').first(); - if ($this.text() === " Loading...") { + if ($self.text() === " Loading...") { return; } - loadingButton($this.attr('id'), "success"); + loadingButton($self.attr('id'), "success"); approveRequest($form, null, function () { - $("#" + $this.attr('id') + "notapproved").prop("class", "fa fa-check"); + $("#" + $self.attr('id') + "notapproved").prop("class", "fa fa-check"); + - var $group = $this.parent('.btn-split'); + var $group = $self.parent('.btn-split'); if ($group.length > 0) { $group.remove(); } else { - $this.remove(); + $self.remove(); } }); }); +// Deny single request +$(document).on("click", ".deny", function (e) { + e.preventDefault(); + var $self = $(this); + var $form = $self.parents('form').first(); + + if ($self.text() === " Loading...") { + return; + } + loadingButton($self.attr('id')+"deny", "success"); + + denyRequest($form, function () { + // Remove the form + $("#" + "deny" + $self.attr('id')).remove(); + // remove the approve button + var id = $self.attr("custom-button"); + $("#" + id).remove(); + + var $group = $self.parent('.btn-split'); + if ($group.length > 0) { + $group.remove(); + } + else { + $self.remove(); + } + }); +}); + +// Deny single request with reason (modal) +$(document).on("click", ".denySaveReason", function (e) { + var comment = $("#denyReason").val(); + e.preventDefault(); + + var $form = $("#denyReasonForm"); + var data = $form.serialize(); + data = data + "&reason=" + comment; + + $.ajax({ + type: $form.prop("method"), + url: $form.prop("action"), + data: data, + dataType: "json", + success: function (response) { + if (checkJsonResponse(response)) { + generateNotify(response.message, "success"); + $("#denyReasonModal").modal("hide"); + } + }, + error: function (e) { + console.log(e); + generateNotify("Something went wrong!", "danger"); + } + }); +}); + + $(document).on("click", ".approve-with-quality", function (e) { e.preventDefault(); var $this = $(this); @@ -524,6 +592,35 @@ function approveRequest($form, qualityId, successCallback) { }); } +function denyRequest($form, successCallback) { + + var formData = $form.serialize(); + $.ajax({ + type: $form.prop('method'), + url: $form.prop('action'), + data: formData, + dataType: "json", + success: function (response) { + + if (checkJsonResponse(response)) { + if (response.message) { + generateNotify(response.message, "success"); + } else { + generateNotify("Success! Request Approved.", "success"); + } + + if (successCallback) { + successCallback(); + } + } + }, + error: function (e) { + console.log(e); + generateNotify("Something went wrong!", "danger"); + } + }); +} + function mixItUpConfig(activeState) { var conf = mixItUpDefault; @@ -556,6 +653,11 @@ function movieLoad() { var html = searchTemplate(context); $ml.append(html); }); + + + $('.customTooltip').tooltipster({ + contentCloning: true + }); } else { $ml.html(noResultsHtml.format("movie")); @@ -658,6 +760,8 @@ function buildRequestContext(result, type) { hasQualities: result.qualities && result.qualities.length > 0, artist: result.artistName, musicBrainzId: result.musicBrainzId, + denied: result.denied, + deniedReason: result.deniedReason, }; return context; diff --git a/PlexRequests.UI/Content/search.js b/PlexRequests.UI/Content/search.js index 310ba27fe..cab9171e1 100644 --- a/PlexRequests.UI/Content/search.js +++ b/PlexRequests.UI/Content/search.js @@ -467,7 +467,8 @@ $(function () { available: result.available, episodes: result.episodes, tvFullyAvailable: result.tvFullyAvailable, - url: result.plexUrl + url: result.plexUrl, + tvPartialAvailable : result.tvPartialAvailable }; return context; } diff --git a/PlexRequests.UI/Helpers/TvSender.cs b/PlexRequests.UI/Helpers/TvSender.cs index 05b2799ab..fe1572044 100644 --- a/PlexRequests.UI/Helpers/TvSender.cs +++ b/PlexRequests.UI/Helpers/TvSender.cs @@ -35,6 +35,7 @@ using PlexRequests.Api.Models.Sonarr; using PlexRequests.Core.SettingModels; using PlexRequests.Store; using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; using PlexRequests.Helpers.Exceptions; @@ -70,7 +71,7 @@ namespace PlexRequests.UI.Helpers { int.TryParse(sonarrSettings.QualityProfile, out qualityProfile); } - + var series = await GetSonarrSeries(sonarrSettings, model.ProviderId); if (episodeRequest) @@ -81,7 +82,7 @@ namespace PlexRequests.UI.Helpers // Series Exists // Request the episodes in the existing series await RequestEpisodesWithExistingSeries(model, series, sonarrSettings); - return new SonarrAddSeries {title = series.title}; + return new SonarrAddSeries { title = series.title }; } @@ -93,7 +94,7 @@ namespace PlexRequests.UI.Helpers // Get the series that was just added series = await GetSonarrSeries(sonarrSettings, model.ProviderId); - series.monitored = false; // Un-monitor the series + series.monitored = true; // We want to make sure we are monitoring the series // Un-monitor all seasons foreach (var season in series.seasons) @@ -113,15 +114,44 @@ namespace PlexRequests.UI.Helpers if (series != null) { - // Monitor the seasons that we have chosen - foreach (var season in series.seasons) + var requestAll = model.SeasonsRequested.Equals("All", StringComparison.CurrentCultureIgnoreCase); + var first = model.SeasonsRequested.Equals("First", StringComparison.CurrentCultureIgnoreCase); + var latest = model.SeasonsRequested.Equals("Latest", StringComparison.CurrentCultureIgnoreCase); + + if (model.SeasonList.Any()) { - if (model.SeasonList.Contains(season.seasonNumber)) + // Monitor the seasons that we have chosen + foreach (var season in series.seasons) + { + if (model.SeasonList.Contains(season.seasonNumber)) + { + season.monitored = true; + } + } + } + + if (requestAll) + { + // Monitor all seasons + foreach (var season in series.seasons) { season.monitored = true; } } + if (first) + { + var firstSeries = series?.seasons?.OrderBy(x => x.seasonNumber)?.FirstOrDefault() ?? new Season(); + firstSeries.monitored = true; + } + + if (latest) + { + var lastSeries = series?.seasons?.OrderByDescending(x => x.seasonNumber)?.FirstOrDefault() ?? new Season(); + lastSeries.monitored = true; + } + + // Update the series in sonarr with the new monitored status SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri); await RequestAllEpisodesInASeasonWithExistingSeries(model, series, sonarrSettings); @@ -201,7 +231,7 @@ namespace PlexRequests.UI.Helpers var tasks = new List(); foreach (var r in episodes) { - if(r.monitored || r.hasFile) // If it's already montiored or has the file, there is no point in updating it + if (r.monitored || r.hasFile) // If it's already montiored or has the file, there is no point in updating it { continue; } @@ -230,9 +260,22 @@ namespace PlexRequests.UI.Helpers var internalEpisodeIds = new List(); var tasks = new List(); + + var requestedEpisodes = model.Episodes; + foreach (var r in episodes) { - if (r.hasFile || !model.SeasonList.Contains(r.seasonNumber)) // If it already has the file, there is no point in updating it + if (r.hasFile) // If it already has the file, there is no point in updating it + { + continue; + } + var epComparison = new EpisodesModel + { + EpisodeNumber = r.episodeNumber, + SeasonNumber = r.seasonNumber + }; + // Make sure we are looking for the right episode and season + if (!requestedEpisodes.Contains(epComparison)) { continue; } diff --git a/PlexRequests.UI/Models/RequestViewModel.cs b/PlexRequests.UI/Models/RequestViewModel.cs index f4855106c..6d1ecb0c5 100644 --- a/PlexRequests.UI/Models/RequestViewModel.cs +++ b/PlexRequests.UI/Models/RequestViewModel.cs @@ -55,5 +55,7 @@ namespace PlexRequests.UI.Models public QualityModel[] Qualities { get; set; } public string ArtistName { get; set; } public Store.EpisodesModel[] Episodes { get; set; } + public bool Denied { get; set; } + public string DeniedReason { get; set; } } } diff --git a/PlexRequests.UI/Modules/AdminModule.cs b/PlexRequests.UI/Modules/AdminModule.cs index a04edca3c..b8a62516e 100644 --- a/PlexRequests.UI/Modules/AdminModule.cs +++ b/PlexRequests.UI/Modules/AdminModule.cs @@ -175,16 +175,16 @@ namespace PlexRequests.UI.Modules Get["/emailnotification"] = _ => EmailNotifications(); Post["/emailnotification"] = _ => SaveEmailNotifications(); - Post["/testemailnotification"] = _ => TestEmailNotifications(); + Post["/testemailnotification", true] = async (x, ct) => await TestEmailNotifications(); Get["/status", true] = async (x, ct) => await Status(); Get["/pushbulletnotification"] = _ => PushbulletNotifications(); Post["/pushbulletnotification"] = _ => SavePushbulletNotifications(); - Post["/testpushbulletnotification"] = _ => TestPushbulletNotifications(); + Post["/testpushbulletnotification", true] = async (x, ct) => await TestPushbulletNotifications(); Get["/pushovernotification"] = _ => PushoverNotifications(); Post["/pushovernotification"] = _ => SavePushoverNotifications(); - Post["/testpushovernotification"] = _ => TestPushoverNotifications(); + Post["/testpushovernotification", true] = async (x, ct) => await TestPushoverNotifications(); Get["/logs"] = _ => Logs(); Get["/loglevel"] = _ => GetLogLevels(); @@ -198,7 +198,7 @@ namespace PlexRequests.UI.Modules Post["/autoupdate"] = x => AutoUpdate(); - Post["/testslacknotification"] = _ => TestSlackNotification(); + Post["/testslacknotification", true] = async (x,ct) => await TestSlackNotification(); Get["/slacknotification"] = _ => SlackNotifications(); Post["/slacknotification"] = _ => SaveSlackNotifications(); @@ -477,7 +477,7 @@ namespace PlexRequests.UI.Modules return View["EmailNotifications", settings]; } - private Response TestEmailNotifications() + private async Task TestEmailNotifications() { var settings = this.Bind(); var valid = this.Validate(settings); @@ -485,6 +485,7 @@ namespace PlexRequests.UI.Modules { return Response.AsJson(valid.SendJsonError()); } + var currentSettings = await EmailService.GetSettingsAsync(); var notificationModel = new NotificationModel { NotificationType = NotificationType.Test, @@ -503,7 +504,10 @@ namespace PlexRequests.UI.Modules } finally { - NotificationService.UnSubscribe(new EmailMessageNotification(EmailService)); + if (!currentSettings.Enabled) + { + NotificationService.UnSubscribe(new EmailMessageNotification(EmailService)); + } } return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Email Notification!" }); } @@ -595,7 +599,7 @@ namespace PlexRequests.UI.Modules : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); } - private Response TestPushbulletNotifications() + private async Task TestPushbulletNotifications() { var settings = this.Bind(); var valid = this.Validate(settings); @@ -608,6 +612,7 @@ namespace PlexRequests.UI.Modules NotificationType = NotificationType.Test, DateTime = DateTime.Now }; + var currentSettings = await PushbulletService.GetSettingsAsync(); try { NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); @@ -621,7 +626,10 @@ namespace PlexRequests.UI.Modules } finally { - NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); + if (!currentSettings.Enabled) + { + NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); + } } return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushbullet Notification!" }); } @@ -657,7 +665,7 @@ namespace PlexRequests.UI.Modules : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); } - private Response TestPushoverNotifications() + private async Task TestPushoverNotifications() { var settings = this.Bind(); var valid = this.Validate(settings); @@ -670,6 +678,7 @@ namespace PlexRequests.UI.Modules NotificationType = NotificationType.Test, DateTime = DateTime.Now }; + var currentSettings = await PushbulletService.GetSettingsAsync(); try { NotificationService.Subscribe(new PushoverNotification(PushoverApi, PushoverService)); @@ -683,7 +692,10 @@ namespace PlexRequests.UI.Modules } finally { - NotificationService.UnSubscribe(new PushoverNotification(PushoverApi, PushoverService)); + if (!currentSettings.Enabled) + { + NotificationService.UnSubscribe(new PushoverNotification(PushoverApi, PushoverService)); + } } return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushover Notification!" }); } @@ -803,7 +815,7 @@ namespace PlexRequests.UI.Modules return Response.AsJson(apiKey); } - private Response TestSlackNotification() + private async Task TestSlackNotification() { var settings = this.BindAndValidate(); if (!ModelValidationResult.IsValid) @@ -815,11 +827,13 @@ namespace PlexRequests.UI.Modules NotificationType = NotificationType.Test, DateTime = DateTime.Now }; + + var currentSlackSettings = await SlackSettings.GetSettingsAsync(); try { NotificationService.Subscribe(new SlackNotification(SlackApi, SlackSettings)); settings.Enabled = true; - NotificationService.Publish(notificationModel, settings); + await NotificationService.Publish(notificationModel, settings); Log.Info("Sent slack notification test"); } catch (Exception e) @@ -828,7 +842,10 @@ namespace PlexRequests.UI.Modules } finally { - NotificationService.UnSubscribe(new SlackNotification(SlackApi, SlackSettings)); + if (!currentSlackSettings.Enabled) + { + NotificationService.UnSubscribe(new SlackNotification(SlackApi, SlackSettings)); + } } return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Slack Notification! If you do not receive it please check the logs." }); } diff --git a/PlexRequests.UI/Modules/ApiSettingsModule.cs b/PlexRequests.UI/Modules/ApiSettingsModule.cs index 4d84a2c4a..3ae55afec 100644 --- a/PlexRequests.UI/Modules/ApiSettingsModule.cs +++ b/PlexRequests.UI/Modules/ApiSettingsModule.cs @@ -36,6 +36,7 @@ using Newtonsoft.Json; using PlexRequests.Core; using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; namespace PlexRequests.UI.Modules { @@ -45,6 +46,9 @@ namespace PlexRequests.UI.Modules ISettingsService plexSettings, ISettingsService cp, ISettingsService sonarr, ISettingsService sr, ISettingsService hp) : base("api", pr) { + Get["GetVersion", "/version"] = x => GetVersion(); + + Get["GetAuthSettings", "/settings/authentication"] = x => GetAuthSettings(); Post["PostAuthSettings", "/settings/authentication"] = x => PostAuthSettings(); @@ -83,6 +87,12 @@ namespace PlexRequests.UI.Modules private ISettingsService SickRageSettings { get; } private ISettingsService HeadphonesSettings { get; } + private Response GetVersion() + { + return ReturnReponse(AssemblyHelper.GetProductVersion()); + } + + private Response GetPrSettings() { var model = new ApiModel(); diff --git a/PlexRequests.UI/Modules/ApprovalModule.cs b/PlexRequests.UI/Modules/ApprovalModule.cs index 9ad74aa83..478f8424c 100644 --- a/PlexRequests.UI/Modules/ApprovalModule.cs +++ b/PlexRequests.UI/Modules/ApprovalModule.cs @@ -65,6 +65,7 @@ namespace PlexRequests.UI.Modules HeadphoneApi = hpApi; Post["/approve", true] = async (x, ct) => await Approve((int)Request.Form.requestid, (string)Request.Form.qualityId); + Post["/deny", true] = async (x, ct) => await DenyRequest((int)Request.Form.requestid, (string)Request.Form.reason); Post["/approveall", true] = async (x, ct) => await ApproveAll(); Post["/approveallmovies", true] = async (x, ct) => await ApproveAllMovies(); Post["/approvealltvshows", true] = async (x, ct) => await ApproveAllTVShows(); @@ -262,7 +263,7 @@ namespace PlexRequests.UI.Modules { var requests = await Service.GetAllAsync(); - requests = requests.Where(x => x.CanApprove && x.Type == RequestType.Movie); + requests = requests.Where(x => x.CanApprove && x.Type == RequestType.Movie); var requestedModels = requests as RequestedModel[] ?? requests.ToArray(); if (!requestedModels.Any()) { @@ -491,6 +492,24 @@ namespace PlexRequests.UI.Modules } } + private async Task DenyRequest(int requestId, string reason) + { + // Get the request from the DB + var request = await Service.GetAsync(requestId); + + // Deny it + request.Denied = true; + request.DeniedReason = reason; + + // Update the new value + var result = await Service.UpdateRequestAsync(request); + + return result + ? Response.AsJson(new JsonResponseModel { Result = true, Message = "Request has been denied" }) + : Response.AsJson(new JsonResponseModel { Result = false, Message = "An error happened, could not update the DB" }); + + } + private bool SendMovie(CouchPotatoSettings settings, RequestedModel r, ICouchPotatoApi cp) { Log.Info("Adding movie to CP : {0}", r.Title); diff --git a/PlexRequests.UI/Modules/BaseAuthModule.cs b/PlexRequests.UI/Modules/BaseAuthModule.cs index d5dde01c9..d3d3de5d9 100644 --- a/PlexRequests.UI/Modules/BaseAuthModule.cs +++ b/PlexRequests.UI/Modules/BaseAuthModule.cs @@ -53,14 +53,15 @@ namespace PlexRequests.UI.Modules private Response CheckAuth() { var settings = PlexRequestSettings.GetSettings(); + + var baseUrl = settings.BaseUrl; + // Have we been through the wizard? if (!settings.Wizard) { - return Context.GetRedirect("~/wizard"); + return Context.GetRedirect(string.IsNullOrEmpty(baseUrl) ? "~/wizard" : $"~/{baseUrl}/wizard"); } - var baseUrl = settings.BaseUrl; - var redirectPath = string.IsNullOrEmpty(baseUrl) ? "~/userlogin" : $"~/{baseUrl}/userlogin"; return Session[SessionKeys.UsernameKey] == null @@ -68,4 +69,4 @@ namespace PlexRequests.UI.Modules : null; } } -} \ No newline at end of file +} diff --git a/PlexRequests.UI/Modules/LoginModule.cs b/PlexRequests.UI/Modules/LoginModule.cs index 8c3bd550d..00c4e0b4a 100644 --- a/PlexRequests.UI/Modules/LoginModule.cs +++ b/PlexRequests.UI/Modules/LoginModule.cs @@ -1,4 +1,5 @@ #region Copyright + // /************************************************************************ // Copyright (c) 2016 Jamie Rees // File: LoginModule.cs @@ -23,10 +24,12 @@ // 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.Dynamic; - +using System.Security; using Nancy; using Nancy.Authentication.Forms; using Nancy.Extensions; @@ -43,7 +46,8 @@ namespace PlexRequests.UI.Modules { public class LoginModule : BaseModule { - public LoginModule(ISettingsService pr, ICustomUserMapper m, IResourceLinker linker) : base(pr) + public LoginModule(ISettingsService pr, ICustomUserMapper m, IResourceLinker linker) + : base(pr) { UserMapper = m; Get["/login"] = _ => @@ -61,7 +65,14 @@ namespace PlexRequests.UI.Modules return View["Index", model]; }; - Get["/logout"] = x => this.LogoutAndRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/" : "~/"); + Get["/logout"] = x => + { + if (Session[SessionKeys.UsernameKey] != null) + { + Session.Delete(SessionKeys.UsernameKey); + } + return this.LogoutAndRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/" : "~/"); + }; Post["/login"] = x => { @@ -74,7 +85,10 @@ namespace PlexRequests.UI.Modules if (userId == null) { - return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/login?error=true&username=" + username : "~/login?error=true&username=" + username); + return + Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) + ? $"~/{BaseUrl}/login?error=true&username=" + username + : "~/login?error=true&username=" + username); } DateTime? expiry = null; if (Request.Form.RememberMe.HasValue) @@ -106,7 +120,10 @@ namespace PlexRequests.UI.Modules var exists = UserMapper.DoUsersExist(); if (exists) { - return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/register?error=true" : "~/register?error=true"); + return + Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) + ? $"~/{BaseUrl}/register?error=true" + : "~/register?error=true"); } var userId = UserMapper.CreateAdmin(username, Request.Form.Password); Session[SessionKeys.UsernameKey] = username; @@ -116,6 +133,7 @@ namespace PlexRequests.UI.Modules Get["/changepassword"] = _ => ChangePassword(); Post["/changepassword"] = _ => ChangePasswordPost(); } + private ICustomUserMapper UserMapper { get; } private Negotiator ChangePassword() @@ -141,14 +159,20 @@ namespace PlexRequests.UI.Modules { return Response.AsJson(new JsonResponseModel { Message = "The passwords do not match", Result = false }); } - - var result = UserMapper.UpdatePassword(username, oldPass, newPassword); - if (result) + try { - return Response.AsJson(new JsonResponseModel { Message = "Password has been changed!", Result = true }); - } + var result = UserMapper.UpdatePassword(username, oldPass, newPassword); + if (result) + { + return Response.AsJson(new JsonResponseModel { Message = "Password has been changed!", Result = true }); + } - return Response.AsJson(new JsonResponseModel { Message = "Could not update the password in the database", Result = false }); + return Response.AsJson(new JsonResponseModel { Message = "Could not update the password in the database", Result = false }); + } + catch (SecurityException e) + { + return Response.AsJson(new JsonResponseModel { Message = e.ToString(), Result = false }); + } } } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/RequestsModule.cs b/PlexRequests.UI/Modules/RequestsModule.cs index 8a2a9821a..dc46b2473 100644 --- a/PlexRequests.UI/Modules/RequestsModule.cs +++ b/PlexRequests.UI/Modules/RequestsModule.cs @@ -178,6 +178,8 @@ namespace PlexRequests.UI.Modules Available = movie.Available, Admin = IsAdmin, IssueId = movie.IssueId, + Denied = movie.Denied, + DeniedReason = movie.DeniedReason, Qualities = qualities.ToArray() }).ToList(); @@ -249,6 +251,8 @@ namespace PlexRequests.UI.Modules Available = tv.Available, Admin = IsAdmin, IssueId = tv.IssueId, + Denied = tv.Denied, + DeniedReason = tv.DeniedReason, TvSeriesRequestType = tv.SeasonsRequested, Qualities = qualities.ToArray(), Episodes = tv.Episodes.ToArray(), @@ -290,6 +294,8 @@ namespace PlexRequests.UI.Modules Available = album.Available, Admin = IsAdmin, IssueId = album.IssueId, + Denied = album.Denied, + DeniedReason = album.DeniedReason, TvSeriesRequestType = album.SeasonsRequested, MusicBrainzId = album.MusicBrainzId, ArtistName = album.ArtistName diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index 73f016f24..2a0bbe4f6 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -369,7 +369,7 @@ namespace PlexRequests.UI.Modules viewT.Requested = true; } } - + viewTv.Add(viewT); } @@ -657,7 +657,11 @@ namespace PlexRequests.UI.Modules return await AddUserToRequest(existingRequest, settings, fullShowName, true); } - // We have an episode that has not yet been requested, let's continue + else + { + // We no episodes to approve + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" }); + } } else if (model.SeasonList.Except(existingRequest.SeasonList).Any()) { @@ -765,7 +769,7 @@ namespace PlexRequests.UI.Modules UpdateRequest(existingRequest, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); } - + return await UpdateRequest(existingRequest, settings, $"{fullShowName} {Resources.UI.Search_AlreadyRequested}"); } @@ -1033,7 +1037,8 @@ namespace PlexRequests.UI.Modules Name = ep.name, EpisodeId = ep.id }); - }return model; + } + return model; } diff --git a/PlexRequests.UI/Modules/UserLoginModule.cs b/PlexRequests.UI/Modules/UserLoginModule.cs index e30f4adc2..713f43987 100644 --- a/PlexRequests.UI/Modules/UserLoginModule.cs +++ b/PlexRequests.UI/Modules/UserLoginModule.cs @@ -216,7 +216,7 @@ namespace PlexRequests.UI.Modules private bool IsUserInDeniedList(string username, AuthenticationSettings settings) { - return settings.DeniedUserList.Any(x => x.Equals(username)); + return settings.DeniedUserList.Any(x => x.Equals(username, StringComparison.CurrentCultureIgnoreCase)); } } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/UserWizardModule.cs b/PlexRequests.UI/Modules/UserWizardModule.cs index 1bfbbd679..a0a94bb4f 100644 --- a/PlexRequests.UI/Modules/UserWizardModule.cs +++ b/PlexRequests.UI/Modules/UserWizardModule.cs @@ -64,6 +64,7 @@ namespace PlexRequests.UI.Modules a.TrackEventAsync(Category.Wizard, Action.Start, "Started the wizard", Username, CookieHelper.GetAnalyticClientId(Cookies)); var settings = await PlexRequestSettings.GetSettingsAsync(); + if (settings.Wizard) { return Context.GetRedirect("~/search"); diff --git a/PlexRequests.UI/Views/Admin/CouchPotato.cshtml b/PlexRequests.UI/Views/Admin/CouchPotato.cshtml index bb6f8795f..f4f33cbab 100644 --- a/PlexRequests.UI/Views/Admin/CouchPotato.cshtml +++ b/PlexRequests.UI/Views/Admin/CouchPotato.cshtml @@ -171,7 +171,7 @@ generateNotify("Success!", "success"); $('#ApiKey').val(response.apiKey); } else { - generateNotify(response.message, "warning"); + generateNotify("Could not automatically get the API key", "warning"); } }, error: function (e) { diff --git a/PlexRequests.UI/Views/Admin/LandingPage.cshtml b/PlexRequests.UI/Views/Admin/LandingPage.cshtml index 38d3cc6fa..e7ddb4417 100644 --- a/PlexRequests.UI/Views/Admin/LandingPage.cshtml +++ b/PlexRequests.UI/Views/Admin/LandingPage.cshtml @@ -54,7 +54,7 @@

Notice Message

- +
diff --git a/PlexRequests.UI/Views/Requests/Index.cshtml b/PlexRequests.UI/Views/Requests/Index.cshtml index d34c7432e..8865450d0 100644 --- a/PlexRequests.UI/Views/Requests/Index.cshtml +++ b/PlexRequests.UI/Views/Requests/Index.cshtml @@ -1,4 +1,5 @@ @using Nancy.Security +@using Nancy.Security @using PlexRequests.UI.Helpers @using PlexRequests.UI.Resources @{ @@ -136,13 +137,13 @@ {{#if episodes}}
{{#each episodes}} - Season: {{this.seasonNumber}} -
- Episodes Requested: - {{#each this.episodes}} - {{this}} - {{/each}} -
+ Season: {{this.seasonNumber}} +
+ Episodes Requested: + {{#each this.episodes}} + {{this}} + {{/each}} +
{{/each}}
{{/if}} @@ -170,12 +171,21 @@ {{status}}
+ {{#if denied}} +
+ Denied: + {{#if deniedReason}} + + {{/if}} +
+ + {{/if}} {{#if_eq releaseDate "01/01/0001 00:00:00"}}
@UI.Requests_ReleaseDate: {{releaseDate}}
{{else}}
@UI.Requests_ReleaseDate: {{releaseDate}}
{{/if_eq}} - + {{#unless denied}}
@UI.Common_Approved: {{#if_eq approved false}} @@ -185,6 +195,7 @@ {{/if_eq}}
+ {{/unless}}
@UI.Requests_Available {{#if_eq available false}} @@ -216,6 +227,7 @@
{{#if_eq admin true}} + {{#if_eq approved false}}
@@ -236,6 +248,22 @@ {{/if_eq}}
+ {{#unless denied}} +
+ + +
+ + + +
+
+ {{/unless}} {{/if_eq}}
@@ -324,6 +352,12 @@
@UI.Requests_RequestedBy: {{requestedUsers}}
{{/if}}
@UI.Requests_RequestedDate: {{requestedDate}}
+ {{#if denied}} +
Denied:
+ {{#if deniedReason}} +
Reason: {{deniedReason}}
+ {{/if}} + {{/if}}
{{#if_eq admin true}} @@ -332,6 +366,20 @@ +
+ + +
+ + + +
+
{{/if_eq}}
@@ -393,6 +441,27 @@
+ + @Html.LoadRequestAssets() diff --git a/PlexRequests.UI/Views/Search/Index.cshtml b/PlexRequests.UI/Views/Search/Index.cshtml index 950a7f06d..b8e9f3bfc 100644 --- a/PlexRequests.UI/Views/Search/Index.cshtml +++ b/PlexRequests.UI/Views/Search/Index.cshtml @@ -166,6 +166,18 @@

{{title}} ({{year}})

{{/if_eq}} + {{#if_eq type "tv"}} + {{#if available}} + Available + {{else}} + Not Available + {{/if}} + {{#if requested}} + Requested + {{/if}} +
+
+ {{/if_eq}}

{{overview}}

@@ -176,6 +188,7 @@ {{#if_eq available true}}
+
@UI.Search_ViewInPlex {{else}} {{#if_eq requested true}} @@ -189,7 +202,6 @@ {{#if_eq tvFullyAvailable true}} @*//TODO Not used yet*@
- @UI.Search_ViewInPlex {{else}} + {{#if available}} +
+ @UI.Search_ViewInPlex + {{/if}} {{/if_eq}} {{/if_eq}} diff --git a/appveyor.yml b/appveyor.yml index 1285d7e4b..6c9824580 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,9 +3,9 @@ configuration: Release assembly_info: patch: true file: '**\AssemblyInfo.*' - assembly_version: '1.9.1' + assembly_version: '1.9.2' assembly_file_version: '{version}' - assembly_informational_version: '1.9.1' + assembly_informational_version: '1.9.2' before_build: - cmd: appveyor-retry nuget restore build: