diff --git a/README.md b/README.md index 2ff3e0ccd..60f886a72 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,8 @@ Search the existing requests to see if your suggestion has already been submitte ___ Get it on Google Play
-_**Note:** There is no longer an iOS app due to complications outside of our control._ - +Get it on the App Store +
# Features Here are some of the features Ombi has: diff --git a/src/Ombi.Core/Engine/BaseMediaEngine.cs b/src/Ombi.Core/Engine/BaseMediaEngine.cs index d2e18d2b2..deef9ebea 100644 --- a/src/Ombi.Core/Engine/BaseMediaEngine.cs +++ b/src/Ombi.Core/Engine/BaseMediaEngine.cs @@ -48,6 +48,8 @@ namespace Ombi.Core.Engine protected readonly ISettingsService OmbiSettings; protected readonly IRepository _subscriptionRepository; + private bool _demo = DemoSingleton.Instance.Demo; + protected async Task> GetMovieRequests() { var now = DateTime.Now.Ticks; @@ -193,6 +195,23 @@ namespace Ombi.Core.Engine return ombiSettings ?? (ombiSettings = await OmbiSettings.GetSettingsAsync()); } + protected bool DemoCheck(string title) + { + if (!title.HasValue()) + { + return false; + } + if (_demo) + { + if (ExcludedDemo.ExcludedContent.Any(x => title.Contains(x, System.Globalization.CompareOptions.OrdinalIgnoreCase))) + { + return true; + } + return false; + } + return false; + } + public class HideResult { public bool Hide { get; set; } diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 00655e921..531fa161d 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -26,19 +26,21 @@ using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; using Ombi.Core.Models; using System.Threading; +using Microsoft.Extensions.Logging; namespace Ombi.Core.Engine { public class TvRequestEngine : BaseMediaEngine, ITvRequestEngine { public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user, - INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager, + INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager, ILogger logger, ITvSender sender, IRepository rl, ISettingsService settings, ICacheService cache, IRepository sub) : base(user, requestService, rule, manager, cache, settings, sub) { TvApi = tvApi; MovieDbApi = movApi; NotificationHelper = helper; + _logger = logger; TvSender = sender; _requestLog = rl; } @@ -47,6 +49,8 @@ namespace Ombi.Core.Engine private ITvMazeApi TvApi { get; } private IMovieDbApi MovieDbApi { get; } private ITvSender TvSender { get; } + + private readonly ILogger _logger; private readonly IRepository _requestLog; public async Task RequestTvShow(TvRequestViewModel tv) @@ -69,7 +73,7 @@ namespace Ombi.Core.Engine } } - var tvBuilder = new TvShowRequestBuilder(TvApi, MovieDbApi); + var tvBuilder = new TvShowRequestBuilder(TvApi, MovieDbApi, _logger); (await tvBuilder .GetShowInfo(tv.TvDbId)) .CreateTvList(tv) diff --git a/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs b/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs index f131662be..f3023c848 100644 --- a/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs +++ b/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs @@ -315,6 +315,12 @@ namespace Ombi.Core.Engine.V2 foreach (var movie in movies) { var result = await ProcessSingleMovie(movie); + + if (DemoCheck(result.Title)) + { + continue; + } + if (settings.HideAvailableFromDiscover && result.Available) { continue; diff --git a/src/Ombi.Core/Engine/V2/MultiSearchEngine.cs b/src/Ombi.Core/Engine/V2/MultiSearchEngine.cs index b24f5a42c..fc2595676 100644 --- a/src/Ombi.Core/Engine/V2/MultiSearchEngine.cs +++ b/src/Ombi.Core/Engine/V2/MultiSearchEngine.cs @@ -35,6 +35,8 @@ namespace Ombi.Core.Engine.V2 private readonly ISettingsService _lidarrSettings; private readonly IMusicBrainzApi _musicApi; + private bool _demo = DemoSingleton.Instance.Demo; + public async Task> MultiSearch(string searchTerm, MultiSearchFilter filter, CancellationToken cancellationToken) { @@ -60,6 +62,12 @@ namespace Ombi.Core.Engine.V2 foreach (var multiSearch in movieDbData) { + + if (DemoCheck(multiSearch.title) || DemoCheck(multiSearch.name)) + { + continue; + } + var result = new MultiSearchResult { MediaType = multiSearch.media_type, diff --git a/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs b/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs index e02ae1c51..2dbd59712 100644 --- a/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs +++ b/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs @@ -155,6 +155,10 @@ namespace Ombi.Core.Engine.V2 foreach (var tvMazeSearch in items) { + if (DemoCheck(tvMazeSearch.Title)) + { + continue; + } if (settings.HideAvailableFromDiscover) { // To hide, we need to know if it's fully available, the only way to do this is to lookup it's episodes to check if we have every episode diff --git a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs index 39667eaca..fc66d197d 100644 --- a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs +++ b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs @@ -12,16 +12,19 @@ using Ombi.Helpers; using Ombi.Store.Entities; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository.Requests; +using Microsoft.Extensions.Logging; namespace Ombi.Core.Helpers { public class TvShowRequestBuilder { + private readonly ILogger _logger; - public TvShowRequestBuilder(ITvMazeApi tvApi, IMovieDbApi movApi) + public TvShowRequestBuilder(ITvMazeApi tvApi, IMovieDbApi movApi, ILogger logger) { TvApi = tvApi; MovieDbApi = movApi; + _logger = logger; } private ITvMazeApi TvApi { get; } @@ -45,6 +48,7 @@ namespace Ombi.Core.Helpers { if (result.Name.Equals(ShowInfo.name, StringComparison.InvariantCultureIgnoreCase)) { + _logger.LogInformation($"Found matching MovieDb entry for show name {ShowInfo.name}"); TheMovieDbRecord = result; var showIds = await MovieDbApi.GetTvExternals(result.Id); ShowInfo.externals.imdb = showIds.imdb_id; @@ -237,18 +241,19 @@ namespace Ombi.Core.Helpers public TvShowRequestBuilder CreateNewRequest(TvRequestViewModel tv) { + _logger.LogInformation($"Building Request for {ShowInfo.name} with Provider ID {TheMovieDbRecord?.Id ?? 0}"); NewRequest = new TvRequests { Overview = ShowInfo.summary.RemoveHtml(), PosterPath = PosterPath, Title = ShowInfo.name, ReleaseDate = FirstAir, - ExternalProviderId = TheMovieDbRecord.Id, + ExternalProviderId = TheMovieDbRecord?.Id ?? 0, Status = ShowInfo.status, ImdbId = ShowInfo.externals?.imdb ?? string.Empty, TvDbId = tv.TvDbId, ChildRequests = new List(), - TotalSeasons = tv.Seasons.Count(), + TotalSeasons = tv.Seasons?.Count ?? 0, Background = BackdropPath }; NewRequest.ChildRequests.Add(ChildRequest); diff --git a/src/Ombi.Helpers/DemoSingleton.cs b/src/Ombi.Helpers/DemoSingleton.cs index 22b6b2f31..b94c7dbb3 100644 --- a/src/Ombi.Helpers/DemoSingleton.cs +++ b/src/Ombi.Helpers/DemoSingleton.cs @@ -1,4 +1,6 @@ -namespace Ombi.Helpers +using System.Collections.Generic; + +namespace Ombi.Helpers { public class DemoSingleton { @@ -10,4 +12,460 @@ public bool Demo { get; set; } } + + public static class ExcludedDemo + { + public static HashSet ExcludedContent => new HashSet + { + "101 Dalmatians", + "102 Dalmatians", + "20,000 Leagues Under the Sea", + "A Bug's Life", + "A Far Off Place", + "A Goofy Movie", + "A Kid in King Arthur's Court", + "A Tale of Two Critters", + "A Tiger Walks", + "A Wrinkle in Time", + "ABCD 2", + "African Cats", + "Air Bud", + "Air Bud: Golden Receiver", + "Aladdin", + "Aladdin", + "Alexander and the Terrible, Horrible, No Good, Very Bad Day", + "Alice Through the Looking Glass", + "Alice in Wonderland", + "Alice in Wonderland", + "Aliens of the Deep", + "Almost Angels", + "America's Heart and Soul", + "Amy", + "Anaganaga O Dheerudu", + "Angels in the Outfield", + "Arjun: The Warrior Prince", + "Around the World in 80 Days", + "Artemis Fowl", + "Atlantis: The Lost Empire", + "Babes in Toyland", + "Bambi", + "Bears", + "Beauty and the Beast", + "Beauty and the Beast", + "Bedknobs and Broomsticks", + "Bedtime Stories", + "Benji the Hunted", + "Beverly Hills Chihuahua", + "Big Hero 6", + "Big Red", + "Blackbeard's Ghost", + "Blank Check", + "Blue", + "Bolt", + "Bon Voyage!", + "Born in China", + "Brave", + "Bridge to Terabithia", + "Brother Bear", + "Candleshoe", + "Cars", + "Cars 2", + "Cars 3", + "Charley and the Angel", + "Charlie, the Lonesome Cougar", + "Cheetah", + "Chicken Little", + "Chimpanzee", + "Christopher Robin", + "Cinderella", + "Cinderella", + "Coco", + "College Road Trip", + "Condorman", + "Confessions of a Teenage Drama Queen", + "Cool Runnings", + "D2: The Mighty Ducks", + "D3: The Mighty Ducks", + "Dangal", + "Darby O'Gill and the Little People", + "Dasavathaaram", + "Davy Crockett and the River Pirates", + "Davy Crockett, King of the Wild Frontier", + "Dinosaur", + "Disney's A Christmas Carol", + "Disney's The Kid", + "Do Dooni Chaar", + "Dolphin Reef", + "Doug's 1st Movie", + "Dragonslayer", + "DuckTales the Movie: Treasure of the Lost Lamp", + "Dumbo", + "Dumbo", + "Earth", + "Eight Below", + "Emil and the Detectives", + "Enchanted", + "Endurance", + "Escape to Witch Mountain", + "Expedition China", + "Fantasia", + "Fantasia 2000", + "Finding Dory", + "Finding Nemo", + "First Kid", + "Flight of the Navigator", + "Flubber", + "Follow Me, Boys!", + "Frank and Ollie", + "Frankenweenie", + "Freaky Friday", + "Freaky Friday", + "Frozen", + "Frozen II", + "Onward", + "Star Wars", + "Raya", + "Mandalorian", + "Fun and Fancy Free", + "G-Force", + "George of the Jungle", + "Ghost in the Shell 2: Innocence GITS2", + "Ghost of the Mountains", + "Ghosts of the Abyss", + "Glory Road", + "Greyfriars Bobby", + "Growing Up Wild", + "Gus", + "Hannah Montana and Miley Cyrus: Best of Both Worlds Concert", + "Hannah Montana: The Movie", + "Heavyweights", + "Herbie Goes Bananas", + "Herbie Goes to Monte Carlo", + "Herbie Rides Again", + "Herbie: Fully Loaded", + "Hercules", + "High School Musical 3: Senior Year", + "Hocus Pocus", + "Holes", + "Home on the Range", + "Homeward Bound II: Lost in San Francisco", + "Homeward Bound: The Incredible Journey", + "Honey, I Blew Up the Kid", + "Honey, I Shrunk the Kids", + "Hot Lead and Cold Feet", + "I'll Be Home for Christmas", + "Ice Princess", + "In Search of the Castaways", + "Incredibles 2", + "Inside Out", + "Inspector Gadget", + "Into the Woods", + "Invincible", + "Iron Will", + "Jagga Jasoos", + "James and the Giant Peach", + "John Carter", + "Johnny Tremain", + "Jonas Brothers: The 3D Concert Experience", + "Jungle 2 Jungle", + "Jungle Cat", + "Khoobsurat", + "Kidnapped", + "King of the Grizzlies", + "L'Empereur - March of the Penguins 2: The Next Step[a]", + "Lady and the Tramp", + "Lady and the Tramp", + "Lilly the Witch: The Dragon and the Magic Book", + "Lilly the Witch: The Journey to Mandolan", + "Lilo & Stitch", + "Lt. Robin Crusoe, U.S.N.", + "Make Mine Music", + "Maleficent", + "Maleficent: Mistress of Evil", + "Man of the House", + "Mars Needs Moms", + "Mary Poppins", + "Mary Poppins Returns", + "Max Keeble's Big Move", + "McFarland, USA", + "Meet the Deedles", + "Meet the Robinsons", + "Melody Time", + "Midnight Madness", + "Mighty Joe Young", + "Million Dollar Arm", + "Miracle", + "Miracle of the White Stallions", + "Moana", + "Monkey Kingdom", + "Monkeys, Go Home!", + "Monsters University", + "Monsters, Inc.", + "Moon Pilot", + "Morning Light", + "Mr. Magoo", + "Mulan", + "Muppet Treasure Island", + "Muppets Most Wanted", + "My Favorite Martian", + "Napoleon and Samantha", + "National Treasure", + "National Treasure: Book of Secrets", + "Never Cry Wolf", + "Never a Dull Moment", + "Newsies", + "Night Crossing", + "Nikki, Wild Dog of the North", + "No Deposit, No Return", + "Now You See Him, Now You Don't", + "Oceans", + "Old Dogs", + "Old Yeller", + "Oliver & Company", + "One Hundred and One Dalmatians", + "One Little Indian", + "One Magic Christmas", + "One of Our Dinosaurs Is Missing", + "Operation Dumbo Drop", + "Oz the Great and Powerful", + "Penguins", + "Perri", + "Pete's Dragon", + "Pete's Dragon", + "Peter Pan", + "Piglet's Big Movie", + "Pinocchio", + "Pirates of the Caribbean: At World's End", + "Pirates of the Caribbean: Dead Man's Chest", + "Pirates of the Caribbean: Dead Men Tell No Tales", + "Pirates of the Caribbean: On Stranger Tides", + "Pirates of the Caribbean: The Curse of the Black Pearl", + "Planes", + "Planes: Fire & Rescue", + "Pocahontas", + "Pollyanna", + "Pooh's Heffalump Movie", + "Popeye", + "Prince of Persia: The Sands of Time", + "Prom", + "Queen of Katwe", + "Race to Witch Mountain", + "Ralph Breaks the Internet", + "Rascal", + "Ratatouille", + "Recess: School's Out", + "Remember the Titans", + "Return from Witch Mountain", + "Return to Never Land", + "Return to Oz", + "Return to Snowy River", + "Ride a Wild Pony", + "Roadside Romeo", + "Rob Roy, the Highland Rogue", + "Robin Hood", + "RocketMan", + "Roving Mars", + "Run, Cougar, Run", + "Sacred Planet", + "Saludos Amigos", + "Savage Sam", + "Saving Mr. Banks", + "Scandalous John", + "Secretariat", + "Secrets of Life", + "Shipwrecked", + "Sky High", + "Sleeping Beauty", + "Smith!", + "Snow Dogs", + "Snow White and the Seven Dwarfs", + "Snowball Express", + "So Dear to My Heart", + "Something Wicked This Way Comes", + "Son of Flubber", + "Song of the South", + "Squanto: A Warrior's Tale", + "Summer Magic", + "Superdad", + "Swiss Family Robinson", + "Tall Tale", + "Tangled", + "Tarzan", + "Teacher's Pet", + "Ten Who Dared", + "Tex", + "That Darn Cat", + "That Darn Cat!", + "The Absent-Minded Professor", + "The Adventures of Bullwhip Griffin", + "The Adventures of Huck Finn", + "The Adventures of Ichabod and Mr. Toad", + "The African Lion", + "The Apple Dumpling Gang", + "The Apple Dumpling Gang Rides Again", + "The Aristocats", + "The BFG", + "The Barefoot Executive", + "The Bears and I", + "The Best of Walt Disney's True-Life Adventures", + "The Big Green", + "The Biscuit Eater", + "The Black Cauldron", + "The Black Hole", + "The Boatniks", + "The Book of Masters", + "The Boys: The Sherman Brothers' Story", + "The Castaway Cowboy", + "The Cat from Outer Space", + "The Chronicles of Narnia: Prince Caspian", + "The Chronicles of Narnia: The Lion, the Witch and the Wardrobe", + "The Computer Wore Tennis Shoes", + "The Country Bears", + "The Crimson Wing: Mystery of the Flamingos", + "The Devil and Max Devlin", + "The Emperor's New Groove", + "The Fighting Prince of Donegal", + "The Finest Hours", + "The Fox and the Hound", + "The Game Plan", + "The Gnome-Mobile", + "The Good Dinosaur", + "The Great Locomotive Chase", + "The Great Mouse Detective", + "The Greatest Game Ever Played", + "The Happiest Millionaire", + "The Haunted Mansion", + "The Horse in the Gray Flannel Suit", + "The Hunchback of Notre Dame", + "The Incredible Journey", + "The Incredibles", + "The Island at the Top of the World", + "The Journey of Natty Gann", + "The Jungle Book", + "The Jungle Book", + "The Jungle Book", + "The Jungle Book 2", + "The Last Flight of Noah's Ark", + "The Legend of Lobo", + "The Light in the Forest", + "The Lion King", + "The Lion King", + "The Little Mermaid", + "The Littlest Horse Thieves", + "The Littlest Outlaw", + "The Living Desert", + "The Lizzie McGuire Movie", + "The London Connection", + "The Lone Ranger", + "The Love Bug", + "The Many Adventures of Winnie the Pooh", + "The Mighty Ducks", + "The Million Dollar Duck", + "The Misadventures of Merlin Jones", + "The Monkey's Uncle", + "The Moon-Spinners", + "The Muppet Christmas Carol", + "The Muppets", + "The Nightmare Before Christmas 3D TNBC", + "The North Avenue Irregulars", + "The Nutcracker and the Four Realms", + "The Odd Life of Timothy Green", + "The One and Only, Genuine, Original Family Band", + "The Pacifier", + "The Parent Trap", + "The Parent Trap", + "The Pixar Story", + "The Princess Diaries", + "The Princess Diaries 2: Royal Engagement", + "The Princess and the Frog", + "The Reluctant Dragon", + "The Rescuers", + "The Rescuers Down Under", + "The Rocketeer TR", + "The Rookie", + "The Santa Clause 2", + "The Santa Clause 3: The Escape Clause", + "The Santa Clause TSC", + "The Shaggy D.A.", + "The Shaggy Dog", + "The Shaggy Dog", + "The Sign of Zorro", + "The Sorcerer's Apprentice", + "The Story of Robin Hood and His Merrie Men", + "The Straight Story", + "The Strongest Man in the World", + "The Sword and the Rose", + "The Sword in the Stone", + "The Three Caballeros", + "The Three Lives of Thomasina", + "The Three Musketeers", + "The Tigger Movie", + "The Ugly Dachshund", + "The Vanishing Prairie", + "The Watcher in the Woods", + "The Wild", + "The Wild Country", + "The World's Greatest Athlete", + "The Young Black Stallion", + "Third Man on the Mountain", + "Those Calloways", + "Toby Tyler", + "Tom and Huck", + "Tomorrowland", + "Tonka", + "Toy Story", + "Toy Story 2", + "Toy Story 3", + "Toy Story 4", + "Trail of the Panda", + "Treasure Island", + "Treasure Planet", + "Treasure of Matecumbe", + "Trenchcoat", + "Tron", + "Tron: Legacy", + "Tuck Everlasting", + "Underdog", + "Unidentified Flying Oddball", + "Up", + "Valiant", + "Victory Through Air Power", + "WALL-E", + "Waking Sleeping Beauty", + "Walt & El Grupo", + "Westward Ho the Wagons!", + "White Fang", + "White Fang 2: Myth of the White Wolf", + "White Wilderness", + "Wild Hearts Can't Be Broken", + "Wings of Life", + "Winnie the Pooh", + "Wreck-It Ralph", + "Zokkomon", + "Zootopia", + "Zorro the Avenger", + "Iron Man", + "Hulk", + "Thor", + "Avengers", + "Guardians of the Galaxy", + "Ant-Man", + "Captain America", + "Doctor Strange", + "Guardians of the Galaxy", + "Spider-Man", + "Black Panther", + "Marvel", + "Spider Man", + "SpiderMan", + "Loki", + "Winter Soldier", + "Wanda", + "Small Fry", + "Rex", + "Lamp life", + "Toy", + "Hawaiian" + }; + } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/External/TheMovieDbSettings.cs b/src/Ombi.Settings/Settings/Models/External/TheMovieDbSettings.cs index fe0c238d8..c2df1b9b3 100644 --- a/src/Ombi.Settings/Settings/Models/External/TheMovieDbSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/TheMovieDbSettings.cs @@ -7,5 +7,9 @@ namespace Ombi.Core.Settings.Models.External public bool ShowAdultMovies { get; set; } public List ExcludedKeywordIds { get; set; } + + public List ExcludedMovieGenreIds { get; set; } + + public List ExcludedTvGenreIds { get; set; } } } diff --git a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs index ea2465c1d..fb15aab5f 100644 --- a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs @@ -4,6 +4,10 @@ using System.Threading.Tasks; using Ombi.Api.TheMovieDb.Models; using Ombi.TheMovieDbApi.Models; +// Due to conflicting Genre models in +// Ombi.TheMovieDbApi.Models and Ombi.Api.TheMovieDb.Models +using Genre = Ombi.TheMovieDbApi.Models.Genre; + namespace Ombi.Api.TheMovieDb { public interface IMovieDbApi @@ -34,5 +38,6 @@ namespace Ombi.Api.TheMovieDb Task GetKeyword(int keywordId); Task GetMovieWatchProviders(int theMoviedbId, CancellationToken token); Task GetTvWatchProviders(int theMoviedbId, CancellationToken token); + Task> GetGenres(string media); } -} \ No newline at end of file +} diff --git a/src/Ombi.TheMovieDbApi/Models/TheMovieDbContainer.cs b/src/Ombi.TheMovieDbApi/Models/TheMovieDbContainer.cs index 72f58dd02..5eb5dbeac 100644 --- a/src/Ombi.TheMovieDbApi/Models/TheMovieDbContainer.cs +++ b/src/Ombi.TheMovieDbApi/Models/TheMovieDbContainer.cs @@ -36,4 +36,9 @@ namespace Ombi.TheMovieDbApi.Models public int total_results { get; set; } public int total_pages { get; set; } } -} \ No newline at end of file + + public class GenreContainer + { + public List genres { get; set; } + } +} diff --git a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs index 74c849d59..c5a290e04 100644 --- a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs @@ -13,6 +13,10 @@ using Ombi.Core.Settings.Models.External; using Ombi.Helpers; using Ombi.TheMovieDbApi.Models; +// Due to conflicting Genre models in +// Ombi.TheMovieDbApi.Models and Ombi.Api.TheMovieDb.Models +using Genre = Ombi.TheMovieDbApi.Models.Genre; + namespace Ombi.Api.TheMovieDb { public class TheMovieDbApi : IMovieDbApi @@ -198,6 +202,7 @@ namespace Ombi.Api.TheMovieDb request.AddQueryString("page", page.ToString()); } await AddDiscoverSettings(request); + await AddGenreFilter(request, type); AddRetry(request); var result = await Api.Request>(request, cancellationToken); return Mapper.Map>(result.results); @@ -233,6 +238,7 @@ namespace Ombi.Api.TheMovieDb request.AddQueryString("vote_count.gte", "250"); await AddDiscoverSettings(request); + await AddGenreFilter(request, type); AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); @@ -269,6 +275,7 @@ namespace Ombi.Api.TheMovieDb request.AddQueryString("page", page.ToString()); } await AddDiscoverSettings(request); + await AddGenreFilter(request, type); AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); @@ -297,6 +304,7 @@ namespace Ombi.Api.TheMovieDb } await AddDiscoverSettings(request); + await AddGenreFilter(request, "movie"); AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); @@ -344,6 +352,16 @@ namespace Ombi.Api.TheMovieDb return keyword == null || keyword.Id == 0 ? null : keyword; } + public async Task> GetGenres(string media) + { + var request = new Request($"genre/{media}/list", BaseUri, HttpMethod.Get); + request.AddQueryString("api_key", ApiToken); + AddRetry(request); + + var result = await Api.Request>(request); + return result.genres ?? new List(); + } + public Task> MultiSearch(string searchTerm, string languageCode, CancellationToken cancellationToken) { var request = new Request("search/multi", BaseUri, HttpMethod.Get); @@ -380,6 +398,28 @@ namespace Ombi.Api.TheMovieDb } } + private async Task AddGenreFilter(Request request, string media_type) + { + var settings = await Settings; + List excludedGenres; + + switch (media_type) { + case "tv": + excludedGenres = settings.ExcludedTvGenreIds; + break; + case "movie": + excludedGenres = settings.ExcludedMovieGenreIds; + break; + default: + return; + } + + if (excludedGenres?.Any() == true) + { + request.AddQueryString("without_genres", string.Join(",", excludedGenres)); + } + } + private static void AddRetry(Request request) { request.Retry = true; diff --git a/src/Ombi/ClientApp/src/app/app.component.html b/src/Ombi/ClientApp/src/app/app.component.html index a8e527a6b..351b8ef49 100644 --- a/src/Ombi/ClientApp/src/app/app.component.html +++ b/src/Ombi/ClientApp/src/app/app.component.html @@ -170,7 +170,17 @@
- + diff --git a/src/Ombi/ClientApp/src/app/app.component.ts b/src/Ombi/ClientApp/src/app/app.component.ts index 356107039..038fcc7d7 100644 --- a/src/Ombi/ClientApp/src/app/app.component.ts +++ b/src/Ombi/ClientApp/src/app/app.component.ts @@ -33,6 +33,7 @@ export class AppComponent implements OnInit { public applicationName: string = "Ombi" public isAdmin: boolean; public username: string; + public accessToken: string; private hubConnected: boolean; @@ -55,6 +56,7 @@ export class AppComponent implements OnInit { if (this.authService.loggedIn()) { this.user = this.authService.claims(); this.username = this.user.name; + this.identity.getAccessToken().subscribe(x => this.accessToken = x); if (!this.hubConnected) { this.signalrNotification.initialize(); this.hubConnected = true; diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts index d73fdddc8..5dfaa3e15 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts @@ -284,6 +284,8 @@ export interface IVoteSettings extends ISettings { export interface ITheMovieDbSettings extends ISettings { showAdultMovies: boolean; excludedKeywordIds: number[]; + excludedMovieGenreIds: number[]; + excludedTvGenreIds: number[] } export interface IUpdateModel diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html index c0434980d..4f44fe1be 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html @@ -65,7 +65,7 @@ - -
- +
+ +
diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-requests/tv-requests-panel.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-requests/tv-requests-panel.component.ts index e51414902..d4e8f894c 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-requests/tv-requests-panel.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-requests/tv-requests-panel.component.ts @@ -1,9 +1,10 @@ import { Component, Input } from "@angular/core"; import { IChildRequests, RequestType } from "../../../../../interfaces"; -import { RequestService } from "../../../../../services/request.service"; -import { MessageService } from "../../../../../services"; -import { MatDialog } from "@angular/material/dialog"; + import { DenyDialogComponent } from "../../../shared/deny-dialog/deny-dialog.component"; +import { MatDialog } from "@angular/material/dialog"; +import { MessageService } from "../../../../../services"; +import { RequestService } from "../../../../../services/request.service"; import { RequestServiceV2 } from "../../../../../services/requestV2.service"; @Component({ @@ -14,6 +15,7 @@ import { RequestServiceV2 } from "../../../../../services/requestV2.service"; export class TvRequestsPanelComponent { @Input() public tvRequest: IChildRequests[]; @Input() public isAdmin: boolean; + @Input() public manageOwnRequests: boolean; public displayedColumns: string[] = ['number', 'title', 'airDate', 'status']; diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.html b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.html index d0590ad1b..dc8902a1b 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.html @@ -126,7 +126,7 @@ {{'Requests.Title' | translate}} - + diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts index f9b686a48..7b1c98ba8 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts @@ -27,6 +27,7 @@ export class TvDetailsComponent implements OnInit { public showRequest: ITvRequests; public fromSearch: boolean; public isAdmin: boolean; + public manageOwnRequests: boolean; public advancedOptions: IAdvancedData; public showAdvanced: boolean; // Set on the UI public requestType = RequestType.tvShow; @@ -53,6 +54,7 @@ export class TvDetailsComponent implements OnInit { this.issuesEnabled = this.settingsState.getIssue(); this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser"); + this.manageOwnRequests = this.auth.hasRole('ManageOwnRequests'); if (this.isAdmin) { this.showAdvanced = await this.sonarrService.isEnabled(); diff --git a/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.html b/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.html index f73c9bbc8..95f31198b 100644 --- a/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.html +++ b/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.html @@ -28,6 +28,13 @@ + + + +  {{ 'NavigationBar.OpenMobileApp' | translate }} + + diff --git a/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.scss b/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.scss index 753759af1..f76109b96 100644 --- a/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.scss +++ b/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.scss @@ -107,10 +107,21 @@ margin-right:5px; } +#nav-openMobile { + display: none; +} + @media (max-width: 600px) { .profile-username{ display:none; } + +} + +@media (max-width: 950px) { + #nav-openMobile { + display: block; + } } .profile-img img { diff --git a/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.ts b/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.ts index d65307bdb..57bfaca91 100644 --- a/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.ts +++ b/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.ts @@ -1,16 +1,17 @@ -import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { INavBar } from '../interfaces/ICommon'; -import { StorageService } from '../shared/storage/storage-service'; -import { SettingsService, SettingsStateService } from '../services'; -import { MatSlideToggleChange } from '@angular/material/slide-toggle'; -import { SearchFilter } from './SearchFilter'; -import { Md5 } from 'ts-md5/dist/md5'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { IUser, RequestType, UserType } from '../interfaces'; +import { SettingsService, SettingsStateService } from '../services'; + import { FilterService } from '../discover/services/filter-service'; import { ILocalUser } from '../auth/IUserLogin'; +import { INavBar } from '../interfaces/ICommon'; +import { MatSlideToggleChange } from '@angular/material/slide-toggle'; +import { Md5 } from 'ts-md5/dist/md5'; +import { Observable } from 'rxjs'; +import { SearchFilter } from './SearchFilter'; +import { StorageService } from '../shared/storage/storage-service'; +import { map } from 'rxjs/operators'; export enum SearchFilterType { Movie = 1, @@ -34,6 +35,8 @@ export class MyNavComponent implements OnInit { @Input() public showNav: boolean; @Input() public applicationName: string; @Input() public applicationLogo: string; + @Input() public applicationUrl: string; + @Input() public accessToken: string; @Input() public username: string; @Input() public isAdmin: string; @Input() public email: string; @@ -122,4 +125,12 @@ export class MyNavComponent implements OnInit { var fallback = this.applicationLogo ? this.applicationLogo : 'https://raw.githubusercontent.com/Ombi-app/Ombi/gh-pages/img/android-chrome-512x512.png'; return `https://www.gravatar.com/avatar/${this.emailHash}?d=${fallback}`; } + + public openMobileApp(event: any) { + event.preventDefault(); + + const url = `ombi://${this.applicationUrl}|${this.accessToken}`; + window.location.assign(url); +} + } diff --git a/src/Ombi/ClientApp/src/app/requests-list/components/movies-grid/movies-grid.component.html b/src/Ombi/ClientApp/src/app/requests-list/components/movies-grid/movies-grid.component.html index e8926b69a..00cd67d6e 100644 --- a/src/Ombi/ClientApp/src/app/requests-list/components/movies-grid/movies-grid.component.html +++ b/src/Ombi/ClientApp/src/app/requests-list/components/movies-grid/movies-grid.component.html @@ -76,7 +76,7 @@ - + diff --git a/src/Ombi/ClientApp/src/app/requests-list/components/movies-grid/movies-grid.component.ts b/src/Ombi/ClientApp/src/app/requests-list/components/movies-grid/movies-grid.component.ts index c73084c2b..c033238b1 100644 --- a/src/Ombi/ClientApp/src/app/requests-list/components/movies-grid/movies-grid.component.ts +++ b/src/Ombi/ClientApp/src/app/requests-list/components/movies-grid/movies-grid.component.ts @@ -1,18 +1,18 @@ -import { Component, AfterViewInit, ViewChild, EventEmitter, Output, ChangeDetectorRef, OnInit } from "@angular/core"; +import { AfterViewInit, ChangeDetectorRef, Component, EventEmitter, OnInit, Output, ViewChild } from "@angular/core"; import { IMovieRequests, IRequestEngineResult, IRequestsViewModel } from "../../../interfaces"; -import { MatPaginator } from "@angular/material/paginator"; -import { MatSort } from "@angular/material/sort"; -import { merge, Observable, of as observableOf, forkJoin } from 'rxjs'; +import { NotificationService, RequestService } from "../../../services"; +import { Observable, forkJoin, merge, of as observableOf } from 'rxjs'; import { catchError, map, startWith, switchMap } from 'rxjs/operators'; -import { RequestServiceV2 } from "../../../services/requestV2.service"; import { AuthService } from "../../../auth/auth.service"; -import { StorageService } from "../../../shared/storage/storage-service"; +import { MatPaginator } from "@angular/material/paginator"; +import { MatSort } from "@angular/material/sort"; +import { MatTableDataSource } from "@angular/material/table"; import { RequestFilterType } from "../../models/RequestFilterType"; +import { RequestServiceV2 } from "../../../services/requestV2.service"; import { SelectionModel } from "@angular/cdk/collections"; -import { NotificationService, RequestService } from "../../../services"; +import { StorageService } from "../../../shared/storage/storage-service"; import { TranslateService } from "@ngx-translate/core"; -import { MatTableDataSource } from "@angular/material/table"; @Component({ templateUrl: "./movies-grid.component.html", @@ -26,6 +26,7 @@ export class MoviesGridComponent implements OnInit, AfterViewInit { public displayedColumns: string[] = ['title', 'requestedUser.requestedBy', 'status', 'requestStatus','requestedDate', 'actions']; public gridCount: string = "15"; public isAdmin: boolean; + public manageOwnRequests: boolean; public defaultSort: string = "requestedDate"; public defaultOrder: string = "desc"; public currentFilter: RequestFilterType = RequestFilterType.All; @@ -39,7 +40,7 @@ export class MoviesGridComponent implements OnInit, AfterViewInit { private storageKeyGridCount = "Movie_DefaultGridCount"; private storageKeyCurrentFilter = "Movie_DefaultFilter"; - @Output() public onOpenOptions = new EventEmitter<{ request: any, filter: any, onChange: any }>(); + @Output() public onOpenOptions = new EventEmitter<{ request: any, filter: any, onChange: any, manageOwnRequests: boolean, isAdmin: boolean }>(); @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; @@ -53,6 +54,7 @@ export class MoviesGridComponent implements OnInit, AfterViewInit { public ngOnInit() { this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser"); + this.manageOwnRequests = this.auth.hasRole("ManageOwnRequests") if (this.isAdmin) { this.displayedColumns.unshift('select'); } @@ -135,7 +137,8 @@ export class MoviesGridComponent implements OnInit, AfterViewInit { this.ref.detectChanges(); }; - this.onOpenOptions.emit({ request: request, filter: filter, onChange: onChange }); + const data = { request: request, filter: filter, onChange: onChange, manageOwnRequests: this.manageOwnRequests, isAdmin: this.isAdmin }; + this.onOpenOptions.emit(data); } public switchFilter(type: RequestFilterType) { diff --git a/src/Ombi/ClientApp/src/app/requests-list/components/options/request-options.component.html b/src/Ombi/ClientApp/src/app/requests-list/components/options/request-options.component.html index 399b35e16..ac974ece8 100644 --- a/src/Ombi/ClientApp/src/app/requests-list/components/options/request-options.component.html +++ b/src/Ombi/ClientApp/src/app/requests-list/components/options/request-options.component.html @@ -1,11 +1,11 @@ - + {{'Requests.RequestPanel.Delete' | translate}} - + {{'Requests.RequestPanel.Approve' | translate}} - + {{'Requests.RequestPanel.ChangeAvailability' | translate}} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/requests-list/components/requests-list.component.ts b/src/Ombi/ClientApp/src/app/requests-list/components/requests-list.component.ts index 6cdd84af3..351c5a794 100644 --- a/src/Ombi/ClientApp/src/app/requests-list/components/requests-list.component.ts +++ b/src/Ombi/ClientApp/src/app/requests-list/components/requests-list.component.ts @@ -1,8 +1,9 @@ import { Component, ViewChild } from "@angular/core"; + import { MatBottomSheet } from "@angular/material/bottom-sheet"; +import { MoviesGridComponent } from "./movies-grid/movies-grid.component"; import { RequestOptionsComponent } from "./options/request-options.component"; import { UpdateType } from "../models/UpdateType"; -import { MoviesGridComponent } from "./movies-grid/movies-grid.component"; @Component({ templateUrl: "./requests-list.component.html", @@ -12,8 +13,8 @@ export class RequestsListComponent { constructor(private bottomSheet: MatBottomSheet) { } - public onOpenOptions(event: { request: any, filter: any, onChange: any }) { - const ref = this.bottomSheet.open(RequestOptionsComponent, { data: { id: event.request.id, type: event.request.requestType, canApprove: event.request.canApprove } }); + public onOpenOptions(event: { request: any, filter: any, onChange: any, manageOwnRequests: boolean, isAdmin: boolean }) { + const ref = this.bottomSheet.open(RequestOptionsComponent, { data: { id: event.request.id, type: event.request.requestType, canApprove: event.request.canApprove, manageOwnRequests: event.manageOwnRequests, isAdmin: event.isAdmin } }); ref.afterDismissed().subscribe((result) => { if(!result) { diff --git a/src/Ombi/ClientApp/src/app/services/applications/themoviedb.service.ts b/src/Ombi/ClientApp/src/app/services/applications/themoviedb.service.ts index 677034143..ff76d591b 100644 --- a/src/Ombi/ClientApp/src/app/services/applications/themoviedb.service.ts +++ b/src/Ombi/ClientApp/src/app/services/applications/themoviedb.service.ts @@ -22,4 +22,8 @@ export class TheMovieDbService extends ServiceHelpers { return this.http.get(`${this.url}/Keywords/${keywordId}`, { headers: this.headers }) .pipe(catchError((error: HttpErrorResponse) => error.status === 404 ? empty() : throwError(error))); } + + public getGenres(media: string): Observable { + return this.http.get(`${this.url}/Genres/${media}`, { headers: this.headers }) + } } diff --git a/src/Ombi/ClientApp/src/app/settings/customization/customization.component.html b/src/Ombi/ClientApp/src/app/settings/customization/customization.component.html index 3b3044780..1625c1b77 100644 --- a/src/Ombi/ClientApp/src/app/settings/customization/customization.component.html +++ b/src/Ombi/ClientApp/src/app/settings/customization/customization.component.html @@ -1,4 +1,4 @@ - +
@@ -12,9 +12,8 @@
- The application url should be your Externally Accessible URL for example, your internal URL is http://192.168.1.50/ but your Externally - Accessible URL is 'https://mydomain.com/requests' Please ensure this field is correct as it drives a lot of functionality include the QR code for the - mobile app and it affects the way email notifications are sent. + The application url should be your Externally Accessible URL (the address you use to reach Ombi from outside your system). For example, 'https://example.com/requests'.
Please ensure this field is correct as it drives a lot of functionality, including the QR code for the + mobile app, and the way email notifications are sent.
Application URL @@ -81,4 +80,4 @@ -
\ No newline at end of file + diff --git a/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.html b/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.html index 4d47aa8a9..fb10cc142 100644 --- a/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.html +++ b/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.html @@ -15,7 +15,7 @@
- + (removed)="remove(key, 'keyword')"> + {{key.name}} + + + + +
+ + Movie Genres + + + {{genre.name}} + + + +
+ + + + {{key.name}} + + + + +
+ + Tv Genres + + + {{genre.name}} + + + +
+ + + {{key.name}} @@ -52,4 +90,4 @@ - \ No newline at end of file + diff --git a/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.ts b/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.ts index 228d4fc42..49ec3e51f 100644 --- a/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.ts @@ -23,8 +23,13 @@ export class TheMovieDbComponent implements OnInit { public settings: ITheMovieDbSettings; public excludedKeywords: IKeywordTag[]; + public excludedMovieGenres: IKeywordTag[]; + public excludedTvGenres: IKeywordTag[]; public tagForm: FormGroup; public filteredTags: IMovieDbKeyword[]; + public filteredMovieGenres: IMovieDbKeyword[]; + public filteredTvGenres: IMovieDbKeyword[]; + @ViewChild('fruitInput') public fruitInput: ElementRef; constructor(private settingsService: SettingsService, @@ -35,9 +40,13 @@ export class TheMovieDbComponent implements OnInit { public ngOnInit() { this.tagForm = this.fb.group({ input: null, + excludedMovieGenres: null, + excludedTvGenres: null, }); this.settingsService.getTheMovieDbSettings().subscribe(settings => { this.settings = settings; + + // Map Keyword ids -> keyword name this.excludedKeywords = settings.excludedKeywordIds ? settings.excludedKeywordIds.map(id => ({ id, @@ -45,13 +54,56 @@ export class TheMovieDbComponent implements OnInit { initial: true, })) : []; - this.excludedKeywords.forEach(key => { - this.tmdbService.getKeyword(key.id).subscribe(keyResult => { - this.excludedKeywords.filter((val, idx) => { - val.name = keyResult.name; - }) + + this.excludedKeywords.forEach(key => { + this.tmdbService.getKeyword(key.id).subscribe(keyResult => { + this.excludedKeywords.filter((val, idx) => { + val.name = keyResult.name; + }) + }); + }); + + // Map Movie Genre ids -> genre name + this.excludedMovieGenres = settings.excludedMovieGenreIds + ? settings.excludedMovieGenreIds.map(id => ({ + id, + name: "", + initial: true, + })) + : []; + + this.tmdbService.getGenres("movie").subscribe(results => { + this.filteredMovieGenres = results; + + this.excludedMovieGenres.forEach(genre => { + results.forEach(result => { + if (genre.id == result.id) { + genre.name = result.name; + } }); }); + }); + + // Map Tv Genre ids -> genre name + this.excludedTvGenres = settings.excludedTvGenreIds + ? settings.excludedTvGenreIds.map(id => ({ + id, + name: "", + initial: true, + })) + : []; + + this.tmdbService.getGenres("tv").subscribe(results => { + this.filteredTvGenres = results; + + this.excludedTvGenres.forEach(genre => { + results.forEach(result => { + if (genre.id == result.id) { + genre.name = result.name; + } + }); + }); + }); }); this.tagForm @@ -65,19 +117,48 @@ export class TheMovieDbComponent implements OnInit { }) ) .subscribe((r) => (this.filteredTags = r)); - } - public remove(tag: IKeywordTag): void { - const index = this.excludedKeywords.indexOf(tag); + public remove(tag: IKeywordTag, tag_type: string): void { + var exclusion_list; + + switch (tag_type) { + case "keyword": + exclusion_list = this.excludedKeywords; + break; + case "movieGenre": + exclusion_list = this.excludedMovieGenres; + break; + case "tvGenre": + exclusion_list = this.excludedTvGenres; + break; + default: + return; + } + + const index = exclusion_list.indexOf(tag); if (index >= 0) { - this.excludedKeywords.splice(index, 1); + exclusion_list.splice(index, 1); } } public save() { + + var selectedMovieGenres: number[] = this.tagForm.controls.excludedMovieGenres.value ?? []; + var selectedTvGenres: number[] = this.tagForm.controls.excludedTvGenres.value ?? []; + + var movieIds: number[] = this.excludedMovieGenres.map(k => k.id); + var tvIds: number[] = this.excludedTvGenres.map(k => k.id) + + // Concat and dedup already excluded genres + newly selected ones + selectedMovieGenres = movieIds.concat(selectedMovieGenres.filter(item => movieIds.indexOf(item) < 0)); + selectedTvGenres = tvIds.concat(selectedTvGenres.filter(item => tvIds.indexOf(item) < 0)); + this.settings.excludedKeywordIds = this.excludedKeywords.map(k => k.id); + this.settings.excludedMovieGenreIds = selectedMovieGenres; + this.settings.excludedTvGenreIds = selectedTvGenres; + this.settingsService.saveTheMovieDbSettings(this.settings).subscribe(x => { if (x) { this.notificationService.success("Successfully saved The Movie Database settings"); diff --git a/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.html b/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.html index 0f3dbb082..d513c1f63 100644 --- a/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.html +++ b/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.html @@ -56,17 +56,17 @@
- Get it on Google Play + Get it on Google Play
- Get it from the App Store + Get it from the App Store
- +
diff --git a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.html b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.html index 74770a3d8..e247f0ea1 100644 --- a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.html +++ b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.html @@ -145,13 +145,14 @@
-
+
- +
diff --git a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.ts b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.ts index bfbabd0a3..c091d3861 100644 --- a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.ts +++ b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.ts @@ -1,9 +1,10 @@ -import { Location } from "@angular/common"; -import { AfterViewInit, Component, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; +import { Component, OnInit } from "@angular/core"; +import { ICheckbox, ICustomizationSettings, INotificationAgent, INotificationPreferences, IRadarrProfile, IRadarrRootFolder, ISonarrProfile, ISonarrRootFolder, IUser, UserType } from "../interfaces"; +import { IdentityService, MessageService, RadarrService, SettingsService, SonarrService } from "../services"; -import { ICheckbox, INotificationAgent, INotificationPreferences, IRadarrProfile, IRadarrRootFolder, ISonarrProfile, ISonarrRootFolder, IUser, UserType } from "../interfaces"; -import { IdentityService, RadarrService, SonarrService, MessageService } from "../services"; +import { Clipboard } from '@angular/cdk/clipboard'; +import { Location } from "@angular/common"; @Component({ templateUrl: "./usermanagement-user.component.html", @@ -27,12 +28,17 @@ export class UserManagementUserComponent implements OnInit { public countries: string[]; + private customization: ICustomizationSettings; + private accessToken: string; + constructor(private identityService: IdentityService, private notificationService: MessageService, + private readonly settingsService: SettingsService, private router: Router, private route: ActivatedRoute, private sonarrService: SonarrService, private radarrService: RadarrService, + private clipboard: Clipboard, private location: Location) { this.route.params.subscribe((params: any) => { @@ -60,6 +66,9 @@ export class UserManagementUserComponent implements OnInit { this.radarrService.getQualityProfilesFromSettings().subscribe(x => this.radarrQualities = x); this.radarrService.getRootFoldersFromSettings().subscribe(x => this.radarrRootFolders = x); + this.settingsService.getCustomization().subscribe(x => this.customization = x); + this.identityService.getAccessToken().subscribe(x => this.accessToken = x); + if(!this.edit) { this.user = { alias: "", @@ -178,7 +187,12 @@ export class UserManagementUserComponent implements OnInit { } }); } - + + public async appLink() { + this.clipboard.copy(`ombi://${this.customization.applicationUrl}|${this.accessToken}`); + this.notificationService.send("Copied!"); + } + public back() { this.location.back(); } diff --git a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.module.ts b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.module.ts index 885e6e2e4..a55702101 100644 --- a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.module.ts +++ b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.module.ts @@ -1,23 +1,19 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; -import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { RouterModule, Routes } from "@angular/router"; -import { ConfirmDialogModule } from "primeng/confirmdialog"; -import { MultiSelectModule } from "primeng/multiselect"; -import { SidebarModule } from "primeng/sidebar"; -import { TooltipModule } from "primeng/tooltip"; - -import { UserManagementUserComponent } from "./usermanagement-user.component"; -import { UserManagementComponent } from "./usermanagement.component"; - -import { PipeModule } from "../pipes/pipe.module"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { IdentityService, PlexService, RadarrService, SonarrService } from "../services"; +import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "../auth/auth.guard"; - +import { CommonModule } from "@angular/common"; +import { ConfirmDialogModule } from "primeng/confirmdialog"; +import { MultiSelectModule } from "primeng/multiselect"; +import { NgModule } from "@angular/core"; import { OrderModule } from "ngx-order-pipe"; - +import { PipeModule } from "../pipes/pipe.module"; import { SharedModule } from "../shared/shared.module"; +import { SidebarModule } from "primeng/sidebar"; +import { TooltipModule } from "primeng/tooltip"; +import { UserManagementComponent } from "./usermanagement.component"; +import { UserManagementUserComponent } from "./usermanagement-user.component"; const routes: Routes = [ { path: "", component: UserManagementComponent, canActivate: [AuthGuard] }, diff --git a/src/Ombi/Controllers/V1/External/TheMovieDbController.cs b/src/Ombi/Controllers/V1/External/TheMovieDbController.cs index 714831633..ac4bb4eea 100644 --- a/src/Ombi/Controllers/V1/External/TheMovieDbController.cs +++ b/src/Ombi/Controllers/V1/External/TheMovieDbController.cs @@ -5,6 +5,10 @@ using Ombi.Attributes; using System.Collections.Generic; using System.Threading.Tasks; +// Due to conflicting Genre models in +// Ombi.TheMovieDbApi.Models and Ombi.Api.TheMovieDb.Models +using Genre = Ombi.TheMovieDbApi.Models.Genre; + namespace Ombi.Controllers.External { [Admin] @@ -34,5 +38,13 @@ namespace Ombi.Controllers.External var keyword = await TmdbApi.GetKeyword(keywordId); return keyword == null ? NotFound() : (IActionResult)Ok(keyword); } + + /// + /// Gets the genres for either Tv or Movies depending on media type + /// + /// Either `tv` or `movie`. + [HttpGet("Genres/{media}")] + public async Task> GetGenres(string media) => + await TmdbApi.GetGenres(media); } } diff --git a/src/Ombi/Controllers/V1/StatusController.cs b/src/Ombi/Controllers/V1/StatusController.cs index 074127e8b..0e16c311e 100644 --- a/src/Ombi/Controllers/V1/StatusController.cs +++ b/src/Ombi/Controllers/V1/StatusController.cs @@ -82,7 +82,17 @@ namespace Ombi.Controllers.V1 { var settings = await Ombi.GetSettingsAsync(); - return new { Result = settings?.Wizard ?? false}; + return new { Result = settings?.Wizard ?? false }; + } + + [ApiExplorerSettings(IgnoreApi = true)] + [HttpGet("demo")] + public IActionResult Demo() + { + var instance = DemoSingleton.Instance; + + instance.Demo = !instance.Demo; + return new OkResult(); } } } \ No newline at end of file diff --git a/src/Ombi/Extensions/StartupExtensions.cs b/src/Ombi/Extensions/StartupExtensions.cs index 860ab40fb..5149c4fea 100644 --- a/src/Ombi/Extensions/StartupExtensions.cs +++ b/src/Ombi/Extensions/StartupExtensions.cs @@ -72,8 +72,6 @@ namespace Ombi services.Configure(configuration.GetSection("TokenAuthentication")); services.Configure(configuration.GetSection("LandingPageBackground")); services.Configure(configuration.GetSection("Demo")); - var enabledDemo = Convert.ToBoolean(configuration.GetSection("Demo:Enabled").Value); - DemoSingleton.Instance.Demo = enabledDemo; } public static void AddJwtAuthentication(this IServiceCollection services)