60
CHANGELOG.md
|
@ -4,14 +4,68 @@
|
|||
|
||||
### **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]
|
||||
|
||||
### **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]
|
||||
|
||||
- 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)
|
||||
|
||||
|
@ -19,6 +73,10 @@
|
|||
|
||||
- Updated the .Net core dependancies #2072. [Jamie]
|
||||
|
||||
### **Fixes**
|
||||
|
||||
- Delete Ombi.testdb. [Jamie]
|
||||
|
||||
|
||||
## v3.0.3020 (2018-03-13)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Core.Models.Requests;
|
||||
using Ombi.Core.Models.Search;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
|
||||
|
@ -9,7 +10,7 @@ namespace Ombi.Core.Engine.Interfaces
|
|||
{
|
||||
|
||||
Task RemoveTvRequest(int requestId);
|
||||
Task<RequestEngineResult> RequestTvShow(SearchTvShowViewModel tv);
|
||||
Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv);
|
||||
Task<RequestEngineResult> DenyChildRequest(int requestId);
|
||||
Task<IEnumerable<TvRequests>> SearchTvRequest(string search);
|
||||
Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvRequestTree(string search);
|
||||
|
|
|
@ -54,7 +54,7 @@ namespace Ombi.Core.Engine
|
|||
{
|
||||
Result = false,
|
||||
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 =
|
||||
|
|
|
@ -43,13 +43,13 @@ namespace Ombi.Core.Engine
|
|||
private IAuditRepository Audit { get; }
|
||||
private readonly IRepository<RequestLog> _requestLog;
|
||||
|
||||
public async Task<RequestEngineResult> RequestTvShow(SearchTvShowViewModel tv)
|
||||
public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv)
|
||||
{
|
||||
var user = await GetUser();
|
||||
|
||||
var tvBuilder = new TvShowRequestBuilder(TvApi);
|
||||
(await tvBuilder
|
||||
.GetShowInfo(tv.Id))
|
||||
.GetShowInfo(tv.TvDbId))
|
||||
.CreateTvList(tv)
|
||||
.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)
|
||||
{
|
||||
// Remove requests we already have, we just want new ones
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using Ombi.Api.TvMaze;
|
||||
using Ombi.Api.TvMaze.Models;
|
||||
using Ombi.Core.Models.Requests;
|
||||
using Ombi.Core.Models.Search;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Entities;
|
||||
|
@ -23,7 +24,7 @@ namespace Ombi.Core.Helpers
|
|||
private ITvMazeApi TvApi { get; }
|
||||
|
||||
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 DateTime FirstAir { get; protected set; }
|
||||
public TvRequests NewRequest { get; protected set; }
|
||||
|
@ -33,7 +34,7 @@ namespace Ombi.Core.Helpers
|
|||
{
|
||||
ShowInfo = await TvApi.ShowLookupByTheTvDbId(id);
|
||||
|
||||
DateTime.TryParse(ShowInfo.premiered, out DateTime dt);
|
||||
DateTime.TryParse(ShowInfo.premiered, out var dt);
|
||||
|
||||
FirstAir = dt;
|
||||
|
||||
|
@ -43,37 +44,29 @@ namespace Ombi.Core.Helpers
|
|||
return this;
|
||||
}
|
||||
|
||||
public TvShowRequestBuilder CreateChild(SearchTvShowViewModel model, string userId)
|
||||
public TvShowRequestBuilder CreateChild(TvRequestViewModel model, string userId)
|
||||
{
|
||||
ChildRequest = new ChildRequests
|
||||
{
|
||||
Id = model.Id,
|
||||
Id = model.TvDbId,
|
||||
RequestType = RequestType.TvShow,
|
||||
RequestedDate = DateTime.UtcNow,
|
||||
Approved = false,
|
||||
RequestedUserId = userId,
|
||||
SeasonRequests = new List<SeasonRequests>(),
|
||||
Title = model.Title,
|
||||
Title = ShowInfo.name,
|
||||
SeriesType = ShowInfo.type.Equals("Animation", StringComparison.CurrentCultureIgnoreCase) ? SeriesType.Anime : SeriesType.Standard
|
||||
};
|
||||
|
||||
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
|
||||
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())
|
||||
{
|
||||
TvRequests.Add(season);
|
||||
|
@ -81,11 +74,10 @@ namespace Ombi.Core.Helpers
|
|||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public async Task<TvShowRequestBuilder> BuildEpisodes(SearchTvShowViewModel tv)
|
||||
public async Task<TvShowRequestBuilder> BuildEpisodes(TvRequestViewModel tv)
|
||||
{
|
||||
if (tv.RequestAll)
|
||||
{
|
||||
|
@ -173,26 +165,68 @@ namespace Ombi.Core.Helpers
|
|||
else
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
public TvShowRequestBuilder CreateNewRequest(SearchTvShowViewModel tv)
|
||||
public TvShowRequestBuilder CreateNewRequest(TvRequestViewModel tv)
|
||||
{
|
||||
NewRequest = new TvRequests
|
||||
{
|
||||
Id = tv.Id,
|
||||
Overview = ShowInfo.summary.RemoveHtml(),
|
||||
PosterPath = PosterPath,
|
||||
Title = ShowInfo.name,
|
||||
ReleaseDate = FirstAir,
|
||||
Status = ShowInfo.status,
|
||||
ImdbId = ShowInfo.externals?.imdb ?? string.Empty,
|
||||
TvDbId = tv.Id,
|
||||
TvDbId = tv.TvDbId,
|
||||
ChildRequests = new List<ChildRequests>(),
|
||||
TotalSeasons = tv.SeasonRequests.Count()
|
||||
TotalSeasons = tv.Seasons.Count()
|
||||
};
|
||||
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">
|
||||
<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>
|
||||
</tr>
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Notifications.Models;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
using Ombi.Store.Repository.Requests;
|
||||
|
||||
namespace Ombi.Notifications
|
||||
{
|
||||
|
@ -25,9 +28,9 @@ namespace Ombi.Notifications
|
|||
}
|
||||
ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty;
|
||||
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
|
||||
RequestedUser = string.IsNullOrEmpty(req?.RequestedUser?.Alias)
|
||||
? req?.RequestedUser?.UserName
|
||||
: req?.RequestedUser?.Alias;
|
||||
RequestedUser = req?.RequestedUser?.UserName;
|
||||
UserName = req?.RequestedUser?.UserName;
|
||||
Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName;
|
||||
Title = title;
|
||||
RequestedDate = req?.RequestedDate.ToString("D");
|
||||
Type = req?.RequestType.ToString();
|
||||
|
@ -43,6 +46,8 @@ namespace Ombi.Notifications
|
|||
ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty;
|
||||
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
|
||||
RequestedUser = username.UserName;
|
||||
UserName = username.UserName;
|
||||
Alias = username.Alias.HasValue() ? username.Alias : username.UserName;
|
||||
}
|
||||
|
||||
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;
|
||||
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
|
||||
RequestedUser = string.IsNullOrEmpty(req?.RequestedUser.Alias)
|
||||
? req?.RequestedUser.UserName
|
||||
: req?.RequestedUser.Alias;
|
||||
RequestedUser = req?.RequestedUser?.UserName;
|
||||
UserName = req?.RequestedUser?.UserName;
|
||||
Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName;
|
||||
Title = title;
|
||||
RequestedDate = req?.RequestedDate.ToString("D");
|
||||
Type = req?.RequestType.ToString();
|
||||
|
@ -71,6 +76,40 @@ namespace Ombi.Notifications
|
|||
$"https://image.tmdb.org/t/p/w300{req?.ParentRequest.PosterPath}" : req?.ParentRequest.PosterPath;
|
||||
AdditionalInformation = opts.AdditionalInformation;
|
||||
// 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)
|
||||
|
@ -88,13 +127,14 @@ namespace Ombi.Notifications
|
|||
IssueStatus = opts.Substitutes.TryGetValue("IssueStatus", 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;
|
||||
RequestedUser = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty;
|
||||
UserName = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty;
|
||||
}
|
||||
|
||||
// User Defined
|
||||
public string RequestedUser { get; set; }
|
||||
public string UserName => RequestedUser;
|
||||
public string IssueUser => RequestedUser;
|
||||
public string UserName { get; set; }
|
||||
public string IssueUser => UserName;
|
||||
public string Alias { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
public string RequestedDate { get; set; }
|
||||
|
@ -144,6 +184,7 @@ namespace Ombi.Notifications
|
|||
{nameof(NewIssueComment),NewIssueComment},
|
||||
{nameof(IssueUser),IssueUser},
|
||||
{nameof(UserName),UserName},
|
||||
{nameof(Alias),Alias},
|
||||
};
|
||||
}
|
||||
}
|
|
@ -76,8 +76,8 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
var customization = await _customizationSettings.GetSettingsAsync();
|
||||
|
||||
// Get the Content
|
||||
var plexContent = _plex.GetAll().Include(x => x.Episodes);
|
||||
var embyContent = _emby.GetAll().Include(x => x.Episodes);
|
||||
var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking();
|
||||
var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking();
|
||||
|
||||
var addedLog = _recentlyAddedLog.GetAll();
|
||||
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 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 embyContentTvToSend = embyContent.Where(x => x.Type == EmbyMediaType.Series && x.Episodes.Any(e => !addedEmbyEpisodesLogIds.Contains(e.Id)));
|
||||
|
||||
var plexContentToSend = plexContentMoviesToSend.Union(plexContentTvToSend);
|
||||
var embyContentToSend = embyContentMoviesToSend.Union(embyContentTvToSend);
|
||||
var plexEpisodesToSend = _plex.GetAllEpisodes().Include(x => x.Series).Where(x => !addedPlexEpisodesLogIds.Contains(x.Id)).AsNoTracking();
|
||||
var embyEpisodesToSend = _emby.GetAllEpisodes().Include(x => x.Series).Where(x => !addedEmbyEpisodesLogIds.Contains(x.Id)).AsNoTracking();
|
||||
|
||||
var body = string.Empty;
|
||||
if (test)
|
||||
{
|
||||
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 plext = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Show).OrderByDescending(x => x.AddedAt).Take(10);
|
||||
var embyt = embyContent.Where(x => x.Type == EmbyMediaType.Series).OrderByDescending(x => x.AddedAt).Take(10);
|
||||
body = await BuildHtml(plexm.Union(plext), embym.Union(embyt));
|
||||
var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10);
|
||||
var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10);
|
||||
body = await BuildHtml(plexm, embym, plext, embyt);
|
||||
}
|
||||
else
|
||||
{
|
||||
body = await BuildHtml(plexContentToSend, embyContentToSend);
|
||||
body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend);
|
||||
if (body.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
|
@ -137,37 +134,34 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo);
|
||||
|
||||
emailTasks.Add(_email.Send(
|
||||
new NotificationMessage {Message = html, Subject = messageContent.Subject, To = user.Email},
|
||||
new NotificationMessage { Message = html, Subject = messageContent.Subject, To = user.Email },
|
||||
emailSettings));
|
||||
}
|
||||
|
||||
// Now add all of this to the Recently Added log
|
||||
var recentlyAddedLog = new HashSet<RecentlyAddedLog>();
|
||||
foreach (var p in plexContentMoviesToSend)
|
||||
{
|
||||
if (p.Type == PlexMediaTypeEntity.Movie)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Plex,
|
||||
ContentType = ContentType.Parent,
|
||||
ContentId = p.Id
|
||||
});
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the episodes
|
||||
foreach (var ep in p.Episodes)
|
||||
|
||||
foreach (var p in plexEpisodesToSend)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Plex,
|
||||
ContentId = ep.Id
|
||||
ContentType = ContentType.Episode,
|
||||
ContentId = p.Id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var e in embyContentMoviesToSend)
|
||||
{
|
||||
|
@ -177,23 +171,22 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Emby,
|
||||
ContentType = ContentType.Parent,
|
||||
ContentId = e.Id
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the episodes
|
||||
foreach (var ep in e.Episodes)
|
||||
}
|
||||
|
||||
foreach (var p in embyEpisodesToSend)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Plex,
|
||||
ContentId = ep.Id
|
||||
Type = RecentlyAddedType.Emby,
|
||||
ContentType = ContentType.Episode,
|
||||
ContentId = p.Id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
await _recentlyAddedLog.AddRange(recentlyAddedLog);
|
||||
|
||||
await Task.WhenAll(emailTasks.ToArray());
|
||||
|
@ -236,7 +229,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
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();
|
||||
|
||||
|
@ -249,13 +242,11 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
await ProcessEmbyMovies(embyMovies, sb);
|
||||
}
|
||||
|
||||
var plexTv = plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Show);
|
||||
var embyTv = embyContentToSend.Where(x => x.Type == EmbyMediaType.Series);
|
||||
if (plexTv.Any() || embyTv.Any())
|
||||
if (plexEpisodes.Any() || embyEp.Any())
|
||||
{
|
||||
sb.Append("<h1>New Episodes:</h1><br /><br />");
|
||||
await ProcessPlexTv(plexTv, sb);
|
||||
await ProcessEmbyMovies(embyTv, sb);
|
||||
await ProcessPlexTv(plexEpisodes, sb);
|
||||
await ProcessEmbyTv(embyEp, sb);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
|
@ -288,22 +279,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
}
|
||||
try
|
||||
{
|
||||
AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/original{info.BackdropPath}");
|
||||
|
||||
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);
|
||||
CreateMovieHtmlContent(sb, info);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -316,6 +292,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessEmbyMovies(IQueryable<EmbyContent> embyContent, StringBuilder sb)
|
||||
{
|
||||
sb.Append(
|
||||
|
@ -323,30 +300,21 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
var ordered = embyContent.OrderByDescending(x => x.AddedAt);
|
||||
foreach (var content in ordered)
|
||||
{
|
||||
int.TryParse(content.ProviderId, out var movieDbId);
|
||||
var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId);
|
||||
var imdbId = content.ProviderId;
|
||||
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)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
try
|
||||
{
|
||||
AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/original{info.BackdropPath}");
|
||||
|
||||
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);
|
||||
CreateMovieHtmlContent(sb, info);
|
||||
}
|
||||
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(
|
||||
"<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)
|
||||
|
@ -413,14 +429,14 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
sb.Append(
|
||||
"<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}/");
|
||||
Header(sb, 3, title);
|
||||
EndTag(sb, "a");
|
||||
|
||||
// Group by the season number
|
||||
var results = t.Episodes?.GroupBy(p => p.SeasonNumber,
|
||||
var results = t.Episodes.GroupBy(p => p.SeasonNumber,
|
||||
(key, g) => new
|
||||
{
|
||||
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(
|
||||
"<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)
|
||||
|
|
|
@ -119,7 +119,10 @@ namespace Ombi.Store.Context
|
|||
var roles = Roles.Where(x => x.Name == OmbiRoles.RecievesNewsletter);
|
||||
if (!roles.Any())
|
||||
{
|
||||
Roles.Add(new IdentityRole(OmbiRoles.RecievesNewsletter));
|
||||
Roles.Add(new IdentityRole(OmbiRoles.RecievesNewsletter)
|
||||
{
|
||||
NormalizedName = OmbiRoles.RecievesNewsletter.ToUpper()
|
||||
});
|
||||
}
|
||||
//Check if templates exist
|
||||
var templates = NotificationTemplates.ToList();
|
||||
|
@ -143,7 +146,7 @@ namespace Ombi.Store.Context
|
|||
notificationToAdd = new NotificationTemplates
|
||||
{
|
||||
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}!",
|
||||
Agent = agent,
|
||||
Enabled = true,
|
||||
|
@ -153,7 +156,7 @@ namespace Ombi.Store.Context
|
|||
notificationToAdd = new NotificationTemplates
|
||||
{
|
||||
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}!",
|
||||
Agent = agent,
|
||||
Enabled = true,
|
||||
|
@ -163,7 +166,7 @@ namespace Ombi.Store.Context
|
|||
notificationToAdd = new NotificationTemplates
|
||||
{
|
||||
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!",
|
||||
Agent = agent,
|
||||
Enabled = true,
|
||||
|
@ -207,7 +210,7 @@ namespace Ombi.Store.Context
|
|||
notificationToAdd = new NotificationTemplates
|
||||
{
|
||||
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}!",
|
||||
Agent = agent,
|
||||
Enabled = true,
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Ombi.Store.Repository.Requests
|
|||
public class SeasonRequests : Entity
|
||||
{
|
||||
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; }
|
||||
[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;
|
||||
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,13 +1,16 @@
|
|||
<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>
|
||||
<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-success">{{issue.issueCategory.value}}</span>
|
||||
|
||||
<h3 *ngIf="issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}: {{issue.userReported.alias}}</h3>
|
||||
<h3 *ngIf="!issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}: {{issue.userReported.userName}}</h3>
|
||||
<h3 *ngIf="issue.subject">{{'Issues.Subject' | translate}}: {{issue.subject}}</h3>
|
||||
<br>
|
||||
<br>
|
||||
<div class="form-group">
|
||||
<label for="description" class="control-label" [translate]="'Issues.Description'"></label>
|
||||
<div>
|
||||
|
@ -26,7 +29,8 @@
|
|||
<div class="panel-heading top-bar">
|
||||
<div class="col-md-8 col-xs-8">
|
||||
<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>
|
||||
|
||||
|
@ -51,8 +55,7 @@
|
|||
</div>
|
||||
<div class="panel-footer">
|
||||
<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">
|
||||
<button class="btn btn-primary btn-sm" id="btn-chat" (click)="addComment()" [translate]="'Issues.SendMessageButton'"></button>
|
||||
</span>
|
||||
|
|
|
@ -64,6 +64,15 @@ body{
|
|||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
.myBg {
|
||||
z-index: -1;
|
||||
}
|
||||
.tint {
|
||||
z-index: -1;
|
||||
}
|
||||
img-responsive poster {
|
||||
display:block;
|
||||
}
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
|
|
@ -2,8 +2,9 @@ import { Component, OnInit } from "@angular/core";
|
|||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
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";
|
||||
|
||||
@Component({
|
||||
|
@ -22,6 +23,8 @@ export class IssueDetailsComponent implements OnInit {
|
|||
public IssueStatus = IssueStatus;
|
||||
public isAdmin: boolean;
|
||||
public settings: IIssueSettings;
|
||||
public backgroundPath: any;
|
||||
public posterPath: any;
|
||||
|
||||
private issueId: number;
|
||||
|
||||
|
@ -29,7 +32,9 @@ export class IssueDetailsComponent implements OnInit {
|
|||
private route: ActivatedRoute,
|
||||
private authService: AuthService,
|
||||
private settingsService: SettingsService,
|
||||
private notificationService: NotificationService) {
|
||||
private notificationService: NotificationService,
|
||||
private imageService: ImageService,
|
||||
private sanitizer: DomSanitizer) {
|
||||
this.route.params
|
||||
.subscribe((params: any) => {
|
||||
this.issueId = parseInt(params.id);
|
||||
|
@ -56,8 +61,8 @@ export class IssueDetailsComponent implements OnInit {
|
|||
providerId: x.providerId,
|
||||
userReported: x.userReported,
|
||||
};
|
||||
this.setBackground(x);
|
||||
});
|
||||
|
||||
this.loadComments();
|
||||
}
|
||||
|
||||
|
@ -85,4 +90,26 @@ export class IssueDetailsComponent implements OnInit {
|
|||
private loadComments() {
|
||||
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 { PaginatorModule, SharedModule, TabViewModule } from "primeng/primeng";
|
||||
|
||||
import { IdentityService } from "../services";
|
||||
import { IdentityService, SearchService } from "../services";
|
||||
|
||||
import { AuthGuard } from "../auth/auth.guard";
|
||||
|
||||
|
@ -43,6 +43,7 @@ const routes: Routes = [
|
|||
],
|
||||
providers: [
|
||||
IdentityService,
|
||||
SearchService,
|
||||
],
|
||||
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<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="row">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { PlatformLocation } from "@angular/common";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
|
||||
import { IMediaServerStatus } from "../interfaces";
|
||||
import { ICustomizationSettings, ILandingPageSettings } from "../interfaces";
|
||||
|
@ -9,17 +9,21 @@ import { SettingsService } from "../services";
|
|||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { ImageService } from "../services";
|
||||
|
||||
import { fadeInOutAnimation } from "../animations/fadeinout";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./landingpage.component.html",
|
||||
animations: [fadeInOutAnimation],
|
||||
styleUrls: ["./landingpage.component.scss"],
|
||||
})
|
||||
export class LandingPageComponent implements OnInit {
|
||||
export class LandingPageComponent implements OnDestroy, OnInit {
|
||||
|
||||
public customizationSettings: ICustomizationSettings;
|
||||
public landingPageSettings: ILandingPageSettings;
|
||||
public background: any;
|
||||
public mediaServerStatus: IMediaServerStatus;
|
||||
public baseUrl: string;
|
||||
private timer: any;
|
||||
|
||||
constructor(private settingsService: SettingsService,
|
||||
private images: ImageService, private sanitizer: DomSanitizer, private landingPageService: LandingPageService,
|
||||
|
@ -31,6 +35,9 @@ export class LandingPageComponent implements OnInit {
|
|||
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.timer = setInterval(() => {
|
||||
this.cycleBackground();
|
||||
}, 10000);
|
||||
|
||||
const base = this.location.getBaseHrefFromDOM();
|
||||
if (base.length > 1) {
|
||||
|
@ -41,4 +48,18 @@ export class LandingPageComponent implements OnInit {
|
|||
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="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="card card-container">
|
||||
<!-- <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 { ActivatedRoute, Router } from "@angular/router";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
|
@ -13,11 +13,14 @@ import { StatusService } from "../services";
|
|||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { ImageService } from "../services";
|
||||
|
||||
import { fadeInOutAnimation } from "../animations/fadeinout";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./login.component.html",
|
||||
animations: [fadeInOutAnimation],
|
||||
styleUrls: ["./login.component.scss"],
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
export class LoginComponent implements OnDestroy, OnInit {
|
||||
|
||||
public form: FormGroup;
|
||||
public customizationSettings: ICustomizationSettings;
|
||||
|
@ -25,6 +28,7 @@ export class LoginComponent implements OnInit {
|
|||
public background: any;
|
||||
public landingFlag: boolean;
|
||||
public baseUrl: string;
|
||||
private timer: any;
|
||||
|
||||
private errorBody: string;
|
||||
private errorValidation: string;
|
||||
|
@ -67,6 +71,10 @@ export class LoginComponent implements OnInit {
|
|||
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.timer = setInterval(() => {
|
||||
this.cycleBackground();
|
||||
}, 10000);
|
||||
|
||||
const base = this.location.getBaseHrefFromDOM();
|
||||
if (base.length > 1) {
|
||||
this.baseUrl = base;
|
||||
|
@ -102,4 +110,19 @@ export class LoginComponent implements OnInit {
|
|||
}, 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"
|
||||
[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">
|
||||
|
|
|
@ -105,4 +105,4 @@
|
|||
|
||||
|
||||
<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.issueCategorySelected = catId;
|
||||
this.issuesBarVisible = true;
|
||||
this.issueProviderId = req.id.toString();
|
||||
}
|
||||
|
||||
private removeRequestFromUi(key: IChildRequests) {
|
||||
|
|
|
@ -90,12 +90,6 @@ export class TvRequestsComponent implements OnInit {
|
|||
}
|
||||
|
||||
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.currentlyLoaded = 1000;
|
||||
this.tvRequests = [];
|
||||
|
@ -204,7 +198,7 @@ export class TvRequestsComponent implements OnInit {
|
|||
this.loadInit();
|
||||
}
|
||||
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
|
||||
("url(" + x + ")");
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@ import { NotificationService } from "../services";
|
|||
import { RequestService } from "../services";
|
||||
import { SearchService } from "../services";
|
||||
|
||||
import { INewSeasonRequests, IRequestEngineResult } from "../interfaces";
|
||||
import { INewSeasonRequests, IRequestEngineResult, ISeasonsViewModel, ITvRequestViewModel } from "../interfaces";
|
||||
import { IEpisodesRequests } from "../interfaces";
|
||||
import { ISearchTvResult } from "../interfaces";
|
||||
|
||||
|
@ -46,7 +46,22 @@ export class SeriesInformationComponent implements OnInit {
|
|||
|
||||
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 => {
|
||||
this.result = x as IRequestEngineResult;
|
||||
if (this.result.result) {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { ImageService, NotificationService, RequestService, SearchService} from
|
|||
|
||||
import { TreeNode } from "primeng/primeng";
|
||||
import { IRequestEngineResult } from "../interfaces";
|
||||
import { IIssueCategory, ISearchTvResult } from "../interfaces";
|
||||
import { IIssueCategory, ISearchTvResult, ISeasonsViewModel, ITvRequestViewModel } from "../interfaces";
|
||||
|
||||
@Component({
|
||||
selector: "tv-search",
|
||||
|
@ -129,7 +129,6 @@ export class TvSearchComponent implements OnInit {
|
|||
|
||||
public getExtraInfo() {
|
||||
this.tvResults.forEach((val, index) => {
|
||||
|
||||
this.imageService.getTvBanner(val.data.id).subscribe(x => {
|
||||
|
||||
val.data.background = this.sanitizer.
|
||||
|
@ -155,7 +154,23 @@ export class TvSearchComponent implements OnInit {
|
|||
if (this.authService.hasRole("admin") || this.authService.hasRole("AutoApproveMovie")) {
|
||||
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 => {
|
||||
this.result = x;
|
||||
if (this.result.result) {
|
||||
|
|
|
@ -20,10 +20,21 @@ export class ImageService extends ServiceHelpers {
|
|||
public getTvBanner(tvdbid: number): Observable<string> {
|
||||
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> {
|
||||
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> {
|
||||
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 { IRequestEngineResult } 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";
|
||||
|
||||
@Injectable()
|
||||
|
@ -20,7 +20,7 @@ export class RequestService extends ServiceHelpers {
|
|||
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});
|
||||
}
|
||||
|
||||
|
|
|
@ -33,12 +33,12 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<!-- <div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="enable" [(ngModel)]="settings.recentlyAddedPage" [checked]="settings.recentlyAddedPage">
|
||||
<label for="enable">Enable Recently Added Page</label>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="form-group">
|
||||
<label for="logo" class="control-label">Custom Logo</label>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<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">
|
||||
<fieldset>
|
||||
<legend>Newsletter</legend>
|
||||
|
@ -30,14 +30,18 @@
|
|||
<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,
|
||||
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>
|
||||
<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 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>
|
||||
</fieldset>
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { INewsletterNotificationSettings, NotificationType } from "../../interfaces";
|
||||
import { NotificationService } from "../../services";
|
||||
import { SettingsService } from "../../services";
|
||||
import { JobService, NotificationService, SettingsService } from "../../services";
|
||||
import { TesterService } from "./../../services/applications/tester.service";
|
||||
|
||||
@Component({
|
||||
|
@ -16,7 +14,8 @@ export class NewsletterComponent implements OnInit {
|
|||
|
||||
constructor(private settingsService: SettingsService,
|
||||
private notificationService: NotificationService,
|
||||
private testService: TesterService) { }
|
||||
private testService: TesterService,
|
||||
private jobService: JobService) { }
|
||||
|
||||
public ngOnInit() {
|
||||
this.settingsService.getNewsletterSettings().subscribe(x => {
|
||||
|
@ -26,10 +25,17 @@ export class NewsletterComponent implements OnInit {
|
|||
|
||||
public updateDatabase() {
|
||||
this.settingsService.updateNewsletterDatabase().subscribe();
|
||||
this.notificationService.success("Database updated");
|
||||
}
|
||||
|
||||
public test() {
|
||||
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() {
|
||||
|
|
|
@ -15,6 +15,8 @@ export class IssuesReportComponent {
|
|||
@Input() public issueCategory: IIssueCategory;
|
||||
@Input() public movie: boolean;
|
||||
@Input() public providerId: string;
|
||||
@Input() public background: string;
|
||||
@Input() public posterPath: string;
|
||||
|
||||
@Output() public visibleChange = new EventEmitter<boolean>();
|
||||
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
@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.Store.Repository;
|
||||
using System;
|
||||
|
@ -117,6 +117,56 @@ namespace Ombi.Controllers
|
|||
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")]
|
||||
public async Task<object> GetBackgroundImage()
|
||||
{
|
||||
|
@ -141,7 +191,7 @@ namespace Ombi.Controllers
|
|||
|
||||
movieUrl = result.moviebackground[0].url;
|
||||
}
|
||||
if(tvArray.Any())
|
||||
if (tvArray.Any())
|
||||
{
|
||||
var item = rand.Next(tvArray.Length);
|
||||
var result = await FanartTvApi.GetTvImages(tvArray[item], key.Value);
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace Ombi.Controllers
|
|||
{
|
||||
public JobController(IOmbiAutomaticUpdater updater, IPlexUserImporter userImporter,
|
||||
ICacheService mem, IEmbyUserImporter embyImporter, IPlexContentSync plexContentSync,
|
||||
IEmbyContentSync embyContentSync)
|
||||
IEmbyContentSync embyContentSync, INewsletterJob newsletter)
|
||||
{
|
||||
_updater = updater;
|
||||
_plexUserImporter = userImporter;
|
||||
|
@ -28,6 +28,7 @@ namespace Ombi.Controllers
|
|||
_memCache = mem;
|
||||
_plexContentSync = plexContentSync;
|
||||
_embyContentSync = embyContentSync;
|
||||
_newsletterJob = newsletter;
|
||||
}
|
||||
|
||||
private readonly IOmbiAutomaticUpdater _updater;
|
||||
|
@ -36,6 +37,7 @@ namespace Ombi.Controllers
|
|||
private readonly ICacheService _memCache;
|
||||
private readonly IPlexContentSync _plexContentSync;
|
||||
private readonly IEmbyContentSync _embyContentSync;
|
||||
private readonly INewsletterJob _newsletterJob;
|
||||
|
||||
/// <summary>
|
||||
/// Runs the update job
|
||||
|
@ -129,5 +131,16 @@ namespace Ombi.Controllers
|
|||
BackgroundJob.Enqueue(() => _embyContentSync.Start());
|
||||
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>
|
||||
/// <returns></returns>
|
||||
[HttpPost("tv")]
|
||||
public async Task<RequestEngineResult> RequestTv([FromBody] SearchTvShowViewModel tv)
|
||||
public async Task<RequestEngineResult> RequestTv([FromBody] TvRequestViewModel tv)
|
||||
{
|
||||
return await TvRequestEngine.RequestTvShow(tv);
|
||||
}
|
||||
|
|
|
@ -98,4 +98,12 @@
|
|||
<None Include="wwwroot\translations\*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="ClientApp\app\animations\fadeinout.ts" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<TypeScriptCompile Include="ClientApp\app\animations\fadeinout.ts" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -165,6 +165,12 @@ namespace Ombi
|
|||
var ombiService =
|
||||
app.ApplicationServices.GetService<ISettingsService<OmbiSettings>>();
|
||||
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())
|
||||
{
|
||||
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"/>
|
||||
<title>@appName</title>
|
||||
<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="60x60" href="~/images/favicon/apple-icon-60x60.png"/>
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="~/images/favicon/apple-icon-72x72.png"/>
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="~/images/favicon/apple-icon-76x76.png"/>
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="~/images/favicon/apple-icon-114x114.png"/>
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="~/images/favicon/apple-icon-120x120.png"/>
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="~/images/favicon/apple-icon-144x144.png"/>
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="~/images/favicon/apple-icon-152x152.png"/>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="~/images/favicon/apple-icon-180x180.png"/>
|
||||
<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 rel="apple-touch-icon" sizes="180x180" href="~/images/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="~/images/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="~/images/favicon/favicon-16x16.png">
|
||||
<link rel="manifest" href="~/images/favicon/site.webmanifest">
|
||||
<link rel="mask-icon" href="~/images/favicon/safari-pinned-tab.svg" color="#df691a">
|
||||
<link rel="shortcut icon" href="~/images/favicon/favicon.ico">
|
||||
<meta name="msapplication-TileColor" content="#df691a">
|
||||
<meta name="msapplication-config" content="~/images/favicon/browserconfig.xml">
|
||||
<meta name="theme-color" content="#df691a">
|
||||
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ module.exports = (env: any) => {
|
|||
},
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", Hammer: "hammerjs/hammer" }), // Global identifiers
|
||||
new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)esm5/, path.join(__dirname,"./client")), // Workaround for https://github.com/angular/angular/issues/20357
|
||||
new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)esm5/, path.join(__dirname, "./client")), // Workaround for https://github.com/angular/angular/issues/20357
|
||||
new webpack.ContextReplacementPlugin(/\@angular\b.*\b(bundles|linker)/, path.join(__dirname, "./ClientApp")), // Workaround for https://github.com/angular/angular/issues/11580
|
||||
new webpack.ContextReplacementPlugin(/angular(\\|\/)core(\\|\/)@angular/, path.join(__dirname, "./ClientApp")), // Workaround for https://github.com/angular/angular/issues/14898
|
||||
extractCSS,
|
||||
|
|
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"?>
|
||||
<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"
|
||||
}
|