60
CHANGELOG.md
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
25
src/Ombi.Core/Models/Requests/TvRequestViewModel.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
@ -144,30 +141,27 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
// Now add all of this to the Recently Added log
|
// Now add all of this to the Recently Added log
|
||||||
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,
|
AddedAt = DateTime.Now,
|
||||||
Type = RecentlyAddedType.Plex,
|
Type = RecentlyAddedType.Plex,
|
||||||
|
ContentType = ContentType.Parent,
|
||||||
ContentId = p.Id
|
ContentId = p.Id
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
foreach (var p in plexEpisodesToSend)
|
||||||
// Add the episodes
|
|
||||||
foreach (var ep in p.Episodes)
|
|
||||||
{
|
{
|
||||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||||
{
|
{
|
||||||
AddedAt = DateTime.Now,
|
AddedAt = DateTime.Now,
|
||||||
Type = RecentlyAddedType.Plex,
|
Type = RecentlyAddedType.Plex,
|
||||||
ContentId = ep.Id
|
ContentType = ContentType.Episode,
|
||||||
|
ContentId = p.Id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var e in embyContentMoviesToSend)
|
foreach (var e in embyContentMoviesToSend)
|
||||||
{
|
{
|
||||||
|
@ -177,23 +171,22 @@ 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
|
}
|
||||||
{
|
|
||||||
// Add the episodes
|
foreach (var p in embyEpisodesToSend)
|
||||||
foreach (var ep in e.Episodes)
|
|
||||||
{
|
{
|
||||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||||
{
|
{
|
||||||
AddedAt = DateTime.Now,
|
AddedAt = DateTime.Now,
|
||||||
Type = RecentlyAddedType.Plex,
|
Type = RecentlyAddedType.Emby,
|
||||||
ContentId = ep.Id
|
ContentType = ContentType.Episode,
|
||||||
|
ContentId = p.Id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
await _recentlyAddedLog.AddRange(recentlyAddedLog);
|
await _recentlyAddedLog.AddRange(recentlyAddedLog);
|
||||||
|
|
||||||
await Task.WhenAll(emailTasks.ToArray());
|
await Task.WhenAll(emailTasks.ToArray());
|
||||||
|
@ -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)
|
||||||
|
@ -413,14 +429,14 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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))]
|
||||||
|
|
12
src/Ombi/ClientApp/app/animations/fadeinout.ts
Normal 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 })),
|
||||||
|
]),
|
||||||
|
]);
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
<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">
|
||||||
|
<img class="img-responsive poster" src="{{posterPath}}" alt="poster">
|
||||||
<span class="label label-info">{{IssueStatus[issue.status]}}</span>
|
<span class="label label-info">{{IssueStatus[issue.status]}}</span>
|
||||||
<span class="label label-success">{{issue.issueCategory.value}}</span>
|
<span class="label label-success">{{issue.issueCategory.value}}</span>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
|
@ -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%;
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
],
|
],
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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 + ")");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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="" /> -->
|
||||||
|
|
|
@ -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 + ")");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
|
@ -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) {
|
||||||
|
|
|
@ -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 + ")");
|
||||||
});
|
});
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -20,10 +20,21 @@ 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 });
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
||||||
<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>
|
||||||
|
|
||||||
|
|
||||||
<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>
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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>();
|
||||||
|
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
@import './styles.scss';
|
@import './styles.scss';
|
||||||
|
@import './scrollbar.scss';
|
23
src/Ombi/ClientApp/styles/scrollbar.scss
Normal 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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,4 +98,12 @@
|
||||||
<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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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">
|
||||||
|
|
||||||
|
|
BIN
src/Ombi/wwwroot/images/favicon/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
src/Ombi/wwwroot/images/favicon/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 14 KiB |
BIN
src/Ombi/wwwroot/images/favicon/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
|
@ -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>
|
||||||
|
|
Before Width: | Height: | Size: 680 B After Width: | Height: | Size: 505 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 738 B |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 15 KiB |
|
@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
Before Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 3.7 KiB |
BIN
src/Ombi/wwwroot/images/favicon/mstile-150x150.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
1
src/Ombi/wwwroot/images/favicon/safari-pinned-tab.svg
Normal 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 |
19
src/Ombi/wwwroot/images/favicon/site.webmanifest
Normal 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"
|
||||||
|
}
|