From 8393a31a48655a8c3bc0c384b98741cd16f523fc Mon Sep 17 00:00:00 2001 From: tidusjar Date: Fri, 3 Feb 2017 20:26:28 +0000 Subject: [PATCH] Reworked the newsletter for Emby! Need to rework it for Plex and use the new way to do it. Fixed collections for Emby #435 --- Ombi.Api.Interfaces/IEmbyApi.cs | 1 + Ombi.Api/EmbyApi.cs | 21 ++ Ombi.Core.Migration/Migrations/Version2200.cs | 8 + Ombi.Services/Jobs/EmbyContentCacher.cs | 76 ++-- Ombi.Services/Jobs/EmbyEpisodeCacher.cs | 51 ++- .../EmbyRecentlyAddedNewsletter.cs | 341 ++++++++++++++++++ .../IEmbyAddedNewsletter.cs | 7 + .../RecentlyAddedNewsletter.cs} | 169 +++++---- Ombi.Services/Ombi.Services.csproj | 4 +- Ombi.Store/Models/Emby/EmbyContent.cs | 1 + Ombi.Store/Models/Emby/EmbyEpisodes.cs | 2 + Ombi.Store/Models/RecenetlyAddedLog.cs | 40 ++ Ombi.Store/Ombi.Store.csproj | 1 + Ombi.Store/SqlTables.sql | 17 +- Ombi.UI/Jobs/Scheduler.cs | 3 +- Ombi.UI/Modules/Admin/AdminModule.cs | 5 + Ombi.UI/NinjectModules/ServicesModule.cs | 6 +- 17 files changed, 625 insertions(+), 128 deletions(-) create mode 100644 Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs create mode 100644 Ombi.Services/Jobs/RecentlyAddedNewsletter/IEmbyAddedNewsletter.cs rename Ombi.Services/Jobs/{RecentlyAdded.cs => RecentlyAddedNewsletter/RecentlyAddedNewsletter.cs} (79%) create mode 100644 Ombi.Store/Models/RecenetlyAddedLog.cs diff --git a/Ombi.Api.Interfaces/IEmbyApi.cs b/Ombi.Api.Interfaces/IEmbyApi.cs index 7a2e4f6c4..ddc85868c 100644 --- a/Ombi.Api.Interfaces/IEmbyApi.cs +++ b/Ombi.Api.Interfaces/IEmbyApi.cs @@ -9,6 +9,7 @@ namespace Ombi.Api.Interfaces EmbyItemContainer GetAllMovies(string apiKey, string userId, Uri baseUri); EmbyItemContainer GetAllShows(string apiKey, string userId, Uri baseUri); EmbyItemContainer GetAllEpisodes(string apiKey, string userId, Uri baseUri); + EmbyItemContainer GetCollection(string mediaId, string apiKey, string userId, Uri baseUrl); List GetUsers(Uri baseUri, string apiKey); EmbyItemContainer ViewLibrary(string apiKey, string userId, Uri baseUri); EmbyInformation GetInformation(string mediaId, EmbyMediaType type, string apiKey, string userId, Uri baseUri); diff --git a/Ombi.Api/EmbyApi.cs b/Ombi.Api/EmbyApi.cs index bbd25dadd..1cfc0bf0a 100644 --- a/Ombi.Api/EmbyApi.cs +++ b/Ombi.Api/EmbyApi.cs @@ -103,6 +103,27 @@ namespace Ombi.Api return GetAll("Episode", apiKey, userId, baseUri); } + public EmbyItemContainer GetCollection(string mediaId, string apiKey, string userId, Uri baseUrl) + { + var request = new RestRequest + { + Resource = "emby/users/{userId}/items?parentId={mediaId}", + Method = Method.GET + }; + + request.AddUrlSegment("userId", userId); + request.AddUrlSegment("mediaId", mediaId); + + AddHeaders(request, apiKey); + + + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetCollections for Emby, Retrying {0}", timespan), new[] { + TimeSpan.FromSeconds (1), + TimeSpan.FromSeconds(5) + }); + return policy.Execute(() => Api.ExecuteJson>(request, baseUrl)); + } + public EmbyInformation GetInformation(string mediaId, EmbyMediaType type, string apiKey, string userId, Uri baseUri) { var request = new RestRequest diff --git a/Ombi.Core.Migration/Migrations/Version2200.cs b/Ombi.Core.Migration/Migrations/Version2200.cs index f2b3b6fd2..79939c8e3 100644 --- a/Ombi.Core.Migration/Migrations/Version2200.cs +++ b/Ombi.Core.Migration/Migrations/Version2200.cs @@ -30,6 +30,7 @@ using System.Data; using NLog; using Ombi.Core.SettingModels; +using Ombi.Store; namespace Ombi.Core.Migration.Migrations { @@ -53,9 +54,16 @@ namespace Ombi.Core.Migration.Migrations { UpdatePlexSettings(); UpdateCustomSettings(); + AddNewColumns(con); UpdateSchema(con, Version); } + private void AddNewColumns(IDbConnection con) + { + con.AlterTable("EmbyContent", "ADD", "AddedAt", true, "VARCHAR(50)"); + con.AlterTable("EmbyEpisodes", "ADD", "AddedAt", true, "VARCHAR(50)"); + } + private void UpdatePlexSettings() { #if !DEBUG diff --git a/Ombi.Services/Jobs/EmbyContentCacher.cs b/Ombi.Services/Jobs/EmbyContentCacher.cs index 65a47df32..a3eacab65 100644 --- a/Ombi.Services/Jobs/EmbyContentCacher.cs +++ b/Ombi.Services/Jobs/EmbyContentCacher.cs @@ -46,7 +46,7 @@ namespace Ombi.Services.Jobs public class EmbyContentCacher : IJob, IEmbyContentCacher { public EmbyContentCacher(ISettingsService embySettings, IRequestService request, IEmbyApi emby, ICacheProvider cache, - IJobRecord rec, IRepository repo,IRepository content) + IJobRecord rec, IRepository repo, IRepository content) { Emby = embySettings; RequestService = request; @@ -108,35 +108,23 @@ namespace Ombi.Services.Jobs foreach (var m in movies) { - var movieInfo = EmbyApi.GetInformation(m.Id, EmbyMediaType.Movie, embySettings.ApiKey, - embySettings.AdministratorId, embySettings.FullUri).MovieInformation; - - if (string.IsNullOrEmpty(movieInfo.ProviderIds.Imdb)) + if (m.Type.Equals("boxset", StringComparison.CurrentCultureIgnoreCase)) { - Log.Error("Provider Id on movie {0} is null", movieInfo.Name); - continue; + var info = EmbyApi.GetCollection(m.Id, embySettings.ApiKey, + embySettings.AdministratorId, embySettings.FullUri); + foreach (var item in info.Items) + { + var movieInfo = EmbyApi.GetInformation(item.Id, EmbyMediaType.Movie, embySettings.ApiKey, + embySettings.AdministratorId, embySettings.FullUri).MovieInformation; + ProcessMovies(movieInfo); + } } - - // Check if it exists - var item = EmbyContent.Custom(connection => + else { - connection.Open(); - var media = connection.QueryFirstOrDefault("select * from EmbyContent where ProviderId = @ProviderId and type = @type", new { ProviderId = movieInfo.ProviderIds.Imdb, type = 0 }); - connection.Dispose(); - return media; - }); + var movieInfo = EmbyApi.GetInformation(m.Id, EmbyMediaType.Movie, embySettings.ApiKey, + embySettings.AdministratorId, embySettings.FullUri).MovieInformation; - if (item == null) - { - // Doesn't exist, insert it - EmbyContent.Insert(new EmbyContent - { - ProviderId = movieInfo.ProviderIds.Imdb, - PremierDate = movieInfo.PremiereDate, - Title = movieInfo.Name, - Type = Store.Models.Plex.EmbyMediaType.Movie, - EmbyId = m.Id - }); + ProcessMovies(movieInfo); } } @@ -170,7 +158,8 @@ namespace Ombi.Services.Jobs PremierDate = tvInfo.PremiereDate, Title = tvInfo.Name, Type = Store.Models.Plex.EmbyMediaType.Series, - EmbyId = t.Id + EmbyId = t.Id, + AddedAt = DateTime.UtcNow }); } } @@ -216,7 +205,7 @@ namespace Ombi.Services.Jobs } } - + private bool ValidateSettings(EmbySettings emby) { @@ -249,5 +238,36 @@ namespace Ombi.Services.Jobs Job.SetRunning(false, JobNames.EmbyCacher); } } + + private void ProcessMovies(EmbyMovieInformation movieInfo) + { + if (string.IsNullOrEmpty(movieInfo.ProviderIds.Imdb)) + { + Log.Error("Provider Id on movie {0} is null", movieInfo.Name); + return; + } + // Check if it exists + var item = EmbyContent.Custom(connection => + { + connection.Open(); + var media = connection.QueryFirstOrDefault("select * from EmbyContent where ProviderId = @ProviderId and type = @type", new { ProviderId = movieInfo.ProviderIds.Imdb, type = 0 }); + connection.Dispose(); + return media; + }); + + if (item == null) + { + // Doesn't exist, insert it + EmbyContent.Insert(new EmbyContent + { + ProviderId = movieInfo.ProviderIds.Imdb, + PremierDate = movieInfo.PremiereDate, + Title = movieInfo.Name, + Type = Store.Models.Plex.EmbyMediaType.Movie, + EmbyId = movieInfo.Id, + AddedAt = DateTime.UtcNow + }); + } + } } } \ No newline at end of file diff --git a/Ombi.Services/Jobs/EmbyEpisodeCacher.cs b/Ombi.Services/Jobs/EmbyEpisodeCacher.cs index 5679a24b9..0135592cc 100644 --- a/Ombi.Services/Jobs/EmbyEpisodeCacher.cs +++ b/Ombi.Services/Jobs/EmbyEpisodeCacher.cs @@ -28,6 +28,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Dapper; using NLog; using Ombi.Api.Interfaces; using Ombi.Api.Models.Emby; @@ -65,7 +66,8 @@ namespace Ombi.Services.Jobs private const string TableName = "EmbyEpisodes"; - + // Note, once an episode exists, we store it and it always exists. + // We might want to look at checking if something has been removed from the server in the future. public void CacheEpisodes(EmbySettings settings) { var allEpisodes = EmbyApi.GetAllEpisodes(settings.ApiKey, settings.AdministratorId, settings.FullUri); @@ -74,25 +76,40 @@ namespace Ombi.Services.Jobs { var epInfo = EmbyApi.GetInformation(ep.Id, EmbyMediaType.Episode, settings.ApiKey, settings.AdministratorId, settings.FullUri); - if (epInfo.EpisodeInformation?.ProviderIds?.Tvdb == null) - { - continue; - } - model.Add(new EmbyEpisodes + if (epInfo.EpisodeInformation?.ProviderIds?.Tvdb == null) { - EmbyId = ep.Id, - EpisodeNumber = ep.IndexNumber, - SeasonNumber = ep.ParentIndexNumber, - EpisodeTitle = ep.Name, - ParentId = ep.SeriesId, - ShowTitle = ep.SeriesName, - ProviderId = epInfo.EpisodeInformation.ProviderIds.Tvdb - }); - } + continue; + } - // Delete all of the current items - Repo.DeleteAll(TableName); + // Check it this episode exists + var item = Repo.Custom(connection => + { + connection.Open(); + var media = + connection.QueryFirstOrDefault( + "select * from EmbyEpisodes where ProviderId = @ProviderId", + new {ProviderId = epInfo.EpisodeInformation?.ProviderIds?.Tvdb}); + connection.Dispose(); + return media; + }); + if (item == null) + { + // add it + model.Add(new EmbyEpisodes + { + EmbyId = ep.Id, + EpisodeNumber = ep.IndexNumber, + SeasonNumber = ep.ParentIndexNumber, + EpisodeTitle = ep.Name, + ParentId = ep.SeriesId, + ShowTitle = ep.SeriesName, + ProviderId = epInfo.EpisodeInformation.ProviderIds.Tvdb, + AddedAt = DateTime.UtcNow + }); + } + } + // Insert the new items var result = Repo.BatchInsert(model, TableName, typeof(EmbyEpisodes).GetPropertyNames()); diff --git a/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs b/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs new file mode 100644 index 000000000..cfe752c8a --- /dev/null +++ b/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs @@ -0,0 +1,341 @@ +#region Copyright + +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: RecentlyAddedModel.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.Linq; +using System.Text; +using NLog; +using Ombi.Api; +using Ombi.Api.Interfaces; +using Ombi.Api.Models.Emby; +using Ombi.Core; +using Ombi.Core.SettingModels; +using Ombi.Services.Jobs.Templates; +using Ombi.Store.Models; +using Ombi.Store.Models.Emby; +using Ombi.Store.Repository; +using EmbyMediaType = Ombi.Store.Models.Plex.EmbyMediaType; + +namespace Ombi.Services.Jobs.RecentlyAddedNewsletter +{ + public class EmbyAddedNewsletter : HtmlTemplateGenerator, IEmbyAddedNewsletter + { + public EmbyAddedNewsletter(IEmbyApi api, ISettingsService embySettings, + ISettingsService email, + ISettingsService newsletter, IRepository log, + IRepository embyContent, IRepository episodes) + { + Api = api; + EmbySettings = embySettings; + EmailSettings = email; + NewsletterSettings = newsletter; + Content = embyContent; + MovieApi = new TheMovieDbApi(); + TvApi = new TvMazeApi(); + Episodes = episodes; + RecentlyAddedLog = log; + } + + private IEmbyApi Api { get; } + private TheMovieDbApi MovieApi { get; } + private TvMazeApi TvApi { get; } + private ISettingsService EmbySettings { get; } + private ISettingsService EmailSettings { get; } + private ISettingsService NewsletterSettings { get; } + private IRepository Content { get; } + private IRepository Episodes { get; } + private IRepository RecentlyAddedLog { get; } + + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + + public string GetNewsletterHtml(bool test) + { + try + { + return GetHtml(test); + } + catch (Exception e) + { + Log.Error(e); + return string.Empty; + } + } + + private class EmbyRecentlyAddedModel + { + public EmbyInformation EmbyInformation { get; set; } + public EmbyContent EmbyContent { get; set; } + public List EpisodeInformation { get; set; } + } + + private string GetHtml(bool test) + { + var sb = new StringBuilder(); + var embySettings = EmbySettings.GetSettings(); + + var embyContent = Content.GetAll().ToList(); + + var series = embyContent.Where(x => x.Type == EmbyMediaType.Series).ToList(); + var episodes = Episodes.GetAll().ToList(); + var movie = embyContent.Where(x => x.Type == EmbyMediaType.Movie).ToList(); + + var recentlyAdded = RecentlyAddedLog.GetAll(); + + var filteredMovies = movie.Where(m => recentlyAdded.All(x => x.ProviderId != m.ProviderId)).ToList(); + var filteredEp = episodes.Where(m => recentlyAdded.All(x => x.ProviderId != m.ProviderId)).ToList(); + + + var info = new List(); + foreach (var m in filteredMovies) + { + + var i = Api.GetInformation(m.EmbyId, Ombi.Api.Models.Emby.EmbyMediaType.Movie, + embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri); + info.Add(new EmbyRecentlyAddedModel + { + EmbyInformation = i, + EmbyContent = m + }); + } + GenerateMovieHtml(info, sb); + + info.Clear(); + foreach (var t in series) + { + var i = Api.GetInformation(t.EmbyId, Ombi.Api.Models.Emby.EmbyMediaType.Series, + embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri); + var ep = filteredEp.Where(x => x.ParentId == t.EmbyId); + + if (ep.Any()) + { + var episodeList = new List(); + foreach (var embyEpisodese in ep) + { + var epInfo = Api.GetInformation(embyEpisodese.EmbyId, Ombi.Api.Models.Emby.EmbyMediaType.Episode, + embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri); + episodeList.Add(epInfo.EpisodeInformation); + } + info.Add(new EmbyRecentlyAddedModel + { + EmbyContent = t, + EmbyInformation = i, + EpisodeInformation = episodeList + }); + } + } + GenerateTvHtml(info, sb); + + var template = new RecentlyAddedTemplate(); + var html = template.LoadTemplate(sb.ToString()); + Log.Debug("Loaded the template"); + + if (!test) + { + foreach (var a in filteredMovies) + { + RecentlyAddedLog.Insert(new RecentlyAddedLog + { + ProviderId = a.ProviderId, + AddedAt = DateTime.UtcNow + }); + } + foreach (var a in filteredEp) + { + RecentlyAddedLog.Insert(new RecentlyAddedLog + { + ProviderId = a.ProviderId, + AddedAt = DateTime.UtcNow + }); + } + } + + var escapedHtml = new string(html.Where(c => !char.IsControl(c)).ToArray()); + Log.Debug(escapedHtml); + return escapedHtml; + } + + private void GenerateMovieHtml(IEnumerable movies, StringBuilder sb) + { + if (!movies.Any()) + { + return; + } + var orderedMovies = movies.OrderByDescending(x => x.EmbyContent.AddedAt).Select(x => x.EmbyInformation.MovieInformation).ToList(); + sb.Append("

New Movies:



"); + sb.Append( + ""); + foreach (var movie in orderedMovies) + { + try + { + + var imdbId = movie.ProviderIds.Imdb; + var info = MovieApi.GetMovieInformation(imdbId).Result; + if (info == null) + { + throw new Exception($"Movie with Imdb id {imdbId} returned null from the MovieApi"); + } + AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/w500{info.BackdropPath}"); + + sb.Append(""); + sb.Append( + "
"); + + Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/"); + Header(sb, 3, $"{info.Title} {info.ReleaseDate?.ToString("yyyy") ?? string.Empty}"); + EndTag(sb, "a"); + + if (info.Genres.Any()) + { + AddParagraph(sb, + $"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}"); + } + + AddParagraph(sb, info.Overview); + } + catch (Exception e) + { + Log.Error(e); + Log.Error("Error for movie with IMDB Id = {0}", movie.ProviderIds.Imdb); + } + finally + { + EndLoopHtml(sb); + } + + } + sb.Append("


"); + } + + private class TvModel + { + public EmbySeriesInformation Series { get; set; } + public List Episodes { get; set; } + } + private void GenerateTvHtml(List tv, StringBuilder sb) + { + if (!tv.Any()) + { + return; + } + var orderedTv = tv.OrderByDescending(x => x.EmbyContent.AddedAt).ToList(); + + // TV + sb.Append("

New Episodes:



"); + sb.Append( + ""); + foreach (var t in orderedTv) + { + var seriesItem = t.EmbyInformation.SeriesInformation; + var relatedEpisodes = t.EpisodeInformation; + + + try + { + var info = TvApi.ShowLookupByTheTvDbId(int.Parse(seriesItem.ProviderIds.Tvdb)); + + var banner = info.image?.original; + if (!string.IsNullOrEmpty(banner)) + { + banner = banner.Replace("http", "https"); // Always use the Https banners + } + AddImageInsideTable(sb, banner); + + sb.Append(""); + sb.Append( + "
"); + + var title = $"{seriesItem.Name} {seriesItem.PremiereDate.Year}"; + + Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/"); + Header(sb, 3, title); + EndTag(sb, "a"); + + var results = relatedEpisodes.GroupBy(p => p.ParentIndexNumber, + (key, g) => new + { + ParentIndexNumber = key, + IndexNumber = g.ToList() + } + ); + // Group the episodes + foreach (var embyEpisodeInformation in results.OrderBy(x => x.ParentIndexNumber)) + { + var epSb = new StringBuilder(); + for (var i = 0; i < embyEpisodeInformation.IndexNumber.Count; i++) + { + var ep = embyEpisodeInformation.IndexNumber[i]; + if (i < embyEpisodeInformation.IndexNumber.Count) + { + epSb.Append($"{ep.IndexNumber},"); + } + else + { + epSb.Append(ep); + } + } + AddParagraph(sb, $"Season: {embyEpisodeInformation.ParentIndexNumber}, Episode: {epSb}"); + } + + if (info.genres.Any()) + { + AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}"); + } + + AddParagraph(sb, string.IsNullOrEmpty(seriesItem.Overview) ? info.summary : seriesItem.Overview); + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + EndLoopHtml(sb); + } + } + sb.Append("


"); + } + + + + + private void EndLoopHtml(StringBuilder sb) + { + //NOTE: BR have to be in TD's as per html spec or it will be put outside of the table... + //Source: http://stackoverflow.com/questions/6588638/phantom-br-tag-rendered-by-browsers-prior-to-table-tag + sb.Append("
"); + sb.Append("
"); + sb.Append("
"); + sb.Append(""); + sb.Append(""); + } + + } +} \ No newline at end of file diff --git a/Ombi.Services/Jobs/RecentlyAddedNewsletter/IEmbyAddedNewsletter.cs b/Ombi.Services/Jobs/RecentlyAddedNewsletter/IEmbyAddedNewsletter.cs new file mode 100644 index 000000000..bef09ce6e --- /dev/null +++ b/Ombi.Services/Jobs/RecentlyAddedNewsletter/IEmbyAddedNewsletter.cs @@ -0,0 +1,7 @@ +namespace Ombi.Services.Jobs.RecentlyAddedNewsletter +{ + public interface IEmbyAddedNewsletter + { + string GetNewsletterHtml(bool test); + } +} \ No newline at end of file diff --git a/Ombi.Services/Jobs/RecentlyAdded.cs b/Ombi.Services/Jobs/RecentlyAddedNewsletter/RecentlyAddedNewsletter.cs similarity index 79% rename from Ombi.Services/Jobs/RecentlyAdded.cs rename to Ombi.Services/Jobs/RecentlyAddedNewsletter/RecentlyAddedNewsletter.cs index 4f9e3b5ca..0183058d0 100644 --- a/Ombi.Services/Jobs/RecentlyAdded.cs +++ b/Ombi.Services/Jobs/RecentlyAddedNewsletter/RecentlyAddedNewsletter.cs @@ -46,14 +46,15 @@ using Ombi.Services.Interfaces; using Ombi.Services.Jobs.Templates; using Quartz; -namespace Ombi.Services.Jobs +namespace Ombi.Services.Jobs.RecentlyAddedNewsletter { - public class RecentlyAdded : HtmlTemplateGenerator, IJob, IRecentlyAdded, IMassEmail + public class RecentlyAddedNewsletter : HtmlTemplateGenerator, IJob, IRecentlyAdded, IMassEmail { - public RecentlyAdded(IPlexApi api, ISettingsService plexSettings, + public RecentlyAddedNewsletter(IPlexApi api, ISettingsService plexSettings, ISettingsService email, IJobRecord rec, ISettingsService newsletter, - IPlexReadOnlyDatabase db, IUserHelper userHelper) + IPlexReadOnlyDatabase db, IUserHelper userHelper, IEmbyAddedNewsletter embyNews, + ISettingsService embyS) { JobRecord = rec; Api = api; @@ -62,17 +63,21 @@ namespace Ombi.Services.Jobs NewsletterSettings = newsletter; PlexDb = db; UserHelper = userHelper; + EmbyNewsletter = embyNews; + EmbySettings = embyS; } private IPlexApi Api { get; } private TvMazeApi TvApi = new TvMazeApi(); private readonly TheMovieDbApi _movieApi = new TheMovieDbApi(); private ISettingsService PlexSettings { get; } + private ISettingsService EmbySettings { get; } private ISettingsService EmailSettings { get; } private ISettingsService NewsletterSettings { get; } private IJobRecord JobRecord { get; } private IPlexReadOnlyDatabase PlexDb { get; } private IUserHelper UserHelper { get; } + private IEmbyAddedNewsletter EmbyNewsletter { get; } private static readonly Logger Log = LogManager.GetCurrentClassLogger(); @@ -128,97 +133,107 @@ namespace Ombi.Services.Jobs private void StartNewsLetter(NewletterSettings newletterSettings, bool testEmail = false) { - var sb = new StringBuilder(); - var plexSettings = PlexSettings.GetSettings(); - Log.Debug("Got Plex Settings"); + var embySettings = EmbySettings.GetSettings(); + if (embySettings.Enable) + { + var html = EmbyNewsletter.GetNewsletterHtml(testEmail); + + var escapedHtml = new string(html.Where(c => !char.IsControl(c)).ToArray()); + Log.Debug(escapedHtml); + SendNewsletter(newletterSettings, escapedHtml, testEmail); + } + else + { + var sb = new StringBuilder(); + var plexSettings = PlexSettings.GetSettings(); + Log.Debug("Got Plex Settings"); - var libs = Api.GetLibrarySections(plexSettings.PlexAuthToken, plexSettings.FullUri); - Log.Debug("Getting Plex Library Sections"); + var libs = Api.GetLibrarySections(plexSettings.PlexAuthToken, plexSettings.FullUri); + Log.Debug("Getting Plex Library Sections"); - var tvSections = libs.Directories.Where(x => x.type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)); // We could have more than 1 lib - Log.Debug("Filtered sections for TV"); - var movieSection = libs.Directories.Where(x => x.type.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase)); // We could have more than 1 lib - Log.Debug("Filtered sections for Movies"); + var tvSections = libs.Directories.Where(x => x.type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)); // We could have more than 1 lib + Log.Debug("Filtered sections for TV"); + var movieSection = libs.Directories.Where(x => x.type.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase)); // We could have more than 1 lib + Log.Debug("Filtered sections for Movies"); - var plexVersion = Api.GetStatus(plexSettings.PlexAuthToken, plexSettings.FullUri).Version; + var plexVersion = Api.GetStatus(plexSettings.PlexAuthToken, plexSettings.FullUri).Version; - var html = string.Empty; - if (plexVersion.StartsWith("1.3")) - { - var tvMetadata = new List(); - var movieMetadata = new List(); - foreach (var tvSection in tvSections) + var html = string.Empty; + if (plexVersion.StartsWith("1.3")) { - var item = Api.RecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri, - tvSection?.Key); - if (item?.MediaContainer?.Metadata != null) + var tvMetadata = new List(); + var movieMetadata = new List(); + foreach (var tvSection in tvSections) { - tvMetadata.AddRange(item?.MediaContainer?.Metadata); + var item = Api.RecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri, + tvSection?.Key); + if (item?.MediaContainer?.Metadata != null) + { + tvMetadata.AddRange(item?.MediaContainer?.Metadata); + } } - } - Log.Debug("Got RecentlyAdded TV Shows"); - foreach (var movie in movieSection) - { - var recentlyAddedMovies = Api.RecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri, movie?.Key); - if (recentlyAddedMovies?.MediaContainer?.Metadata != null) + Log.Debug("Got RecentlyAdded TV Shows"); + foreach (var movie in movieSection) { - movieMetadata.AddRange(recentlyAddedMovies?.MediaContainer?.Metadata); + var recentlyAddedMovies = Api.RecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri, movie?.Key); + if (recentlyAddedMovies?.MediaContainer?.Metadata != null) + { + movieMetadata.AddRange(recentlyAddedMovies?.MediaContainer?.Metadata); + } } + Log.Debug("Got RecentlyAdded Movies"); + + Log.Debug("Started Generating Movie HTML"); + GenerateMovieHtml(movieMetadata, plexSettings, sb); + Log.Debug("Finished Generating Movie HTML"); + Log.Debug("Started Generating TV HTML"); + GenerateTvHtml(tvMetadata, plexSettings, sb); + Log.Debug("Finished Generating TV HTML"); + + var template = new RecentlyAddedTemplate(); + html = template.LoadTemplate(sb.ToString()); + Log.Debug("Loaded the template"); } - Log.Debug("Got RecentlyAdded Movies"); - - Log.Debug("Started Generating Movie HTML"); - GenerateMovieHtml(movieMetadata, plexSettings, sb); - Log.Debug("Finished Generating Movie HTML"); - Log.Debug("Started Generating TV HTML"); - GenerateTvHtml(tvMetadata, plexSettings, sb); - Log.Debug("Finished Generating TV HTML"); - - var template = new RecentlyAddedTemplate(); - html = template.LoadTemplate(sb.ToString()); - Log.Debug("Loaded the template"); - } - else - { - // Old API - var tvChild = new List(); - var movieChild = new List(); - foreach (var tvSection in tvSections) + else { - var recentlyAddedTv = Api.RecentlyAddedOld(plexSettings.PlexAuthToken, plexSettings.FullUri, tvSection?.Key); - if (recentlyAddedTv?._children != null) + // Old API + var tvChild = new List(); + var movieChild = new List(); + foreach (var tvSection in tvSections) { - tvChild.AddRange(recentlyAddedTv?._children); + var recentlyAddedTv = Api.RecentlyAddedOld(plexSettings.PlexAuthToken, plexSettings.FullUri, tvSection?.Key); + if (recentlyAddedTv?._children != null) + { + tvChild.AddRange(recentlyAddedTv?._children); + } } - } - Log.Debug("Got RecentlyAdded TV Shows"); - foreach (var movie in movieSection) - { - var recentlyAddedMovies = Api.RecentlyAddedOld(plexSettings.PlexAuthToken, plexSettings.FullUri, movie?.Key); - if (recentlyAddedMovies?._children != null) + Log.Debug("Got RecentlyAdded TV Shows"); + foreach (var movie in movieSection) { - tvChild.AddRange(recentlyAddedMovies?._children); + var recentlyAddedMovies = Api.RecentlyAddedOld(plexSettings.PlexAuthToken, plexSettings.FullUri, movie?.Key); + if (recentlyAddedMovies?._children != null) + { + tvChild.AddRange(recentlyAddedMovies?._children); + } } + Log.Debug("Got RecentlyAdded Movies"); + + Log.Debug("Started Generating Movie HTML"); + GenerateMovieHtml(movieChild, plexSettings, sb); + Log.Debug("Finished Generating Movie HTML"); + Log.Debug("Started Generating TV HTML"); + GenerateTvHtml(tvChild, plexSettings, sb); + Log.Debug("Finished Generating TV HTML"); + + var template = new RecentlyAddedTemplate(); + html = template.LoadTemplate(sb.ToString()); + Log.Debug("Loaded the template"); } - Log.Debug("Got RecentlyAdded Movies"); - - Log.Debug("Started Generating Movie HTML"); - GenerateMovieHtml(movieChild, plexSettings, sb); - Log.Debug("Finished Generating Movie HTML"); - Log.Debug("Started Generating TV HTML"); - GenerateTvHtml(tvChild, plexSettings, sb); - Log.Debug("Finished Generating TV HTML"); - - var template = new RecentlyAddedTemplate(); - html = template.LoadTemplate(sb.ToString()); - Log.Debug("Loaded the template"); + string escapedHtml = new string(html.Where(c => !char.IsControl(c)).ToArray()); + Log.Debug(escapedHtml); + SendNewsletter(newletterSettings, escapedHtml, testEmail); } - - - string escapedHtml = new string(html.Where(c => !char.IsControl(c)).ToArray()); - Log.Debug(escapedHtml); - SendNewsletter(newletterSettings, escapedHtml, testEmail); } private void GenerateMovieHtml(List movies, PlexSettings plexSettings, StringBuilder sb) diff --git a/Ombi.Services/Ombi.Services.csproj b/Ombi.Services/Ombi.Services.csproj index a37279f7d..17093e031 100644 --- a/Ombi.Services/Ombi.Services.csproj +++ b/Ombi.Services/Ombi.Services.csproj @@ -108,6 +108,8 @@ + + @@ -117,7 +119,7 @@ - + diff --git a/Ombi.Store/Models/Emby/EmbyContent.cs b/Ombi.Store/Models/Emby/EmbyContent.cs index 799487755..07f211cc3 100644 --- a/Ombi.Store/Models/Emby/EmbyContent.cs +++ b/Ombi.Store/Models/Emby/EmbyContent.cs @@ -39,5 +39,6 @@ namespace Ombi.Store.Models.Emby public DateTime PremierDate { get; set; } public string ProviderId { get; set; } public EmbyMediaType Type { get; set; } + public DateTime AddedAt { get; set; } } } \ No newline at end of file diff --git a/Ombi.Store/Models/Emby/EmbyEpisodes.cs b/Ombi.Store/Models/Emby/EmbyEpisodes.cs index a1b900455..24d41f052 100644 --- a/Ombi.Store/Models/Emby/EmbyEpisodes.cs +++ b/Ombi.Store/Models/Emby/EmbyEpisodes.cs @@ -25,6 +25,7 @@ // ************************************************************************/ #endregion +using System; using Dapper.Contrib.Extensions; namespace Ombi.Store.Models.Emby @@ -39,5 +40,6 @@ namespace Ombi.Store.Models.Emby public int SeasonNumber { get; set; } public string ParentId { get; set; } public string ProviderId { get; set; } + public DateTime AddedAt { get; set; } } } \ No newline at end of file diff --git a/Ombi.Store/Models/RecenetlyAddedLog.cs b/Ombi.Store/Models/RecenetlyAddedLog.cs new file mode 100644 index 000000000..4f7a75aba --- /dev/null +++ b/Ombi.Store/Models/RecenetlyAddedLog.cs @@ -0,0 +1,40 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: LogEntity.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 Dapper.Contrib.Extensions; +using Newtonsoft.Json; + +namespace Ombi.Store.Models +{ + [Table("RecentlyAddedLog")] + public class RecentlyAddedLog : Entity + { + public string ProviderId { get; set; } + public DateTime AddedAt { get; set; } + } +} diff --git a/Ombi.Store/Ombi.Store.csproj b/Ombi.Store/Ombi.Store.csproj index 6bfaa021b..06a0b59fc 100644 --- a/Ombi.Store/Ombi.Store.csproj +++ b/Ombi.Store/Ombi.Store.csproj @@ -68,6 +68,7 @@ + diff --git a/Ombi.Store/SqlTables.sql b/Ombi.Store/SqlTables.sql index f548381a8..cdf5a2f80 100644 --- a/Ombi.Store/SqlTables.sql +++ b/Ombi.Store/SqlTables.sql @@ -187,7 +187,8 @@ CREATE TABLE IF NOT EXISTS EmbyEpisodes SeasonNumber INTEGER NOT NULL, EpisodeNumber INTEGER NOT NULL, ParentId VARCHAR(100) NOT NULL, - ProviderId VARCHAR(100) NOT NULL + ProviderId VARCHAR(100) NOT NULL, + AddedAt VARCHAR(100) NOT NULL ); CREATE UNIQUE INDEX IF NOT EXISTS EmbyEpisodes_Id ON EmbyEpisodes (Id); @@ -198,9 +199,21 @@ CREATE TABLE IF NOT EXISTS EmbyContent PremierDate VARCHAR(100) NOT NULL, EmbyId VARCHAR(100) NOT NULL, ProviderId VARCHAR(100) NOT NULL, - Type INTEGER NOT NULL + Type INTEGER NOT NULL, + AddedAt VARCHAR(100) NOT NULL ); CREATE UNIQUE INDEX IF NOT EXISTS EmbyEpisodes_Id ON EmbyEpisodes (Id); +CREATE TABLE IF NOT EXISTS RecentlyAddedLog +( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + ProviderId VARCHAR(100) NOT NULL, + AddedAt VARCHAR(100) NOT NULL + +); +CREATE UNIQUE INDEX IF NOT EXISTS RecentlyAddedLog_Id ON RecentlyAddedLog (Id); + +CREATE INDEX IF NOT EXISTS RecentlyAddedLog_ProviderId ON RecentlyAddedLog (ProviderId); + COMMIT; \ No newline at end of file diff --git a/Ombi.UI/Jobs/Scheduler.cs b/Ombi.UI/Jobs/Scheduler.cs index 8f43a02b3..d824701c9 100644 --- a/Ombi.UI/Jobs/Scheduler.cs +++ b/Ombi.UI/Jobs/Scheduler.cs @@ -35,6 +35,7 @@ using Ombi.Core; using Ombi.Core.SettingModels; using Ombi.Services.Interfaces; using Ombi.Services.Jobs; +using Ombi.Services.Jobs.RecentlyAddedNewsletter; using Ombi.UI.Helpers; using Quartz; using Quartz.Impl; @@ -70,7 +71,7 @@ namespace Ombi.UI.Jobs JobBuilder.Create().WithIdentity("StoreBackup", "Database").Build(), JobBuilder.Create().WithIdentity("StoreCleanup", "Database").Build(), JobBuilder.Create().WithIdentity("UserRequestLimiter", "Request").Build(), - JobBuilder.Create().WithIdentity("RecentlyAddedModel", "Email").Build(), + JobBuilder.Create().WithIdentity("RecentlyAddedModel", "Email").Build(), JobBuilder.Create().WithIdentity("FaultQueueHandler", "Fault").Build(), JobBuilder.Create().WithIdentity("RadarrCacher", "Cache").Build(), diff --git a/Ombi.UI/Modules/Admin/AdminModule.cs b/Ombi.UI/Modules/Admin/AdminModule.cs index 388a66ef0..8faab45d6 100644 --- a/Ombi.UI/Modules/Admin/AdminModule.cs +++ b/Ombi.UI/Modules/Admin/AdminModule.cs @@ -42,6 +42,7 @@ using Nancy.Validation; using NLog; using Ombi.Api; using Ombi.Api.Interfaces; +using Ombi.Api.Models.Movie; using Ombi.Core; using Ombi.Core.Models; using Ombi.Core.SettingModels; @@ -823,6 +824,10 @@ namespace Ombi.UI.Modules.Admin { return Response.AsJson(valid.SendJsonError()); } + if (!settings.Enabled) + { + return Response.AsJson(new CouchPotatoProfiles{list = new List()}); + } var profiles = CpApi.GetProfiles(settings.FullUri, settings.ApiKey); // set the cache diff --git a/Ombi.UI/NinjectModules/ServicesModule.cs b/Ombi.UI/NinjectModules/ServicesModule.cs index 95fbe4ba5..210cdfd3e 100644 --- a/Ombi.UI/NinjectModules/ServicesModule.cs +++ b/Ombi.UI/NinjectModules/ServicesModule.cs @@ -32,6 +32,7 @@ using Ombi.Helpers.Analytics; using Ombi.Services.Interfaces; using Ombi.Services.Jobs; using Ombi.Services.Jobs.Interfaces; +using Ombi.Services.Jobs.RecentlyAddedNewsletter; using Ombi.UI.Jobs; using Quartz; using Quartz.Impl; @@ -48,8 +49,8 @@ namespace Ombi.UI.NinjectModules Bind().To(); Bind().To(); Bind().To(); - Bind().To(); - Bind().To(); + Bind().To(); + Bind().To(); Bind().To(); Bind().To(); Bind().To(); @@ -65,6 +66,7 @@ namespace Ombi.UI.NinjectModules Bind().To(); Bind().To(); Bind().To(); + Bind().To(); Bind().To();