#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) { 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 }; 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 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 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"); //#if !DEBUG 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(); 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 (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, }; 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); 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) { 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); } } }