Merge pull request #2 from tidusjar/develop

Getting Develop up to date
This commit is contained in:
Anojh Thayaparan 2018-03-29 15:20:10 -07:00 committed by GitHub
commit 8a94f967a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
76 changed files with 698 additions and 261 deletions

View file

@ -4,14 +4,68 @@
### **New Features** ### **New Features**
- Added the Recently Added Newsletter! You are welcome. [tidusjar]
- Added a new scrollbar to Ombi. [tidusjar]
- Added the ability to automatically generate the API Key on startup if it does not exist #2070. [tidusjar]
- Updated npm dependancies. [Jamie]
- Update README.md. [Jamie]
- Update README.md. [Jamie]
- Update ISSUE_TEMPLATE.md. [Jamie]
- Update appveyor.yml. [Jamie]
- Added recently added stuff. [Jamie]
- Added the recently added engine with some basic methods. [Jamie]
- Added the ability to refresh out backend metadata (#2078) [Jamie] - Added the ability to refresh out backend metadata (#2078) [Jamie]
### **Fixes** ### **Fixes**
- Fixed #2074 and #2079. [Jamie] - Specific favicons for different platforms. [louis-lau]
- MovieDbId was switched to string fron number so accomodated for change. [Anojh]
- Removing duplicate functions. [Anojh Thayaparan]
- Conflict resolving and adopting Jamie's new method. [Anojh]
- Wrote new calls to just get poster and bg. [Anojh]
- Fix for issue #1907, which is to add content poster and bg to issue details page. [Anojh]
- Dynamic Background Animation. [Anojh]
- Improved the message for #2037. [tidusjar]
- Improved the way we use the notification variables, we have now split out the Username and Alias (Requested User is depricated but not removed) [tidusjar]
- Removed redundant timers. [Anojh]
- More optimizations by reducing requests. [Anojh]
- Improved version. [Anojh]
- Dynamic Background Animation. [Anojh]
- Fixed #2055 and #1903. [Jamie]
- Small changes to the auto updater, let's see how this works. [Jamie] - Small changes to the auto updater, let's see how this works. [Jamie]
- Fixed build. [Jamie]
- Fixed the update check for the master build. [Jamie]
- Fixed build. [Jamie]
- Fixed #2074 and #2079. [Jamie]
## v3.0.3030 (2018-03-14) ## v3.0.3030 (2018-03-14)
@ -19,6 +73,10 @@
- Updated the .Net core dependancies #2072. [Jamie] - Updated the .Net core dependancies #2072. [Jamie]
### **Fixes**
- Delete Ombi.testdb. [Jamie]
## v3.0.3020 (2018-03-13) ## v3.0.3020 (2018-03-13)

View file

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search; using Ombi.Core.Models.Search;
using Ombi.Store.Entities.Requests; using Ombi.Store.Entities.Requests;
@ -9,7 +10,7 @@ namespace Ombi.Core.Engine.Interfaces
{ {
Task RemoveTvRequest(int requestId); Task RemoveTvRequest(int requestId);
Task<RequestEngineResult> RequestTvShow(SearchTvShowViewModel tv); Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv);
Task<RequestEngineResult> DenyChildRequest(int requestId); Task<RequestEngineResult> DenyChildRequest(int requestId);
Task<IEnumerable<TvRequests>> SearchTvRequest(string search); Task<IEnumerable<TvRequests>> SearchTvRequest(string search);
Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvRequestTree(string search); Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvRequestTree(string search);

View file

@ -54,7 +54,7 @@ namespace Ombi.Core.Engine
{ {
Result = false, Result = false,
Message = "There was an issue adding this movie!", Message = "There was an issue adding this movie!",
ErrorMessage = $"TheMovieDb didn't have any information for ID {model.TheMovieDbId}" ErrorMessage = $"Please try again later"
}; };
} }
var fullMovieName = var fullMovieName =

View file

@ -43,13 +43,13 @@ namespace Ombi.Core.Engine
private IAuditRepository Audit { get; } private IAuditRepository Audit { get; }
private readonly IRepository<RequestLog> _requestLog; private readonly IRepository<RequestLog> _requestLog;
public async Task<RequestEngineResult> RequestTvShow(SearchTvShowViewModel tv) public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv)
{ {
var user = await GetUser(); var user = await GetUser();
var tvBuilder = new TvShowRequestBuilder(TvApi); var tvBuilder = new TvShowRequestBuilder(TvApi);
(await tvBuilder (await tvBuilder
.GetShowInfo(tv.Id)) .GetShowInfo(tv.TvDbId))
.CreateTvList(tv) .CreateTvList(tv)
.CreateChild(tv, user.Id); .CreateChild(tv, user.Id);
@ -78,9 +78,9 @@ namespace Ombi.Core.Engine
} }
} }
await Audit.Record(AuditType.Added, AuditArea.TvRequest, $"Added Request {tv.Title}", Username); await Audit.Record(AuditType.Added, AuditArea.TvRequest, $"Added Request {tvBuilder.ChildRequest.Title}", Username);
var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.Id); var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.TvDbId);
if (existingRequest != null) if (existingRequest != null)
{ {
// Remove requests we already have, we just want new ones // Remove requests we already have, we just want new ones
@ -127,7 +127,7 @@ namespace Ombi.Core.Engine
var newRequest = tvBuilder.CreateNewRequest(tv); var newRequest = tvBuilder.CreateNewRequest(tv);
return await AddRequest(newRequest.NewRequest); return await AddRequest(newRequest.NewRequest);
} }
public async Task<IEnumerable<TvRequests>> GetRequests(int count, int position) public async Task<IEnumerable<TvRequests>> GetRequests(int count, int position)
{ {
var shouldHide = await HideFromOtherUsers(); var shouldHide = await HideFromOtherUsers();

View file

@ -4,6 +4,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ombi.Api.TvMaze; using Ombi.Api.TvMaze;
using Ombi.Api.TvMaze.Models; using Ombi.Api.TvMaze.Models;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search; using Ombi.Core.Models.Search;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Store.Entities; using Ombi.Store.Entities;
@ -23,7 +24,7 @@ namespace Ombi.Core.Helpers
private ITvMazeApi TvApi { get; } private ITvMazeApi TvApi { get; }
public ChildRequests ChildRequest { get; set; } public ChildRequests ChildRequest { get; set; }
public List<SeasonRequests> TvRequests { get; protected set; } public List<SeasonsViewModel> TvRequests { get; protected set; }
public string PosterPath { get; protected set; } public string PosterPath { get; protected set; }
public DateTime FirstAir { get; protected set; } public DateTime FirstAir { get; protected set; }
public TvRequests NewRequest { get; protected set; } public TvRequests NewRequest { get; protected set; }
@ -33,7 +34,7 @@ namespace Ombi.Core.Helpers
{ {
ShowInfo = await TvApi.ShowLookupByTheTvDbId(id); ShowInfo = await TvApi.ShowLookupByTheTvDbId(id);
DateTime.TryParse(ShowInfo.premiered, out DateTime dt); DateTime.TryParse(ShowInfo.premiered, out var dt);
FirstAir = dt; FirstAir = dt;
@ -43,37 +44,29 @@ namespace Ombi.Core.Helpers
return this; return this;
} }
public TvShowRequestBuilder CreateChild(SearchTvShowViewModel model, string userId) public TvShowRequestBuilder CreateChild(TvRequestViewModel model, string userId)
{ {
ChildRequest = new ChildRequests ChildRequest = new ChildRequests
{ {
Id = model.Id, Id = model.TvDbId,
RequestType = RequestType.TvShow, RequestType = RequestType.TvShow,
RequestedDate = DateTime.UtcNow, RequestedDate = DateTime.UtcNow,
Approved = false, Approved = false,
RequestedUserId = userId, RequestedUserId = userId,
SeasonRequests = new List<SeasonRequests>(), SeasonRequests = new List<SeasonRequests>(),
Title = model.Title, Title = ShowInfo.name,
SeriesType = ShowInfo.type.Equals("Animation", StringComparison.CurrentCultureIgnoreCase) ? SeriesType.Anime : SeriesType.Standard SeriesType = ShowInfo.type.Equals("Animation", StringComparison.CurrentCultureIgnoreCase) ? SeriesType.Anime : SeriesType.Standard
}; };
return this; return this;
} }
public TvShowRequestBuilder CreateTvList(SearchTvShowViewModel tv) public TvShowRequestBuilder CreateTvList(TvRequestViewModel tv)
{ {
TvRequests = new List<SeasonRequests>(); TvRequests = new List<SeasonsViewModel>();
// Only have the TV requests we actually requested and not everything // Only have the TV requests we actually requested and not everything
foreach (var season in tv.SeasonRequests) foreach (var season in tv.Seasons)
{ {
for (int i = season.Episodes.Count - 1; i >= 0; i--)
{
if (!season.Episodes[i].Requested)
{
season.Episodes.RemoveAt(i); // Remove the episode since it's not requested
}
}
if (season.Episodes.Any()) if (season.Episodes.Any())
{ {
TvRequests.Add(season); TvRequests.Add(season);
@ -81,11 +74,10 @@ namespace Ombi.Core.Helpers
} }
return this; return this;
} }
public async Task<TvShowRequestBuilder> BuildEpisodes(SearchTvShowViewModel tv) public async Task<TvShowRequestBuilder> BuildEpisodes(TvRequestViewModel tv)
{ {
if (tv.RequestAll) if (tv.RequestAll)
{ {
@ -173,26 +165,68 @@ namespace Ombi.Core.Helpers
else else
{ {
// It's a custom request // It's a custom request
ChildRequest.SeasonRequests = TvRequests; var seasonRequests = new List<SeasonRequests>();
var episodes = await TvApi.EpisodeLookup(ShowInfo.id);
foreach (var ep in episodes)
{
var existingSeasonRequest = seasonRequests.FirstOrDefault(x => x.SeasonNumber == ep.season);
if (existingSeasonRequest != null)
{
var requestedSeason = tv.Seasons.FirstOrDefault(x => x.SeasonNumber == ep.season);
var requestedEpisode = requestedSeason?.Episodes?.Any(x => x.EpisodeNumber == ep.number) ?? false;
if (requestedSeason != null && requestedEpisode)
{
// We already have this, let's just add the episodes to it
existingSeasonRequest.Episodes.Add(new EpisodeRequests
{
EpisodeNumber = ep.number,
AirDate = FormatDate(ep.airdate),
Title = ep.name,
Url = ep.url,
});
}
}
else
{
var newRequest = new SeasonRequests {SeasonNumber = ep.season};
var requestedSeason = tv.Seasons.FirstOrDefault(x => x.SeasonNumber == ep.season);
var requestedEpisode = requestedSeason?.Episodes?.Any(x => x.EpisodeNumber == ep.number) ?? false;
if (requestedSeason != null && requestedEpisode)
{
newRequest.Episodes.Add(new EpisodeRequests
{
EpisodeNumber = ep.number,
AirDate = FormatDate(ep.airdate),
Title = ep.name,
Url = ep.url,
});
seasonRequests.Add(newRequest);
}
}
}
foreach (var s in seasonRequests)
{
ChildRequest.SeasonRequests.Add(s);
}
} }
return this; return this;
} }
public TvShowRequestBuilder CreateNewRequest(SearchTvShowViewModel tv) public TvShowRequestBuilder CreateNewRequest(TvRequestViewModel tv)
{ {
NewRequest = new TvRequests NewRequest = new TvRequests
{ {
Id = tv.Id,
Overview = ShowInfo.summary.RemoveHtml(), Overview = ShowInfo.summary.RemoveHtml(),
PosterPath = PosterPath, PosterPath = PosterPath,
Title = ShowInfo.name, Title = ShowInfo.name,
ReleaseDate = FirstAir, ReleaseDate = FirstAir,
Status = ShowInfo.status, Status = ShowInfo.status,
ImdbId = ShowInfo.externals?.imdb ?? string.Empty, ImdbId = ShowInfo.externals?.imdb ?? string.Empty,
TvDbId = tv.Id, TvDbId = tv.TvDbId,
ChildRequests = new List<ChildRequests>(), ChildRequests = new List<ChildRequests>(),
TotalSeasons = tv.SeasonRequests.Count() TotalSeasons = tv.Seasons.Count()
}; };
NewRequest.ChildRequests.Add(ChildRequest); NewRequest.ChildRequests.Add(ChildRequest);

View file

@ -0,0 +1,25 @@
using System.Collections.Generic;
namespace Ombi.Core.Models.Requests
{
public class TvRequestViewModel
{
public bool RequestAll { get; set; }
public bool LatestSeason { get; set; }
public bool FirstSeason { get; set; }
public int TvDbId { get; set; }
public List<SeasonsViewModel> Seasons { get; set; } = new List<SeasonsViewModel>();
}
public class SeasonsViewModel
{
public int SeasonNumber { get; set; }
public List<EpisodesViewModel> Episodes { get; set; } = new List<EpisodesViewModel>();
}
public class EpisodesViewModel
{
public int EpisodeNumber { get; set; }
}
}

View file

@ -151,7 +151,7 @@
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top"> <td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">
<br /> <br />
<br /> <br />
<p style="font-family: sans-serif; font-size: 20px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Here is a list of Movies and TV Shows that have recently been added!</p> <p style="font-family: sans-serif; font-size: 20px; font-weight: normal; margin: 0; Margin-bottom: 15px;">{@INTRO}</p>
</td> </td>
</tr> </tr>

View file

@ -1,10 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Notifications.Models; using Ombi.Notifications.Models;
using Ombi.Settings.Settings.Models; using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities; using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests; using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository.Requests;
namespace Ombi.Notifications namespace Ombi.Notifications
{ {
@ -25,9 +28,9 @@ namespace Ombi.Notifications
} }
ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty; ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty;
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName; ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
RequestedUser = string.IsNullOrEmpty(req?.RequestedUser?.Alias) RequestedUser = req?.RequestedUser?.UserName;
? req?.RequestedUser?.UserName UserName = req?.RequestedUser?.UserName;
: req?.RequestedUser?.Alias; Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName;
Title = title; Title = title;
RequestedDate = req?.RequestedDate.ToString("D"); RequestedDate = req?.RequestedDate.ToString("D");
Type = req?.RequestType.ToString(); Type = req?.RequestType.ToString();
@ -43,6 +46,8 @@ namespace Ombi.Notifications
ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty; ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty;
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName; ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
RequestedUser = username.UserName; RequestedUser = username.UserName;
UserName = username.UserName;
Alias = username.Alias.HasValue() ? username.Alias : username.UserName;
} }
public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s) public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s)
@ -59,9 +64,9 @@ namespace Ombi.Notifications
} }
ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty; ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty;
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName; ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
RequestedUser = string.IsNullOrEmpty(req?.RequestedUser.Alias) RequestedUser = req?.RequestedUser?.UserName;
? req?.RequestedUser.UserName UserName = req?.RequestedUser?.UserName;
: req?.RequestedUser.Alias; Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName;
Title = title; Title = title;
RequestedDate = req?.RequestedDate.ToString("D"); RequestedDate = req?.RequestedDate.ToString("D");
Type = req?.RequestType.ToString(); Type = req?.RequestType.ToString();
@ -71,6 +76,40 @@ namespace Ombi.Notifications
$"https://image.tmdb.org/t/p/w300{req?.ParentRequest.PosterPath}" : req?.ParentRequest.PosterPath; $"https://image.tmdb.org/t/p/w300{req?.ParentRequest.PosterPath}" : req?.ParentRequest.PosterPath;
AdditionalInformation = opts.AdditionalInformation; AdditionalInformation = opts.AdditionalInformation;
// DO Episode and Season Lists // DO Episode and Season Lists
var episodes = req?.SeasonRequests?.SelectMany(x => x.Episodes) ?? new List<EpisodeRequests>();
var seasons = req?.SeasonRequests?.OrderBy(x => x.SeasonNumber).ToList() ?? new List<SeasonRequests>();
var orderedEpisodes = episodes.OrderBy(x => x.EpisodeNumber).ToList();
var epSb = new StringBuilder();
var seasonSb = new StringBuilder();
for (var i = 0; i < orderedEpisodes.Count; i++)
{
var ep = orderedEpisodes[i];
if (i < orderedEpisodes.Count - 1)
{
epSb.Append($"{ep.EpisodeNumber},");
}
else
{
epSb.Append($"{ep.EpisodeNumber}");
}
}
for (var i = 0; i < seasons.Count; i++)
{
var ep = seasons[i];
if (i < seasons.Count - 1)
{
seasonSb.Append($"{ep.SeasonNumber},");
}
else
{
seasonSb.Append($"{ep.SeasonNumber}");
}
}
EpisodesList = epSb.ToString();
SeasonsList = seasonSb.ToString();
} }
public void Setup(OmbiUser user, CustomizationSettings s) public void Setup(OmbiUser user, CustomizationSettings s)
@ -88,13 +127,14 @@ namespace Ombi.Notifications
IssueStatus = opts.Substitutes.TryGetValue("IssueStatus", out val) ? val : string.Empty; IssueStatus = opts.Substitutes.TryGetValue("IssueStatus", out val) ? val : string.Empty;
IssueSubject = opts.Substitutes.TryGetValue("IssueSubject", out val) ? val : string.Empty; IssueSubject = opts.Substitutes.TryGetValue("IssueSubject", out val) ? val : string.Empty;
NewIssueComment = opts.Substitutes.TryGetValue("NewIssueComment", out val) ? val : string.Empty; NewIssueComment = opts.Substitutes.TryGetValue("NewIssueComment", out val) ? val : string.Empty;
RequestedUser = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty; UserName = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty;
} }
// User Defined // User Defined
public string RequestedUser { get; set; } public string RequestedUser { get; set; }
public string UserName => RequestedUser; public string UserName { get; set; }
public string IssueUser => RequestedUser; public string IssueUser => UserName;
public string Alias { get; set; }
public string Title { get; set; } public string Title { get; set; }
public string RequestedDate { get; set; } public string RequestedDate { get; set; }
@ -144,6 +184,7 @@ namespace Ombi.Notifications
{nameof(NewIssueComment),NewIssueComment}, {nameof(NewIssueComment),NewIssueComment},
{nameof(IssueUser),IssueUser}, {nameof(IssueUser),IssueUser},
{nameof(UserName),UserName}, {nameof(UserName),UserName},
{nameof(Alias),Alias},
}; };
} }
} }

View file

@ -76,8 +76,8 @@ namespace Ombi.Schedule.Jobs.Ombi
var customization = await _customizationSettings.GetSettingsAsync(); var customization = await _customizationSettings.GetSettingsAsync();
// Get the Content // Get the Content
var plexContent = _plex.GetAll().Include(x => x.Episodes); var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking();
var embyContent = _emby.GetAll().Include(x => x.Episodes); var embyContent = _emby.GetAll().Include(x => x.Episodes).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);
@ -90,24 +90,21 @@ namespace Ombi.Schedule.Jobs.Ombi
var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.Contains(x.Id)); var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.Contains(x.Id));
var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(x.Id)); var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(x.Id));
var plexContentTvToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Show && x.Episodes.Any(e => !addedPlexEpisodesLogIds.Contains(e.Id))); var plexEpisodesToSend = _plex.GetAllEpisodes().Include(x => x.Series).Where(x => !addedPlexEpisodesLogIds.Contains(x.Id)).AsNoTracking();
var embyContentTvToSend = embyContent.Where(x => x.Type == EmbyMediaType.Series && x.Episodes.Any(e => !addedEmbyEpisodesLogIds.Contains(e.Id))); var embyEpisodesToSend = _emby.GetAllEpisodes().Include(x => x.Series).Where(x => !addedEmbyEpisodesLogIds.Contains(x.Id)).AsNoTracking();
var plexContentToSend = plexContentMoviesToSend.Union(plexContentTvToSend);
var embyContentToSend = embyContentMoviesToSend.Union(embyContentTvToSend);
var body = string.Empty; var body = string.Empty;
if (test) if (test)
{ {
var plexm = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie).OrderByDescending(x => x.AddedAt).Take(10); var plexm = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie).OrderByDescending(x => x.AddedAt).Take(10);
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 = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Show).OrderByDescending(x => x.AddedAt).Take(10); var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10);
var embyt = embyContent.Where(x => x.Type == EmbyMediaType.Series).OrderByDescending(x => x.AddedAt).Take(10); var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10);
body = await BuildHtml(plexm.Union(plext), embym.Union(embyt)); body = await BuildHtml(plexm, embym, plext, embyt);
} }
else else
{ {
body = await BuildHtml(plexContentToSend, embyContentToSend); body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend);
if (body.IsNullOrEmpty()) if (body.IsNullOrEmpty())
{ {
return; return;
@ -137,7 +134,7 @@ namespace Ombi.Schedule.Jobs.Ombi
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo); var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo);
emailTasks.Add(_email.Send( emailTasks.Add(_email.Send(
new NotificationMessage {Message = html, Subject = messageContent.Subject, To = user.Email}, new NotificationMessage { Message = html, Subject = messageContent.Subject, To = user.Email },
emailSettings)); emailSettings));
} }
@ -145,28 +142,25 @@ namespace Ombi.Schedule.Jobs.Ombi
var recentlyAddedLog = new HashSet<RecentlyAddedLog>(); var recentlyAddedLog = new HashSet<RecentlyAddedLog>();
foreach (var p in plexContentMoviesToSend) foreach (var p in plexContentMoviesToSend)
{ {
if (p.Type == PlexMediaTypeEntity.Movie) recentlyAddedLog.Add(new RecentlyAddedLog
{ {
recentlyAddedLog.Add(new RecentlyAddedLog AddedAt = DateTime.Now,
{ Type = RecentlyAddedType.Plex,
AddedAt = DateTime.Now, ContentType = ContentType.Parent,
Type = RecentlyAddedType.Plex, ContentId = p.Id
ContentId = p.Id });
});
} }
else
foreach (var p in plexEpisodesToSend)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{ {
// Add the episodes AddedAt = DateTime.Now,
foreach (var ep in p.Episodes) Type = RecentlyAddedType.Plex,
{ ContentType = ContentType.Episode,
recentlyAddedLog.Add(new RecentlyAddedLog ContentId = p.Id
{ });
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex,
ContentId = ep.Id
});
}
}
} }
foreach (var e in embyContentMoviesToSend) foreach (var e in embyContentMoviesToSend)
@ -177,22 +171,21 @@ namespace Ombi.Schedule.Jobs.Ombi
{ {
AddedAt = DateTime.Now, AddedAt = DateTime.Now,
Type = RecentlyAddedType.Emby, Type = RecentlyAddedType.Emby,
ContentType = ContentType.Parent,
ContentId = e.Id ContentId = e.Id
}); });
} }
else }
foreach (var p in embyEpisodesToSend)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{ {
// Add the episodes AddedAt = DateTime.Now,
foreach (var ep in e.Episodes) Type = RecentlyAddedType.Emby,
{ ContentType = ContentType.Episode,
recentlyAddedLog.Add(new RecentlyAddedLog ContentId = p.Id
{ });
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex,
ContentId = ep.Id
});
}
}
} }
await _recentlyAddedLog.AddRange(recentlyAddedLog); await _recentlyAddedLog.AddRange(recentlyAddedLog);
@ -212,7 +205,7 @@ namespace Ombi.Schedule.Jobs.Ombi
var email = new NewsletterTemplate(); var email = new NewsletterTemplate();
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo); var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo);
await _email.Send( await _email.Send(
new NotificationMessage { Message = html, Subject = messageContent.Subject, To = a.Email }, new NotificationMessage { Message = html, Subject = messageContent.Subject, To = a.Email },
emailSettings); emailSettings);
@ -236,7 +229,7 @@ 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) private async Task<string> BuildHtml(IQueryable<PlexServerContent> plexContentToSend, IQueryable<EmbyContent> embyContentToSend, IQueryable<PlexEpisode> plexEpisodes, IQueryable<EmbyEpisode> embyEp)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
@ -249,13 +242,11 @@ namespace Ombi.Schedule.Jobs.Ombi
await ProcessEmbyMovies(embyMovies, sb); await ProcessEmbyMovies(embyMovies, sb);
} }
var plexTv = plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Show); if (plexEpisodes.Any() || embyEp.Any())
var embyTv = embyContentToSend.Where(x => x.Type == EmbyMediaType.Series);
if (plexTv.Any() || embyTv.Any())
{ {
sb.Append("<h1>New Episodes:</h1><br /><br />"); sb.Append("<h1>New Episodes:</h1><br /><br />");
await ProcessPlexTv(plexTv, sb); await ProcessPlexTv(plexEpisodes, sb);
await ProcessEmbyMovies(embyTv, sb); await ProcessEmbyTv(embyEp, sb);
} }
return sb.ToString(); return sb.ToString();
@ -288,22 +279,7 @@ namespace Ombi.Schedule.Jobs.Ombi
} }
try try
{ {
AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/original{info.BackdropPath}"); CreateMovieHtmlContent(sb, info);
sb.Append("<tr>");
TableData(sb);
Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/");
Header(sb, 3, $"{info.Title} {info.ReleaseDate ?? string.Empty}");
EndTag(sb, "a");
if (info.Genres.Any())
{
AddParagraph(sb,
$"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}");
}
AddParagraph(sb, info.Overview);
} }
catch (Exception e) catch (Exception e)
{ {
@ -316,6 +292,7 @@ namespace Ombi.Schedule.Jobs.Ombi
} }
} }
} }
private async Task ProcessEmbyMovies(IQueryable<EmbyContent> embyContent, StringBuilder sb) private async Task ProcessEmbyMovies(IQueryable<EmbyContent> embyContent, StringBuilder sb)
{ {
sb.Append( sb.Append(
@ -323,30 +300,21 @@ namespace Ombi.Schedule.Jobs.Ombi
var ordered = embyContent.OrderByDescending(x => x.AddedAt); var ordered = embyContent.OrderByDescending(x => x.AddedAt);
foreach (var content in ordered) foreach (var content in ordered)
{ {
int.TryParse(content.ProviderId, out var movieDbId); var imdbId = content.ProviderId;
var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId); var findResult = await _movieApi.Find(imdbId, ExternalSource.imdb_id);
var result = findResult.movie_results?.FirstOrDefault();
if(result == null)
{
continue;
}
var info = await _movieApi.GetMovieInformationWithExtraInfo(result.id);
if (info == null) if (info == null)
{ {
continue; continue;
} }
try try
{ {
AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/original{info.BackdropPath}"); CreateMovieHtmlContent(sb, info);
sb.Append("<tr>");
TableData(sb);
Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/");
Header(sb, 3, $"{info.Title} {info.ReleaseDate ?? string.Empty}");
EndTag(sb, "a");
if (info.Genres.Any())
{
AddParagraph(sb,
$"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}");
}
AddParagraph(sb, info.Overview);
} }
catch (Exception e) catch (Exception e)
{ {
@ -360,9 +328,57 @@ namespace Ombi.Schedule.Jobs.Ombi
} }
} }
private async Task ProcessPlexTv(IQueryable<PlexServerContent> plexContent, StringBuilder sb) private void CreateMovieHtmlContent(StringBuilder sb, MovieResponseDto info)
{ {
var orderedTv = plexContent.OrderByDescending(x => x.AddedAt); AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/original{info.PosterPath}");
sb.Append("<tr>");
TableData(sb);
Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/");
var releaseDate = string.Empty;
try
{
releaseDate = $"({DateTime.Parse(info.ReleaseDate).Year})";
}
catch (Exception)
{
// Swallow, couldn't parse the date
}
Header(sb, 3, $"{info.Title} {releaseDate}");
EndTag(sb, "a");
if (info.Genres.Any())
{
AddParagraph(sb,
$"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}");
}
AddParagraph(sb, info.Overview);
}
private async Task ProcessPlexTv(IQueryable<PlexEpisode> plexContent, StringBuilder sb)
{
var series = new List<PlexServerContent>();
foreach (var plexEpisode in plexContent)
{
var alreadyAdded = series.FirstOrDefault(x => x.Key == plexEpisode.Series.Key);
if (alreadyAdded != null)
{
var episodeExists = alreadyAdded.Episodes.Any(x => x.Key == plexEpisode.Key);
if (!episodeExists)
{
alreadyAdded.Episodes.Add(plexEpisode);
}
}
else
{
plexEpisode.Series.Episodes = new List<PlexEpisode> { plexEpisode };
series.Add(plexEpisode.Series);
}
}
var orderedTv = series.OrderByDescending(x => x.AddedAt);
sb.Append( sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">"); "<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var t in orderedTv) foreach (var t in orderedTv)
@ -412,15 +428,15 @@ namespace Ombi.Schedule.Jobs.Ombi
sb.Append("<tr>"); sb.Append("<tr>");
sb.Append( sb.Append(
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">"); "<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
var title = $"{t.Title} {t.ReleaseYear}"; var title = $"{t.Title} ({t.ReleaseYear})";
Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/"); Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/");
Header(sb, 3, title); Header(sb, 3, title);
EndTag(sb, "a"); EndTag(sb, "a");
// Group by the season number // Group by the season number
var results = t.Episodes?.GroupBy(p => p.SeasonNumber, var results = t.Episodes.GroupBy(p => p.SeasonNumber,
(key, g) => new (key, g) => new
{ {
SeasonNumber = key, SeasonNumber = key,
@ -469,9 +485,26 @@ namespace Ombi.Schedule.Jobs.Ombi
} }
private async Task ProcessEmbyTv(IQueryable<EmbyContent> plexContent, StringBuilder sb) private async Task ProcessEmbyTv(IQueryable<EmbyEpisode> embyContent, StringBuilder sb)
{ {
var orderedTv = plexContent.OrderByDescending(x => x.AddedAt); var series = new List<EmbyContent>();
foreach (var episode in embyContent)
{
var alreadyAdded = series.FirstOrDefault(x => x.EmbyId == episode.Series.EmbyId);
if (alreadyAdded != null)
{
alreadyAdded.Episodes.Add(episode);
}
else
{
episode.Series.Episodes = new List<EmbyEpisode>
{
episode
};
series.Add(episode.Series);
}
}
var orderedTv = series.OrderByDescending(x => x.AddedAt);
sb.Append( sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">"); "<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var t in orderedTv) foreach (var t in orderedTv)

View file

@ -119,7 +119,10 @@ namespace Ombi.Store.Context
var roles = Roles.Where(x => x.Name == OmbiRoles.RecievesNewsletter); var roles = Roles.Where(x => x.Name == OmbiRoles.RecievesNewsletter);
if (!roles.Any()) if (!roles.Any())
{ {
Roles.Add(new IdentityRole(OmbiRoles.RecievesNewsletter)); Roles.Add(new IdentityRole(OmbiRoles.RecievesNewsletter)
{
NormalizedName = OmbiRoles.RecievesNewsletter.ToUpper()
});
} }
//Check if templates exist //Check if templates exist
var templates = NotificationTemplates.ToList(); var templates = NotificationTemplates.ToList();
@ -143,7 +146,7 @@ namespace Ombi.Store.Context
notificationToAdd = new NotificationTemplates notificationToAdd = new NotificationTemplates
{ {
NotificationType = notificationType, NotificationType = notificationType,
Message = "Hello! The user '{RequestedUser}' has requested the {Type} '{Title}'! Please log in to approve this request. Request Date: {RequestedDate}", Message = "Hello! The user '{UserName}' has requested the {Type} '{Title}'! Please log in to approve this request. Request Date: {RequestedDate}",
Subject = "{ApplicationName}: New {Type} request for {Title}!", Subject = "{ApplicationName}: New {Type} request for {Title}!",
Agent = agent, Agent = agent,
Enabled = true, Enabled = true,
@ -153,7 +156,7 @@ namespace Ombi.Store.Context
notificationToAdd = new NotificationTemplates notificationToAdd = new NotificationTemplates
{ {
NotificationType = notificationType, NotificationType = notificationType,
Message = "Hello! The user '{IssueUser}' has reported a new issue for the title {Title}! </br> {IssueCategory} - {IssueSubject} : {IssueDescription}", Message = "Hello! The user '{UserName}' has reported a new issue for the title {Title}! </br> {IssueCategory} - {IssueSubject} : {IssueDescription}",
Subject = "{ApplicationName}: New issue for {Title}!", Subject = "{ApplicationName}: New issue for {Title}!",
Agent = agent, Agent = agent,
Enabled = true, Enabled = true,
@ -163,7 +166,7 @@ namespace Ombi.Store.Context
notificationToAdd = new NotificationTemplates notificationToAdd = new NotificationTemplates
{ {
NotificationType = notificationType, NotificationType = notificationType,
Message = "Hello! You requested {Title} on {ApplicationName}! This is now available! :)", Message = "Hello! You {Title} on {ApplicationName}! This is now available! :)",
Subject = "{ApplicationName}: {Title} is now available!", Subject = "{ApplicationName}: {Title} is now available!",
Agent = agent, Agent = agent,
Enabled = true, Enabled = true,
@ -207,7 +210,7 @@ namespace Ombi.Store.Context
notificationToAdd = new NotificationTemplates notificationToAdd = new NotificationTemplates
{ {
NotificationType = notificationType, NotificationType = notificationType,
Message = "Hello {IssueUser} Your issue for {Title} has now been resolved.", Message = "Hello {UserName} Your issue for {Title} has now been resolved.",
Subject = "{ApplicationName}: Issue has been resolved for {Title}!", Subject = "{ApplicationName}: Issue has been resolved for {Title}!",
Agent = agent, Agent = agent,
Enabled = true, Enabled = true,

View file

@ -10,7 +10,7 @@ namespace Ombi.Store.Repository.Requests
public class SeasonRequests : Entity public class SeasonRequests : Entity
{ {
public int SeasonNumber { get; set; } public int SeasonNumber { get; set; }
public List<EpisodeRequests> Episodes { get; set; } public List<EpisodeRequests> Episodes { get; set; } = new List<EpisodeRequests>();
public int ChildRequestId { get; set; } public int ChildRequestId { get; set; }
[ForeignKey(nameof(ChildRequestId))] [ForeignKey(nameof(ChildRequestId))]

View file

@ -0,0 +1,12 @@
import { animate, style, transition, trigger } from "@angular/animations";
import { AnimationEntryMetadata } from "@angular/core";
export const fadeInOutAnimation: AnimationEntryMetadata = trigger("fadeInOut", [
transition(":enter", [ // :enter is alias to 'void => *'
style({ opacity: 0 }),
animate(1000, style({ opacity: 1 })),
]),
transition(":leave", [ // :leave is alias to '* => void'
animate(1000, style({ opacity: 0 })),
]),
]);

View file

@ -30,3 +30,20 @@ export interface ISearchTvResult {
firstSeason: boolean; firstSeason: boolean;
latestSeason: boolean; latestSeason: boolean;
} }
export interface ITvRequestViewModel {
requestAll: boolean;
firstSeason: boolean;
latestSeason: boolean;
tvDbId: number;
seasons: ISeasonsViewModel[];
}
export interface ISeasonsViewModel {
seasonNumber: number;
episodes: IEpisodesViewModel[];
}
export interface IEpisodesViewModel {
episodeNumber: number;
}

View file

@ -1,13 +1,16 @@
<div *ngIf="issue"> <div *ngIf="issue">
<div class="myBg backdrop" [style.background-image]="backgroundPath"></div>
<div class="tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
<h1>{{issue.title}} </h1> <h1>{{issue.title}} </h1>
<div class="col-md-6"> <div class="col-md-6">
<span class="label label-info">{{IssueStatus[issue.status]}}</span> <img class="img-responsive poster" src="{{posterPath}}" alt="poster">
<span class="label label-success">{{issue.issueCategory.value}}</span> <span class="label label-info">{{IssueStatus[issue.status]}}</span>
<span class="label label-success">{{issue.issueCategory.value}}</span>
<h3 *ngIf="issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}: {{issue.userReported.alias}}</h3>
<h3 *ngIf="!issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}: {{issue.userReported.userName}}</h3> <h3 *ngIf="issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}: {{issue.userReported.alias}}</h3>
<h3 *ngIf="!issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}: {{issue.userReported.userName}}</h3>
<h3 *ngIf="issue.subject">{{'Issues.Subject' | translate}}: {{issue.subject}}</h3> <h3 *ngIf="issue.subject">{{'Issues.Subject' | translate}}: {{issue.subject}}</h3>
<br> <br>
<div class="form-group"> <div class="form-group">
<label for="description" class="control-label" [translate]="'Issues.Description'"></label> <label for="description" class="control-label" [translate]="'Issues.Description'"></label>
<div> <div>
@ -26,7 +29,8 @@
<div class="panel-heading top-bar"> <div class="panel-heading top-bar">
<div class="col-md-8 col-xs-8"> <div class="col-md-8 col-xs-8">
<h3 class="panel-title"> <h3 class="panel-title">
<span class="glyphicon glyphicon-comment"></span> {{'Issues.Comments' | translate}}</h3> <span class="glyphicon glyphicon-comment"></span> {{'Issues.Comments' | translate}}
</h3>
</div> </div>
</div> </div>
@ -51,8 +55,7 @@
</div> </div>
<div class="panel-footer"> <div class="panel-footer">
<div class="input-group"> <div class="input-group">
<input id="btn-input" type="text" class="form-control input-sm chat_input" [(ngModel)]="newComment.comment" [attr.placeholder]="'Issues.WriteMessagePlaceholder' | translate" <input id="btn-input" type="text" class="form-control input-sm chat_input" [(ngModel)]="newComment.comment" [attr.placeholder]="'Issues.WriteMessagePlaceholder' | translate" />
/>
<span class="input-group-btn"> <span class="input-group-btn">
<button class="btn btn-primary btn-sm" id="btn-chat" (click)="addComment()" [translate]="'Issues.SendMessageButton'"></button> <button class="btn btn-primary btn-sm" id="btn-chat" (click)="addComment()" [translate]="'Issues.SendMessageButton'"></button>
</span> </span>

View file

@ -64,6 +64,15 @@ body{
overflow: hidden; overflow: hidden;
display: flex; display: flex;
} }
.myBg {
z-index: -1;
}
.tint {
z-index: -1;
}
img-responsive poster {
display:block;
}
img { img {
display: block; display: block;
width: 100%; width: 100%;

View file

@ -2,8 +2,9 @@ import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { AuthService } from "../auth/auth.service"; import { AuthService } from "../auth/auth.service";
import { IssuesService, NotificationService, SettingsService } from "../services"; import { ImageService, IssuesService, NotificationService, SettingsService } from "../services";
import { DomSanitizer } from "@angular/platform-browser";
import { IIssues, IIssuesChat, IIssueSettings, INewIssueComments, IssueStatus } from "../interfaces"; import { IIssues, IIssuesChat, IIssueSettings, INewIssueComments, IssueStatus } from "../interfaces";
@Component({ @Component({
@ -22,6 +23,8 @@ export class IssueDetailsComponent implements OnInit {
public IssueStatus = IssueStatus; public IssueStatus = IssueStatus;
public isAdmin: boolean; public isAdmin: boolean;
public settings: IIssueSettings; public settings: IIssueSettings;
public backgroundPath: any;
public posterPath: any;
private issueId: number; private issueId: number;
@ -29,7 +32,9 @@ export class IssueDetailsComponent implements OnInit {
private route: ActivatedRoute, private route: ActivatedRoute,
private authService: AuthService, private authService: AuthService,
private settingsService: SettingsService, private settingsService: SettingsService,
private notificationService: NotificationService) { private notificationService: NotificationService,
private imageService: ImageService,
private sanitizer: DomSanitizer) {
this.route.params this.route.params
.subscribe((params: any) => { .subscribe((params: any) => {
this.issueId = parseInt(params.id); this.issueId = parseInt(params.id);
@ -56,8 +61,8 @@ export class IssueDetailsComponent implements OnInit {
providerId: x.providerId, providerId: x.providerId,
userReported: x.userReported, userReported: x.userReported,
}; };
this.setBackground(x);
}); });
this.loadComments(); this.loadComments();
} }
@ -85,4 +90,26 @@ export class IssueDetailsComponent implements OnInit {
private loadComments() { private loadComments() {
this.issueService.getComments(this.issueId).subscribe(x => this.comments = x); this.issueService.getComments(this.issueId).subscribe(x => this.comments = x);
} }
private setBackground(issue: any) {
if (issue.requestType === 1) {
this.imageService.getMovieBackground(issue.providerId).subscribe(x => {
this.backgroundPath = this.sanitizer.bypassSecurityTrustStyle
("url(" + x + ")");
});
this.imageService.getMoviePoster(issue.providerId).subscribe(x => {
this.posterPath = x.toString();
});
} else {
this.imageService.getTvBackground(Number(issue.providerId)).subscribe(x => {
this.backgroundPath = this.sanitizer.bypassSecurityTrustStyle
("url(" + x + ")");
});
this.imageService.getTvPoster(Number(issue.providerId)).subscribe(x => {
this.posterPath = x.toString();
});
}
}
} }

View file

@ -5,7 +5,7 @@ import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
import { OrderModule } from "ngx-order-pipe"; import { OrderModule } from "ngx-order-pipe";
import { PaginatorModule, SharedModule, TabViewModule } from "primeng/primeng"; import { PaginatorModule, SharedModule, TabViewModule } from "primeng/primeng";
import { IdentityService } from "../services"; import { IdentityService, SearchService } from "../services";
import { AuthGuard } from "../auth/auth.guard"; import { AuthGuard } from "../auth/auth.guard";
@ -43,6 +43,7 @@ const routes: Routes = [
], ],
providers: [ providers: [
IdentityService, IdentityService,
SearchService,
], ],
}) })

View file

@ -1,5 +1,5 @@
<div *ngIf="landingPageSettings && customizationSettings"> <div *ngIf="landingPageSettings && customizationSettings">
<div *ngIf="background" class="bg" [style.background-image]="background"></div> <div *ngIf="background" @fadeInOut class="bg" [style.background-image]="background"></div>
<div class="centered col-md-12"> <div class="centered col-md-12">
<div class="row"> <div class="row">

View file

@ -1,5 +1,5 @@
import { PlatformLocation } from "@angular/common"; import { PlatformLocation } from "@angular/common";
import { Component, OnInit } from "@angular/core"; import { Component, OnDestroy, OnInit } from "@angular/core";
import { IMediaServerStatus } from "../interfaces"; import { IMediaServerStatus } from "../interfaces";
import { ICustomizationSettings, ILandingPageSettings } from "../interfaces"; import { ICustomizationSettings, ILandingPageSettings } from "../interfaces";
@ -9,17 +9,21 @@ import { SettingsService } from "../services";
import { DomSanitizer } from "@angular/platform-browser"; import { DomSanitizer } from "@angular/platform-browser";
import { ImageService } from "../services"; import { ImageService } from "../services";
import { fadeInOutAnimation } from "../animations/fadeinout";
@Component({ @Component({
templateUrl: "./landingpage.component.html", templateUrl: "./landingpage.component.html",
animations: [fadeInOutAnimation],
styleUrls: ["./landingpage.component.scss"], styleUrls: ["./landingpage.component.scss"],
}) })
export class LandingPageComponent implements OnInit { export class LandingPageComponent implements OnDestroy, OnInit {
public customizationSettings: ICustomizationSettings; public customizationSettings: ICustomizationSettings;
public landingPageSettings: ILandingPageSettings; public landingPageSettings: ILandingPageSettings;
public background: any; public background: any;
public mediaServerStatus: IMediaServerStatus; public mediaServerStatus: IMediaServerStatus;
public baseUrl: string; public baseUrl: string;
private timer: any;
constructor(private settingsService: SettingsService, constructor(private settingsService: SettingsService,
private images: ImageService, private sanitizer: DomSanitizer, private landingPageService: LandingPageService, private images: ImageService, private sanitizer: DomSanitizer, private landingPageService: LandingPageService,
@ -31,6 +35,9 @@ export class LandingPageComponent implements OnInit {
this.images.getRandomBackground().subscribe(x => { this.images.getRandomBackground().subscribe(x => {
this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%), url(" + x.url + ")"); this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%), url(" + x.url + ")");
}); });
this.timer = setInterval(() => {
this.cycleBackground();
}, 10000);
const base = this.location.getBaseHrefFromDOM(); const base = this.location.getBaseHrefFromDOM();
if (base.length > 1) { if (base.length > 1) {
@ -41,4 +48,18 @@ export class LandingPageComponent implements OnInit {
this.mediaServerStatus = x; this.mediaServerStatus = x;
}); });
} }
public ngOnDestroy() {
clearInterval(this.timer);
}
public cycleBackground() {
this.images.getRandomBackground().subscribe(x => {
this.background = "";
});
this.images.getRandomBackground().subscribe(x => {
this.background = this.sanitizer
.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%), url(" + x.url + ")");
});
}
} }

View file

@ -4,7 +4,7 @@ include the remember me checkbox
--> -->
<div *ngIf="form && customizationSettings"> <div *ngIf="form && customizationSettings">
<div *ngIf="background" class="bg" [style.background-image]="background"></div> <div *ngIf="background" @fadeInOut class="bg" [style.background-image]="background"></div>
<div class="container" id="login"> <div class="container" id="login">
<div class="card card-container"> <div class="card card-container">
<!-- <img class="profile-img-card" src="//lh3.googleusercontent.com/-6V8xOA6M7BA/AAAAAAAAAAI/AAAAAAAAAAA/rzlHcD0KYwo/photo.jpg?sz=120" alt="" /> --> <!-- <img class="profile-img-card" src="//lh3.googleusercontent.com/-6V8xOA6M7BA/AAAAAAAAAAI/AAAAAAAAAAA/rzlHcD0KYwo/photo.jpg?sz=120" alt="" /> -->

View file

@ -1,4 +1,4 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { TranslateService } from "@ngx-translate/core"; import { TranslateService } from "@ngx-translate/core";
@ -13,11 +13,14 @@ import { StatusService } from "../services";
import { DomSanitizer } from "@angular/platform-browser"; import { DomSanitizer } from "@angular/platform-browser";
import { ImageService } from "../services"; import { ImageService } from "../services";
import { fadeInOutAnimation } from "../animations/fadeinout";
@Component({ @Component({
templateUrl: "./login.component.html", templateUrl: "./login.component.html",
animations: [fadeInOutAnimation],
styleUrls: ["./login.component.scss"], styleUrls: ["./login.component.scss"],
}) })
export class LoginComponent implements OnInit { export class LoginComponent implements OnDestroy, OnInit {
public form: FormGroup; public form: FormGroup;
public customizationSettings: ICustomizationSettings; public customizationSettings: ICustomizationSettings;
@ -25,6 +28,7 @@ export class LoginComponent implements OnInit {
public background: any; public background: any;
public landingFlag: boolean; public landingFlag: boolean;
public baseUrl: string; public baseUrl: string;
private timer: any;
private errorBody: string; private errorBody: string;
private errorValidation: string; private errorValidation: string;
@ -67,6 +71,10 @@ export class LoginComponent implements OnInit {
this.images.getRandomBackground().subscribe(x => { this.images.getRandomBackground().subscribe(x => {
this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%),url(" + x.url + ")"); this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%),url(" + x.url + ")");
}); });
this.timer = setInterval(() => {
this.cycleBackground();
}, 10000);
const base = this.location.getBaseHrefFromDOM(); const base = this.location.getBaseHrefFromDOM();
if (base.length > 1) { if (base.length > 1) {
this.baseUrl = base; this.baseUrl = base;
@ -102,4 +110,19 @@ export class LoginComponent implements OnInit {
}, err => this.notify.error(this.errorBody)); }, err => this.notify.error(this.errorBody));
}); });
} }
public ngOnDestroy() {
clearInterval(this.timer);
}
private cycleBackground() {
this.images.getRandomBackground().subscribe(x => {
this.background = "";
});
this.images.getRandomBackground().subscribe(x => {
this.background = this.sanitizer
.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%), url(" + x.url + ")");
});
}
} }

View file

@ -207,7 +207,7 @@
<issue-report [movie]="true" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequest?.title" <issue-report [movie]="true" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequest?.title"
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]=""></issue-report> [issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]="issueProviderId"></issue-report>
<p-sidebar [(visible)]="filterDisplay" styleClass="ui-sidebar-md side-back side-small"> <p-sidebar [(visible)]="filterDisplay" styleClass="ui-sidebar-md side-back side-small">

View file

@ -105,4 +105,4 @@
<issue-report [movie]="false" [visible]="issuesBarVisible" [title]="issueRequest?.title" <issue-report [movie]="false" [visible]="issuesBarVisible" [title]="issueRequest?.title"
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" (visibleChange)="issuesBarVisible = $event;"></issue-report> [issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]="issueProviderId" (visibleChange)="issuesBarVisible = $event;"></issue-report>

View file

@ -105,6 +105,7 @@ export class TvRequestChildrenComponent {
this.issueRequest = req; this.issueRequest = req;
this.issueCategorySelected = catId; this.issueCategorySelected = catId;
this.issuesBarVisible = true; this.issuesBarVisible = true;
this.issueProviderId = req.id.toString();
} }
private removeRequestFromUi(key: IChildRequests) { private removeRequestFromUi(key: IChildRequests) {

View file

@ -90,12 +90,6 @@ export class TvRequestsComponent implements OnInit {
} }
public ngOnInit() { public ngOnInit() {
const profile = <ISonarrProfile>{name:"test",id:1 };
const folder = <ISonarrRootFolder>{path:"testpath", id:1};
this.sonarrProfiles.push(profile);
this.sonarrRootFolders.push(folder);
this.amountToLoad = 1000; this.amountToLoad = 1000;
this.currentlyLoaded = 1000; this.currentlyLoaded = 1000;
this.tvRequests = []; this.tvRequests = [];
@ -204,7 +198,7 @@ export class TvRequestsComponent implements OnInit {
this.loadInit(); this.loadInit();
} }
private loadBackdrop(val: TreeNode): void { private loadBackdrop(val: TreeNode): void {
this.imageService.getTvBanner(val.data.id).subscribe(x => { this.imageService.getTvBanner(val.data.tvDbId).subscribe(x => {
val.data.background = this.sanitizer.bypassSecurityTrustStyle val.data.background = this.sanitizer.bypassSecurityTrustStyle
("url(" + x + ")"); ("url(" + x + ")");
}); });

View file

@ -5,7 +5,7 @@ import { NotificationService } from "../services";
import { RequestService } from "../services"; import { RequestService } from "../services";
import { SearchService } from "../services"; import { SearchService } from "../services";
import { INewSeasonRequests, IRequestEngineResult } from "../interfaces"; import { INewSeasonRequests, IRequestEngineResult, ISeasonsViewModel, ITvRequestViewModel } from "../interfaces";
import { IEpisodesRequests } from "../interfaces"; import { IEpisodesRequests } from "../interfaces";
import { ISearchTvResult } from "../interfaces"; import { ISearchTvResult } from "../interfaces";
@ -46,7 +46,22 @@ export class SeriesInformationComponent implements OnInit {
this.series.requested = true; this.series.requested = true;
this.requestService.requestTv(this.series) const viewModel = <ITvRequestViewModel>{ firstSeason: this.series.firstSeason, latestSeason: this.series.latestSeason, requestAll: this.series.requestAll, tvDbId: this.series.id};
viewModel.seasons = [];
this.series.seasonRequests.forEach((season) => {
const seasonsViewModel = <ISeasonsViewModel>{seasonNumber: season.seasonNumber, episodes: []};
season.episodes.forEach(ep => {
if(!this.series.latestSeason || !this.series.requestAll || !this.series.firstSeason) {
if(ep.requested) {
seasonsViewModel.episodes.push({episodeNumber: ep.episodeNumber});
}
}
});
viewModel.seasons.push(seasonsViewModel);
});
this.requestService.requestTv(viewModel)
.subscribe(x => { .subscribe(x => {
this.result = x as IRequestEngineResult; this.result = x as IRequestEngineResult;
if (this.result.result) { if (this.result.result) {

View file

@ -7,7 +7,7 @@ import { ImageService, NotificationService, RequestService, SearchService} from
import { TreeNode } from "primeng/primeng"; import { TreeNode } from "primeng/primeng";
import { IRequestEngineResult } from "../interfaces"; import { IRequestEngineResult } from "../interfaces";
import { IIssueCategory, ISearchTvResult } from "../interfaces"; import { IIssueCategory, ISearchTvResult, ISeasonsViewModel, ITvRequestViewModel } from "../interfaces";
@Component({ @Component({
selector: "tv-search", selector: "tv-search",
@ -129,7 +129,6 @@ export class TvSearchComponent implements OnInit {
public getExtraInfo() { public getExtraInfo() {
this.tvResults.forEach((val, index) => { this.tvResults.forEach((val, index) => {
this.imageService.getTvBanner(val.data.id).subscribe(x => { this.imageService.getTvBanner(val.data.id).subscribe(x => {
val.data.background = this.sanitizer. val.data.background = this.sanitizer.
@ -155,7 +154,23 @@ export class TvSearchComponent implements OnInit {
if (this.authService.hasRole("admin") || this.authService.hasRole("AutoApproveMovie")) { if (this.authService.hasRole("admin") || this.authService.hasRole("AutoApproveMovie")) {
searchResult.approved = true; searchResult.approved = true;
} }
this.requestService.requestTv(searchResult)
const viewModel = <ITvRequestViewModel>{ firstSeason: searchResult.firstSeason, latestSeason: searchResult.latestSeason, requestAll: searchResult.requestAll, tvDbId: searchResult.id};
viewModel.seasons = [];
searchResult.seasonRequests.forEach((season) => {
const seasonsViewModel = <ISeasonsViewModel>{seasonNumber: season.seasonNumber, episodes: []};
season.episodes.forEach(ep => {
if(!searchResult.latestSeason || !searchResult.requestAll || !searchResult.firstSeason) {
if(ep.requested) {
seasonsViewModel.episodes.push({episodeNumber: ep.episodeNumber});
}
}
});
viewModel.seasons.push(seasonsViewModel);
});
this.requestService.requestTv(viewModel)
.subscribe(x => { .subscribe(x => {
this.result = x; this.result = x;
if (this.result.result) { if (this.result.result) {

View file

@ -19,11 +19,22 @@ export class ImageService extends ServiceHelpers {
public getTvBanner(tvdbid: number): Observable<string> { public getTvBanner(tvdbid: number): Observable<string> {
return this.http.get<string>(`${this.url}tv/${tvdbid}`, {headers: this.headers}); return this.http.get<string>(`${this.url}tv/${tvdbid}`, {headers: this.headers});
}
public getMoviePoster(themoviedbid: string): Observable<string> {
return this.http.get<string>(`${this.url}poster/movie/${themoviedbid}`, {headers: this.headers});
} }
public getMoviePoster(movieDbId: string): Observable<string> {
return this.http.get<string>(`${this.url}poster/movie/${movieDbId}`, { headers: this.headers });
}
public getTvPoster(tvdbid: number): Observable<string> { public getTvPoster(tvdbid: number): Observable<string> {
return this.http.get<string>(`${this.url}poster/tv/${tvdbid}`, {headers: this.headers}); return this.http.get<string>(`${this.url}poster/tv/${tvdbid}`, { headers: this.headers });
} }
public getMovieBackground(movieDbId: string): Observable<string> {
return this.http.get<string>(`${this.url}background/movie/${movieDbId}`, { headers: this.headers });
}
public getTvBackground(tvdbid: number): Observable<string> {
return this.http.get<string>(`${this.url}background/tv/${tvdbid}`, { headers: this.headers });
}
} }

View file

@ -38,4 +38,8 @@ export class JobService extends ServiceHelpers {
public runEmbyCacher(): Observable<boolean> { public runEmbyCacher(): Observable<boolean> {
return this.http.post<boolean>(`${this.url}embycontentcacher/`, {headers: this.headers}); return this.http.post<boolean>(`${this.url}embycontentcacher/`, {headers: this.headers});
} }
public runNewsletter(): Observable<boolean> {
return this.http.post<boolean>(`${this.url}newsletter/`, {headers: this.headers});
}
} }

View file

@ -7,7 +7,7 @@ import { Observable } from "rxjs/Rx";
import { TreeNode } from "primeng/primeng"; import { TreeNode } from "primeng/primeng";
import { IRequestEngineResult } from "../interfaces"; import { IRequestEngineResult } from "../interfaces";
import { IChildRequests, IFilter, IMovieRequestModel, IMovieRequests, IMovieUpdateModel, ITvRequests, ITvUpdateModel } from "../interfaces"; import { IChildRequests, IFilter, IMovieRequestModel, IMovieRequests, IMovieUpdateModel, ITvRequests, ITvUpdateModel } from "../interfaces";
import { ISearchTvResult } from "../interfaces"; import { ITvRequestViewModel } from "../interfaces";
import { ServiceHelpers } from "./service.helpers"; import { ServiceHelpers } from "./service.helpers";
@Injectable() @Injectable()
@ -20,7 +20,7 @@ export class RequestService extends ServiceHelpers {
return this.http.post<IRequestEngineResult>(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers}); return this.http.post<IRequestEngineResult>(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers});
} }
public requestTv(tv: ISearchTvResult): Observable<IRequestEngineResult> { public requestTv(tv: ITvRequestViewModel): Observable<IRequestEngineResult> {
return this.http.post<IRequestEngineResult>(`${this.url}TV/`, JSON.stringify(tv), {headers: this.headers}); return this.http.post<IRequestEngineResult>(`${this.url}TV/`, JSON.stringify(tv), {headers: this.headers});
} }

View file

@ -33,12 +33,12 @@
</div> </div>
</div> </div>
<div class="form-group"> <!-- <div class="form-group">
<div class="checkbox"> <div class="checkbox">
<input type="checkbox" id="enable" [(ngModel)]="settings.recentlyAddedPage" [checked]="settings.recentlyAddedPage"> <input type="checkbox" id="enable" [(ngModel)]="settings.recentlyAddedPage" [checked]="settings.recentlyAddedPage">
<label for="enable">Enable Recently Added Page</label> <label for="enable">Enable Recently Added Page</label>
</div> </div>
</div> </div> -->
<div class="form-group"> <div class="form-group">
<label for="logo" class="control-label">Custom Logo</label> <label for="logo" class="control-label">Custom Logo</label>

View file

@ -1,6 +1,6 @@
<settings-menu></settings-menu> <settings-menu></settings-menu>
<wiki [url]="'https://github.com/tidusjar/Ombi/wiki/Newsletter'"></wiki> <wiki [url]="'https://github.com/tidusjar/Ombi/wiki/Newsletter-Settings'"></wiki>
<div *ngIf="settings"> <div *ngIf="settings">
<fieldset> <fieldset>
<legend>Newsletter</legend> <legend>Newsletter</legend>
@ -30,14 +30,18 @@
<button type="button" (click)="test()" class="btn btn-danger-outline">Test</button> <button type="button" (click)="test()" class="btn btn-danger-outline">Test</button>
<button type="button" (click)="updateDatabase()" class="btn btn-info-outline" tooltipPosition="top" pTooltip="I recommend running this with a fresh Ombi install, this will set all the current *found* content to have been sent via Newsletter, <button type="button" (click)="updateDatabase()" class="btn btn-info-outline" tooltipPosition="top" pTooltip="I recommend running this with a fresh Ombi install, this will set all the current *found* content to have been sent via Newsletter,
if you do not do this then everything that Ombi has found in your libraries will go out on the first email!">Update Database</button> if you do not do this then everything that Ombi has found in your libraries will go out on the first email!">Update Database</button>
<button type="button" (click)="trigger()" class="btn btn-danger-outline">Trigger now</button>
</div> </div>
<small>NOTE: Please see the tooltip on the Update Database button.</small> </div>
<small>When testing, the test newsletter will go to all users that have the Admin role, please ensure that there are valid email addresses for this. The test will also only grab the latest 10 movies and 10 shows just to give you an example.</small>
</div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<small>NOTE: Please see the tooltip on the Update Database button - Please see the wiki for more information</small>
<br/>
<br/>
<small>When testing, the test newsletter will go to all users that have the Admin role, please ensure that there are valid email addresses for this. The test will also only grab the latest 10 movies and 10 shows just to give you an example.</small>
</div> </div>
</fieldset> </fieldset>

View file

@ -1,9 +1,7 @@
 import { Component, OnInit } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { INewsletterNotificationSettings, NotificationType } from "../../interfaces"; import { INewsletterNotificationSettings, NotificationType } from "../../interfaces";
import { NotificationService } from "../../services"; import { JobService, NotificationService, SettingsService } from "../../services";
import { SettingsService } from "../../services";
import { TesterService } from "./../../services/applications/tester.service"; import { TesterService } from "./../../services/applications/tester.service";
@Component({ @Component({
@ -16,7 +14,8 @@ export class NewsletterComponent implements OnInit {
constructor(private settingsService: SettingsService, constructor(private settingsService: SettingsService,
private notificationService: NotificationService, private notificationService: NotificationService,
private testService: TesterService) { } private testService: TesterService,
private jobService: JobService) { }
public ngOnInit() { public ngOnInit() {
this.settingsService.getNewsletterSettings().subscribe(x => { this.settingsService.getNewsletterSettings().subscribe(x => {
@ -26,10 +25,17 @@ export class NewsletterComponent implements OnInit {
public updateDatabase() { public updateDatabase() {
this.settingsService.updateNewsletterDatabase().subscribe(); this.settingsService.updateNewsletterDatabase().subscribe();
this.notificationService.success("Database updated");
} }
public test() { public test() {
this.testService.newsletterTest(this.settings).subscribe(); this.testService.newsletterTest(this.settings).subscribe();
this.notificationService.success("Test message queued");
}
public trigger() {
this.jobService.runNewsletter().subscribe();
this.notificationService.success("Triggered newsletter job");
} }
public onSubmit() { public onSubmit() {

View file

@ -15,6 +15,8 @@ export class IssuesReportComponent {
@Input() public issueCategory: IIssueCategory; @Input() public issueCategory: IIssueCategory;
@Input() public movie: boolean; @Input() public movie: boolean;
@Input() public providerId: string; @Input() public providerId: string;
@Input() public background: string;
@Input() public posterPath: string;
@Output() public visibleChange = new EventEmitter<boolean>(); @Output() public visibleChange = new EventEmitter<boolean>();

View file

@ -1 +1,2 @@
@import './styles.scss'; @import './styles.scss';
@import './scrollbar.scss';

View file

@ -0,0 +1,23 @@
::-webkit-scrollbar {
width: 15px;
}
::-webkit-scrollbar-track {
background-color: rgba(0,0,0,.2);
}
::-webkit-scrollbar-thumb {
min-height: 50px;
background-color: rgba(255,255,255,.15);
border: 3px solid transparent;
border-radius: 8px;
background-clip: padding-box;
}
pre::-webkit-scrollbar-track {
background-color: rgba(255,255,255,.2);
}
pre::-webkit-scrollbar-thumb {
background-color: rgba(0,0,0,.15);
}

View file

@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Ombi.Api.FanartTv; using Ombi.Api.FanartTv;
using Ombi.Store.Repository; using Ombi.Store.Repository;
using System; using System;
@ -31,7 +31,7 @@ namespace Ombi.Controllers
private IApplicationConfigRepository Config { get; } private IApplicationConfigRepository Config { get; }
private LandingPageBackground Options { get; } private LandingPageBackground Options { get; }
private readonly ICacheService _cache; private readonly ICacheService _cache;
[HttpGet("tv/{tvdbid}")] [HttpGet("tv/{tvdbid}")]
public async Task<string> GetTvBanner(int tvdbid) public async Task<string> GetTvBanner(int tvdbid)
{ {
@ -71,7 +71,7 @@ namespace Ombi.Controllers
if (images.movieposter?.Any() ?? false) if (images.movieposter?.Any() ?? false)
{ {
var enImage = images.movieposter.Where(x => x.lang == "en").OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault(); var enImage = images.movieposter.Where(x => x.lang == "en").OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault();
if (enImage == null) if (enImage == null)
{ {
return images.movieposter.OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault(); return images.movieposter.OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault();
@ -117,6 +117,56 @@ namespace Ombi.Controllers
return string.Empty; return string.Empty;
} }
[HttpGet("background/movie/{movieDbId}")]
public async Task<string> GetMovieBackground(string movieDbId)
{
var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.Get(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1));
var images = await FanartTvApi.GetMovieImages(movieDbId, key.Value);
if (images == null)
{
return string.Empty;
}
if (images.moviebackground?.Any() ?? false)
{
var enImage = images.moviebackground.Where(x => x.lang == "en").OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault();
if (enImage == null)
{
return images.moviebackground.OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault();
}
return enImage;
}
return string.Empty;
}
[HttpGet("background/tv/{tvdbid}")]
public async Task<string> GetTvBackground(int tvdbid)
{
var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.Get(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1));
var images = await FanartTvApi.GetTvImages(tvdbid, key.Value);
if (images == null)
{
return string.Empty;
}
if (images.showbackground?.Any() ?? false)
{
var enImage = images.showbackground.Where(x => x.lang == "en").OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault();
if (enImage == null)
{
return images.showbackground.OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault();
}
return enImage;
}
return string.Empty;
}
[HttpGet("background")] [HttpGet("background")]
public async Task<object> GetBackgroundImage() public async Task<object> GetBackgroundImage()
{ {
@ -133,7 +183,7 @@ namespace Ombi.Controllers
{ {
var item = rand.Next(moviesArray.Length); var item = rand.Next(moviesArray.Length);
var result = await FanartTvApi.GetMovieImages(moviesArray[item].ToString(), key.Value); var result = await FanartTvApi.GetMovieImages(moviesArray[item].ToString(), key.Value);
while (!result.moviebackground.Any()) while (!result.moviebackground.Any())
{ {
result = await FanartTvApi.GetMovieImages(moviesArray[item].ToString(), key.Value); result = await FanartTvApi.GetMovieImages(moviesArray[item].ToString(), key.Value);
@ -141,7 +191,7 @@ namespace Ombi.Controllers
movieUrl = result.moviebackground[0].url; movieUrl = result.moviebackground[0].url;
} }
if(tvArray.Any()) if (tvArray.Any())
{ {
var item = rand.Next(tvArray.Length); var item = rand.Next(tvArray.Length);
var result = await FanartTvApi.GetTvImages(tvArray[item], key.Value); var result = await FanartTvApi.GetTvImages(tvArray[item], key.Value);
@ -157,8 +207,8 @@ namespace Ombi.Controllers
if (!string.IsNullOrEmpty(movieUrl) && !string.IsNullOrEmpty(tvUrl)) if (!string.IsNullOrEmpty(movieUrl) && !string.IsNullOrEmpty(tvUrl))
{ {
var result = rand.Next(2); var result = rand.Next(2);
if (result == 0) return new { url = movieUrl }; if (result == 0) return new { url = movieUrl };
if (result == 1) return new { url = tvUrl }; if (result == 1) return new { url = tvUrl };
} }
if (!string.IsNullOrEmpty(movieUrl)) if (!string.IsNullOrEmpty(movieUrl))

View file

@ -20,7 +20,7 @@ namespace Ombi.Controllers
{ {
public JobController(IOmbiAutomaticUpdater updater, IPlexUserImporter userImporter, public JobController(IOmbiAutomaticUpdater updater, IPlexUserImporter userImporter,
ICacheService mem, IEmbyUserImporter embyImporter, IPlexContentSync plexContentSync, ICacheService mem, IEmbyUserImporter embyImporter, IPlexContentSync plexContentSync,
IEmbyContentSync embyContentSync) IEmbyContentSync embyContentSync, INewsletterJob newsletter)
{ {
_updater = updater; _updater = updater;
_plexUserImporter = userImporter; _plexUserImporter = userImporter;
@ -28,6 +28,7 @@ namespace Ombi.Controllers
_memCache = mem; _memCache = mem;
_plexContentSync = plexContentSync; _plexContentSync = plexContentSync;
_embyContentSync = embyContentSync; _embyContentSync = embyContentSync;
_newsletterJob = newsletter;
} }
private readonly IOmbiAutomaticUpdater _updater; private readonly IOmbiAutomaticUpdater _updater;
@ -36,6 +37,7 @@ namespace Ombi.Controllers
private readonly ICacheService _memCache; private readonly ICacheService _memCache;
private readonly IPlexContentSync _plexContentSync; private readonly IPlexContentSync _plexContentSync;
private readonly IEmbyContentSync _embyContentSync; private readonly IEmbyContentSync _embyContentSync;
private readonly INewsletterJob _newsletterJob;
/// <summary> /// <summary>
/// Runs the update job /// Runs the update job
@ -129,5 +131,16 @@ namespace Ombi.Controllers
BackgroundJob.Enqueue(() => _embyContentSync.Start()); BackgroundJob.Enqueue(() => _embyContentSync.Start());
return true; return true;
} }
/// <summary>
/// Runs the newsletter
/// </summary>
/// <returns></returns>
[HttpPost("newsletter")]
public bool StartNewsletter()
{
BackgroundJob.Enqueue(() => _newsletterJob.Start());
return true;
}
} }
} }

View file

@ -174,7 +174,7 @@ namespace Ombi.Controllers
/// <param name="tv">The tv.</param> /// <param name="tv">The tv.</param>
/// <returns></returns> /// <returns></returns>
[HttpPost("tv")] [HttpPost("tv")]
public async Task<RequestEngineResult> RequestTv([FromBody] SearchTvShowViewModel tv) public async Task<RequestEngineResult> RequestTv([FromBody] TvRequestViewModel tv)
{ {
return await TvRequestEngine.RequestTvShow(tv); return await TvRequestEngine.RequestTvShow(tv);
} }

View file

@ -97,5 +97,13 @@
<None Include="wwwroot\loading.css" /> <None Include="wwwroot\loading.css" />
<None Include="wwwroot\translations\*.json" /> <None Include="wwwroot\translations\*.json" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="ClientApp\app\animations\fadeinout.ts" />
</ItemGroup>
<ItemGroup>
<TypeScriptCompile Include="ClientApp\app\animations\fadeinout.ts" />
</ItemGroup>
</Project> </Project>

View file

@ -165,6 +165,12 @@ namespace Ombi
var ombiService = var ombiService =
app.ApplicationServices.GetService<ISettingsService<OmbiSettings>>(); app.ApplicationServices.GetService<ISettingsService<OmbiSettings>>();
var settings = ombiService.GetSettings(); var settings = ombiService.GetSettings();
if (settings.ApiKey.IsNullOrEmpty())
{
// Generate a API Key
settings.ApiKey = Guid.NewGuid().ToString("N");
ombiService.SaveSettings(settings);
}
if (settings.BaseUrl.HasValue()) if (settings.BaseUrl.HasValue())
{ {
app.UsePathBase(settings.BaseUrl); app.UsePathBase(settings.BaseUrl);

View file

@ -64,26 +64,16 @@ O:::::::OOO:::::::Om::::m m::::m m::::mb:::::bbbbbb::::::bi::::::i
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>@appName</title> <title>@appName</title>
<base href="/@baseUrl"/> <base href="/@baseUrl"/>
<link rel="SHORTCUT ICON" href="~/images/favicon/favicon.ico"/>
<link rel="icon" href="~/images/favicon/favicon.ico" type="image/ico"/>
<link rel="apple-touch-icon" sizes="57x57" href="~/images/favicon/apple-icon-57x57.png"/> <link rel="apple-touch-icon" sizes="180x180" href="~/images/favicon/apple-touch-icon.png">
<link rel="apple-touch-icon" sizes="60x60" href="~/images/favicon/apple-icon-60x60.png"/> <link rel="icon" type="image/png" sizes="32x32" href="~/images/favicon/favicon-32x32.png">
<link rel="apple-touch-icon" sizes="72x72" href="~/images/favicon/apple-icon-72x72.png"/> <link rel="icon" type="image/png" sizes="16x16" href="~/images/favicon/favicon-16x16.png">
<link rel="apple-touch-icon" sizes="76x76" href="~/images/favicon/apple-icon-76x76.png"/> <link rel="manifest" href="~/images/favicon/site.webmanifest">
<link rel="apple-touch-icon" sizes="114x114" href="~/images/favicon/apple-icon-114x114.png"/> <link rel="mask-icon" href="~/images/favicon/safari-pinned-tab.svg" color="#df691a">
<link rel="apple-touch-icon" sizes="120x120" href="~/images/favicon/apple-icon-120x120.png"/> <link rel="shortcut icon" href="~/images/favicon/favicon.ico">
<link rel="apple-touch-icon" sizes="144x144" href="~/images/favicon/apple-icon-144x144.png"/> <meta name="msapplication-TileColor" content="#df691a">
<link rel="apple-touch-icon" sizes="152x152" href="~/images/favicon/apple-icon-152x152.png"/> <meta name="msapplication-config" content="~/images/favicon/browserconfig.xml">
<link rel="apple-touch-icon" sizes="180x180" href="~/images/favicon/apple-icon-180x180.png"/> <meta name="theme-color" content="#df691a">
<link rel="icon" type="image/png" sizes="192x192" href="~/images/favicon/android-icon-192x192.png"/>
<link rel="icon" type="image/png" sizes="32x32" href="~/images/favicon/favicon-32x32.png"/>
<link rel="icon" type="image/png" sizes="96x96" href="~/images/favicon/favicon-96x96.png"/>
<link rel="icon" type="image/png" sizes="16x16" href="~/images/favicon/favicon-16x16.png"/>
<link rel="manifest" href="~/images/favicon/manifest.json">
<meta name="msapplication-TileColor" content="#ffffff"/>
<meta name="msapplication-TileImage" content="~/images/favicon/ms-icon-144x144.png"/>
<meta name="theme-color" content="#ffffff"/>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

View file

@ -80,7 +80,7 @@ module.exports = (env: any) => {
}, },
plugins: [ plugins: [
new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", Hammer: "hammerjs/hammer" }), // Global identifiers new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", Hammer: "hammerjs/hammer" }), // Global identifiers
new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)esm5/, path.join(__dirname,"./client")), // Workaround for https://github.com/angular/angular/issues/20357 new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)esm5/, path.join(__dirname, "./client")), // Workaround for https://github.com/angular/angular/issues/20357
new webpack.ContextReplacementPlugin(/\@angular\b.*\b(bundles|linker)/, path.join(__dirname, "./ClientApp")), // Workaround for https://github.com/angular/angular/issues/11580 new webpack.ContextReplacementPlugin(/\@angular\b.*\b(bundles|linker)/, path.join(__dirname, "./ClientApp")), // Workaround for https://github.com/angular/angular/issues/11580
new webpack.ContextReplacementPlugin(/angular(\\|\/)core(\\|\/)@angular/, path.join(__dirname, "./ClientApp")), // Workaround for https://github.com/angular/angular/issues/14898 new webpack.ContextReplacementPlugin(/angular(\\|\/)core(\\|\/)@angular/, path.join(__dirname, "./ClientApp")), // Workaround for https://github.com/angular/angular/issues/14898
extractCSS, extractCSS,

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -1,2 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig> <browserconfig>
<msapplication>
<tile>
<square150x150logo src="/images/favicon/mstile-150x150.png"/>
<TileColor>#df691a</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 680 B

After

Width:  |  Height:  |  Size: 505 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 738 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

View file

@ -1,41 +0,0 @@
{
"name": "App",
"icons": [
{
"src": "\/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "\/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "\/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "\/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "\/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "\/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1 @@
<svg version="1" xmlns="http://www.w3.org/2000/svg" width="1365.333" height="1365.333" viewBox="0 0 1024.000000 1024.000000"><path d="M487.2 71c-140.4 9.8-263.6 80.9-341 196.7-39.5 59-63.1 124.3-71.8 198.8-2.5 21.1-2.5 71.9 0 93 6.5 56 19.9 101.7 43.7 150 21.9 44.5 48.5 81.2 85.3 118 47.8 47.7 105.8 83.8 168.9 105 160.4 53.9 335.7 13.4 454.3-105 36.8-36.8 63.4-73.5 85.3-118 23.8-48.3 37.2-94 43.7-150 2.5-21.1 2.5-71.9 0-93-5.2-44.4-15.1-83.6-30.9-122.2-32.3-78.8-88.7-148.4-159.2-196.6-61.7-42.1-130.7-67.4-205.5-75.2-17.9-1.9-56.3-2.7-72.8-1.5zM391 283c11.3 3.2 21.2 8.6 31.4 17.1 5 4.2 28.7 27.4 52.6 51.5 46.2 46.6 48 48.8 54.4 64.8 4.3 10.7 5.9 19.7 5.8 33.1 0 13.9-1.9 22.6-7.8 35.4l-3.7 8.2 3.8 3.9 3.8 3.9 4.6-2.3c19-9.6 43.2-12.1 63.1-6.6 9.7 2.7 22.1 8.7 29.6 14.4 3.2 2.4 27.1 25.7 52.9 51.8 41.7 42 47.5 48.2 51.4 55.1 8.8 15.7 12.8 32.1 11.7 48.7-.7 10.7-3.4 23-6.7 30.5-6.2 14.2-20 30.1-34.1 39.2-25.2 16.3-57.3 17.7-85.8 3.7l-11.5-5.7-50.1-50.1c-54.7-54.7-56.7-57.1-62.5-73.9-4-11.5-5.2-21.2-4.6-34.7.7-13.3 2.3-20.2 7.7-31.7l3.8-8.1-3.7-3.6-3.7-3.7-7.9 3.6c-27.5 12.6-61.6 10-86.2-6.5-6.6-4.5-98.4-96-103.9-103.7-10.6-14.6-15.7-31.5-15.7-51.3 0-14.6 1.6-22.4 7.3-34.6 12.5-27.1 34.7-44.6 63.5-50.3 8.4-1.7 31.9-.6 40.5 1.9zm179 66c1.9 1.9 2 3.3 2 35.2 0 32.7 0 33.2-2.2 35.5-3.1 3.3-7.7 3.1-11.1-.5l-2.7-2.8V384c0-31.9 0-32.4 2.2-34.7 2.8-3 8.9-3.2 11.8-.3zm74.1 30.9c5.6 5.6 5.3 6.1-20.3 31.8-25.6 25.6-26.9 26.5-31.9 21.7-2.1-2-2.9-3.7-2.9-6.3 0-3.4 1.5-5.1 22.3-25.7 12.2-12.2 23.1-22.7 24.2-23.3 3.3-1.7 5.5-1.3 8.6 1.8zm29.9 73.6c2.1 1.1 3.1 2.5 3.6 5.2.6 3.3.3 4-2.5 6.5L672 468h-64.4l-2.8-2.7c-3.6-3.4-3.7-7.5-.3-10.8l2.4-2.5H639c26.6 0 32.6.3 35 1.5z"/><path d="M358.4 324.5c-28.1 6.1-42.3 34.4-30.7 61 2.4 5.4 7.3 10.6 48.1 51.7 48.4 48.6 50 50 62.7 53.3 7.1 1.8 14.4 1.9 18 .3 2.5-1.1 2.3-1.4-20.8-24.7-21.3-21.5-23.5-24-24.6-28.3-3.3-13.7 6.7-26.8 20.6-26.8 2.9 0 7 .7 9.1 1.6 2.4.9 11.8 9.5 26.3 24l22.6 22.5 1.3-3.1c2.8-6.6-.7-22.3-6.9-31.6-2.3-3.4-22.5-24.3-47.8-49.4-47-46.8-48.3-47.8-61-50.5-8.1-1.7-9-1.7-16.9 0zM567.8 533.7c-1 .2-1.8.7-1.8 1.1 0 .3 13.2 13.9 29.4 30.2 28.2 28.2 29.5 29.7 31 35.1 2 6.8 1.1 12.1-3 18-2.9 4.2-8.8 8.2-13.8 9.3-1.5.4-5.4.2-8.5-.5l-5.6-1.1-30-29.9c-16.6-16.4-30.3-29.9-30.7-29.9-.3 0-1.1 1.9-1.7 4.3-1.9 7.6 1.2 20.8 6.9 29.7 4.2 6.4 90.5 92.1 96 95.2 16.7 9.5 36.8 7 51.4-6.3 13.7-12.5 17.2-33 8.5-50.8-2.8-6-7.6-11.1-48.8-52.2-48.8-48.7-49.3-49.1-61.9-51.8-5.2-1.1-14-1.3-17.4-.4z"/></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,19 @@
{
"name": "Ombi",
"short_name": "Ombi",
"icons": [
{
"src": "/images/favicon/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/images/favicon/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#df691a",
"background_color": "#df691a",
"display": "standalone"
}