From 96f27f8f1a300fb2625fa3913d1384f953844382 Mon Sep 17 00:00:00 2001 From: Drewster727 Date: Tue, 21 Jun 2016 19:43:31 -0500 Subject: [PATCH] add an option to stop sending notifications for requests that don't require approval #345 --- .../SettingModels/PlexRequestSettings.cs | 155 +- .../Notification/EmailMessageNotification.cs | 400 ++-- PlexRequests.UI/Modules/SearchModule.cs | 1966 ++++++++--------- PlexRequests.UI/Views/Admin/Settings.cshtml | 601 ++--- appveyor.yml | 52 +- 5 files changed, 1595 insertions(+), 1579 deletions(-) diff --git a/PlexRequests.Core/SettingModels/PlexRequestSettings.cs b/PlexRequests.Core/SettingModels/PlexRequestSettings.cs index 493d8bf39..4e986c555 100644 --- a/PlexRequests.Core/SettingModels/PlexRequestSettings.cs +++ b/PlexRequests.Core/SettingModels/PlexRequestSettings.cs @@ -1,77 +1,78 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: PlexRequestSettings.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 Newtonsoft.Json; -using System; -using System.Collections.Generic; - -namespace PlexRequests.Core.SettingModels -{ - public class PlexRequestSettings : Settings - { - public int Port { get; set; } - public string BaseUrl { get; set; } - public bool SearchForMovies { get; set; } - public bool SearchForTvShows { get; set; } - public bool SearchForMusic { get; set; } - public bool RequireMovieApproval { get; set; } - public bool RequireTvShowApproval { get; set; } - public bool RequireMusicApproval { get; set; } - public bool UsersCanViewOnlyOwnRequests { get; set; } - public bool UsersCanViewOnlyOwnIssues { get; set; } - public int WeeklyRequestLimit { get; set; } - public string NoApprovalUsers { get; set; } - public bool CollectAnalyticData { get; set; } - - /// - /// The CSS name of the theme we want - /// - public string ThemeName { get; set; } - - public string ApiKey { get; set; } - - [JsonIgnore] - public List ApprovalWhiteList - { - get - { - var users = new List(); - if (string.IsNullOrEmpty(NoApprovalUsers)) - { - return users; - } - - var splitUsers = NoApprovalUsers.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - foreach (var user in splitUsers) - { - if (!string.IsNullOrWhiteSpace(user)) - users.Add(user.Trim()); - } - return users; - } - } - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexRequestSettings.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 Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace PlexRequests.Core.SettingModels +{ + public class PlexRequestSettings : Settings + { + public int Port { get; set; } + public string BaseUrl { get; set; } + public bool SearchForMovies { get; set; } + public bool SearchForTvShows { get; set; } + public bool SearchForMusic { get; set; } + public bool RequireMovieApproval { get; set; } + public bool RequireTvShowApproval { get; set; } + public bool RequireMusicApproval { get; set; } + public bool UsersCanViewOnlyOwnRequests { get; set; } + public bool UsersCanViewOnlyOwnIssues { get; set; } + public int WeeklyRequestLimit { get; set; } + public string NoApprovalUsers { get; set; } + public bool CollectAnalyticData { get; set; } + public bool IgnoreNotifyForAutoApprovedRequests { get; set; } + + /// + /// The CSS name of the theme we want + /// + public string ThemeName { get; set; } + + public string ApiKey { get; set; } + + [JsonIgnore] + public List ApprovalWhiteList + { + get + { + var users = new List(); + if (string.IsNullOrEmpty(NoApprovalUsers)) + { + return users; + } + + var splitUsers = NoApprovalUsers.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var user in splitUsers) + { + if (!string.IsNullOrWhiteSpace(user)) + users.Add(user.Trim()); + } + return users; + } + } + } +} diff --git a/PlexRequests.Services/Notification/EmailMessageNotification.cs b/PlexRequests.Services/Notification/EmailMessageNotification.cs index 20ef3f612..080091504 100644 --- a/PlexRequests.Services/Notification/EmailMessageNotification.cs +++ b/PlexRequests.Services/Notification/EmailMessageNotification.cs @@ -1,201 +1,201 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: EmailMessageNotification.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.Threading.Tasks; - -using MailKit.Security; - -using MimeKit; -using NLog; - -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Services.Interfaces; -using SmtpClient = MailKit.Net.Smtp.SmtpClient; - -namespace PlexRequests.Services.Notification -{ - public class EmailMessageNotification : INotification - { - public EmailMessageNotification(ISettingsService settings) - { - EmailNotificationSettings = settings; - } - - private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - private ISettingsService EmailNotificationSettings { get; } - public string NotificationName => "EmailMessageNotification"; - - public async Task NotifyAsync(NotificationModel model) - { - var configuration = GetConfiguration(); - await NotifyAsync(model, configuration); - } - - public async Task NotifyAsync(NotificationModel model, Settings settings) - { - if (settings == null) await NotifyAsync(model); - - var emailSettings = (EmailNotificationSettings)settings; - - if (!ValidateConfiguration(emailSettings)) - { - return; - } - - switch (model.NotificationType) - { - case NotificationType.NewRequest: - await EmailNewRequest(model, emailSettings); - break; - case NotificationType.Issue: - await EmailIssue(model, emailSettings); - break; - case NotificationType.RequestAvailable: - await EmailAvailableRequest(model, emailSettings); - break; - case NotificationType.RequestApproved: - throw new NotImplementedException(); - - case NotificationType.AdminNote: - throw new NotImplementedException(); - - case NotificationType.Test: - await EmailTest(model, emailSettings); - break; - } - - } - - private EmailNotificationSettings GetConfiguration() - { - var settings = EmailNotificationSettings.GetSettings(); - return settings; - } - - private bool ValidateConfiguration(EmailNotificationSettings settings) - { - if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString())) - { - return false; - } - - if (!settings.EnableUserEmailNotifications) - { - if (!settings.Enabled) - { - return false; - } - } - - return true; - } - - private async Task EmailNewRequest(NotificationModel model, EmailNotificationSettings settings) - { - var message = new MimeMessage - { - Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has requested {model.Title}! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" }, - Subject = $"Plex Requests: New request for {model.Title}!" - }; - message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); - message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); - - - await Send(message, settings); - } - - private async Task EmailIssue(NotificationModel model, EmailNotificationSettings settings) - { - var message = new MimeMessage - { - Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!" }, - Subject = $"Plex Requests: New issue for {model.Title}!" - }; - message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); - message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); - - - await Send(message, settings); - } - - private async Task EmailAvailableRequest(NotificationModel model, EmailNotificationSettings settings) - { - if (!settings.EnableUserEmailNotifications) - { - await Task.FromResult(false); - } - - var message = new MimeMessage - { - Body = new TextPart("plain") { Text = $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)" }, - Subject = $"Plex Requests: {model.Title} is now available!" - }; - message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); - message.To.Add(new MailboxAddress(model.UserEmail, model.UserEmail)); - - await Send(message, settings); - } - - private async Task Send(MimeMessage message, EmailNotificationSettings settings) - { - try - { - using (var client = new SmtpClient()) - { - client.Connect(settings.EmailHost, settings.EmailPort, SecureSocketOptions.Auto); // 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"); - - client.Authenticate(settings.EmailUsername, settings.EmailPassword); - - await client.SendAsync(message); - await client.DisconnectAsync(true); - } - } - catch (Exception e) - { - Log.Error(e); - } - } - - private async Task EmailTest(NotificationModel model, EmailNotificationSettings settings) - { - var message = new MimeMessage - { - Body = new TextPart("plain") { Text = "This is just a test! Success!" }, - Subject = "Plex Requests: Test Message!", - }; - message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); - message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); - - await Send(message, settings); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: EmailMessageNotification.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.Threading.Tasks; + +using MailKit.Security; + +using MimeKit; +using NLog; + +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Services.Interfaces; +using SmtpClient = MailKit.Net.Smtp.SmtpClient; + +namespace PlexRequests.Services.Notification +{ + public class EmailMessageNotification : INotification + { + public EmailMessageNotification(ISettingsService settings) + { + EmailNotificationSettings = settings; + } + + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + private ISettingsService EmailNotificationSettings { get; } + public string NotificationName => "EmailMessageNotification"; + + public async Task NotifyAsync(NotificationModel model) + { + var configuration = GetConfiguration(); + await NotifyAsync(model, configuration); + } + + public async Task NotifyAsync(NotificationModel model, Settings settings) + { + if (settings == null) await NotifyAsync(model); + + var emailSettings = (EmailNotificationSettings)settings; + + if (!ValidateConfiguration(emailSettings)) + { + return; + } + + switch (model.NotificationType) + { + case NotificationType.NewRequest: + await EmailNewRequest(model, emailSettings); + break; + case NotificationType.Issue: + await EmailIssue(model, emailSettings); + break; + case NotificationType.RequestAvailable: + await EmailAvailableRequest(model, emailSettings); + break; + case NotificationType.RequestApproved: + throw new NotImplementedException(); + + case NotificationType.AdminNote: + throw new NotImplementedException(); + + case NotificationType.Test: + await EmailTest(model, emailSettings); + break; + } + + } + + private EmailNotificationSettings GetConfiguration() + { + var settings = EmailNotificationSettings.GetSettings(); + return settings; + } + + private bool ValidateConfiguration(EmailNotificationSettings settings) + { + if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString())) + { + return false; + } + + if (!settings.EnableUserEmailNotifications) + { + if (!settings.Enabled) + { + return false; + } + } + + return true; + } + + private async Task EmailNewRequest(NotificationModel model, EmailNotificationSettings settings) + { + var message = new MimeMessage + { + Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has requested {model.Title}! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" }, + Subject = $"Plex Requests: New request for {model.Title}!" + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); + + + await Send(message, settings); + } + + private async Task EmailIssue(NotificationModel model, EmailNotificationSettings settings) + { + var message = new MimeMessage + { + Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!" }, + Subject = $"Plex Requests: New issue for {model.Title}!" + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); + + + await Send(message, settings); + } + + private async Task EmailAvailableRequest(NotificationModel model, EmailNotificationSettings settings) + { + if (!settings.EnableUserEmailNotifications) + { + await Task.FromResult(false); + } + + var message = new MimeMessage + { + Body = new TextPart("plain") { Text = $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)" }, + Subject = $"Plex Requests: {model.Title} is now available!" + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(model.UserEmail, model.UserEmail)); + + await Send(message, settings); + } + + private async Task Send(MimeMessage message, EmailNotificationSettings settings) + { + try + { + using (var client = new SmtpClient()) + { + client.Connect(settings.EmailHost, settings.EmailPort, SecureSocketOptions.Auto); // 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"); + + client.Authenticate(settings.EmailUsername, settings.EmailPassword); + + await client.SendAsync(message); + await client.DisconnectAsync(true); + } + } + catch (Exception e) + { + Log.Error(e); + } + } + + private async Task EmailTest(NotificationModel model, EmailNotificationSettings settings) + { + var message = new MimeMessage + { + Body = new TextPart("plain") { Text = "This is just a test! Success!" }, + Subject = "Plex Requests: Test Message!", + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); + + await Send(message, settings); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index 380df096f..e42ae0739 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -1,983 +1,983 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: SearchModule.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.Globalization; -using System.Linq; - -using Nancy; -using Nancy.Responses.Negotiation; - -using NLog; - -using PlexRequests.Api; -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Music; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Helpers; -using PlexRequests.Helpers.Exceptions; -using PlexRequests.Services.Interfaces; -using PlexRequests.Services.Notification; -using PlexRequests.Store; -using PlexRequests.UI.Helpers; -using PlexRequests.UI.Models; -using System.Threading.Tasks; - -using Nancy.Extensions; -using Nancy.Responses; - -using PlexRequests.Api.Models.Tv; -using PlexRequests.Core.Models; -using PlexRequests.Store.Models; -using PlexRequests.Store.Repository; - -using TMDbLib.Objects.General; - -namespace PlexRequests.UI.Modules -{ - public class SearchModule : BaseAuthModule - { - public SearchModule(ICacheProvider cache, ISettingsService cpSettings, - ISettingsService prSettings, IAvailabilityChecker checker, - IRequestService request, ISonarrApi sonarrApi, ISettingsService sonarrSettings, - ISettingsService sickRageService, ICouchPotatoApi cpApi, ISickRageApi srApi, - INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, ISettingsService hpService, - ICouchPotatoCacher cpCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi, - ISettingsService plexService, ISettingsService auth, IRepository u, ISettingsService email, - IIssueService issue) : base("search", prSettings) - { - Auth = auth; - PlexService = plexService; - PlexApi = plexApi; - CpService = cpSettings; - PrService = prSettings; - MovieApi = new TheMovieDbApi(); - Cache = cache; - Checker = checker; - CpCacher = cpCacher; - SonarrCacher = sonarrCacher; - SickRageCacher = sickRageCacher; - RequestService = request; - SonarrApi = sonarrApi; - SonarrService = sonarrSettings; - CouchPotatoApi = cpApi; - SickRageService = sickRageService; - SickrageApi = srApi; - NotificationService = notify; - MusicBrainzApi = mbApi; - HeadphonesApi = hpApi; - HeadphonesService = hpService; - UsersToNotifyRepo = u; - EmailNotificationSettings = email; - IssueService = issue; - - - Get["/", true] = async (x, ct) => await RequestLoad(); - - Get["movie/{searchTerm}", true] = async (x, ct) => await SearchMovie((string)x.searchTerm); - Get["tv/{searchTerm}", true] = async (x, ct) => await SearchTvShow((string)x.searchTerm); - Get["music/{searchTerm}", true] = async (x, ct) => await SearchMusic((string)x.searchTerm); - Get["music/coverArt/{id}"] = p => GetMusicBrainzCoverArt((string)p.id); - - Get["movie/upcoming", true] = async (x, ct) => await UpcomingMovies(); - Get["movie/playing", true] = async (x, ct) => await CurrentlyPlayingMovies(); - - Post["request/movie", true] = async (x, ct) => await RequestMovie((int)Request.Form.movieId); - Post["request/tv", true] = async (x, ct) => await RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons); - Post["request/album", true] = async (x, ct) => await RequestAlbum((string)Request.Form.albumId); - - Post["/notifyuser", true] = async (x, ct) => await NotifyUser((bool)Request.Form.notify); - Get["/notifyuser", true] = async (x, ct) => await GetUserNotificationSettings(); - - Get["/seasons"] = x => GetSeasons(); - } - private IPlexApi PlexApi { get; } - private TheMovieDbApi MovieApi { get; } - private INotificationService NotificationService { get; } - private ICouchPotatoApi CouchPotatoApi { get; } - private ISonarrApi SonarrApi { get; } - private ISickRageApi SickrageApi { get; } - private IRequestService RequestService { get; } - private ICacheProvider Cache { get; } - private ISettingsService Auth { get; } - private ISettingsService PlexService { get; } - private ISettingsService CpService { get; } - private ISettingsService PrService { get; } - private ISettingsService SonarrService { get; } - private ISettingsService SickRageService { get; } - private ISettingsService HeadphonesService { get; } - private ISettingsService EmailNotificationSettings { get; } - private IAvailabilityChecker Checker { get; } - private ICouchPotatoCacher CpCacher { get; } - private ISonarrCacher SonarrCacher { get; } - private ISickRageCacher SickRageCacher { get; } - private IMusicBrainzApi MusicBrainzApi { get; } - private IHeadphonesApi HeadphonesApi { get; } - private IRepository UsersToNotifyRepo { get; } - private IIssueService IssueService { get; } - private static Logger Log = LogManager.GetCurrentClassLogger(); - - private async Task RequestLoad() - { - var settings = await PrService.GetSettingsAsync(); - - Log.Trace("Loading Index"); - return View["Search/Index", settings]; - } - - private async Task UpcomingMovies() - { - Log.Trace("Loading upcoming movies"); - return await ProcessMovies(MovieSearchType.Upcoming, string.Empty); - } - - private async Task CurrentlyPlayingMovies() - { - Log.Trace("Loading currently playing movies"); - return await ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty); - } - - private async Task SearchMovie(string searchTerm) - { - Log.Trace("Searching for Movie {0}", searchTerm); - return await ProcessMovies(MovieSearchType.Search, searchTerm); - } - - private async Task ProcessMovies(MovieSearchType searchType, string searchTerm) - { - var apiMovies = new List(); - await Task.Factory.StartNew( - () => - { - switch (searchType) - { - case MovieSearchType.Search: - return - MovieApi.SearchMovie(searchTerm) - .Result.Select( - x => - new MovieResult() - { - Adult = x.Adult, - BackdropPath = x.BackdropPath, - GenreIds = x.GenreIds, - Id = x.Id, - OriginalLanguage = x.OriginalLanguage, - OriginalTitle = x.OriginalTitle, - Overview = x.Overview, - Popularity = x.Popularity, - PosterPath = x.PosterPath, - ReleaseDate = x.ReleaseDate, - Title = x.Title, - Video = x.Video, - VoteAverage = x.VoteAverage, - VoteCount = x.VoteCount - }) - .ToList(); - case MovieSearchType.CurrentlyPlaying: - return MovieApi.GetCurrentPlayingMovies().Result.ToList(); - case MovieSearchType.Upcoming: - return MovieApi.GetUpcomingMovies().Result.ToList(); - default: - return new List(); - } - }).ContinueWith( - (t) => - { - apiMovies = t.Result; - }); - - var allResults = await RequestService.GetAllAsync(); - allResults = allResults.Where(x => x.Type == RequestType.Movie); - - var distinctResults = allResults.DistinctBy(x => x.ProviderId); - var dbMovies = distinctResults.ToDictionary(x => x.ProviderId); - - - var cpCached = CpCacher.QueuedIds(); - var plexMovies = Checker.GetPlexMovies(); - var settings = await PrService.GetSettingsAsync(); - var viewMovies = new List(); - foreach (MovieResult movie in apiMovies) - { - var viewMovie = new SearchMovieViewModel - { - Adult = movie.Adult, - BackdropPath = movie.BackdropPath, - GenreIds = movie.GenreIds, - Id = movie.Id, - OriginalLanguage = movie.OriginalLanguage, - OriginalTitle = movie.OriginalTitle, - Overview = movie.Overview, - Popularity = movie.Popularity, - PosterPath = movie.PosterPath, - ReleaseDate = movie.ReleaseDate, - Title = movie.Title, - Video = movie.Video, - VoteAverage = movie.VoteAverage, - VoteCount = movie.VoteCount - }; - var canSee = CanUserSeeThisRequest(viewMovie.Id, settings.UsersCanViewOnlyOwnRequests, dbMovies); - if (Checker.IsMovieAvailable(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString())) - { - viewMovie.Available = true; - } - else if (dbMovies.ContainsKey(movie.Id) && canSee) // compare to the requests db - { - var dbm = dbMovies[movie.Id]; - - viewMovie.Requested = true; - viewMovie.Approved = dbm.Approved; - viewMovie.Available = dbm.Available; - } - else if (cpCached.Contains(movie.Id) && canSee) // compare to the couchpotato db - { - viewMovie.Requested = true; - } - - viewMovies.Add(viewMovie); - } - - return Response.AsJson(viewMovies); - } - - private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests, Dictionary moviesInDb) - { - if (usersCanViewOnlyOwnRequests) - { - var result = moviesInDb.FirstOrDefault(x => x.Value.ProviderId == movieId); - return result.Value == null || result.Value.UserHasRequested(Username); - } - - return true; - } - - private async Task SearchTvShow(string searchTerm) - { - var plexSettings = await PlexService.GetSettingsAsync(); - Log.Trace("Searching for TV Show {0}", searchTerm); - - var apiTv = new List(); - await Task.Factory.StartNew(() => new TvMazeApi().Search(searchTerm)).ContinueWith((t) => - { - apiTv = t.Result; - }); - - var allResults = await RequestService.GetAllAsync(); - allResults = allResults.Where(x => x.Type == RequestType.TvShow); - - var dbTv = allResults.ToDictionary(x => x.ProviderId); - - if (!apiTv.Any()) - { - Log.Trace("TV Show data is null"); - return Response.AsJson(""); - } - - var sonarrCached = SonarrCacher.QueuedIds(); - var sickRageCache = SickRageCacher.QueuedIds(); // consider just merging sonarr/sickrage arrays - var plexTvShows = Checker.GetPlexTvShows(); - - var viewTv = new List(); - foreach (var t in apiTv) - { - var banner = t.show.image?.medium; - if (!string.IsNullOrEmpty(banner)) - { - banner = banner.Replace("http", "https"); - } - - var viewT = new SearchTvShowViewModel - { - Banner = banner, - FirstAired = t.show.premiered, - Id = t.show.externals?.thetvdb ?? 0, - ImdbId = t.show.externals?.imdb, - Network = t.show.network?.name, - NetworkId = t.show.network?.id.ToString(), - Overview = t.show.summary.RemoveHtml(), - Rating = t.score.ToString(CultureInfo.CurrentUICulture), - Runtime = t.show.runtime.ToString(), - SeriesId = t.show.id, - SeriesName = t.show.name, - Status = t.show.status - }; - - - var providerId = string.Empty; - - if (plexSettings.AdvancedSearch) - { - providerId = viewT.Id.ToString(); - } - - if (Checker.IsTvShowAvailable(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId)) - { - viewT.Available = true; - } - else if (t.show?.externals?.thetvdb != null) - { - int tvdbid = (int)t.show.externals.thetvdb; - - if (dbTv.ContainsKey(tvdbid)) - { - var dbt = dbTv[tvdbid]; - - viewT.Requested = true; - viewT.Approved = dbt.Approved; - viewT.Available = dbt.Available; - } - else if (sonarrCached.Contains(tvdbid) || sickRageCache.Contains(tvdbid)) // compare to the sonarr/sickrage db - { - viewT.Requested = true; - } - } - - viewTv.Add(viewT); - } - - return Response.AsJson(viewTv); - } - - private async Task SearchMusic(string searchTerm) - { - var apiAlbums = new List(); - await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) => - { - apiAlbums = t.Result.releases ?? new List(); - }); - - var allResults = await RequestService.GetAllAsync(); - allResults = allResults.Where(x => x.Type == RequestType.Album); - - var dbAlbum = allResults.ToDictionary(x => x.MusicBrainzId); - - var plexAlbums = Checker.GetPlexAlbums(); - - var viewAlbum = new List(); - foreach (var a in apiAlbums) - { - var viewA = new SearchMusicViewModel - { - Title = a.title, - Id = a.id, - Artist = a.ArtistCredit?.Select(x => x.artist?.name).FirstOrDefault(), - Overview = a.disambiguation, - ReleaseDate = a.date, - TrackCount = a.TrackCount, - ReleaseType = a.status, - Country = a.country - }; - - DateTime release; - DateTimeHelper.CustomParse(a.ReleaseEvents?.FirstOrDefault()?.date, out release); - var artist = a.ArtistCredit?.FirstOrDefault()?.artist; - if (Checker.IsAlbumAvailable(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name)) - { - viewA.Available = true; - } - if (!string.IsNullOrEmpty(a.id) && dbAlbum.ContainsKey(a.id)) - { - var dba = dbAlbum[a.id]; - - viewA.Requested = true; - viewA.Approved = dba.Approved; - viewA.Available = dba.Available; - } - - viewAlbum.Add(viewA); - } - return Response.AsJson(viewAlbum); - } - - private async Task RequestMovie(int movieId) - { - var movieInfo = MovieApi.GetMovieInformation(movieId).Result; - var fullMovieName = $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}"; - Log.Trace("Getting movie info from TheMovieDb"); - - var settings = await PrService.GetSettingsAsync(); - - // check if the movie has already been requested - Log.Info("Requesting movie with id {0}", movieId); - var existingRequest = await RequestService.CheckRequestAsync(movieId); - if (existingRequest != null) - { - // check if the current user is already marked as a requester for this movie, if not, add them - if (!existingRequest.UserHasRequested(Username)) - { - existingRequest.RequestedUsers.Add(Username); - await RequestService.UpdateRequestAsync(existingRequest); - } - - return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullMovieName} was successfully added!" : $"{fullMovieName} has already been requested!" }); - } - - Log.Debug("movie with id {0} doesnt exists", movieId); - - try - { - var movies = Checker.GetPlexMovies(); - if (Checker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullMovieName} is already in Plex!" }); - } - } - catch (Exception e) - { - Log.Error(e); - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {fullMovieName} is in Plex, are you sure it's correctly setup?" }); - } - //#endif - - var model = new RequestedModel - { - ProviderId = movieInfo.Id, - Type = RequestType.Movie, - Overview = movieInfo.Overview, - ImdbId = movieInfo.ImdbId, - PosterPath = "https://image.tmdb.org/t/p/w150/" + movieInfo.PosterPath, - Title = movieInfo.Title, - ReleaseDate = movieInfo.ReleaseDate ?? DateTime.MinValue, - Status = movieInfo.Status, - RequestedDate = DateTime.UtcNow, - Approved = false, - RequestedUsers = new List { Username }, - Issues = IssueState.None, - - }; - - if (ShouldAutoApprove(RequestType.Movie, settings)) - { - var cpSettings = await CpService.GetSettingsAsync(); - - if (cpSettings.Enabled) - { - Log.Info("Adding movie to CP (No approval required)"); - var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, - cpSettings.FullUri, cpSettings.ProfileId); - Log.Debug("Adding movie to CP result {0}", result); - if (result) - { - model.Approved = true; - Log.Info("Adding movie to database (No approval required)"); - await RequestService.AddRequestAsync(model); - - if (ShouldSendNotification()) - { - var notificationModel = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notificationModel); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); - } - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = - "Something went wrong adding the movie to CouchPotato! Please check your settings." - }); - } - else - { - model.Approved = true; - Log.Info("Adding movie to database (No approval required)"); - await RequestService.AddRequestAsync(model); - - if (ShouldSendNotification()) - { - var notificationModel = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notificationModel); - } - - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); - } - } - - try - { - Log.Info("Adding movie to database"); - var id = await RequestService.AddRequestAsync(model); - - var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; - await NotificationService.Publish(notificationModel); - - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); - } - catch (Exception e) - { - Log.Fatal(e); - - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something went wrong adding the movie to CouchPotato! Please check your settings." }); - } - } - - /// - /// Requests the tv show. - /// - /// The show identifier. - /// The seasons. - /// - private async Task RequestTvShow(int showId, string seasons) - { - var tvApi = new TvMazeApi(); - - var showInfo = tvApi.ShowLookupByTheTvDbId(showId); - DateTime firstAir; - DateTime.TryParse(showInfo.premiered, out firstAir); - string fullShowName = $"{showInfo.name} ({firstAir.Year})"; - //#if !DEBUG - - var settings = await PrService.GetSettingsAsync(); - - // check if the show has already been requested - Log.Info("Requesting tv show with id {0}", showId); - var existingRequest = await RequestService.CheckRequestAsync(showId); - if (existingRequest != null) - { - // check if the current user is already marked as a requester for this show, if not, add them - if (!existingRequest.UserHasRequested(Username)) - { - existingRequest.RequestedUsers.Add(Username); - await RequestService.UpdateRequestAsync(existingRequest); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullShowName} was successfully added!" : $"{fullShowName} has already been requested!" }); - } - - try - { - var shows = Checker.GetPlexTvShows(); - var providerId = string.Empty; - var plexSettings = await PlexService.GetSettingsAsync(); - if (plexSettings.AdvancedSearch) - { - providerId = showId.ToString(); - } - if (Checker.IsTvShowAvailable(shows.ToArray(), showInfo.name, showInfo.premiered?.Substring(0, 4), providerId)) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} is already in Plex!" }); - } - } - catch (Exception) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {fullShowName} is in Plex, are you sure it's correctly setup?" }); - } - //#endif - - - var model = new RequestedModel - { - ProviderId = showInfo.externals?.thetvdb ?? 0, - Type = RequestType.TvShow, - Overview = showInfo.summary.RemoveHtml(), - PosterPath = showInfo.image?.medium, - Title = showInfo.name, - ReleaseDate = firstAir, - Status = showInfo.status, - RequestedDate = DateTime.UtcNow, - Approved = false, - RequestedUsers = new List { Username }, - Issues = IssueState.None, - ImdbId = showInfo.externals?.imdb ?? string.Empty, - SeasonCount = showInfo.seasonCount, - TvDbId = showId.ToString() - }; - - var seasonsList = new List(); - switch (seasons) - { - case "first": - seasonsList.Add(1); - model.SeasonsRequested = "First"; - break; - case "latest": - seasonsList.Add(model.SeasonCount); - model.SeasonsRequested = "Latest"; - break; - case "all": - model.SeasonsRequested = "All"; - break; - default: - model.SeasonsRequested = seasons; - var split = seasons.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - var seasonsCount = new int[split.Length]; - for (var i = 0; i < split.Length; i++) - { - int tryInt; - int.TryParse(split[i], out tryInt); - seasonsCount[i] = tryInt; - } - seasonsList.AddRange(seasonsCount); - break; - } - - model.SeasonList = seasonsList.ToArray(); - - if (ShouldAutoApprove(RequestType.TvShow, settings)) - { - var sonarrSettings = await SonarrService.GetSettingsAsync(); - var sender = new TvSender(SonarrApi, SickrageApi); - if (sonarrSettings.Enabled) - { - var result = sender.SendToSonarr(sonarrSettings, model); - if (!string.IsNullOrEmpty(result?.title)) - { - model.Approved = true; - Log.Debug("Adding tv to database requests (No approval required & Sonarr)"); - await RequestService.AddRequestAsync(model); - - if (ShouldSendNotification()) - { - var notify1 = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notify1); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); - } - - - return Response.AsJson(ValidationHelper.SendSonarrError(result?.ErrorMessages)); - - } - - var srSettings = SickRageService.GetSettings(); - if (srSettings.Enabled) - { - var result = sender.SendToSickRage(srSettings, model); - if (result?.result == "success") - { - model.Approved = true; - Log.Debug("Adding tv to database requests (No approval required & SickRage)"); - await RequestService.AddRequestAsync(model); - if (ShouldSendNotification()) - { - var notify2 = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notify2); - } - - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); - } - return Response.AsJson(new JsonResponseModel { Result = false, Message = result?.message != null ? "Message From SickRage: " + result.message : "Something went wrong adding the movie to SickRage! Please check your settings." }); - } - - if (!srSettings.Enabled && !sonarrSettings.Enabled) - { - model.Approved = true; - Log.Debug("Adding tv to database requests (No approval required) and Sonarr/Sickrage not setup"); - await RequestService.AddRequestAsync(model); - if (ShouldSendNotification()) - { - var notify2 = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notify2); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); - } - - return Response.AsJson(new JsonResponseModel { Result = false, Message = "The request of TV Shows is not correctly set up. Please contact your admin." }); - - } - - await RequestService.AddRequestAsync(model); - - var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; - await NotificationService.Publish(notificationModel); - - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); - } - - private bool ShouldSendNotification() - { - var sendNotification = true; - var claims = Context.CurrentUser?.Claims; - if (claims != null) - { - var enumerable = claims as string[] ?? claims.ToArray(); - if (enumerable.Contains(UserClaims.Admin) || enumerable.Contains(UserClaims.PowerUser)) - { - sendNotification = false; // Don't bother sending a notification if the user is an admin - } - } - return sendNotification; - } - - - private async Task RequestAlbum(string releaseId) - { - var settings = await PrService.GetSettingsAsync(); - var existingRequest = await RequestService.CheckRequestAsync(releaseId); - Log.Debug("Checking for an existing request"); - - if (existingRequest != null) - { - Log.Debug("We do have an existing album request"); - if (!existingRequest.UserHasRequested(Username)) - { - - Log.Debug("Not in the requested list so adding them and updating the request. User: {0}", Username); - existingRequest.RequestedUsers.Add(Username); - await RequestService.UpdateRequestAsync(existingRequest); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{existingRequest.Title} was successfully added!" : $"{existingRequest.Title} has already been requested!" }); - } - - Log.Debug("This is a new request"); - - var albumInfo = MusicBrainzApi.GetAlbum(releaseId); - DateTime release; - DateTimeHelper.CustomParse(albumInfo.ReleaseEvents?.FirstOrDefault()?.date, out release); - - var artist = albumInfo.ArtistCredits?.FirstOrDefault()?.artist; - if (artist == null) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "We could not find the artist on MusicBrainz. Please try again later or contact your admin" }); - } - - var albums = Checker.GetPlexAlbums(); - var alreadyInPlex = Checker.IsAlbumAvailable(albums.ToArray(), albumInfo.title, release.ToString("yyyy"), artist.name); - - if (alreadyInPlex) - { - return Response.AsJson(new JsonResponseModel - { - Result = false, - Message = $"{albumInfo.title} is already in Plex!" - }); - } - - var img = GetMusicBrainzCoverArt(albumInfo.id); - - var model = new RequestedModel - { - Title = albumInfo.title, - MusicBrainzId = albumInfo.id, - Overview = albumInfo.disambiguation, - PosterPath = img, - Type = RequestType.Album, - ProviderId = 0, - RequestedUsers = new List { Username }, - Status = albumInfo.status, - Issues = IssueState.None, - RequestedDate = DateTime.UtcNow, - ReleaseDate = release, - ArtistName = artist.name, - ArtistId = artist.id - }; - - if (ShouldAutoApprove(RequestType.Album, settings)) - { - Log.Debug("We don't require approval OR the user is in the whitelist"); - var hpSettings = HeadphonesService.GetSettings(); - - if (!hpSettings.Enabled) - { - await RequestService.AddRequestAsync(model); - return - Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{model.Title} was successfully added!" - }); - } - - var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService); - await sender.AddAlbum(model); - model.Approved = true; - await RequestService.AddRequestAsync(model); - - if (ShouldSendNotification()) - { - var notify2 = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notify2); - } - - return - Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{model.Title} was successfully added!" - }); - } - - if (ShouldSendNotification()) - { - var notify2 = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notify2); - } - await RequestService.AddRequestAsync(model); - return Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{model.Title} was successfully added!" - }); - } - - private string GetMusicBrainzCoverArt(string id) - { - var coverArt = MusicBrainzApi.GetCoverArt(id); - var firstImage = coverArt?.images?.FirstOrDefault(); - var img = string.Empty; - - if (firstImage != null) - { - img = firstImage.thumbnails?.small ?? firstImage.image; - } - - return img; - } - - private bool ShouldAutoApprove(RequestType requestType, PlexRequestSettings prSettings) - { - // if the user is an admin or they are whitelisted, they go ahead and allow auto-approval - if (IsAdmin || prSettings.ApprovalWhiteList.Any(x => x.Equals(Username, StringComparison.OrdinalIgnoreCase))) return true; - - // check by request type if the category requires approval or not - switch (requestType) - { - case RequestType.Movie: - return !prSettings.RequireMovieApproval; - case RequestType.TvShow: - return !prSettings.RequireTvShowApproval; - case RequestType.Album: - return !prSettings.RequireMusicApproval; - default: - return false; - } - } - - private async Task NotifyUser(bool notify) - { - var authSettings = await Auth.GetSettingsAsync(); - var auth = authSettings.UserAuthentication; - var emailSettings = await EmailNotificationSettings.GetSettingsAsync(); - var email = emailSettings.EnableUserEmailNotifications; - if (!auth) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but this functionality is currently only for users with Plex accounts" }); - } - if (!email) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but your administrator has not yet enabled this functionality." }); - } - var username = Username; - var originalList = await UsersToNotifyRepo.GetAllAsync(); - if (!notify) - { - if (originalList == null) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "We could not remove this notification because you never had it!" }); - } - var userToRemove = originalList.FirstOrDefault(x => x.Username == username); - if (userToRemove != null) - { - await UsersToNotifyRepo.DeleteAsync(userToRemove); - } - return Response.AsJson(new JsonResponseModel { Result = true }); - } - - - if (originalList == null) - { - var userModel = new UsersToNotify { Username = username }; - var insertResult = await UsersToNotifyRepo.InsertAsync(userModel); - return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" }); - } - - var existingUser = originalList.FirstOrDefault(x => x.Username == username); - if (existingUser != null) - { - return Response.AsJson(new JsonResponseModel { Result = true }); // It's already enabled - } - else - { - var userModel = new UsersToNotify { Username = username }; - var insertResult = await UsersToNotifyRepo.InsertAsync(userModel); - return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" }); - } - - } - private async Task GetUserNotificationSettings() - { - var all = await UsersToNotifyRepo.GetAllAsync(); - var retVal = all.FirstOrDefault(x => x.Username == Username); - return Response.AsJson(retVal != null); - } - - private Response GetSeasons() - { - var tv = new TvMazeApi(); - var seriesId = (int)Request.Query.tvId; - var show = tv.ShowLookupByTheTvDbId(seriesId); - var seasons = tv.GetSeasons(show.id); - var model = seasons.Select(x => x.number); - return Response.AsJson(model); - } - - - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SearchModule.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.Globalization; +using System.Linq; + +using Nancy; +using Nancy.Responses.Negotiation; + +using NLog; + +using PlexRequests.Api; +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Music; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Helpers.Exceptions; +using PlexRequests.Services.Interfaces; +using PlexRequests.Services.Notification; +using PlexRequests.Store; +using PlexRequests.UI.Helpers; +using PlexRequests.UI.Models; +using System.Threading.Tasks; + +using Nancy.Extensions; +using Nancy.Responses; + +using PlexRequests.Api.Models.Tv; +using PlexRequests.Core.Models; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; + +using TMDbLib.Objects.General; + +namespace PlexRequests.UI.Modules +{ + public class SearchModule : BaseAuthModule + { + public SearchModule(ICacheProvider cache, ISettingsService cpSettings, + ISettingsService prSettings, IAvailabilityChecker checker, + IRequestService request, ISonarrApi sonarrApi, ISettingsService sonarrSettings, + ISettingsService sickRageService, ICouchPotatoApi cpApi, ISickRageApi srApi, + INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, ISettingsService hpService, + ICouchPotatoCacher cpCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi, + ISettingsService plexService, ISettingsService auth, IRepository u, ISettingsService email, + IIssueService issue) : base("search", prSettings) + { + Auth = auth; + PlexService = plexService; + PlexApi = plexApi; + CpService = cpSettings; + PrService = prSettings; + MovieApi = new TheMovieDbApi(); + Cache = cache; + Checker = checker; + CpCacher = cpCacher; + SonarrCacher = sonarrCacher; + SickRageCacher = sickRageCacher; + RequestService = request; + SonarrApi = sonarrApi; + SonarrService = sonarrSettings; + CouchPotatoApi = cpApi; + SickRageService = sickRageService; + SickrageApi = srApi; + NotificationService = notify; + MusicBrainzApi = mbApi; + HeadphonesApi = hpApi; + HeadphonesService = hpService; + UsersToNotifyRepo = u; + EmailNotificationSettings = email; + IssueService = issue; + + + Get["/", true] = async (x, ct) => await RequestLoad(); + + Get["movie/{searchTerm}", true] = async (x, ct) => await SearchMovie((string)x.searchTerm); + Get["tv/{searchTerm}", true] = async (x, ct) => await SearchTvShow((string)x.searchTerm); + Get["music/{searchTerm}", true] = async (x, ct) => await SearchMusic((string)x.searchTerm); + Get["music/coverArt/{id}"] = p => GetMusicBrainzCoverArt((string)p.id); + + Get["movie/upcoming", true] = async (x, ct) => await UpcomingMovies(); + Get["movie/playing", true] = async (x, ct) => await CurrentlyPlayingMovies(); + + Post["request/movie", true] = async (x, ct) => await RequestMovie((int)Request.Form.movieId); + Post["request/tv", true] = async (x, ct) => await RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons); + Post["request/album", true] = async (x, ct) => await RequestAlbum((string)Request.Form.albumId); + + Post["/notifyuser", true] = async (x, ct) => await NotifyUser((bool)Request.Form.notify); + Get["/notifyuser", true] = async (x, ct) => await GetUserNotificationSettings(); + + Get["/seasons"] = x => GetSeasons(); + } + private IPlexApi PlexApi { get; } + private TheMovieDbApi MovieApi { get; } + private INotificationService NotificationService { get; } + private ICouchPotatoApi CouchPotatoApi { get; } + private ISonarrApi SonarrApi { get; } + private ISickRageApi SickrageApi { get; } + private IRequestService RequestService { get; } + private ICacheProvider Cache { get; } + private ISettingsService Auth { get; } + private ISettingsService PlexService { get; } + private ISettingsService CpService { get; } + private ISettingsService PrService { get; } + private ISettingsService SonarrService { get; } + private ISettingsService SickRageService { get; } + private ISettingsService HeadphonesService { get; } + private ISettingsService EmailNotificationSettings { get; } + private IAvailabilityChecker Checker { get; } + private ICouchPotatoCacher CpCacher { get; } + private ISonarrCacher SonarrCacher { get; } + private ISickRageCacher SickRageCacher { get; } + private IMusicBrainzApi MusicBrainzApi { get; } + private IHeadphonesApi HeadphonesApi { get; } + private IRepository UsersToNotifyRepo { get; } + private IIssueService IssueService { get; } + private static Logger Log = LogManager.GetCurrentClassLogger(); + + private async Task RequestLoad() + { + var settings = await PrService.GetSettingsAsync(); + + Log.Trace("Loading Index"); + return View["Search/Index", settings]; + } + + private async Task UpcomingMovies() + { + Log.Trace("Loading upcoming movies"); + return await ProcessMovies(MovieSearchType.Upcoming, string.Empty); + } + + private async Task CurrentlyPlayingMovies() + { + Log.Trace("Loading currently playing movies"); + return await ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty); + } + + private async Task SearchMovie(string searchTerm) + { + Log.Trace("Searching for Movie {0}", searchTerm); + return await ProcessMovies(MovieSearchType.Search, searchTerm); + } + + private async Task ProcessMovies(MovieSearchType searchType, string searchTerm) + { + var apiMovies = new List(); + await Task.Factory.StartNew( + () => + { + switch (searchType) + { + case MovieSearchType.Search: + return + MovieApi.SearchMovie(searchTerm) + .Result.Select( + x => + new MovieResult() + { + Adult = x.Adult, + BackdropPath = x.BackdropPath, + GenreIds = x.GenreIds, + Id = x.Id, + OriginalLanguage = x.OriginalLanguage, + OriginalTitle = x.OriginalTitle, + Overview = x.Overview, + Popularity = x.Popularity, + PosterPath = x.PosterPath, + ReleaseDate = x.ReleaseDate, + Title = x.Title, + Video = x.Video, + VoteAverage = x.VoteAverage, + VoteCount = x.VoteCount + }) + .ToList(); + case MovieSearchType.CurrentlyPlaying: + return MovieApi.GetCurrentPlayingMovies().Result.ToList(); + case MovieSearchType.Upcoming: + return MovieApi.GetUpcomingMovies().Result.ToList(); + default: + return new List(); + } + }).ContinueWith( + (t) => + { + apiMovies = t.Result; + }); + + var allResults = await RequestService.GetAllAsync(); + allResults = allResults.Where(x => x.Type == RequestType.Movie); + + var distinctResults = allResults.DistinctBy(x => x.ProviderId); + var dbMovies = distinctResults.ToDictionary(x => x.ProviderId); + + + var cpCached = CpCacher.QueuedIds(); + var plexMovies = Checker.GetPlexMovies(); + var settings = await PrService.GetSettingsAsync(); + var viewMovies = new List(); + foreach (MovieResult movie in apiMovies) + { + var viewMovie = new SearchMovieViewModel + { + Adult = movie.Adult, + BackdropPath = movie.BackdropPath, + GenreIds = movie.GenreIds, + Id = movie.Id, + OriginalLanguage = movie.OriginalLanguage, + OriginalTitle = movie.OriginalTitle, + Overview = movie.Overview, + Popularity = movie.Popularity, + PosterPath = movie.PosterPath, + ReleaseDate = movie.ReleaseDate, + Title = movie.Title, + Video = movie.Video, + VoteAverage = movie.VoteAverage, + VoteCount = movie.VoteCount + }; + var canSee = CanUserSeeThisRequest(viewMovie.Id, settings.UsersCanViewOnlyOwnRequests, dbMovies); + if (Checker.IsMovieAvailable(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString())) + { + viewMovie.Available = true; + } + else if (dbMovies.ContainsKey(movie.Id) && canSee) // compare to the requests db + { + var dbm = dbMovies[movie.Id]; + + viewMovie.Requested = true; + viewMovie.Approved = dbm.Approved; + viewMovie.Available = dbm.Available; + } + else if (cpCached.Contains(movie.Id) && canSee) // compare to the couchpotato db + { + viewMovie.Requested = true; + } + + viewMovies.Add(viewMovie); + } + + return Response.AsJson(viewMovies); + } + + private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests, Dictionary moviesInDb) + { + if (usersCanViewOnlyOwnRequests) + { + var result = moviesInDb.FirstOrDefault(x => x.Value.ProviderId == movieId); + return result.Value == null || result.Value.UserHasRequested(Username); + } + + return true; + } + + private async Task SearchTvShow(string searchTerm) + { + var plexSettings = await PlexService.GetSettingsAsync(); + Log.Trace("Searching for TV Show {0}", searchTerm); + + var apiTv = new List(); + await Task.Factory.StartNew(() => new TvMazeApi().Search(searchTerm)).ContinueWith((t) => + { + apiTv = t.Result; + }); + + var allResults = await RequestService.GetAllAsync(); + allResults = allResults.Where(x => x.Type == RequestType.TvShow); + + var dbTv = allResults.ToDictionary(x => x.ProviderId); + + if (!apiTv.Any()) + { + Log.Trace("TV Show data is null"); + return Response.AsJson(""); + } + + var sonarrCached = SonarrCacher.QueuedIds(); + var sickRageCache = SickRageCacher.QueuedIds(); // consider just merging sonarr/sickrage arrays + var plexTvShows = Checker.GetPlexTvShows(); + + var viewTv = new List(); + foreach (var t in apiTv) + { + var banner = t.show.image?.medium; + if (!string.IsNullOrEmpty(banner)) + { + banner = banner.Replace("http", "https"); + } + + var viewT = new SearchTvShowViewModel + { + Banner = banner, + FirstAired = t.show.premiered, + Id = t.show.externals?.thetvdb ?? 0, + ImdbId = t.show.externals?.imdb, + Network = t.show.network?.name, + NetworkId = t.show.network?.id.ToString(), + Overview = t.show.summary.RemoveHtml(), + Rating = t.score.ToString(CultureInfo.CurrentUICulture), + Runtime = t.show.runtime.ToString(), + SeriesId = t.show.id, + SeriesName = t.show.name, + Status = t.show.status + }; + + + var providerId = string.Empty; + + if (plexSettings.AdvancedSearch) + { + providerId = viewT.Id.ToString(); + } + + if (Checker.IsTvShowAvailable(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId)) + { + viewT.Available = true; + } + else if (t.show?.externals?.thetvdb != null) + { + int tvdbid = (int)t.show.externals.thetvdb; + + if (dbTv.ContainsKey(tvdbid)) + { + var dbt = dbTv[tvdbid]; + + viewT.Requested = true; + viewT.Approved = dbt.Approved; + viewT.Available = dbt.Available; + } + else if (sonarrCached.Contains(tvdbid) || sickRageCache.Contains(tvdbid)) // compare to the sonarr/sickrage db + { + viewT.Requested = true; + } + } + + viewTv.Add(viewT); + } + + return Response.AsJson(viewTv); + } + + private async Task SearchMusic(string searchTerm) + { + var apiAlbums = new List(); + await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) => + { + apiAlbums = t.Result.releases ?? new List(); + }); + + var allResults = await RequestService.GetAllAsync(); + allResults = allResults.Where(x => x.Type == RequestType.Album); + + var dbAlbum = allResults.ToDictionary(x => x.MusicBrainzId); + + var plexAlbums = Checker.GetPlexAlbums(); + + var viewAlbum = new List(); + foreach (var a in apiAlbums) + { + var viewA = new SearchMusicViewModel + { + Title = a.title, + Id = a.id, + Artist = a.ArtistCredit?.Select(x => x.artist?.name).FirstOrDefault(), + Overview = a.disambiguation, + ReleaseDate = a.date, + TrackCount = a.TrackCount, + ReleaseType = a.status, + Country = a.country + }; + + DateTime release; + DateTimeHelper.CustomParse(a.ReleaseEvents?.FirstOrDefault()?.date, out release); + var artist = a.ArtistCredit?.FirstOrDefault()?.artist; + if (Checker.IsAlbumAvailable(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name)) + { + viewA.Available = true; + } + if (!string.IsNullOrEmpty(a.id) && dbAlbum.ContainsKey(a.id)) + { + var dba = dbAlbum[a.id]; + + viewA.Requested = true; + viewA.Approved = dba.Approved; + viewA.Available = dba.Available; + } + + viewAlbum.Add(viewA); + } + return Response.AsJson(viewAlbum); + } + + private async Task RequestMovie(int movieId) + { + var movieInfo = MovieApi.GetMovieInformation(movieId).Result; + var fullMovieName = $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}"; + Log.Trace("Getting movie info from TheMovieDb"); + + var settings = await PrService.GetSettingsAsync(); + + // check if the movie has already been requested + Log.Info("Requesting movie with id {0}", movieId); + var existingRequest = await RequestService.CheckRequestAsync(movieId); + if (existingRequest != null) + { + // check if the current user is already marked as a requester for this movie, if not, add them + if (!existingRequest.UserHasRequested(Username)) + { + existingRequest.RequestedUsers.Add(Username); + await RequestService.UpdateRequestAsync(existingRequest); + } + + return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullMovieName} was successfully added!" : $"{fullMovieName} has already been requested!" }); + } + + Log.Debug("movie with id {0} doesnt exists", movieId); + + try + { + var movies = Checker.GetPlexMovies(); + if (Checker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullMovieName} is already in Plex!" }); + } + } + catch (Exception e) + { + Log.Error(e); + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {fullMovieName} is in Plex, are you sure it's correctly setup?" }); + } + //#endif + + var model = new RequestedModel + { + ProviderId = movieInfo.Id, + Type = RequestType.Movie, + Overview = movieInfo.Overview, + ImdbId = movieInfo.ImdbId, + PosterPath = "https://image.tmdb.org/t/p/w150/" + movieInfo.PosterPath, + Title = movieInfo.Title, + ReleaseDate = movieInfo.ReleaseDate ?? DateTime.MinValue, + Status = movieInfo.Status, + RequestedDate = DateTime.UtcNow, + Approved = false, + RequestedUsers = new List { Username }, + Issues = IssueState.None, + + }; + + if (ShouldAutoApprove(RequestType.Movie, settings)) + { + var cpSettings = await CpService.GetSettingsAsync(); + + if (cpSettings.Enabled) + { + Log.Info("Adding movie to CP (No approval required)"); + var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, + cpSettings.FullUri, cpSettings.ProfileId); + Log.Debug("Adding movie to CP result {0}", result); + if (result) + { + model.Approved = true; + Log.Info("Adding movie to database (No approval required)"); + await RequestService.AddRequestAsync(model); + + if (ShouldSendNotification(settings)) + { + var notificationModel = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest + }; + await NotificationService.Publish(notificationModel); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); + } + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = + "Something went wrong adding the movie to CouchPotato! Please check your settings." + }); + } + else + { + model.Approved = true; + Log.Info("Adding movie to database (No approval required)"); + await RequestService.AddRequestAsync(model); + + if (ShouldSendNotification(settings)) + { + var notificationModel = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest + }; + await NotificationService.Publish(notificationModel); + } + + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); + } + } + + try + { + Log.Info("Adding movie to database"); + var id = await RequestService.AddRequestAsync(model); + + var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; + await NotificationService.Publish(notificationModel); + + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); + } + catch (Exception e) + { + Log.Fatal(e); + + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something went wrong adding the movie to CouchPotato! Please check your settings." }); + } + } + + /// + /// Requests the tv show. + /// + /// The show identifier. + /// The seasons. + /// + private async Task RequestTvShow(int showId, string seasons) + { + var tvApi = new TvMazeApi(); + + var showInfo = tvApi.ShowLookupByTheTvDbId(showId); + DateTime firstAir; + DateTime.TryParse(showInfo.premiered, out firstAir); + string fullShowName = $"{showInfo.name} ({firstAir.Year})"; + //#if !DEBUG + + var settings = await PrService.GetSettingsAsync(); + + // check if the show has already been requested + Log.Info("Requesting tv show with id {0}", showId); + var existingRequest = await RequestService.CheckRequestAsync(showId); + if (existingRequest != null) + { + // check if the current user is already marked as a requester for this show, if not, add them + if (!existingRequest.UserHasRequested(Username)) + { + existingRequest.RequestedUsers.Add(Username); + await RequestService.UpdateRequestAsync(existingRequest); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullShowName} was successfully added!" : $"{fullShowName} has already been requested!" }); + } + + try + { + var shows = Checker.GetPlexTvShows(); + var providerId = string.Empty; + var plexSettings = await PlexService.GetSettingsAsync(); + if (plexSettings.AdvancedSearch) + { + providerId = showId.ToString(); + } + if (Checker.IsTvShowAvailable(shows.ToArray(), showInfo.name, showInfo.premiered?.Substring(0, 4), providerId)) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} is already in Plex!" }); + } + } + catch (Exception) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {fullShowName} is in Plex, are you sure it's correctly setup?" }); + } + //#endif + + + var model = new RequestedModel + { + ProviderId = showInfo.externals?.thetvdb ?? 0, + Type = RequestType.TvShow, + Overview = showInfo.summary.RemoveHtml(), + PosterPath = showInfo.image?.medium, + Title = showInfo.name, + ReleaseDate = firstAir, + Status = showInfo.status, + RequestedDate = DateTime.UtcNow, + Approved = false, + RequestedUsers = new List { Username }, + Issues = IssueState.None, + ImdbId = showInfo.externals?.imdb ?? string.Empty, + SeasonCount = showInfo.seasonCount, + TvDbId = showId.ToString() + }; + + var seasonsList = new List(); + switch (seasons) + { + case "first": + seasonsList.Add(1); + model.SeasonsRequested = "First"; + break; + case "latest": + seasonsList.Add(model.SeasonCount); + model.SeasonsRequested = "Latest"; + break; + case "all": + model.SeasonsRequested = "All"; + break; + default: + model.SeasonsRequested = seasons; + var split = seasons.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var seasonsCount = new int[split.Length]; + for (var i = 0; i < split.Length; i++) + { + int tryInt; + int.TryParse(split[i], out tryInt); + seasonsCount[i] = tryInt; + } + seasonsList.AddRange(seasonsCount); + break; + } + + model.SeasonList = seasonsList.ToArray(); + + if (ShouldAutoApprove(RequestType.TvShow, settings)) + { + var sonarrSettings = await SonarrService.GetSettingsAsync(); + var sender = new TvSender(SonarrApi, SickrageApi); + if (sonarrSettings.Enabled) + { + var result = sender.SendToSonarr(sonarrSettings, model); + if (!string.IsNullOrEmpty(result?.title)) + { + model.Approved = true; + Log.Debug("Adding tv to database requests (No approval required & Sonarr)"); + await RequestService.AddRequestAsync(model); + + if (ShouldSendNotification(settings)) + { + var notify1 = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest + }; + await NotificationService.Publish(notify1); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); + } + + + return Response.AsJson(ValidationHelper.SendSonarrError(result?.ErrorMessages)); + + } + + var srSettings = SickRageService.GetSettings(); + if (srSettings.Enabled) + { + var result = sender.SendToSickRage(srSettings, model); + if (result?.result == "success") + { + model.Approved = true; + Log.Debug("Adding tv to database requests (No approval required & SickRage)"); + await RequestService.AddRequestAsync(model); + if (ShouldSendNotification(settings)) + { + var notify2 = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest + }; + await NotificationService.Publish(notify2); + } + + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); + } + return Response.AsJson(new JsonResponseModel { Result = false, Message = result?.message != null ? "Message From SickRage: " + result.message : "Something went wrong adding the movie to SickRage! Please check your settings." }); + } + + if (!srSettings.Enabled && !sonarrSettings.Enabled) + { + model.Approved = true; + Log.Debug("Adding tv to database requests (No approval required) and Sonarr/Sickrage not setup"); + await RequestService.AddRequestAsync(model); + if (ShouldSendNotification(settings)) + { + var notify2 = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest + }; + await NotificationService.Publish(notify2); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); + } + + return Response.AsJson(new JsonResponseModel { Result = false, Message = "The request of TV Shows is not correctly set up. Please contact your admin." }); + + } + + await RequestService.AddRequestAsync(model); + + var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; + await NotificationService.Publish(notificationModel); + + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); + } + + private bool ShouldSendNotification(PlexRequestSettings prSettings) + { + var sendNotification = !prSettings.IgnoreNotifyForAutoApprovedRequests; + var claims = Context.CurrentUser?.Claims; + if (claims != null) + { + var enumerable = claims as string[] ?? claims.ToArray(); + if (enumerable.Contains(UserClaims.Admin) || enumerable.Contains(UserClaims.PowerUser)) + { + sendNotification = false; // Don't bother sending a notification if the user is an admin + } + } + return sendNotification; + } + + + private async Task RequestAlbum(string releaseId) + { + var settings = await PrService.GetSettingsAsync(); + var existingRequest = await RequestService.CheckRequestAsync(releaseId); + Log.Debug("Checking for an existing request"); + + if (existingRequest != null) + { + Log.Debug("We do have an existing album request"); + if (!existingRequest.UserHasRequested(Username)) + { + + Log.Debug("Not in the requested list so adding them and updating the request. User: {0}", Username); + existingRequest.RequestedUsers.Add(Username); + await RequestService.UpdateRequestAsync(existingRequest); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{existingRequest.Title} was successfully added!" : $"{existingRequest.Title} has already been requested!" }); + } + + Log.Debug("This is a new request"); + + var albumInfo = MusicBrainzApi.GetAlbum(releaseId); + DateTime release; + DateTimeHelper.CustomParse(albumInfo.ReleaseEvents?.FirstOrDefault()?.date, out release); + + var artist = albumInfo.ArtistCredits?.FirstOrDefault()?.artist; + if (artist == null) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "We could not find the artist on MusicBrainz. Please try again later or contact your admin" }); + } + + var albums = Checker.GetPlexAlbums(); + var alreadyInPlex = Checker.IsAlbumAvailable(albums.ToArray(), albumInfo.title, release.ToString("yyyy"), artist.name); + + if (alreadyInPlex) + { + return Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{albumInfo.title} is already in Plex!" + }); + } + + var img = GetMusicBrainzCoverArt(albumInfo.id); + + var model = new RequestedModel + { + Title = albumInfo.title, + MusicBrainzId = albumInfo.id, + Overview = albumInfo.disambiguation, + PosterPath = img, + Type = RequestType.Album, + ProviderId = 0, + RequestedUsers = new List { Username }, + Status = albumInfo.status, + Issues = IssueState.None, + RequestedDate = DateTime.UtcNow, + ReleaseDate = release, + ArtistName = artist.name, + ArtistId = artist.id + }; + + if (ShouldAutoApprove(RequestType.Album, settings)) + { + Log.Debug("We don't require approval OR the user is in the whitelist"); + var hpSettings = HeadphonesService.GetSettings(); + + if (!hpSettings.Enabled) + { + await RequestService.AddRequestAsync(model); + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{model.Title} was successfully added!" + }); + } + + var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService); + await sender.AddAlbum(model); + model.Approved = true; + await RequestService.AddRequestAsync(model); + + if (ShouldSendNotification(settings)) + { + var notify2 = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest + }; + await NotificationService.Publish(notify2); + } + + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{model.Title} was successfully added!" + }); + } + + if (ShouldSendNotification(settings)) + { + var notify2 = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest + }; + await NotificationService.Publish(notify2); + } + await RequestService.AddRequestAsync(model); + return Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{model.Title} was successfully added!" + }); + } + + private string GetMusicBrainzCoverArt(string id) + { + var coverArt = MusicBrainzApi.GetCoverArt(id); + var firstImage = coverArt?.images?.FirstOrDefault(); + var img = string.Empty; + + if (firstImage != null) + { + img = firstImage.thumbnails?.small ?? firstImage.image; + } + + return img; + } + + private bool ShouldAutoApprove(RequestType requestType, PlexRequestSettings prSettings) + { + // if the user is an admin or they are whitelisted, they go ahead and allow auto-approval + if (IsAdmin || prSettings.ApprovalWhiteList.Any(x => x.Equals(Username, StringComparison.OrdinalIgnoreCase))) return true; + + // check by request type if the category requires approval or not + switch (requestType) + { + case RequestType.Movie: + return !prSettings.RequireMovieApproval; + case RequestType.TvShow: + return !prSettings.RequireTvShowApproval; + case RequestType.Album: + return !prSettings.RequireMusicApproval; + default: + return false; + } + } + + private async Task NotifyUser(bool notify) + { + var authSettings = await Auth.GetSettingsAsync(); + var auth = authSettings.UserAuthentication; + var emailSettings = await EmailNotificationSettings.GetSettingsAsync(); + var email = emailSettings.EnableUserEmailNotifications; + if (!auth) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but this functionality is currently only for users with Plex accounts" }); + } + if (!email) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but your administrator has not yet enabled this functionality." }); + } + var username = Username; + var originalList = await UsersToNotifyRepo.GetAllAsync(); + if (!notify) + { + if (originalList == null) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "We could not remove this notification because you never had it!" }); + } + var userToRemove = originalList.FirstOrDefault(x => x.Username == username); + if (userToRemove != null) + { + await UsersToNotifyRepo.DeleteAsync(userToRemove); + } + return Response.AsJson(new JsonResponseModel { Result = true }); + } + + + if (originalList == null) + { + var userModel = new UsersToNotify { Username = username }; + var insertResult = await UsersToNotifyRepo.InsertAsync(userModel); + return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" }); + } + + var existingUser = originalList.FirstOrDefault(x => x.Username == username); + if (existingUser != null) + { + return Response.AsJson(new JsonResponseModel { Result = true }); // It's already enabled + } + else + { + var userModel = new UsersToNotify { Username = username }; + var insertResult = await UsersToNotifyRepo.InsertAsync(userModel); + return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" }); + } + + } + private async Task GetUserNotificationSettings() + { + var all = await UsersToNotifyRepo.GetAllAsync(); + var retVal = all.FirstOrDefault(x => x.Username == Username); + return Response.AsJson(retVal != null); + } + + private Response GetSeasons() + { + var tv = new TvMazeApi(); + var seriesId = (int)Request.Query.tvId; + var show = tv.ShowLookupByTheTvDbId(seriesId); + var seasons = tv.GetSeasons(show.id); + var model = seasons.Select(x => x.number); + return Response.AsJson(model); + } + + + } +} diff --git a/PlexRequests.UI/Views/Admin/Settings.cshtml b/PlexRequests.UI/Views/Admin/Settings.cshtml index b6ad5d09d..423b98502 100644 --- a/PlexRequests.UI/Views/Admin/Settings.cshtml +++ b/PlexRequests.UI/Views/Admin/Settings.cshtml @@ -1,294 +1,309 @@ -@using PlexRequests.UI.Helpers -@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase -@Html.Partial("_Sidebar") -@{ - int port; - if (Model.Port == 0) - { - port = 3579; - } - else - { - port = Model.Port; - } - - - var baseUrl = Html.GetBaseUrl(); - var formAction = "/admin"; - if (!string.IsNullOrEmpty(baseUrl.ToHtmlString())) - { - formAction = "/" + baseUrl.ToHtmlString() + formAction; - } - var plexTheme = string.Empty; - var originalTheme = string.Empty; - - if (!string.IsNullOrEmpty(Model.ThemeName)) - { - plexTheme = Model.ThemeName.Equals(Themes.PlexTheme) ? "selected=\"selected\"" : string.Empty; - originalTheme = Model.ThemeName.Equals(Themes.OriginalTheme) ? "selected=\"selected\"" : string.Empty; - } - else - { - plexTheme = "selected=\"selected\""; - } -} -
-
-
- Plex Request Settings -
- - -
- -
-
- You will have to restart after changing the port. - -
- - -
- -
-
- You will have to restart after changing the url base. - -
- -
- - -
-
-
-
-
- -
- -
- -
-
- -
-
- - @if (Model.SearchForMovies) - { - - } - else - { - - } - -
-
- -
-
- - @if (Model.SearchForTvShows) - { - - } - else - { - - } - -
-
-
-
- - @if (Model.SearchForMusic) - { - - } - else - { - - } - -
-
-
-
- - @if (Model.RequireMovieApproval) - { - - } - else - { - - } -
-
-
-
- - @if (Model.RequireTvShowApproval) - { - - } - else - { - - } - - -
-
-
-
- - @if (Model.RequireMusicApproval) - { - - } - else - { - - } - - -
-
- -
-
- - @if (Model.UsersCanViewOnlyOwnRequests) - { - - - } - else - { - - } - - -
-
- -
-
- - @if (Model.UsersCanViewOnlyOwnIssues) - { - - - } - else - { - - } -
-
- -
-
- - @if (Model.CollectAnalyticData) - { - - - } - else - { - - } -
-
- - - -

A comma separated list of users whose requests do not require approval.

-
- -
- -
-
- - @*
- -
- -
-
//TODO: Need to implement this*@ - -
-
-
-
- -
-
-
-
-
- - \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 6771fa74b..cfef4c77b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,26 +1,26 @@ -version: 1.8.{build} -configuration: Release -assembly_info: - patch: true - file: '**\AssemblyInfo.*' - assembly_version: '1.8.1' - assembly_file_version: '{version}' - assembly_informational_version: '1.8.1' -before_build: -- cmd: appveyor-retry nuget restore -build: - verbosity: minimal -after_build: -- cmd: >- - 7z a PlexRequests.zip %APPVEYOR_BUILD_FOLDER%\PlexRequests.UI\bin\Release\ - - appveyor PushArtifact PlexRequests.zip - -deploy: -- provider: GitHub - release: PlexRequests v$(appveyor_build_version) - auth_token: - secure: jDpp1/WUQl3uN41fNI3VeZoRZbDiDfs3GPQ1v+C5ZNE3cWdnUvuJfCCfUbYUV1Rp - draft: true - on: - branch: master +version: 1.8.{build} +configuration: Release +assembly_info: + patch: true + file: '**\AssemblyInfo.*' + assembly_version: '1.8.1' + assembly_file_version: '{version}' + assembly_informational_version: '1.8.1' +before_build: +- cmd: appveyor-retry nuget restore +build: + verbosity: minimal +after_build: +- cmd: >- + 7z a PlexRequests.zip %APPVEYOR_BUILD_FOLDER%\PlexRequests.UI\bin\Release\ + + appveyor PushArtifact PlexRequests.zip + +deploy: +- provider: GitHub + release: PlexRequests v$(appveyor_build_version) + auth_token: + secure: jDpp1/WUQl3uN41fNI3VeZoRZbDiDfs3GPQ1v+C5ZNE3cWdnUvuJfCCfUbYUV1Rp + draft: true + on: + branch: master