mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-19 12:59:39 -07:00
Merge branch 'feature/lidarr' of https://github.com/tidusjar/ombi into feature/lidarr
This commit is contained in:
commit
4f505ad536
22 changed files with 1385 additions and 40 deletions
|
@ -5,6 +5,6 @@ namespace Ombi.Api.Pushover
|
||||||
{
|
{
|
||||||
public interface IPushoverApi
|
public interface IPushoverApi
|
||||||
{
|
{
|
||||||
Task<PushoverResponse> PushAsync(string accessToken, string message, string userToken);
|
Task<PushoverResponse> PushAsync(string accessToken, string message, string userToken, sbyte priority, string sound);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,13 +16,13 @@ namespace Ombi.Api.Pushover
|
||||||
private readonly IApi _api;
|
private readonly IApi _api;
|
||||||
private const string PushoverEndpoint = "https://api.pushover.net/1";
|
private const string PushoverEndpoint = "https://api.pushover.net/1";
|
||||||
|
|
||||||
public async Task<PushoverResponse> PushAsync(string accessToken, string message, string userToken)
|
public async Task<PushoverResponse> PushAsync(string accessToken, string message, string userToken, sbyte priority, string sound)
|
||||||
{
|
{
|
||||||
if (message.Contains("'"))
|
if (message.Contains("'"))
|
||||||
{
|
{
|
||||||
message = message.Replace("'", "'");
|
message = message.Replace("'", "'");
|
||||||
}
|
}
|
||||||
var request = new Request($"messages.json?token={accessToken}&user={userToken}&message={WebUtility.HtmlEncode(message)}", PushoverEndpoint, HttpMethod.Post);
|
var request = new Request($"messages.json?token={accessToken}&user={userToken}&priority={priority}&sound={sound}&message={WebUtility.HtmlEncode(message)}", PushoverEndpoint, HttpMethod.Post);
|
||||||
|
|
||||||
var result = await _api.Request<PushoverResponse>(request);
|
var result = await _api.Request<PushoverResponse>(request);
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Moq" Version="4.7.99" />
|
<PackageReference Include="Moq" Version="4.9.0" />
|
||||||
<PackageReference Include="Nunit" Version="3.8.1" />
|
<PackageReference Include="Nunit" Version="3.10.1" />
|
||||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
|
<PackageReference Include="NUnit.ConsoleRunner" Version="3.8.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
|
||||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
|
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
26
src/Ombi.Core.Tests/StringHelperTests.cs
Normal file
26
src/Ombi.Core.Tests/StringHelperTests.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Ombi.Helpers;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class StringHelperTests
|
||||||
|
{
|
||||||
|
[TestCaseSource(nameof(StripCharsData))]
|
||||||
|
public string StripCharacters(string str, char[] chars)
|
||||||
|
{
|
||||||
|
return str.StripCharacters(chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<TestCaseData> StripCharsData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
yield return new TestCaseData("this!is^a*string",new []{'!','^','*'}).Returns("thisisastring").SetName("Basic Strip Multipe Chars");
|
||||||
|
yield return new TestCaseData("What is this madness'",new []{'\'','^','*'}).Returns("What is this madness").SetName("Basic Strip Multipe Chars");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -80,5 +80,9 @@ namespace Ombi.Helpers
|
||||||
{
|
{
|
||||||
return str.Replace(" ", "");
|
return str.Replace(" ", "");
|
||||||
}
|
}
|
||||||
|
public static string StripCharacters(this string str, params char[] chars)
|
||||||
|
{
|
||||||
|
return string.Concat(str.Where(c => !chars.Contains(c)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -177,7 +177,8 @@ namespace Ombi.Notifications.Agents
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Api.PushAsync(settings.AccessToken, model.Message, settings.UserToken);
|
//&+' < >
|
||||||
|
await Api.PushAsync(settings.AccessToken, model.Message.StripCharacters('&','+','<','>'), settings.UserToken, settings.Priority, settings.Sound);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -67,7 +67,8 @@ namespace Ombi.Schedule.Jobs.Lidarr
|
||||||
TrackCount = a.currentRelease.trackCount,
|
TrackCount = a.currentRelease.trackCount,
|
||||||
Monitored = a.monitored,
|
Monitored = a.monitored,
|
||||||
Title = a.title,
|
Title = a.title,
|
||||||
PercentOfTracks = a.statistics?.percentOfEpisodes ?? 0m
|
PercentOfTracks = a.statistics?.percentOfEpisodes ?? 0m,
|
||||||
|
AddedAt = DateTime.Now,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Ombi.Helpers;
|
||||||
|
|
||||||
namespace Ombi.Schedule.Jobs.Ombi
|
namespace Ombi.Schedule.Jobs.Ombi
|
||||||
{
|
{
|
||||||
|
@ -22,13 +23,20 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
|
|
||||||
protected virtual void AddMediaServerUrl(StringBuilder sb, string mediaurl, string url)
|
protected virtual void AddMediaServerUrl(StringBuilder sb, string mediaurl, string url)
|
||||||
{
|
{
|
||||||
sb.Append("<tr>");
|
if (url.HasValue())
|
||||||
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; \">");
|
{
|
||||||
sb.AppendFormat("<a href=\"{0}\" target=\"_blank\">", mediaurl);
|
sb.Append("<tr>");
|
||||||
sb.AppendFormat("<img class=\"poster-overlay\" src=\"{0}\" width=\"150\" height=\"225\" style=\"border: none;-ms-interpolation-mode: bicubic; max-width: 100%;display: block; visibility: hidden; \">", url);
|
sb.Append(
|
||||||
sb.Append("</a>");
|
"<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; \">");
|
||||||
sb.Append("</td>");
|
sb.AppendFormat("<a href=\"{0}\" target=\"_blank\">", mediaurl);
|
||||||
sb.Append("</tr>");
|
sb.AppendFormat(
|
||||||
|
"<img class=\"poster-overlay\" src=\"{0}\" width=\"150\" height=\"225\" style=\"border: none;-ms-interpolation-mode: bicubic; max-width: 100%;display: block; visibility: hidden; \">",
|
||||||
|
url);
|
||||||
|
sb.Append("</a>");
|
||||||
|
sb.Append("</td>");
|
||||||
|
sb.Append("</tr>");
|
||||||
|
}
|
||||||
|
|
||||||
sb.Append("</table>");
|
sb.Append("</table>");
|
||||||
sb.Append("</td>");
|
sb.Append("</td>");
|
||||||
}
|
}
|
||||||
|
@ -44,9 +52,9 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
{
|
{
|
||||||
sb.Append("<tr>");
|
sb.Append("<tr>");
|
||||||
sb.Append("<td class=\"title\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 0.9rem; vertical-align: top; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; line-height: 1.2rem; padding: 5px; \">");
|
sb.Append("<td class=\"title\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 0.9rem; vertical-align: top; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; line-height: 1.2rem; padding: 5px; \">");
|
||||||
sb.AppendFormat("<a href=\"{0}\" target=\"_blank\">", url);
|
if(url.HasValue()) sb.AppendFormat("<a href=\"{0}\" target=\"_blank\">", url);
|
||||||
sb.AppendFormat("<h1 style=\"white-space: normal; line-height: 1;\" >{0}</h1>", title);
|
sb.AppendFormat("<h1 style=\"white-space: normal; line-height: 1;\" >{0}</h1>", title);
|
||||||
sb.Append("</a>");
|
if (url.HasValue()) sb.Append("</a>");
|
||||||
sb.Append("</td>");
|
sb.Append("</td>");
|
||||||
sb.Append("</tr>");
|
sb.Append("</tr>");
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ using MailKit;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ombi.Api.Lidarr;
|
||||||
|
using Ombi.Api.Lidarr.Models;
|
||||||
using Ombi.Api.TheMovieDb;
|
using Ombi.Api.TheMovieDb;
|
||||||
using Ombi.Api.TheMovieDb.Models;
|
using Ombi.Api.TheMovieDb.Models;
|
||||||
using Ombi.Api.TvMaze;
|
using Ombi.Api.TvMaze;
|
||||||
|
@ -18,6 +20,7 @@ using Ombi.Notifications;
|
||||||
using Ombi.Notifications.Models;
|
using Ombi.Notifications.Models;
|
||||||
using Ombi.Notifications.Templates;
|
using Ombi.Notifications.Templates;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
|
using Ombi.Settings.Settings.Models.External;
|
||||||
using Ombi.Settings.Settings.Models.Notifications;
|
using Ombi.Settings.Settings.Models.Notifications;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
|
@ -29,7 +32,8 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository<RecentlyAddedLog> addedLog,
|
public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository<RecentlyAddedLog> addedLog,
|
||||||
IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService<CustomizationSettings> custom,
|
IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService<CustomizationSettings> custom,
|
||||||
ISettingsService<EmailNotificationSettings> emailSettings, INotificationTemplatesRepository templateRepo,
|
ISettingsService<EmailNotificationSettings> emailSettings, INotificationTemplatesRepository templateRepo,
|
||||||
UserManager<OmbiUser> um, ISettingsService<NewsletterSettings> newsletter, ILogger<NewsletterJob> log)
|
UserManager<OmbiUser> um, ISettingsService<NewsletterSettings> newsletter, ILogger<NewsletterJob> log,
|
||||||
|
ILidarrApi lidarrApi, IRepository<LidarrAlbumCache> albumCache, ISettingsService<LidarrSettings> lidarrSettings)
|
||||||
{
|
{
|
||||||
_plex = plex;
|
_plex = plex;
|
||||||
_emby = emby;
|
_emby = emby;
|
||||||
|
@ -46,6 +50,10 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
_customizationSettings.ClearCache();
|
_customizationSettings.ClearCache();
|
||||||
_newsletterSettings.ClearCache();
|
_newsletterSettings.ClearCache();
|
||||||
_log = log;
|
_log = log;
|
||||||
|
_lidarrApi = lidarrApi;
|
||||||
|
_lidarrAlbumRepository = albumCache;
|
||||||
|
_lidarrSettings = lidarrSettings;
|
||||||
|
_lidarrSettings.ClearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly IPlexContentRepository _plex;
|
private readonly IPlexContentRepository _plex;
|
||||||
|
@ -60,6 +68,9 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
private readonly ISettingsService<NewsletterSettings> _newsletterSettings;
|
private readonly ISettingsService<NewsletterSettings> _newsletterSettings;
|
||||||
private readonly UserManager<OmbiUser> _userManager;
|
private readonly UserManager<OmbiUser> _userManager;
|
||||||
private readonly ILogger _log;
|
private readonly ILogger _log;
|
||||||
|
private readonly ILidarrApi _lidarrApi;
|
||||||
|
private readonly IRepository<LidarrAlbumCache> _lidarrAlbumRepository;
|
||||||
|
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
|
||||||
|
|
||||||
public async Task Start(NewsletterSettings settings, bool test)
|
public async Task Start(NewsletterSettings settings, bool test)
|
||||||
{
|
{
|
||||||
|
@ -87,21 +98,26 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
// Get the Content
|
// Get the Content
|
||||||
var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking();
|
var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking();
|
||||||
var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking();
|
var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking();
|
||||||
|
var lidarrContent = _lidarrAlbumRepository.GetAll().AsNoTracking();
|
||||||
|
|
||||||
var addedLog = _recentlyAddedLog.GetAll();
|
var addedLog = _recentlyAddedLog.GetAll();
|
||||||
var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId);
|
var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId);
|
||||||
var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId);
|
var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId);
|
||||||
|
var addedAlbumLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Lidarr && x.ContentType == ContentType.Album).Select(x => x.AlbumId);
|
||||||
|
|
||||||
var addedPlexEpisodesLogIds =
|
var addedPlexEpisodesLogIds =
|
||||||
addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode);
|
addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode);
|
||||||
var addedEmbyEpisodesLogIds =
|
var addedEmbyEpisodesLogIds =
|
||||||
addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode);
|
addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode);
|
||||||
|
|
||||||
|
|
||||||
// Filter out the ones that we haven't sent yet
|
// Filter out the ones that we haven't sent yet
|
||||||
var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && x.HasTheMovieDb && !addedPlexMovieLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId)));
|
var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && x.HasTheMovieDb && !addedPlexMovieLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId)));
|
||||||
var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && x.HasTheMovieDb && !addedEmbyMoviesLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId)));
|
var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && x.HasTheMovieDb && !addedEmbyMoviesLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId)));
|
||||||
|
var lidarrContentAlbumsToSend = lidarrContent.Where(x => !addedAlbumLogIds.Contains(x.ForeignAlbumId)).ToHashSet();
|
||||||
_log.LogInformation("Plex Movies to send: {0}", plexContentMoviesToSend.Count());
|
_log.LogInformation("Plex Movies to send: {0}", plexContentMoviesToSend.Count());
|
||||||
_log.LogInformation("Emby Movies to send: {0}", embyContentMoviesToSend.Count());
|
_log.LogInformation("Emby Movies to send: {0}", embyContentMoviesToSend.Count());
|
||||||
|
_log.LogInformation("Albums to send: {0}", lidarrContentAlbumsToSend.Count());
|
||||||
|
|
||||||
var plexEpisodesToSend =
|
var plexEpisodesToSend =
|
||||||
FilterPlexEpisodes(_plex.GetAllEpisodes().Include(x => x.Series).Where(x => x.Series.HasTvDb).AsNoTracking(), addedPlexEpisodesLogIds);
|
FilterPlexEpisodes(_plex.GetAllEpisodes().Include(x => x.Series).Where(x => x.Series.HasTvDb).AsNoTracking(), addedPlexEpisodesLogIds);
|
||||||
|
@ -117,11 +133,12 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie ).OrderByDescending(x => x.AddedAt).Take(10);
|
var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie ).OrderByDescending(x => x.AddedAt).Take(10);
|
||||||
var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10).ToHashSet();
|
var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10).ToHashSet();
|
||||||
var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet();
|
var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet();
|
||||||
body = await BuildHtml(plexm, embym, plext, embyt, settings);
|
var lidarr = lidarrContent.OrderByDescending(x => x.AddedAt).Take(10).ToHashSet();
|
||||||
|
body = await BuildHtml(plexm, embym, plext, embyt, lidarr, settings);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, settings);
|
body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, lidarrContentAlbumsToSend, settings);
|
||||||
if (body.IsNullOrEmpty())
|
if (body.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -298,7 +315,8 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
return resolver.ParseMessage(template, curlys);
|
return resolver.ParseMessage(template, curlys);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> BuildHtml(IQueryable<PlexServerContent> plexContentToSend, IQueryable<EmbyContent> embyContentToSend, HashSet<PlexEpisode> plexEpisodes, HashSet<EmbyEpisode> embyEp, NewsletterSettings settings)
|
private async Task<string> BuildHtml(IQueryable<PlexServerContent> plexContentToSend, IQueryable<EmbyContent> embyContentToSend,
|
||||||
|
HashSet<PlexEpisode> plexEpisodes, HashSet<EmbyEpisode> embyEp, HashSet<LidarrAlbumCache> albums, NewsletterSettings settings)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
@ -340,6 +358,24 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
sb.Append("</table>");
|
sb.Append("</table>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (albums.Any() && !settings.DisableMusic)
|
||||||
|
{
|
||||||
|
sb.Append("<h1 style=\"text-align: center; max-width: 1042px;\">New Albums</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);
|
||||||
|
sb.Append("</tr>");
|
||||||
|
sb.Append("</table>");
|
||||||
|
sb.Append("</td>");
|
||||||
|
sb.Append("</tr>");
|
||||||
|
sb.Append("</table>");
|
||||||
|
}
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,6 +418,40 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private async Task ProcessAlbums(HashSet<LidarrAlbumCache> albumsToSend, StringBuilder sb)
|
||||||
|
{
|
||||||
|
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(sb, info);
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log.LogError(e, "Error when Processing Lidarr Album {0}", info.title);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
EndLoopHtml(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == 2)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
sb.Append("</tr>");
|
||||||
|
sb.Append("<tr>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ProcessEmbyMovies(IQueryable<EmbyContent> embyContent, StringBuilder sb)
|
private async Task ProcessEmbyMovies(IQueryable<EmbyContent> embyContent, StringBuilder sb)
|
||||||
{
|
{
|
||||||
|
@ -467,6 +537,41 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CreateAlbumHtmlContent(StringBuilder sb, AlbumLookup info)
|
||||||
|
{
|
||||||
|
var cover = info.images
|
||||||
|
.FirstOrDefault(x => x.coverType.Equals("cover", StringComparison.InvariantCultureIgnoreCase))?.url;
|
||||||
|
if (cover.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
cover = info.remoteCover;
|
||||||
|
}
|
||||||
|
AddBackgroundInsideTable(sb, cover);
|
||||||
|
var disk = info.images
|
||||||
|
.FirstOrDefault(x => x.coverType.Equals("disc", StringComparison.InvariantCultureIgnoreCase))?.url;
|
||||||
|
if (disk.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
disk = info.remoteCover;
|
||||||
|
}
|
||||||
|
AddPosterInsideTable(sb, disk);
|
||||||
|
|
||||||
|
AddMediaServerUrl(sb, string.Empty, string.Empty);
|
||||||
|
AddInfoTable(sb);
|
||||||
|
|
||||||
|
var releaseDate = $"({info.releaseDate.Year})";
|
||||||
|
|
||||||
|
AddTitle(sb, string.Empty, $"{info.title} {releaseDate}");
|
||||||
|
|
||||||
|
var summary = info.artist?.artistName ?? string.Empty;
|
||||||
|
if (summary.Length > 280)
|
||||||
|
{
|
||||||
|
summary = summary.Remove(280);
|
||||||
|
summary = summary + "...</p>";
|
||||||
|
}
|
||||||
|
AddParagraph(sb, summary);
|
||||||
|
|
||||||
|
AddGenres(sb, $"Type: {info.albumType}");
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ProcessPlexTv(HashSet<PlexEpisode> plexContent, StringBuilder sb)
|
private async Task ProcessPlexTv(HashSet<PlexEpisode> plexContent, StringBuilder sb)
|
||||||
{
|
{
|
||||||
var series = new List<PlexServerContent>();
|
var series = new List<PlexServerContent>();
|
||||||
|
|
|
@ -6,6 +6,7 @@ namespace Ombi.Settings.Settings.Models.Notifications
|
||||||
{
|
{
|
||||||
public bool DisableTv { get; set; }
|
public bool DisableTv { get; set; }
|
||||||
public bool DisableMovies { get; set; }
|
public bool DisableMovies { get; set; }
|
||||||
|
public bool DisableMusic { get; set; }
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
public List<string> ExternalEmails { get; set; } = new List<string>();
|
public List<string> ExternalEmails { get; set; } = new List<string>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,5 +8,7 @@ namespace Ombi.Settings.Settings.Models.Notifications
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
public string AccessToken { get; set; }
|
public string AccessToken { get; set; }
|
||||||
public string UserToken { get; set; }
|
public string UserToken { get; set; }
|
||||||
|
public sbyte Priority { get; set; } = 0;
|
||||||
|
public string Sound { get; set; } = "pushover";
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,6 +13,7 @@ namespace Ombi.Store.Entities
|
||||||
public bool Monitored { get; set; }
|
public bool Monitored { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public decimal PercentOfTracks { get; set; }
|
public decimal PercentOfTracks { get; set; }
|
||||||
|
public DateTime AddedAt { get; set; }
|
||||||
|
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0;
|
public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0;
|
||||||
|
|
|
@ -11,18 +11,21 @@ namespace Ombi.Store.Entities
|
||||||
public int ContentId { get; set; } // This is dependant on the type, it's either TMDBID or TVDBID
|
public int ContentId { get; set; } // This is dependant on the type, it's either TMDBID or TVDBID
|
||||||
public int? EpisodeNumber { get; set; }
|
public int? EpisodeNumber { get; set; }
|
||||||
public int? SeasonNumber { get; set; }
|
public int? SeasonNumber { get; set; }
|
||||||
|
public string AlbumId { get; set; }
|
||||||
public DateTime AddedAt { get; set; }
|
public DateTime AddedAt { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum RecentlyAddedType
|
public enum RecentlyAddedType
|
||||||
{
|
{
|
||||||
Plex = 0,
|
Plex = 0,
|
||||||
Emby = 1
|
Emby = 1,
|
||||||
|
Lidarr = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ContentType
|
public enum ContentType
|
||||||
{
|
{
|
||||||
Parent = 0,
|
Parent = 0,
|
||||||
Episode = 1
|
Episode = 1,
|
||||||
|
Album = 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
1091
src/Ombi.Store/Migrations/20180828083219_MusicIssues.Designer.cs
generated
Normal file
1091
src/Ombi.Store/Migrations/20180828083219_MusicIssues.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
33
src/Ombi.Store/Migrations/20180828083219_MusicIssues.cs
Normal file
33
src/Ombi.Store/Migrations/20180828083219_MusicIssues.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace Ombi.Store.Migrations
|
||||||
|
{
|
||||||
|
public partial class MusicIssues : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "AlbumId",
|
||||||
|
table: "RecentlyAddedLog",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<DateTime>(
|
||||||
|
name: "AddedAt",
|
||||||
|
table: "LidarrAlbumCache",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "AlbumId",
|
||||||
|
table: "RecentlyAddedLog");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "AddedAt",
|
||||||
|
table: "LidarrAlbumCache");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -249,6 +249,8 @@ namespace Ombi.Store.Migrations
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd();
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<DateTime>("AddedAt");
|
||||||
|
|
||||||
b.Property<int>("ArtistId");
|
b.Property<int>("ArtistId");
|
||||||
|
|
||||||
b.Property<string>("ForeignAlbumId");
|
b.Property<string>("ForeignAlbumId");
|
||||||
|
@ -489,6 +491,8 @@ namespace Ombi.Store.Migrations
|
||||||
|
|
||||||
b.Property<DateTime>("AddedAt");
|
b.Property<DateTime>("AddedAt");
|
||||||
|
|
||||||
|
b.Property<string>("AlbumId");
|
||||||
|
|
||||||
b.Property<int>("ContentId");
|
b.Property<int>("ContentId");
|
||||||
|
|
||||||
b.Property<int>("ContentType");
|
b.Property<int>("ContentType");
|
||||||
|
|
|
@ -88,6 +88,8 @@ export interface IPushoverNotificationSettings extends INotificationSettings {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
notificationTemplates: INotificationTemplates[];
|
notificationTemplates: INotificationTemplates[];
|
||||||
userToken: string;
|
userToken: string;
|
||||||
|
priority: number;
|
||||||
|
sound: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMattermostNotifcationSettings extends INotificationSettings {
|
export interface IMattermostNotifcationSettings extends INotificationSettings {
|
||||||
|
|
|
@ -189,7 +189,7 @@
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issuesBtn">
|
<div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issuesBtn">
|
||||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
|
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
|
||||||
aria-expanded="true">
|
aria-expanded="true">
|
||||||
<i class="fa fa-plus"></i> {{ 'Requests.ReportIssue' | translate }}
|
<i class="fa fa-plus"></i> {{ 'Requests.ReportIssue' | translate }}
|
||||||
|
@ -200,7 +200,7 @@
|
||||||
<a [routerLink]="" (click)="reportIssue(cat, request)">{{cat.value}}</a>
|
<a [routerLink]="" (click)="reportIssue(cat, request)">{{cat.value}}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div> -->
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -45,11 +45,11 @@
|
||||||
<ng-template [ngIf]="result.monitored && !result.fullyAvailable">
|
<ng-template [ngIf]="result.monitored && !result.fullyAvailable">
|
||||||
<span class="label label-info" id="processingRequestLabel" [translate]="'Common.Monitored'"></span>
|
<span class="label label-info" id="processingRequestLabel" [translate]="'Common.Monitored'"></span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template [ngIf]="result.requested && !result.approved && !result.available">
|
<ng-template [ngIf]="result.requested && !result.approved && !result.partiallyAvailable">
|
||||||
<span class="label label-warning" id="pendingApprovalLabel" [translate]="'Common.PendingApproval'"></span>
|
<span class="label label-warning" id="pendingApprovalLabel" [translate]="'Common.PendingApproval'"></span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template [ngIf]="result.approved && !result.available"><span class="label label-info" id="processingRequestLabel" [translate]="'Common.ProcessingRequest'"></span></ng-template>
|
<ng-template [ngIf]="result.approved && !result.fullyAvailable"><span class="label label-info" id="processingRequestLabel" [translate]="'Common.ProcessingRequest'"></span></ng-template>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,18 +68,18 @@
|
||||||
|
|
||||||
<!--Buttons-->
|
<!--Buttons-->
|
||||||
<div class="col-sm-12 small-padding">
|
<div class="col-sm-12 small-padding">
|
||||||
<div class="row" *ngIf="result.requested">
|
<!-- <div class="row" *ngIf="result.requested">
|
||||||
<div class="col-md-2 col-md-push-10">
|
<div class="col-md-2 col-md-push-10">
|
||||||
|
|
||||||
<!-- <a *ngIf="result.showSubscribe && !result.subscribed" style="color:white" (click)="subscribe(result)" pTooltip="Subscribe for notifications"> <i class="fa fa-rss"></i></a>
|
<a *ngIf="result.showSubscribe && !result.subscribed" style="color:white" (click)="subscribe(result)" pTooltip="Subscribe for notifications"> <i class="fa fa-rss"></i></a>
|
||||||
<a *ngIf="result.showSubscribe && result.subscribed" style="color:red" (click)="unSubscribe(result)" pTooltip="Unsubscribe notification"> <i class="fa fa-rss"></i></a> -->
|
<a *ngIf="result.showSubscribe && result.subscribed" style="color:red" (click)="unSubscribe(result)" pTooltip="Unsubscribe notification"> <i class="fa fa-rss"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
<div *ngIf="result.fullyAvailable">
|
<div *ngIf="result.fullyAvailable">
|
||||||
<button style="text-align: right" class="btn btn-success-outline disabled" disabled>
|
<button style="text-align: right" class="btn btn-success-outline disabled" disabled>
|
||||||
<i class="fa fa-check"></i> {{ 'Common.Available' | translate }}</button>
|
<i class="fa fa-check"></i> {{ 'Common.Available' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!result.available">
|
<div *ngIf="!result.fullyAvailable">
|
||||||
<div *ngIf="result.requested || result.approved; then requestedBtn else notRequestedBtn"></div>
|
<div *ngIf="result.requested || result.approved; then requestedBtn else notRequestedBtn"></div>
|
||||||
<ng-template #requestedBtn>
|
<ng-template #requestedBtn>
|
||||||
<button style="text-align: right" class="btn btn-primary-outline disabled" [disabled]>
|
<button style="text-align: right" class="btn btn-primary-outline disabled" [disabled]>
|
||||||
|
@ -93,11 +93,9 @@
|
||||||
| translate }}</button>
|
| translate }}</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
<!-- <button style="text-align: right" class="btn btn-sm btn-info-outline" (click)="similarMovies(result.id)"> <i class="fa fa-eye"></i> {{ 'Search.Similar' | translate }}</button> -->
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<!-- <div class="dropdown" *ngIf="result.available && issueCategories && issuesEnabled">
|
<div class="dropdown" *ngIf="(result.partiallyAvailable || result.fullyAvailable) && issueCategories && issuesEnabled">
|
||||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
|
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
|
||||||
aria-expanded="true">
|
aria-expanded="true">
|
||||||
<i class="fa fa-plus"></i> Report Issue
|
<i class="fa fa-plus"></i> Report Issue
|
||||||
|
@ -106,8 +104,13 @@
|
||||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||||
<li *ngFor="let cat of issueCategories"><a [routerLink]="" (click)="reportIssue(cat, result)">{{cat.value}}</a></li>
|
<li *ngFor="let cat of issueCategories"><a [routerLink]="" (click)="reportIssue(cat, result)">{{cat.value}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div> -->
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<issue-report [movie]="true" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequestTitle"
|
||||||
|
[issueCategory]="issueCategorySelected" [id]="issueRequestId" [providerId]="issueProviderId"></issue-report>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||||
import { TranslateService } from "@ngx-translate/core";
|
import { TranslateService } from "@ngx-translate/core";
|
||||||
|
|
||||||
import { AuthService } from "../../auth/auth.service";
|
import { AuthService } from "../../auth/auth.service";
|
||||||
import { IRequestEngineResult } from "../../interfaces";
|
import { IIssueCategory, IRequestEngineResult } from "../../interfaces";
|
||||||
import { ISearchAlbumResult } from "../../interfaces/ISearchMusicResult";
|
import { ISearchAlbumResult } from "../../interfaces/ISearchMusicResult";
|
||||||
import { NotificationService, RequestService } from "../../services";
|
import { NotificationService, RequestService } from "../../services";
|
||||||
|
|
||||||
|
@ -14,7 +14,15 @@ export class AlbumSearchComponent {
|
||||||
|
|
||||||
@Input() public result: ISearchAlbumResult;
|
@Input() public result: ISearchAlbumResult;
|
||||||
public engineResult: IRequestEngineResult;
|
public engineResult: IRequestEngineResult;
|
||||||
@Input() public defaultPoster: string;
|
@Input() public defaultPoster: string;
|
||||||
|
|
||||||
|
@Input() public issueCategories: IIssueCategory[];
|
||||||
|
@Input() public issuesEnabled: boolean;
|
||||||
|
public issuesBarVisible = false;
|
||||||
|
public issueRequestTitle: string;
|
||||||
|
public issueRequestId: number;
|
||||||
|
public issueProviderId: string;
|
||||||
|
public issueCategorySelected: IIssueCategory;
|
||||||
|
|
||||||
@Output() public setSearch = new EventEmitter<string>();
|
@Output() public setSearch = new EventEmitter<string>();
|
||||||
|
|
||||||
|
@ -29,6 +37,14 @@ export class AlbumSearchComponent {
|
||||||
this.setSearch.emit(artistId);
|
this.setSearch.emit(artistId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public reportIssue(catId: IIssueCategory, req: ISearchAlbumResult) {
|
||||||
|
this.issueRequestId = req.id;
|
||||||
|
this.issueRequestTitle = req.title + `(${req.releaseDate.getFullYear})`;
|
||||||
|
this.issueCategorySelected = catId;
|
||||||
|
this.issuesBarVisible = true;
|
||||||
|
this.issueProviderId = req.id.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public request(searchResult: ISearchAlbumResult) {
|
public request(searchResult: ISearchAlbumResult) {
|
||||||
searchResult.requested = true;
|
searchResult.requested = true;
|
||||||
searchResult.requestProcessing = true;
|
searchResult.requestProcessing = true;
|
||||||
|
|
|
@ -28,6 +28,48 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="priority" class="control-label">Priority</label>
|
||||||
|
<div>
|
||||||
|
<select class="form-control form-control-custom " id="priority" name="priority" formControlName="priority" pTooltip="The priority you want your pushover notifications sent as.">
|
||||||
|
<option value="0">Normal</option>
|
||||||
|
<option value="1">High</option>
|
||||||
|
<option value="-1">Low</option>
|
||||||
|
<option value="-2">Lowest</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sound" class="control-label">Sound</label>
|
||||||
|
<div>
|
||||||
|
<select class="form-control form-control-custom " id="sound" name="sound" formControlName="sound" pTooltip="The sound you want your pushover notifications sent with.">
|
||||||
|
<option value="pushover">Pushover</option>
|
||||||
|
<option value="bike">Bike</option>
|
||||||
|
<option value="bugle">Bugle</option>
|
||||||
|
<option value="cashregister">Cash Register</option>
|
||||||
|
<option value="classical">Classical</option>
|
||||||
|
<option value="cosmic">Cosmic</option>
|
||||||
|
<option value="falling">Falling</option>
|
||||||
|
<option value="gamelan">Gamelan</option>
|
||||||
|
<option value="incoming">Incoming</option>
|
||||||
|
<option value="intermission">Intermission</option>
|
||||||
|
<option value="magic">Magic</option>
|
||||||
|
<option value="mechanical">Mechanical</option>
|
||||||
|
<option value="pianobar">Piano Bar</option>
|
||||||
|
<option value="siren">Siren</option>
|
||||||
|
<option value="spacealarm">Space Alarm</option>
|
||||||
|
<option value="tugboat">Tug Boat</option>
|
||||||
|
<option value="alien">Alien Alarm (long)</option>
|
||||||
|
<option value="climb">Climb (long)</option>
|
||||||
|
<option value="persistent">Persistent (long)</option>
|
||||||
|
<option value="echo">Pushover Echo (long)</option>
|
||||||
|
<option value="updown">Up Down (long)</option>
|
||||||
|
<option value="none">None</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
@ -27,6 +27,8 @@ export class PushoverComponent implements OnInit {
|
||||||
enabled: [x.enabled],
|
enabled: [x.enabled],
|
||||||
userToken: [x.userToken],
|
userToken: [x.userToken],
|
||||||
accessToken: [x.accessToken, [Validators.required]],
|
accessToken: [x.accessToken, [Validators.required]],
|
||||||
|
priority: [x.priority],
|
||||||
|
sound: [x.sound],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue