#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 System.Windows.Forms; using Nancy.Extensions; using PlexRequests.Api.Models.Tv; 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) : 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; Get["/"] = parameters => RequestLoad(); Get["movie/{searchTerm}"] = parameters => SearchMovie((string)parameters.searchTerm); Get["tv/{searchTerm}"] = parameters => SearchTvShow((string)parameters.searchTerm); Get["music/{searchTerm}"] = parameters => SearchMusic((string)parameters.searchTerm); Get["music/coverArt/{id}"] = p => GetMusicBrainzCoverArt((string)p.id); Get["movie/upcoming"] = parameters => UpcomingMovies(); Get["movie/playing"] = parameters => CurrentlyPlayingMovies(); Post["request/movie"] = parameters => RequestMovie((int)Request.Form.movieId); Post["request/tv"] = parameters => RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons); Post["request/album"] = parameters => RequestAlbum((string)Request.Form.albumId); Post["/notifyuser"] = x => NotifyUser((bool)Request.Form.notify); Get["/notifyuser"] = x => 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 static Logger Log = LogManager.GetCurrentClassLogger(); private Negotiator RequestLoad() { var settings = PrService.GetSettings(); Log.Trace("Loading Index"); return View["Search/Index", settings]; } private Response UpcomingMovies() { Log.Trace("Loading upcoming movies"); return ProcessMovies(MovieSearchType.Upcoming, string.Empty); } private Response CurrentlyPlayingMovies() { Log.Trace("Loading currently playing movies"); return ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty); } private Response SearchMovie(string searchTerm) { Log.Trace("Searching for Movie {0}", searchTerm); return ProcessMovies(MovieSearchType.Search, searchTerm); } private Response ProcessMovies(MovieSearchType searchType, string searchTerm) { var taskList = new List(); var apiMovies = new List(); taskList.Add(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; })); Dictionary dbMovies = new Dictionary(); taskList.Add(Task.Factory.StartNew(() => { return RequestService.GetAll().Where(x => x.Type == RequestType.Movie); }).ContinueWith((t) => { var distinctResults = t.Result.DistinctBy(x => x.ProviderId); dbMovies = distinctResults.ToDictionary(x => x.ProviderId); })); Task.WaitAll(taskList.ToArray()); var cpCached = CpCacher.QueuedIds(); var plexMovies = Checker.GetPlexMovies(); var settings = PrService.GetSettings(); 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 Response SearchTvShow(string searchTerm) { Log.Trace("Searching for TV Show {0}", searchTerm); var taskList = new List(); var apiTv = new List(); taskList.Add(Task.Factory.StartNew(() => { return new TvMazeApi().Search(searchTerm); }).ContinueWith((t) => { apiTv = t.Result; })); var dbTv = new Dictionary(); taskList.Add(Task.Factory.StartNew(() => { return RequestService.GetAll().Where(x => x.Type == RequestType.TvShow); }).ContinueWith((t) => { dbTv = t.Result.ToDictionary(x => x.ProviderId); })); Task.WaitAll(taskList.ToArray()); 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 viewT = new SearchTvShowViewModel { // We are constructing the banner with the id: // http://thetvdb.com/banners/_cache/posters/ID-1.jpg Banner = t.show.image?.medium, 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 }; if (Checker.IsTvShowAvailable(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4))) { 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); } Log.Trace("Returning TV Show results: "); Log.Trace(viewTv.DumpJson()); return Response.AsJson(viewTv); } private Response SearchMusic(string searchTerm) { var taskList = new List(); var apiAlbums = new List(); taskList.Add(Task.Factory.StartNew(() => { return MusicBrainzApi.SearchAlbum(searchTerm); }).ContinueWith((t) => { apiAlbums = t.Result.releases ?? new List(); })); var dbAlbum = new Dictionary(); taskList.Add(Task.Factory.StartNew(() => { return RequestService.GetAll().Where(x => x.Type == RequestType.Album); }).ContinueWith((t) => { dbAlbum = t.Result.ToDictionary(x => x.MusicBrainzId); })); Task.WaitAll(taskList.ToArray()); 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 Response RequestMovie(int movieId) { var movieApi = new TheMovieDbApi(); 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"); Log.Trace(movieInfo.DumpJson); //#if !DEBUG var settings = PrService.GetSettings(); // check if the movie has already been requested Log.Info("Requesting movie with id {0}", movieId); var existingRequest = RequestService.CheckRequest(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); RequestService.UpdateRequest(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 (ApplicationSettingsException) { 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 = "http://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, }; Log.Trace(settings.DumpJson()); if (ShouldAutoApprove(RequestType.Movie, settings)) { var cpSettings = CpService.GetSettings(); Log.Trace("Settings: "); Log.Trace(cpSettings.DumpJson); 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)"); RequestService.AddRequest(model); if (ShouldSendNotification()) { var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; 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)"); RequestService.AddRequest(model); if (ShouldSendNotification()) { var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; NotificationService.Publish(notificationModel); } return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); } } try { Log.Info("Adding movie to database"); var id = RequestService.AddRequest(model); var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; 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. /// if set to true [notify]. /// private Response 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 = PrService.GetSettings(); // check if the show has already been requested Log.Info("Requesting tv show with id {0}", showId); var existingRequest = RequestService.CheckRequest(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); RequestService.UpdateRequest(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(); if (Checker.IsTvShowAvailable(shows.ToArray(), showInfo.name, showInfo.premiered?.Substring(0, 4))) { return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} is already in Plex!" }); } } catch (ApplicationSettingsException) { 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, }; 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 = SonarrService.GetSettings(); var sender = new TvSender(SonarrApi, SickrageApi); if (sonarrSettings.Enabled) { var result = sender.SendToSonarr(sonarrSettings, model); if (result != null && !string.IsNullOrEmpty(result.title)) { model.Approved = true; Log.Debug("Adding tv to database requests (No approval required & Sonarr)"); RequestService.AddRequest(model); if (ShouldSendNotification()) { var notify1 = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; 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)"); RequestService.AddRequest(model); if (ShouldSendNotification()) { var notify2 = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; 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." }); } return Response.AsJson(new JsonResponseModel { Result = false, Message = "The request of TV Shows is not correctly set up. Please contact your admin." }); } RequestService.AddRequest(model); var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; 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) { if (claims.Contains(UserClaims.Admin) || claims.Contains(UserClaims.PowerUser)) { sendNotification = false; // Don't bother sending a notification if the user is an admin } } return sendNotification; } private Response RequestAlbum(string releaseId) { var settings = PrService.GetSettings(); var existingRequest = RequestService.CheckRequest(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); RequestService.UpdateRequest(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); Log.Trace("Album Details:"); Log.Trace(albumInfo.DumpJson()); Log.Trace("CoverArt Details:"); Log.Trace(img.DumpJson()); 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(); Log.Trace("Headphone Settings:"); Log.Trace(hpSettings.DumpJson()); if (!hpSettings.Enabled) { RequestService.AddRequest(model); return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{model.Title} was successfully added!" }); } var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService); sender.AddAlbum(model).Wait(); model.Approved = true; RequestService.AddRequest(model); if (ShouldSendNotification()) { var notify2 = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; 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 }; NotificationService.Publish(notify2); } var result = RequestService.AddRequest(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 Response NotifyUser(bool notify) { var auth = Auth.GetSettings().UserAuthentication; var email = EmailNotificationSettings.GetSettings().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 = UsersToNotifyRepo.GetAll(); 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) { UsersToNotifyRepo.Delete(userToRemove); } return Response.AsJson(new JsonResponseModel { Result = true }); } if (originalList == null) { var userModel = new UsersToNotify { Username = username }; var insertResult = UsersToNotifyRepo.Insert(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 = UsersToNotifyRepo.Insert(userModel); return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" }); } } private Response GetUserNotificationSettings() { var retval = UsersToNotifyRepo.GetAll().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); } } }