You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Ombi/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs

847 lines
35 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using MimeKit;
using Ombi.Api.Lidarr;
using Ombi.Api.Lidarr.Models;
4 years ago
using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.Settings;
5 years ago
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Hubs;
using Ombi.I18n.Resources;
using Ombi.Notifications;
using Ombi.Notifications.Models;
using Ombi.Notifications.Templates;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.External;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Quartz;
using ContentType = Ombi.Store.Entities.ContentType;
namespace Ombi.Schedule.Jobs.Ombi
{
public class NewsletterJob : HtmlTemplateGenerator, INewsletterJob
{
public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IJellyfinContentRepository jellyfin, IRepository<RecentlyAddedLog> addedLog,
IMovieDbApi movieApi, IEmailProvider email, ISettingsService<CustomizationSettings> custom,
ISettingsService<EmailNotificationSettings> emailSettings, INotificationTemplatesRepository templateRepo,
UserManager<OmbiUser> um, ISettingsService<NewsletterSettings> newsletter, ILogger<NewsletterJob> log,
ILidarrApi lidarrApi, IExternalRepository<LidarrAlbumCache> albumCache, ISettingsService<LidarrSettings> lidarrSettings,
ISettingsService<OmbiSettings> ombiSettings, ISettingsService<PlexSettings> plexSettings, ISettingsService<EmbySettings> embySettings, ISettingsService<JellyfinSettings> jellyfinSettings,
IHubContext<NotificationHub> notification, IRefreshMetadata refreshMetadata)
{
_plex = plex;
_emby = emby;
_jellyfin = jellyfin;
_recentlyAddedLog = addedLog;
_movieApi = movieApi;
_email = email;
_customizationSettings = custom;
_templateRepo = templateRepo;
_emailSettings = emailSettings;
_newsletterSettings = newsletter;
_userManager = um;
_log = log;
_lidarrApi = lidarrApi;
_lidarrAlbumRepository = albumCache;
_lidarrSettings = lidarrSettings;
_ombiSettings = ombiSettings;
5 years ago
_plexSettings = plexSettings;
_embySettings = embySettings;
_jellyfinSettings = jellyfinSettings;
_notification = notification;
5 years ago
_ombiSettings.ClearCache();
_plexSettings.ClearCache();
_emailSettings.ClearCache();
_customizationSettings.ClearCache();
_refreshMetadata = refreshMetadata;
}
private readonly IMediaServerContentRepository<PlexServerContent> _plex;
private readonly IMediaServerContentRepository<EmbyContent> _emby;
private readonly IMediaServerContentRepository<JellyfinContent> _jellyfin;
private readonly IRepository<RecentlyAddedLog> _recentlyAddedLog;
private readonly IMovieDbApi _movieApi;
private readonly IEmailProvider _email;
private readonly ISettingsService<CustomizationSettings> _customizationSettings;
private readonly INotificationTemplatesRepository _templateRepo;
private readonly ISettingsService<EmailNotificationSettings> _emailSettings;
private readonly ISettingsService<NewsletterSettings> _newsletterSettings;
private readonly ISettingsService<OmbiSettings> _ombiSettings;
private readonly UserManager<OmbiUser> _userManager;
private readonly ILogger _log;
private readonly ILidarrApi _lidarrApi;
private readonly IExternalRepository<LidarrAlbumCache> _lidarrAlbumRepository;
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
5 years ago
private readonly ISettingsService<PlexSettings> _plexSettings;
private readonly ISettingsService<EmbySettings> _embySettings;
private readonly ISettingsService<JellyfinSettings> _jellyfinSettings;
private readonly IHubContext<NotificationHub> _notification;
private readonly IRefreshMetadata _refreshMetadata;
public async Task Start(NewsletterSettings settings, bool test)
{
if (!settings.Enabled)
{
return;
}
var template = await _templateRepo.GetTemplate(NotificationAgent.Email, NotificationType.Newsletter);
if (!template.Enabled)
{
return;
}
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
.SendAsync(NotificationHub.NotificationEvent, "Newsletter Started");
var emailSettings = await _emailSettings.GetSettingsAsync();
if (!ValidateConfiguration(emailSettings))
{
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
.SendAsync(NotificationHub.NotificationEvent, "Newsletter Email Settings Not Configured");
return;
}
try
{
var plexSettings = await _plexSettings.GetSettingsAsync();
var embySettings = await _embySettings.GetSettingsAsync();
var jellyfinSettings = await _jellyfinSettings.GetSettingsAsync();
var customization = await _customizationSettings.GetSettingsAsync();
6 years ago
var moviesContents = new List<IMediaServerContent>();
var seriesContents = new List<IMediaServerEpisode>();
if (plexSettings.Enable)
{
moviesContents.AddRange(await GetMoviesContent(_plex, test));
seriesContents.AddRange(GetSeriesContent(_plex, test));
}
if (embySettings.Enable)
{
moviesContents.AddRange(await GetMoviesContent(_emby, test));
seriesContents.AddRange(GetSeriesContent(_emby, test));
}
if (jellyfinSettings.Enable)
{
moviesContents.AddRange(await GetMoviesContent(_jellyfin, test));
seriesContents.AddRange(GetSeriesContent(_jellyfin, test));
}
var albumsContents = GetMusicContent(_lidarrAlbumRepository, test);
var body = await BuildHtml(moviesContents, seriesContents, albumsContents, settings);
6 years ago
if (body.IsNullOrEmpty())
{
return;
}
6 years ago
if (!test)
{
var users = new List<OmbiUser>();
foreach (var emails in settings.ExternalEmails)
{
users.Add(new OmbiUser
{
UserName = emails,
Email = emails
});
}
// Get the users to send it to
users.AddRange(await _userManager.GetUsersInRoleAsync(OmbiRoles.ReceivesNewsletter));
if (!users.Any())
{
return;
}
var messageContent = ParseTemplate(template, customization);
var email = new NewsletterTemplate();
foreach (var user in users.DistinctBy(x => x.Email))
{ // Get the users to send it to
if (user.Email.IsNullOrEmpty())
{
continue;
}
var url = GenerateUnsubscribeLink(customization.ApplicationUrl, user.Id);
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo, url);
var bodyBuilder = new BodyBuilder
{
HtmlBody = html,
};
var message = new MimeMessage
{
Body = bodyBuilder.ToMessageBody(),
Subject = messageContent.Subject
};
// Send the message to the user
message.To.Add(new MailboxAddress(user.Email.Trim(), user.Email.Trim()));
// Send the email
await _email.Send(message, emailSettings);
}
// Now add all of this to the Recently Added log
var recentlyAddedLog = new HashSet<RecentlyAddedLog>();
AddToRecentlyAddedLog(moviesContents, recentlyAddedLog);
AddToRecentlyAddedLog(seriesContents, recentlyAddedLog);
await _recentlyAddedLog.AddRange(recentlyAddedLog);
}
else
{
var admins = await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin);
foreach (var a in admins)
{
if (a.Email.IsNullOrEmpty())
{
continue;
}
var unsubscribeLink = GenerateUnsubscribeLink(customization.ApplicationUrl, a.Id);
var messageContent = ParseTemplate(template, customization);
var email = new NewsletterTemplate();
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo, unsubscribeLink);
await _email.Send(
new NotificationMessage { Message = html, Subject = messageContent.Subject, To = a.Email },
emailSettings);
}
}
}
catch (Exception e)
{
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
.SendAsync(NotificationHub.NotificationEvent, "Newsletter Failed");
_log.LogError(e, "Error when attempting to create newsletter");
throw;
}
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
.SendAsync(NotificationHub.NotificationEvent, "Newsletter Finished");
}
private void AddToRecentlyAddedLog(ICollection<IMediaServerContent> moviesContents,
HashSet<RecentlyAddedLog> recentlyAddedLog)
3 years ago
{
foreach (var p in moviesContents)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = p.RecentlyAddedType,
ContentType = ContentType.Parent,
ContentId = StringHelper.IntParseLinq(p.TheMovieDbId),
});
}
}
private void AddToRecentlyAddedLog(ICollection<IMediaServerEpisode> episodes,
HashSet<RecentlyAddedLog> recentlyAddedLog)
{
foreach (var p in episodes)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = p.Series.RecentlyAddedType,
ContentType = ContentType.Episode,
ContentId = StringHelper.IntParseLinq(p.Series.TvDbId),
EpisodeNumber = p.EpisodeNumber,
SeasonNumber = p.SeasonNumber
});
}
}
3 years ago
private void GetRecentlyAddedMoviesData(List<RecentlyAddedLog> addedLog, out HashSet<string> addedAlbumLogIds)
{
3 years ago
var lidarrParent = addedLog.Where(x => x.Type == RecentlyAddedType.Lidarr && x.ContentType == ContentType.Album);
addedAlbumLogIds = lidarrParent != null && lidarrParent.Any() ? (lidarrParent?.Select(x => x.AlbumId)?.ToHashSet() ?? new HashSet<string>()) : new HashSet<string>();
}
private async Task<HashSet<IMediaServerContent>> GetMoviesContent<T>(IMediaServerContentRepository<T> repository, bool test) where T : class, IMediaServerContent
{
IQueryable<IMediaServerContent> content = repository.GetAll().Include(x => x.Episodes).AsNoTracking().Where(x => x.Type == MediaType.Movie).OrderByDescending(x => x.AddedAt);
var localDataset = content.Where(x => x.Type == MediaType.Movie && !string.IsNullOrEmpty(x.TheMovieDbId)).ToHashSet();
HashSet<IMediaServerContent> moviesToSend;
if (test)
{
moviesToSend = content.Take(10).ToHashSet();
}
else
{
// Filter out the ones that we haven't sent yet
var parent = _recentlyAddedLog.GetAll().Where(x => x.Type == repository.RecentlyAddedType
&& x.ContentType == ContentType.Parent).ToList();
var addedMovieLogIds = parent != null && parent.Any() ? (parent?.Select(x => x.ContentId)?.ToHashSet() ?? new HashSet<int>()) : new HashSet<int>();
moviesToSend = localDataset.Where(x => !addedMovieLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId))).ToHashSet();
_log.LogInformation("Movies to send: {0}", moviesToSend.Count());
// Find the movies that do not yet have MovieDbIds
var needsMovieDb = content.Where(x => x.Type == MediaType.Movie && !string.IsNullOrEmpty(x.TheMovieDbId)).ToHashSet();
var newMovies = await GetMoviesWithoutId(addedMovieLogIds, needsMovieDb, repository);
moviesToSend = moviesToSend.Union(newMovies).ToHashSet();
}
_log.LogInformation("Movies to send: {0}", moviesToSend.Count());
return moviesToSend.DistinctBy(x => x.Id).ToHashSet();
}
private HashSet<IMediaServerEpisode> GetSeriesContent<T>(IMediaServerContentRepository<T> repository, bool test) where T : class, IMediaServerContent
{
var content = repository.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).AsNoTracking();
HashSet<IMediaServerEpisode> episodesToSend;
if (test)
{
var count = repository.GetAllEpisodes().Count();
episodesToSend = content.Take(10).ToHashSet();
}
else
{
// Filter out the ones that we haven't sent yet
var addedEpisodesLogIds =
_recentlyAddedLog.GetAll().Where(x => x.Type == repository.RecentlyAddedType && x.ContentType == ContentType.Episode);
episodesToSend =
FilterEpisodes(content, addedEpisodesLogIds);
}
_log.LogInformation("Episodes to send: {0}", episodesToSend.Count());
return episodesToSend;
}
private HashSet<LidarrAlbumCache> GetMusicContent(IExternalRepository<LidarrAlbumCache> repository, bool test)
{
var lidarrContent = repository.GetAll().AsNoTracking().ToList().Where(x => x.FullyAvailable);
HashSet<LidarrAlbumCache> albumsToSend;
if (test)
{
albumsToSend = lidarrContent.OrderByDescending(x => x.AddedAt).Take(10).ToHashSet();
}
else
{
// Filter out the ones that we haven't sent yet
var addedLog = _recentlyAddedLog.GetAll().ToList();
HashSet<string> addedAlbumLogIds;
GetRecentlyAddedMoviesData(addedLog, out addedAlbumLogIds);
albumsToSend = lidarrContent.Where(x => !addedAlbumLogIds.Contains(x.ForeignAlbumId)).ToHashSet();
}
_log.LogInformation("Albums to send: {0}", albumsToSend.Count());
return albumsToSend;
}
public static string GenerateUnsubscribeLink(string applicationUrl, string id)
{
if (!applicationUrl.HasValue() || !id.HasValue())
{
return string.Empty;
}
if (!applicationUrl.EndsWith('/'))
{
applicationUrl += '/';
}
var b = new UriBuilder($"{applicationUrl}unsubscribe/{id}");
return b.ToString();
}
private async Task<HashSet<IMediaServerContent>> GetMoviesWithoutId<T>(HashSet<int> addedMovieLogIds, HashSet<IMediaServerContent> needsMovieDb, IMediaServerContentRepository<T> repository) where T : class, IMediaServerContent
{
foreach (var movie in needsMovieDb)
{
var id = await _refreshMetadata.GetTheMovieDbId(false, true, null, movie.ImdbId, movie.Title, true);
movie.TheMovieDbId = id.ToString();
}
var result = needsMovieDb.Where(x => x.HasTheMovieDb && !addedMovieLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId)));
await UpdateTheMovieDbId(result, repository);
// Filter them out now
return result.ToHashSet();
}
private async Task UpdateTheMovieDbId<T>(IEnumerable<IMediaServerContent> content, IMediaServerContentRepository<T> repository) where T : class, IMediaServerContent
{
foreach (var movie in content)
{
if (!movie.HasTheMovieDb)
{
continue;
}
var entity = await repository.Find(movie.Id);
if (entity == null)
{
return;
}
entity.TheMovieDbId = movie.TheMovieDbId;
repository.UpdateWithoutSave(entity);
}
await repository.SaveChangesAsync();
}
public async Task Execute(IJobExecutionContext job)
{
var newsletterSettings = await _newsletterSettings.GetSettingsAsync();
await Start(newsletterSettings, false);
}
private HashSet<IMediaServerEpisode> FilterEpisodes(IEnumerable<IMediaServerEpisode> source, IEnumerable<RecentlyAddedLog> recentlyAdded)
{
var itemsToReturn = new HashSet<IMediaServerEpisode>();
develop to master (#4718) * Remove dead code * Localize TV requests messages on TV details page * Transform buttons with link into anchors * Sonarr sync: stop using seasonpass API * chore(release): :rocket: v4.16.13 * Fix requests when 4k available and 4k disabled Fixes #4610 * chore(release): :rocket: v4.16.14 * Hide subscribe button when request is available * Hide subscribe button when request is denied * Add Title to Partially Available Message If the Title of the show is not menitoned it can be unclear what Episodes are now available. * Better error message when test email fails due to missing recipient * feat(discover): Add original language filter * chore(release): :rocket: v4.16.15 * fix(4616): :bug: fixed mandatory fields * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.16.16 * added test results into the PR pipeline * chore(release): :rocket: v4.16.17 * Add information about cache refresh * Update pr.yml [skip ci] * Update pr.yml [skip ci] * Update pr.yml [skip ci] * chore(release): :rocket: v4.17.0 * feat(discover): Add new trending source experimental feature * fix(settings): Allow toggling features when there are more than one * fix(discover): Fix new trending feature detection * fix(discover): Fix cache mix up * refactor(discover): Move movie trending feature toggle to backend * feat(discover): Default trending source to new logic * chore(release): :rocket: v4.18.0 * feat(sync): Detect reidentified movies in Emby and Jellyfin * feat(sync): Detect reidentified series in Emby and Jellyfin * Fix sync log criticity * Update pr.yml [skip ci] * Update label.yml [skip ci] * Fix formatting * Update pr.yml [skip ci] * Update label.yml [skip ci] * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.19.0 * refactor(newsletter): Clarify very rare cases where newsletter doesn't publish a series * refactor(newsletter): Clarify very rare cases where newsletter doesn't publish movie * chore(release): :rocket: v4.19.1 * feat(discover): Show more relevant shows in upcoming TV * chore(release): :rocket: v4.20.0 * fix(sync): Emby+Jellyfin - sync multi-episode files of 3+ episodes * perf(sync): Emby+Jellyfin - use a more reliable filter to missing items * fix(sync): Emby+Jellyfin - sync multi-episode files of 3+ episodes [skip ci] * fix: added media type tag to media type text (#4638) [skip ci] * fix(sickrage): Fixed issue with incorrect handling of SiCKRAGE episode results returned during episode status changes, now expects array of objects from data path if present (#4648) [skip ci] * fix: Missing Poster broken link fix (#4637) [skip ci] * 🌐 Translations Update (#4622) [skip ci] * Update launch.json (#4650) [skip ci] * fix: Improve Swagger documentation (#4652) * Upgrade Swashbuckle dependency * Document /token response * Add support for Newtonsoft annotations in Swagger * Remove unecessary ActionResult [skip ci] * fix(API): Fix pagination in some edge cases (#4649) [skip ci] * 🌐 Translations Update (#4655) * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(discover): Carousel touch not working when scrolling page and recommendations and similar movie navigation (#4633) * fixed touch not working on carousels * fixed touch not working * Movie details component fixes Fixed recommendations and similar not changing the data on the component by calling the init function again on param change Moved the ngif results > 0 to the mat-expansion panel to avoid rendering the entire element if it doesn't have any results instead of having an empty panel. * removed unused line, added scroll to top on init * updated recommendation refresh implementation Changed the implementation to use the router instead in order to reload the component instead of just reloading the data. This implementation makes sure the component gets destroyed on navigation eliminating any memory leaks, reloading CSS in case of having animations on page load and generally a continuation of the experience you get when you browse into a movie from the discover page. * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.20.1 [skip ci] * fix: :bug: Fixed the Request on Behalf of having blanks (#4667) * chore(release): :rocket: v4.20.2 [skip ci] * fix(plex): 🐛 Fixed an issue with the Plex Sync * chore(release): :rocket: v4.20.3 [skip ci] * fix (technical): Improved some of the date time parsing handling * fix: fixed build * chore(release): :rocket: v4.20.4 [skip ci] * feat: Upgrade to Angular14 (#4668) * refactor: :fire: removed angular-bootstrap-md dependancy * chore: update tsconfig * yeah * ng14 upgrade * refactor: migration changes * fix: fixed CLI * test: Fixed automation * chore: :busts_in_silhouette: Updated Contributors [skip ci] * perf: stop populating obsolete subscribe fields (#4625) * chore(release): :rocket: v4.21.0 [skip ci] * fix(images): Retry images with a backoff when we get a Too Many requests from TheMovieDb #4685 * chore(release): :rocket: v4.21.1 [skip ci] * 🌐 Translations Update (#4683) * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix: Landing and Login page improvements (#4690) * chore(release): :rocket: v4.21.2 [skip ci] * feat(discover): ✨ Added infinite scroll on advanced search results * feat(discover): :sparkles: Added infinite scroll on advanced search results * chore(release): :rocket: v4.22.0 [skip ci] * 🌐 Translations Update (#4694) * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(discover): :bug: Created new Image component to handle 429's from TMDB (#4698) and fixed #4635 (#4699) * chore(release): :rocket: v4.22.1 [skip ci] * fix: fixed an issue where I broke images for some users * chore(release): :rocket: v4.22.2 [skip ci] * ci(Mergify): configuration update (#4701) Signed-off-by: Jamie <tidusjar@gmail.com> [skip ci] * fix: Override Sonarr V3 Profiles endpoint (#4678) * Override Sonarr V3 Profiles endpoint [skip ci] * fix(4K) :4K request fixes (#4702) * GetRequestsByStatus wasn't implementing the MovieRequests object correctly for 4K quality requests with the ProcessingRequest status. * Fixed 4K requests not getting automatically approved if the user has the "Auto Approve Movie" role flag enabled. * Fixed "Request Date" values for the "left-panel-details" div class. Previously when the movie was exclusively 4K (regular request was absent), then "Request Date" equaled DateTime.MinValue (January 1, 0001). * Fixed "Request Status" evaluation in the "left-panel-details" div class. Now it shows the appropriate status instead of an empty spot. "Request Status" displays both regular and 4K statuses at the same time if needed. Added a comma to the end of the "RequestStatus" label to maintain design consistency with the other labels. Also added a "Denied Reason" element for 4K requests. * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.22.3 [skip ci] * chore: Storybook (#4700) [skip ci] * chore: Translations [skip ci] * 🌐 Translations Update (#4704) * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] (#4713) * build: Run automation tests in docker (#4715) [skip ci] * fix: fixed trakt image not loading when base url present (#4711) [skip ci] * fix: :bug: Fixed missing externals (#4712) * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.22.4 [skip ci] * test: fixed automationt tests [skip ci] Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com> Co-authored-by: Teifun2 <Teifun2@users.noreply.github.com> Co-authored-by: contrib-readme-bot <contrib-readme-action@noreply.com> Co-authored-by: dr3amer <91037083+dr3am37@users.noreply.github.com> Co-authored-by: echel0n <echel0n@sickrage.ca> Co-authored-by: Marley <55280588+marleypowell@users.noreply.github.com> Co-authored-by: Igor Borges <igor@borges.dev> Co-authored-by: Lucane <Lucane@users.noreply.github.com> Co-authored-by: mkgeeky <github@mkgeeky.xyz>
2 years ago
foreach (var ep in source.Where(x => x.Series.HasTvDb // needed for recentlyAddedLog
&& x.Series.HasTheMovieDb // needed to fetch info to publish, this is just in case...
))
{
var tvDbId = StringHelper.IntParseLinq(ep.Series.TvDbId);
if (recentlyAdded.Any(x => x.ContentId == tvDbId && x.EpisodeNumber == ep.EpisodeNumber && x.SeasonNumber == ep.SeasonNumber))
{
continue;
}
itemsToReturn.Add(ep);
}
return itemsToReturn;
}
private NotificationMessageContent ParseTemplate(NotificationTemplates template, CustomizationSettings settings)
{
var resolver = new NotificationMessageResolver();
var curlys = new NotificationMessageCurlys();
curlys.SetupNewsletter(settings);
return resolver.ParseMessage(template, curlys);
}
private async Task<string> BuildHtml(ICollection<IMediaServerContent> movies,
IEnumerable<IMediaServerEpisode> episodes, HashSet<LidarrAlbumCache> albums, NewsletterSettings settings)
{
var ombiSettings = await _ombiSettings.GetSettingsAsync();
sb = new StringBuilder();
if (movies.Any() && !settings.DisableMovies)
{
sb.Append($"<h1 style=\"text-align: center; max-width: 1042px;\">{Texts.NewMovies}</h1><br /><br />");
sb.Append(
"<table class=\"movies-table\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; \">");
sb.Append("<tr>");
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; \">");
sb.Append("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; \">");
sb.Append("<tr>");
await ProcessMovies(movies, ombiSettings.DefaultLanguageCode);
sb.Append("</tr>");
sb.Append("</table>");
sb.Append("</td>");
sb.Append("</tr>");
sb.Append("</table>");
}
if (episodes.Any() && !settings.DisableTv)
{
sb.Append($"<br /><br /><h1 style=\"text-align: center; max-width: 1042px;\">{Texts.NewTV}</h1><br /><br />");
sb.Append(
"<table class=\"tv-table\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; \">");
sb.Append("<tr>");
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; \">");
sb.Append("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; \">");
sb.Append("<tr>");
await ProcessTv(episodes, ombiSettings.DefaultLanguageCode);
sb.Append("</tr>");
sb.Append("</table>");
sb.Append("</td>");
sb.Append("</tr>");
sb.Append("</table>");
}
if (albums.Any() && !settings.DisableMusic)
{
sb.Append($"<h1 style=\"text-align: center; max-width: 1042px;\">{Texts.NewAlbums}</h1><br /><br />");
sb.Append(
"<table class=\"movies-table\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; \">");
sb.Append("<tr>");
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; \">");
sb.Append("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; \">");
sb.Append("<tr>");
await ProcessAlbums(albums);
sb.Append("</tr>");
sb.Append("</table>");
sb.Append("</td>");
sb.Append("</tr>");
sb.Append("</table>");
}
return sb.ToString();
}
private async Task ProcessMovies(ICollection<IMediaServerContent> plexContentToSend, string defaultLanguageCode)
{
int count = 0;
var ordered = plexContentToSend;
foreach (var content in ordered)
{
int.TryParse(content.TheMovieDbId, out var movieDbId);
var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId, defaultLanguageCode);
var mediaurl = content.Url;
if (info == null)
{
develop to master (#4718) * Remove dead code * Localize TV requests messages on TV details page * Transform buttons with link into anchors * Sonarr sync: stop using seasonpass API * chore(release): :rocket: v4.16.13 * Fix requests when 4k available and 4k disabled Fixes #4610 * chore(release): :rocket: v4.16.14 * Hide subscribe button when request is available * Hide subscribe button when request is denied * Add Title to Partially Available Message If the Title of the show is not menitoned it can be unclear what Episodes are now available. * Better error message when test email fails due to missing recipient * feat(discover): Add original language filter * chore(release): :rocket: v4.16.15 * fix(4616): :bug: fixed mandatory fields * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.16.16 * added test results into the PR pipeline * chore(release): :rocket: v4.16.17 * Add information about cache refresh * Update pr.yml [skip ci] * Update pr.yml [skip ci] * Update pr.yml [skip ci] * chore(release): :rocket: v4.17.0 * feat(discover): Add new trending source experimental feature * fix(settings): Allow toggling features when there are more than one * fix(discover): Fix new trending feature detection * fix(discover): Fix cache mix up * refactor(discover): Move movie trending feature toggle to backend * feat(discover): Default trending source to new logic * chore(release): :rocket: v4.18.0 * feat(sync): Detect reidentified movies in Emby and Jellyfin * feat(sync): Detect reidentified series in Emby and Jellyfin * Fix sync log criticity * Update pr.yml [skip ci] * Update label.yml [skip ci] * Fix formatting * Update pr.yml [skip ci] * Update label.yml [skip ci] * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.19.0 * refactor(newsletter): Clarify very rare cases where newsletter doesn't publish a series * refactor(newsletter): Clarify very rare cases where newsletter doesn't publish movie * chore(release): :rocket: v4.19.1 * feat(discover): Show more relevant shows in upcoming TV * chore(release): :rocket: v4.20.0 * fix(sync): Emby+Jellyfin - sync multi-episode files of 3+ episodes * perf(sync): Emby+Jellyfin - use a more reliable filter to missing items * fix(sync): Emby+Jellyfin - sync multi-episode files of 3+ episodes [skip ci] * fix: added media type tag to media type text (#4638) [skip ci] * fix(sickrage): Fixed issue with incorrect handling of SiCKRAGE episode results returned during episode status changes, now expects array of objects from data path if present (#4648) [skip ci] * fix: Missing Poster broken link fix (#4637) [skip ci] * 🌐 Translations Update (#4622) [skip ci] * Update launch.json (#4650) [skip ci] * fix: Improve Swagger documentation (#4652) * Upgrade Swashbuckle dependency * Document /token response * Add support for Newtonsoft annotations in Swagger * Remove unecessary ActionResult [skip ci] * fix(API): Fix pagination in some edge cases (#4649) [skip ci] * 🌐 Translations Update (#4655) * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(discover): Carousel touch not working when scrolling page and recommendations and similar movie navigation (#4633) * fixed touch not working on carousels * fixed touch not working * Movie details component fixes Fixed recommendations and similar not changing the data on the component by calling the init function again on param change Moved the ngif results > 0 to the mat-expansion panel to avoid rendering the entire element if it doesn't have any results instead of having an empty panel. * removed unused line, added scroll to top on init * updated recommendation refresh implementation Changed the implementation to use the router instead in order to reload the component instead of just reloading the data. This implementation makes sure the component gets destroyed on navigation eliminating any memory leaks, reloading CSS in case of having animations on page load and generally a continuation of the experience you get when you browse into a movie from the discover page. * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.20.1 [skip ci] * fix: :bug: Fixed the Request on Behalf of having blanks (#4667) * chore(release): :rocket: v4.20.2 [skip ci] * fix(plex): 🐛 Fixed an issue with the Plex Sync * chore(release): :rocket: v4.20.3 [skip ci] * fix (technical): Improved some of the date time parsing handling * fix: fixed build * chore(release): :rocket: v4.20.4 [skip ci] * feat: Upgrade to Angular14 (#4668) * refactor: :fire: removed angular-bootstrap-md dependancy * chore: update tsconfig * yeah * ng14 upgrade * refactor: migration changes * fix: fixed CLI * test: Fixed automation * chore: :busts_in_silhouette: Updated Contributors [skip ci] * perf: stop populating obsolete subscribe fields (#4625) * chore(release): :rocket: v4.21.0 [skip ci] * fix(images): Retry images with a backoff when we get a Too Many requests from TheMovieDb #4685 * chore(release): :rocket: v4.21.1 [skip ci] * 🌐 Translations Update (#4683) * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix: Landing and Login page improvements (#4690) * chore(release): :rocket: v4.21.2 [skip ci] * feat(discover): ✨ Added infinite scroll on advanced search results * feat(discover): :sparkles: Added infinite scroll on advanced search results * chore(release): :rocket: v4.22.0 [skip ci] * 🌐 Translations Update (#4694) * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(discover): :bug: Created new Image component to handle 429's from TMDB (#4698) and fixed #4635 (#4699) * chore(release): :rocket: v4.22.1 [skip ci] * fix: fixed an issue where I broke images for some users * chore(release): :rocket: v4.22.2 [skip ci] * ci(Mergify): configuration update (#4701) Signed-off-by: Jamie <tidusjar@gmail.com> [skip ci] * fix: Override Sonarr V3 Profiles endpoint (#4678) * Override Sonarr V3 Profiles endpoint [skip ci] * fix(4K) :4K request fixes (#4702) * GetRequestsByStatus wasn't implementing the MovieRequests object correctly for 4K quality requests with the ProcessingRequest status. * Fixed 4K requests not getting automatically approved if the user has the "Auto Approve Movie" role flag enabled. * Fixed "Request Date" values for the "left-panel-details" div class. Previously when the movie was exclusively 4K (regular request was absent), then "Request Date" equaled DateTime.MinValue (January 1, 0001). * Fixed "Request Status" evaluation in the "left-panel-details" div class. Now it shows the appropriate status instead of an empty spot. "Request Status" displays both regular and 4K statuses at the same time if needed. Added a comma to the end of the "RequestStatus" label to maintain design consistency with the other labels. Also added a "Denied Reason" element for 4K requests. * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.22.3 [skip ci] * chore: Storybook (#4700) [skip ci] * chore: Translations [skip ci] * 🌐 Translations Update (#4704) * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] (#4713) * build: Run automation tests in docker (#4715) [skip ci] * fix: fixed trakt image not loading when base url present (#4711) [skip ci] * fix: :bug: Fixed missing externals (#4712) * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.22.4 [skip ci] * test: fixed automationt tests [skip ci] Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com> Co-authored-by: Teifun2 <Teifun2@users.noreply.github.com> Co-authored-by: contrib-readme-bot <contrib-readme-action@noreply.com> Co-authored-by: dr3amer <91037083+dr3am37@users.noreply.github.com> Co-authored-by: echel0n <echel0n@sickrage.ca> Co-authored-by: Marley <55280588+marleypowell@users.noreply.github.com> Co-authored-by: Igor Borges <igor@borges.dev> Co-authored-by: Lucane <Lucane@users.noreply.github.com> Co-authored-by: mkgeeky <github@mkgeeky.xyz>
2 years ago
_log.LogError($"TMDB does not know movie {content.Title}. This shouldn't happen because our media server knows it as ID '{movieDbId}'.");
continue;
}
try
{
CreateMovieHtmlContent(info, mediaurl);
count += 1;
}
catch (Exception e)
{
_log.LogError(e, "Error when Processing Movies {0}", info.Title);
}
finally
{
EndLoopHtml();
}
if (count == 2)
{
count = 0;
sb.Append("</tr>");
sb.Append("<tr>");
}
}
}
private async Task ProcessAlbums(HashSet<LidarrAlbumCache> albumsToSend)
{
var settings = await _lidarrSettings.GetSettingsAsync();
int count = 0;
var ordered = albumsToSend.OrderByDescending(x => x.AddedAt);
foreach (var content in ordered)
{
var info = await _lidarrApi.GetAlbumByForeignId(content.ForeignAlbumId, settings.ApiKey, settings.FullUri);
if (info == null)
{
continue;
}
try
{
CreateAlbumHtmlContent(info);
count += 1;
}
catch (Exception e)
{
_log.LogError(e, "Error when Processing Lidarr Album {0}", info.title);
}
finally
{
EndLoopHtml();
}
if (count == 2)
{
count = 0;
sb.Append("</tr>");
sb.Append("<tr>");
}
}
}
private void CreateMovieHtmlContent(MovieResponseDto info, string mediaurl)
{
AddBackgroundInsideTable($"https://image.tmdb.org/t/p/w1280/{info.BackdropPath}");
AddPosterInsideTable($"https://image.tmdb.org/t/p/original{info.PosterPath}");
AddMediaServerUrl(mediaurl, $"https://image.tmdb.org/t/p/original{info.PosterPath}");
AddInfoTable();
var releaseDate = string.Empty;
try
{
releaseDate = $"({DateTime.Parse(info.ReleaseDate).Year})";
}
4 years ago
#pragma warning disable RCS1075 // Avoid empty catch clause that catches System.Exception.
catch (Exception)
4 years ago
#pragma warning restore RCS1075 // Avoid empty catch clause that catches System.Exception.
{
// Swallow, couldn't parse the date
}
AddTitle($"https://www.imdb.com/title/{info.ImdbId}/", $"{info.Title} {releaseDate}");
var summary = info.Overview;
if (summary.Length > 280)
{
summary = summary.Remove(280);
summary = summary + "...</p>";
}
AddParagraph(summary);
if (info.Genres.Any())
{
AddGenres($"{Texts.GenresLabel} {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}");
}
}
private void CreateAlbumHtmlContent(AlbumLookup info)
{
var cover = info.images
.FirstOrDefault(x => x.coverType.Equals("cover", StringComparison.InvariantCultureIgnoreCase))?.url;
if (cover.IsNullOrEmpty())
{
cover = info.remoteCover;
}
AddBackgroundInsideTable(cover);
var disk = info.images
.FirstOrDefault(x => x.coverType.Equals("disc", StringComparison.InvariantCultureIgnoreCase))?.url;
if (disk.IsNullOrEmpty())
{
disk = info.remoteCover;
}
AddPosterInsideTable(disk);
AddMediaServerUrl(string.Empty, string.Empty);
AddInfoTable();
var releaseDate = $"({info.releaseDate.Year})";
AddTitle(string.Empty, $"{info.title} {releaseDate}");
var summary = info.artist?.artistName ?? string.Empty;
if (summary.Length > 280)
{
summary = summary.Remove(280);
summary = summary + "...</p>";
}
AddParagraph(summary);
AddGenres($"{Texts.AlbumTypeLabel} {info.albumType}");
}
private async Task ProcessTv(IEnumerable<IMediaServerEpisode> episodes, string languageCode)
{
var series = new List<IMediaServerContent>();
foreach (var episode in episodes)
{
var existingSeries = episode.SeriesIsIn(series);
if (existingSeries != null)
{
if (!episode.IsIn(existingSeries))
{
existingSeries.Episodes.Add(episode);
}
}
else
{
episode.Series.Episodes = new List<IMediaServerEpisode> { episode };
series.Add(episode.Series);
}
}
int count = 0;
var orderedTv = series.OrderByDescending(x => x.AddedAt);
foreach (var t in orderedTv)
{
try
{
var tvInfo = await _movieApi.GetTVInfo(t.TheMovieDbId, languageCode);
if (tvInfo == null)
{
develop to master (#4718) * Remove dead code * Localize TV requests messages on TV details page * Transform buttons with link into anchors * Sonarr sync: stop using seasonpass API * chore(release): :rocket: v4.16.13 * Fix requests when 4k available and 4k disabled Fixes #4610 * chore(release): :rocket: v4.16.14 * Hide subscribe button when request is available * Hide subscribe button when request is denied * Add Title to Partially Available Message If the Title of the show is not menitoned it can be unclear what Episodes are now available. * Better error message when test email fails due to missing recipient * feat(discover): Add original language filter * chore(release): :rocket: v4.16.15 * fix(4616): :bug: fixed mandatory fields * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.16.16 * added test results into the PR pipeline * chore(release): :rocket: v4.16.17 * Add information about cache refresh * Update pr.yml [skip ci] * Update pr.yml [skip ci] * Update pr.yml [skip ci] * chore(release): :rocket: v4.17.0 * feat(discover): Add new trending source experimental feature * fix(settings): Allow toggling features when there are more than one * fix(discover): Fix new trending feature detection * fix(discover): Fix cache mix up * refactor(discover): Move movie trending feature toggle to backend * feat(discover): Default trending source to new logic * chore(release): :rocket: v4.18.0 * feat(sync): Detect reidentified movies in Emby and Jellyfin * feat(sync): Detect reidentified series in Emby and Jellyfin * Fix sync log criticity * Update pr.yml [skip ci] * Update label.yml [skip ci] * Fix formatting * Update pr.yml [skip ci] * Update label.yml [skip ci] * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.19.0 * refactor(newsletter): Clarify very rare cases where newsletter doesn't publish a series * refactor(newsletter): Clarify very rare cases where newsletter doesn't publish movie * chore(release): :rocket: v4.19.1 * feat(discover): Show more relevant shows in upcoming TV * chore(release): :rocket: v4.20.0 * fix(sync): Emby+Jellyfin - sync multi-episode files of 3+ episodes * perf(sync): Emby+Jellyfin - use a more reliable filter to missing items * fix(sync): Emby+Jellyfin - sync multi-episode files of 3+ episodes [skip ci] * fix: added media type tag to media type text (#4638) [skip ci] * fix(sickrage): Fixed issue with incorrect handling of SiCKRAGE episode results returned during episode status changes, now expects array of objects from data path if present (#4648) [skip ci] * fix: Missing Poster broken link fix (#4637) [skip ci] * 🌐 Translations Update (#4622) [skip ci] * Update launch.json (#4650) [skip ci] * fix: Improve Swagger documentation (#4652) * Upgrade Swashbuckle dependency * Document /token response * Add support for Newtonsoft annotations in Swagger * Remove unecessary ActionResult [skip ci] * fix(API): Fix pagination in some edge cases (#4649) [skip ci] * 🌐 Translations Update (#4655) * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(discover): Carousel touch not working when scrolling page and recommendations and similar movie navigation (#4633) * fixed touch not working on carousels * fixed touch not working * Movie details component fixes Fixed recommendations and similar not changing the data on the component by calling the init function again on param change Moved the ngif results > 0 to the mat-expansion panel to avoid rendering the entire element if it doesn't have any results instead of having an empty panel. * removed unused line, added scroll to top on init * updated recommendation refresh implementation Changed the implementation to use the router instead in order to reload the component instead of just reloading the data. This implementation makes sure the component gets destroyed on navigation eliminating any memory leaks, reloading CSS in case of having animations on page load and generally a continuation of the experience you get when you browse into a movie from the discover page. * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.20.1 [skip ci] * fix: :bug: Fixed the Request on Behalf of having blanks (#4667) * chore(release): :rocket: v4.20.2 [skip ci] * fix(plex): 🐛 Fixed an issue with the Plex Sync * chore(release): :rocket: v4.20.3 [skip ci] * fix (technical): Improved some of the date time parsing handling * fix: fixed build * chore(release): :rocket: v4.20.4 [skip ci] * feat: Upgrade to Angular14 (#4668) * refactor: :fire: removed angular-bootstrap-md dependancy * chore: update tsconfig * yeah * ng14 upgrade * refactor: migration changes * fix: fixed CLI * test: Fixed automation * chore: :busts_in_silhouette: Updated Contributors [skip ci] * perf: stop populating obsolete subscribe fields (#4625) * chore(release): :rocket: v4.21.0 [skip ci] * fix(images): Retry images with a backoff when we get a Too Many requests from TheMovieDb #4685 * chore(release): :rocket: v4.21.1 [skip ci] * 🌐 Translations Update (#4683) * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix: Landing and Login page improvements (#4690) * chore(release): :rocket: v4.21.2 [skip ci] * feat(discover): ✨ Added infinite scroll on advanced search results * feat(discover): :sparkles: Added infinite scroll on advanced search results * chore(release): :rocket: v4.22.0 [skip ci] * 🌐 Translations Update (#4694) * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(discover): :bug: Created new Image component to handle 429's from TMDB (#4698) and fixed #4635 (#4699) * chore(release): :rocket: v4.22.1 [skip ci] * fix: fixed an issue where I broke images for some users * chore(release): :rocket: v4.22.2 [skip ci] * ci(Mergify): configuration update (#4701) Signed-off-by: Jamie <tidusjar@gmail.com> [skip ci] * fix: Override Sonarr V3 Profiles endpoint (#4678) * Override Sonarr V3 Profiles endpoint [skip ci] * fix(4K) :4K request fixes (#4702) * GetRequestsByStatus wasn't implementing the MovieRequests object correctly for 4K quality requests with the ProcessingRequest status. * Fixed 4K requests not getting automatically approved if the user has the "Auto Approve Movie" role flag enabled. * Fixed "Request Date" values for the "left-panel-details" div class. Previously when the movie was exclusively 4K (regular request was absent), then "Request Date" equaled DateTime.MinValue (January 1, 0001). * Fixed "Request Status" evaluation in the "left-panel-details" div class. Now it shows the appropriate status instead of an empty spot. "Request Status" displays both regular and 4K statuses at the same time if needed. Added a comma to the end of the "RequestStatus" label to maintain design consistency with the other labels. Also added a "Denied Reason" element for 4K requests. * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.22.3 [skip ci] * chore: Storybook (#4700) [skip ci] * chore: Translations [skip ci] * 🌐 Translations Update (#4704) * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] (#4713) * build: Run automation tests in docker (#4715) [skip ci] * fix: fixed trakt image not loading when base url present (#4711) [skip ci] * fix: :bug: Fixed missing externals (#4712) * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.22.4 [skip ci] * test: fixed automationt tests [skip ci] Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com> Co-authored-by: Teifun2 <Teifun2@users.noreply.github.com> Co-authored-by: contrib-readme-bot <contrib-readme-action@noreply.com> Co-authored-by: dr3amer <91037083+dr3am37@users.noreply.github.com> Co-authored-by: echel0n <echel0n@sickrage.ca> Co-authored-by: Marley <55280588+marleypowell@users.noreply.github.com> Co-authored-by: Igor Borges <igor@borges.dev> Co-authored-by: Lucane <Lucane@users.noreply.github.com> Co-authored-by: mkgeeky <github@mkgeeky.xyz>
2 years ago
_log.LogError($"TMDB does not know series {t.Title}. This shouldn't happen because our media server knows it as ID '{t.TheMovieDbId}'.");
continue;
}
if (tvInfo.backdrop_path.HasValue())
{
AddBackgroundInsideTable($"https://image.tmdb.org/t/p/w500{tvInfo.backdrop_path}");
}
else
{
AddBackgroundInsideTable($"https://image.tmdb.org/t/p/w1280/");
}
var banner = tvInfo.poster_path;
if (!string.IsNullOrEmpty(banner))
{
banner = $"https://image.tmdb.org/t/p/w300/{banner?.TrimStart('/') ?? string.Empty}";
};
AddPosterInsideTable(banner);
AddMediaServerUrl(t.Url, banner);
AddInfoTable();
AddTvTitle(tvInfo);
var tvEpisodesString = GetTvEpisodesString(tvInfo, t.Episodes);
AddTvEpisodesSummaryGenres(tvEpisodesString, tvInfo);
}
catch (Exception e)
{
develop to master (#4718) * Remove dead code * Localize TV requests messages on TV details page * Transform buttons with link into anchors * Sonarr sync: stop using seasonpass API * chore(release): :rocket: v4.16.13 * Fix requests when 4k available and 4k disabled Fixes #4610 * chore(release): :rocket: v4.16.14 * Hide subscribe button when request is available * Hide subscribe button when request is denied * Add Title to Partially Available Message If the Title of the show is not menitoned it can be unclear what Episodes are now available. * Better error message when test email fails due to missing recipient * feat(discover): Add original language filter * chore(release): :rocket: v4.16.15 * fix(4616): :bug: fixed mandatory fields * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.16.16 * added test results into the PR pipeline * chore(release): :rocket: v4.16.17 * Add information about cache refresh * Update pr.yml [skip ci] * Update pr.yml [skip ci] * Update pr.yml [skip ci] * chore(release): :rocket: v4.17.0 * feat(discover): Add new trending source experimental feature * fix(settings): Allow toggling features when there are more than one * fix(discover): Fix new trending feature detection * fix(discover): Fix cache mix up * refactor(discover): Move movie trending feature toggle to backend * feat(discover): Default trending source to new logic * chore(release): :rocket: v4.18.0 * feat(sync): Detect reidentified movies in Emby and Jellyfin * feat(sync): Detect reidentified series in Emby and Jellyfin * Fix sync log criticity * Update pr.yml [skip ci] * Update label.yml [skip ci] * Fix formatting * Update pr.yml [skip ci] * Update label.yml [skip ci] * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.19.0 * refactor(newsletter): Clarify very rare cases where newsletter doesn't publish a series * refactor(newsletter): Clarify very rare cases where newsletter doesn't publish movie * chore(release): :rocket: v4.19.1 * feat(discover): Show more relevant shows in upcoming TV * chore(release): :rocket: v4.20.0 * fix(sync): Emby+Jellyfin - sync multi-episode files of 3+ episodes * perf(sync): Emby+Jellyfin - use a more reliable filter to missing items * fix(sync): Emby+Jellyfin - sync multi-episode files of 3+ episodes [skip ci] * fix: added media type tag to media type text (#4638) [skip ci] * fix(sickrage): Fixed issue with incorrect handling of SiCKRAGE episode results returned during episode status changes, now expects array of objects from data path if present (#4648) [skip ci] * fix: Missing Poster broken link fix (#4637) [skip ci] * 🌐 Translations Update (#4622) [skip ci] * Update launch.json (#4650) [skip ci] * fix: Improve Swagger documentation (#4652) * Upgrade Swashbuckle dependency * Document /token response * Add support for Newtonsoft annotations in Swagger * Remove unecessary ActionResult [skip ci] * fix(API): Fix pagination in some edge cases (#4649) [skip ci] * 🌐 Translations Update (#4655) * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(discover): Carousel touch not working when scrolling page and recommendations and similar movie navigation (#4633) * fixed touch not working on carousels * fixed touch not working * Movie details component fixes Fixed recommendations and similar not changing the data on the component by calling the init function again on param change Moved the ngif results > 0 to the mat-expansion panel to avoid rendering the entire element if it doesn't have any results instead of having an empty panel. * removed unused line, added scroll to top on init * updated recommendation refresh implementation Changed the implementation to use the router instead in order to reload the component instead of just reloading the data. This implementation makes sure the component gets destroyed on navigation eliminating any memory leaks, reloading CSS in case of having animations on page load and generally a continuation of the experience you get when you browse into a movie from the discover page. * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.20.1 [skip ci] * fix: :bug: Fixed the Request on Behalf of having blanks (#4667) * chore(release): :rocket: v4.20.2 [skip ci] * fix(plex): 🐛 Fixed an issue with the Plex Sync * chore(release): :rocket: v4.20.3 [skip ci] * fix (technical): Improved some of the date time parsing handling * fix: fixed build * chore(release): :rocket: v4.20.4 [skip ci] * feat: Upgrade to Angular14 (#4668) * refactor: :fire: removed angular-bootstrap-md dependancy * chore: update tsconfig * yeah * ng14 upgrade * refactor: migration changes * fix: fixed CLI * test: Fixed automation * chore: :busts_in_silhouette: Updated Contributors [skip ci] * perf: stop populating obsolete subscribe fields (#4625) * chore(release): :rocket: v4.21.0 [skip ci] * fix(images): Retry images with a backoff when we get a Too Many requests from TheMovieDb #4685 * chore(release): :rocket: v4.21.1 [skip ci] * 🌐 Translations Update (#4683) * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix: Landing and Login page improvements (#4690) * chore(release): :rocket: v4.21.2 [skip ci] * feat(discover): ✨ Added infinite scroll on advanced search results * feat(discover): :sparkles: Added infinite scroll on advanced search results * chore(release): :rocket: v4.22.0 [skip ci] * 🌐 Translations Update (#4694) * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(discover): :bug: Created new Image component to handle 429's from TMDB (#4698) and fixed #4635 (#4699) * chore(release): :rocket: v4.22.1 [skip ci] * fix: fixed an issue where I broke images for some users * chore(release): :rocket: v4.22.2 [skip ci] * ci(Mergify): configuration update (#4701) Signed-off-by: Jamie <tidusjar@gmail.com> [skip ci] * fix: Override Sonarr V3 Profiles endpoint (#4678) * Override Sonarr V3 Profiles endpoint [skip ci] * fix(4K) :4K request fixes (#4702) * GetRequestsByStatus wasn't implementing the MovieRequests object correctly for 4K quality requests with the ProcessingRequest status. * Fixed 4K requests not getting automatically approved if the user has the "Auto Approve Movie" role flag enabled. * Fixed "Request Date" values for the "left-panel-details" div class. Previously when the movie was exclusively 4K (regular request was absent), then "Request Date" equaled DateTime.MinValue (January 1, 0001). * Fixed "Request Status" evaluation in the "left-panel-details" div class. Now it shows the appropriate status instead of an empty spot. "Request Status" displays both regular and 4K statuses at the same time if needed. Added a comma to the end of the "RequestStatus" label to maintain design consistency with the other labels. Also added a "Denied Reason" element for 4K requests. * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.22.3 [skip ci] * chore: Storybook (#4700) [skip ci] * chore: Translations [skip ci] * 🌐 Translations Update (#4704) * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] * fix(translations): 🌐 New translations from Crowdin [skip ci] (#4713) * build: Run automation tests in docker (#4715) [skip ci] * fix: fixed trakt image not loading when base url present (#4711) [skip ci] * fix: :bug: Fixed missing externals (#4712) * chore: :busts_in_silhouette: Updated Contributors [skip ci] * chore(release): :rocket: v4.22.4 [skip ci] * test: fixed automationt tests [skip ci] Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com> Co-authored-by: Teifun2 <Teifun2@users.noreply.github.com> Co-authored-by: contrib-readme-bot <contrib-readme-action@noreply.com> Co-authored-by: dr3amer <91037083+dr3am37@users.noreply.github.com> Co-authored-by: echel0n <echel0n@sickrage.ca> Co-authored-by: Marley <55280588+marleypowell@users.noreply.github.com> Co-authored-by: Igor Borges <igor@borges.dev> Co-authored-by: Lucane <Lucane@users.noreply.github.com> Co-authored-by: mkgeeky <github@mkgeeky.xyz>
2 years ago
_log.LogError(e, "Error when processing TV {0}", t.Title);
}
finally
{
EndLoopHtml();
count += 1;
}
if (count == 2)
{
count = 0;
sb.Append("</tr>");
sb.Append("<tr>");
}
}
}
private string GetTvEpisodesString(TvInfo tvInfo, ICollection<IMediaServerEpisode> episodes)
{
if (episodes.Count >= tvInfo.number_of_episodes)
{
// do not list individual episodes when the series is complete
return string.Empty;
}
var sb = new StringBuilder();
// Group by the season number
var seasons = episodes.GroupBy(p => p.SeasonNumber,
(key, g) => new
{
SeasonNumber = key,
Episodes = g.ToList(),
Header = tvInfo?.seasons?.Where(x => x.season_number == key).FirstOrDefault(),
}
);
// Group the episodes
foreach (var season in seasons.OrderBy(x => x.SeasonNumber))
{
string episodeList;
if (season.Episodes.Count >= season.Header.episode_count)
{
// do not list individual episodes when the season is complete
episodeList = string.Empty;
}
else
{
var orderedEpisodes = season.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
episodeList = $"{Texts.EpisodesLabel} {StringHelper.BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber))}";
}
var episodeAirDate = season.Header.air_date;
sb.Append($"{Texts.SeasonLabel} {season.SeasonNumber} - {episodeList} {episodeAirDate}");
sb.Append("<br />");
}
return sb.ToString();
}
private void AddTvTitle(TvInfo tvInfo)
{
var title = "";
if (!String.IsNullOrEmpty(tvInfo.first_air_date) && tvInfo.first_air_date.Length > 4)
{
title = $"{tvInfo.name} ({tvInfo.first_air_date.Remove(4)})";
}
else
{
title = $"{tvInfo.name}";
}
AddTitle($"https://www.themoviedb.org/tv/{tvInfo.id}/", title);
}
private void AddTvEpisodesSummaryGenres(string episodes, TvInfo tvInfo)
{
var summary = tvInfo.overview;
if (summary.Length > 280)
{
summary = summary.Remove(280);
summary = summary + "...</p>";
}
AddTvParagraph(episodes, summary);
if (tvInfo.genres.Any())
{
AddGenres($"{Texts.GenresLabel} {string.Join(", ", tvInfo.genres.Select(x => x.name.ToString()).ToArray())}");
}
}
private void EndLoopHtml()
{
//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("</table>");
sb.Append("</td>");
sb.Append("</tr>");
sb.Append("</table>");
sb.Append("</td>");
sb.Append("</tr>");
sb.Append("</table>");
sb.Append("</td>");
}
protected bool ValidateConfiguration(EmailNotificationSettings settings)
{
if (!settings.Enabled)
{
return false;
}
if (settings.Authentication)
{
if (string.IsNullOrEmpty(settings.Username) || string.IsNullOrEmpty(settings.Password))
{
return false;
}
}
if (string.IsNullOrEmpty(settings.Host) || string.IsNullOrEmpty(settings.Port.ToString()))
{
return false;
}
return true;
}
private bool _disposed;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
4 years ago
//_newsletterSettings?.Dispose();
//_customizationSettings?.Dispose();
//_emailSettings.Dispose();
_templateRepo?.Dispose();
_userManager?.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}