mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-20 21:33:15 -07:00
commit
6391988d71
43 changed files with 1131 additions and 558 deletions
63
CHANGELOG.md
63
CHANGELOG.md
|
@ -1,5 +1,68 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## (unreleased)
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
|
- Added a check for long movie descriptions and dealt with accordingly. [Anojh]
|
||||||
|
|
||||||
|
- Update jobs.component.html. [D34DC3N73R]
|
||||||
|
|
||||||
|
- Added id to emby button to distinguish for UI purposes. [Anojh]
|
||||||
|
|
||||||
|
- Changed theme content textarea to use monospace font. [Anojh]
|
||||||
|
|
||||||
|
- Added classes and ids to issue status. [Anojh]
|
||||||
|
|
||||||
|
- Changed overlay picture to poster pic so we have fallback styling on older clients. [Anojh]
|
||||||
|
|
||||||
|
### **Fixes**
|
||||||
|
|
||||||
|
- Fixed #2224. [Jamie]
|
||||||
|
|
||||||
|
- More robust check for release date. [Anojh]
|
||||||
|
|
||||||
|
- Fixed duplicate titles in Plex Newsletter. [Anojh]
|
||||||
|
|
||||||
|
- Fixed the filter on the Requests page #2219 and added the default sort to be most recent requests. [Jamie Rees]
|
||||||
|
|
||||||
|
- Enable the mobile ntoifications inside Ombi. [Jamie Rees]
|
||||||
|
|
||||||
|
- Made the episode list in the newsletter easier to read. Rather than 1,2,3,4,5,10 we will now show 1-5, 10. [Jamie Rees]
|
||||||
|
|
||||||
|
- Moved the RecentlyAddedSync into it's own job, it still is calls the regular sync but this should make it easier to start the job from the UI (When I add that) [Jamie Rees]
|
||||||
|
|
||||||
|
- Made a massive improvement on the Smaller more frequent Plex Job. This should pick up content a lot quicker now and also get their metadata a lot quicker. [Jamie Rees]
|
||||||
|
|
||||||
|
- Trigger a metadata refresh when we finish scanning the libraries. [Jamie Rees]
|
||||||
|
|
||||||
|
- Fixed a potential issue in the newsletter where it wouldn't send content due to missing metadata, but would mark it as if it was sent. [Jamie Rees]
|
||||||
|
|
||||||
|
- Fixed settings retaining active class when elsewhere in UI. [Anojh]
|
||||||
|
|
||||||
|
- Separated user and subject details into spans and fixed styling. [Anojh]
|
||||||
|
|
||||||
|
- Fixed linting errors. [Anojh]
|
||||||
|
|
||||||
|
- Fixed settings nav item not retaining active class when in other tabs in the settings page. [Anojh]
|
||||||
|
|
||||||
|
- Separated reported by and subject and added classes. [Anojh]
|
||||||
|
|
||||||
|
- Fix for issue #2152. [Anojh]
|
||||||
|
|
||||||
|
- Fix genres being ambigious error. [Anojh]
|
||||||
|
|
||||||
|
- Made text style justified. [Anojh]
|
||||||
|
|
||||||
|
- V1.0, needs TV background and needs styles for outlook. [Anojh]
|
||||||
|
|
||||||
|
- CSS done for the template. [Anojh]
|
||||||
|
|
||||||
|
- Fixing some format issues. [Anojh]
|
||||||
|
|
||||||
|
- Newsletter template structure done. [Anojh]
|
||||||
|
|
||||||
|
|
||||||
## v3.0.3268 (2018-04-28)
|
## v3.0.3268 (2018-04-28)
|
||||||
|
|
||||||
### **Fixes**
|
### **Fixes**
|
||||||
|
|
|
@ -11,15 +11,15 @@ namespace Ombi.Api.Plex.Models
|
||||||
public string summary { get; set; }
|
public string summary { get; set; }
|
||||||
public int index { get; set; }
|
public int index { get; set; }
|
||||||
public float rating { get; set; }
|
public float rating { get; set; }
|
||||||
public int viewCount { get; set; }
|
//public int viewCount { get; set; }
|
||||||
public int lastViewedAt { get; set; }
|
//public int lastViewedAt { get; set; }
|
||||||
public int year { get; set; }
|
public int year { get; set; }
|
||||||
public string thumb { get; set; }
|
public string thumb { get; set; }
|
||||||
public string art { get; set; }
|
public string art { get; set; }
|
||||||
public string banner { get; set; }
|
public string banner { get; set; }
|
||||||
public string theme { get; set; }
|
public string theme { get; set; }
|
||||||
public string duration { get; set; }
|
//public string duration { get; set; }
|
||||||
public string originallyAvailableAt { get; set; }
|
//public string originallyAvailableAt { get; set; }
|
||||||
public int leafCount { get; set; }
|
public int leafCount { get; set; }
|
||||||
public int viewedLeafCount { get; set; }
|
public int viewedLeafCount { get; set; }
|
||||||
public int childCount { get; set; }
|
public int childCount { get; set; }
|
||||||
|
|
|
@ -17,7 +17,7 @@ namespace Ombi.Core.Engine.Interfaces
|
||||||
Task<RequestEngineResult> ApproveMovie(MovieRequests request);
|
Task<RequestEngineResult> ApproveMovie(MovieRequests request);
|
||||||
Task<RequestEngineResult> ApproveMovieById(int requestId);
|
Task<RequestEngineResult> ApproveMovieById(int requestId);
|
||||||
Task<RequestEngineResult> DenyMovieById(int modelId);
|
Task<RequestEngineResult> DenyMovieById(int modelId);
|
||||||
Task<IEnumerable<MovieRequests>> Filter(FilterViewModel vm);
|
Task<FilterResult<MovieRequests>> Filter(FilterViewModel vm);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -131,11 +131,11 @@ namespace Ombi.Core.Engine
|
||||||
List<MovieRequests> allRequests;
|
List<MovieRequests> allRequests;
|
||||||
if (shouldHide.Hide)
|
if (shouldHide.Hide)
|
||||||
{
|
{
|
||||||
allRequests = await MovieRepository.GetWithUser(shouldHide.UserId).Skip(position).Take(count).ToListAsync();
|
allRequests = await MovieRepository.GetWithUser(shouldHide.UserId).Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
allRequests = await MovieRepository.GetWithUser().Skip(position).Take(count).ToListAsync();
|
allRequests = await MovieRepository.GetWithUser().Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync();
|
||||||
}
|
}
|
||||||
allRequests.ForEach(x =>
|
allRequests.ForEach(x =>
|
||||||
{
|
{
|
||||||
|
@ -380,10 +380,13 @@ namespace Ombi.Core.Engine
|
||||||
return new RequestEngineResult { Result = true, Message = $"{movieName} has been successfully added!" };
|
return new RequestEngineResult { Result = true, Message = $"{movieName} has been successfully added!" };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<MovieRequests>> Filter(FilterViewModel vm)
|
public async Task<FilterResult<MovieRequests>> Filter(FilterViewModel vm)
|
||||||
{
|
{
|
||||||
var shouldHide = await HideFromOtherUsers();
|
var shouldHide = await HideFromOtherUsers();
|
||||||
var requests = shouldHide.Hide ? MovieRepository.GetWithUser(shouldHide.UserId) : MovieRepository.GetWithUser();
|
var requests = shouldHide.Hide
|
||||||
|
? MovieRepository.GetWithUser(shouldHide.UserId)
|
||||||
|
: MovieRepository.GetWithUser();
|
||||||
|
|
||||||
switch (vm.AvailabilityFilter)
|
switch (vm.AvailabilityFilter)
|
||||||
{
|
{
|
||||||
case FilterType.None:
|
case FilterType.None:
|
||||||
|
@ -415,7 +418,14 @@ namespace Ombi.Core.Engine
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return requests;
|
var count = await requests.CountAsync();
|
||||||
|
requests = requests.Skip(vm.Position).Take(vm.Count);
|
||||||
|
var retVal = new FilterResult<MovieRequests>
|
||||||
|
{
|
||||||
|
Total = count,
|
||||||
|
Collection = requests
|
||||||
|
};
|
||||||
|
return retVal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -141,7 +141,7 @@ namespace Ombi.Core.Engine
|
||||||
.Include(x => x.ChildRequests)
|
.Include(x => x.ChildRequests)
|
||||||
.ThenInclude(x => x.SeasonRequests)
|
.ThenInclude(x => x.SeasonRequests)
|
||||||
.ThenInclude(x => x.Episodes)
|
.ThenInclude(x => x.Episodes)
|
||||||
.Skip(position).Take(count).ToListAsync();
|
.Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync();
|
||||||
|
|
||||||
// Filter out children
|
// Filter out children
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ namespace Ombi.Core.Engine
|
||||||
.Include(x => x.ChildRequests)
|
.Include(x => x.ChildRequests)
|
||||||
.ThenInclude(x => x.SeasonRequests)
|
.ThenInclude(x => x.SeasonRequests)
|
||||||
.ThenInclude(x => x.Episodes)
|
.ThenInclude(x => x.Episodes)
|
||||||
.Skip(position).Take(count).ToListAsync();
|
.Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
return allRequests;
|
return allRequests;
|
||||||
|
|
10
src/Ombi.Core/Models/Requests/FilterResult.cs
Normal file
10
src/Ombi.Core/Models/Requests/FilterResult.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Models.Requests
|
||||||
|
{
|
||||||
|
public class FilterResult<T>
|
||||||
|
{
|
||||||
|
public int Total { get; set; }
|
||||||
|
public IEnumerable<T> Collection { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,8 @@
|
||||||
{
|
{
|
||||||
public FilterType AvailabilityFilter { get; set; }
|
public FilterType AvailabilityFilter { get; set; }
|
||||||
public FilterType StatusFilter { get; set; }
|
public FilterType StatusFilter { get; set; }
|
||||||
|
public int Position { get; set; }
|
||||||
|
public int Count { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum FilterType
|
public enum FilterType
|
||||||
|
|
|
@ -177,6 +177,7 @@ namespace Ombi.DependencyInjection
|
||||||
services.AddTransient<ISickRageSync, SickRageSync>();
|
services.AddTransient<ISickRageSync, SickRageSync>();
|
||||||
services.AddTransient<IRefreshMetadata, RefreshMetadata>();
|
services.AddTransient<IRefreshMetadata, RefreshMetadata>();
|
||||||
services.AddTransient<INewsletterJob, NewsletterJob>();
|
services.AddTransient<INewsletterJob, NewsletterJob>();
|
||||||
|
services.AddTransient<IPlexRecentlyAddedSync, PlexRecentlyAddedSync>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ namespace Ombi.Mapping.Profiles
|
||||||
.ForMember(x => x.ReleaseDate, o => o.MapFrom(s => s.release_date))
|
.ForMember(x => x.ReleaseDate, o => o.MapFrom(s => s.release_date))
|
||||||
.ForMember(x => x.Type, o => o.MapFrom(s => s.Type));
|
.ForMember(x => x.Type, o => o.MapFrom(s => s.Type));
|
||||||
|
|
||||||
CreateMap<Genre, GenreDto>();
|
CreateMap<TheMovieDbApi.Models.Genre, GenreDto>();
|
||||||
|
|
||||||
CreateMap<MovieSearchResult, SearchMovieViewModel>().ReverseMap();
|
CreateMap<MovieSearchResult, SearchMovieViewModel>().ReverseMap();
|
||||||
CreateMap<MovieResponseDto, SearchMovieViewModel>().ReverseMap();
|
CreateMap<MovieResponseDto, SearchMovieViewModel>().ReverseMap();
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
public abstract class TemplateBase
|
public abstract class TemplateBase
|
||||||
{
|
{
|
||||||
public abstract string TemplateLocation { get; }
|
public abstract string TemplateLocation { get; }
|
||||||
public virtual string OmbiLogo => "http://i.imgur.com/qQsN78U.png";
|
public virtual string OmbiLogo => "http://i.imgur.com/7pqVq7W.png";
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,57 +4,125 @@
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
<title>Ombi</title>
|
<title>Ombi</title>
|
||||||
<style media="all" type="text/css">
|
<style type="text/css">
|
||||||
@media all {
|
img {
|
||||||
.btn-primary table td:hover {
|
border: none;
|
||||||
background-color: #34495e !important;
|
-ms-interpolation-mode: bicubic;
|
||||||
}
|
max-width: 100%;
|
||||||
|
|
||||||
.btn-primary a:hover {
|
|
||||||
background-color: #34495e !important;
|
|
||||||
border-color: #34495e !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all {
|
body {
|
||||||
.btn-secondary a:hover {
|
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||||
border-color: #34495e !important;
|
-webkit-font-smoothing: antialiased;
|
||||||
color: #34495e !important;
|
font-size: 14px;
|
||||||
}
|
line-height: 1.4;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 620px) {
|
table {
|
||||||
|
border-collapse: separate;
|
||||||
|
mso-table-lspace: 0pt;
|
||||||
|
mso-table-rspace: 0pt;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td {
|
||||||
|
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto !important;
|
||||||
|
max-width: 1042px;
|
||||||
|
padding: 10px;
|
||||||
|
width: 1042px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 1037px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
border-radius: 3px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 5px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-block {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
clear: both;
|
||||||
|
margin-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer td,
|
||||||
|
.footer p,
|
||||||
|
.footer span,
|
||||||
|
.footer a {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
margin: 0;
|
||||||
|
color: #ff761b;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p li,
|
||||||
|
ul li {
|
||||||
|
list-style-position: inside;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1040px) {
|
||||||
|
.media-card {
|
||||||
|
display: block !important;
|
||||||
|
margin-top: 0 !important;
|
||||||
|
margin-right: auto !important;
|
||||||
|
margin-bottom: 10px !important;
|
||||||
|
margin-left: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
table[class=body] h1 {
|
table[class=body] h1 {
|
||||||
font-size: 28px !important;
|
font-size: 28px !important;
|
||||||
margin-bottom: 10px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
table[class=body] h2 {
|
|
||||||
font-size: 22px !important;
|
|
||||||
margin-bottom: 10px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
table[class=body] h3 {
|
|
||||||
font-size: 16px !important;
|
|
||||||
margin-bottom: 10px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
table[class=body] p,
|
|
||||||
table[class=body] ul,
|
|
||||||
table[class=body] ol,
|
|
||||||
table[class=body] td,
|
|
||||||
table[class=body] span,
|
|
||||||
table[class=body] a {
|
|
||||||
font-size: 16px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
table[class=body] .wrapper,
|
|
||||||
table[class=body] .article {
|
|
||||||
padding: 10px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
table[class=body] .content {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
table[class=body] .container {
|
table[class=body] .container {
|
||||||
|
@ -62,60 +130,14 @@
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
table[class=body] .header {
|
|
||||||
margin-bottom: 10px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
table[class=body] .main {
|
table[class=body] .main {
|
||||||
border-left-width: 0 !important;
|
border-left-width: 0 !important;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
border-right-width: 0 !important;
|
border-right-width: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
table[class=body] .btn table {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
table[class=body] .btn a {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
table[class=body] .img-responsive {
|
|
||||||
height: auto !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
width: auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
table[class=body] .alert td {
|
|
||||||
border-radius: 0 !important;
|
|
||||||
padding: 10px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
table[class=body] .span-2,
|
|
||||||
table[class=body] .span-3 {
|
|
||||||
max-width: none !important;
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
table[class=body] .receipt {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all {
|
@media all {
|
||||||
.ExternalClass {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ExternalClass,
|
|
||||||
.ExternalClass p,
|
|
||||||
.ExternalClass span,
|
|
||||||
.ExternalClass font,
|
|
||||||
.ExternalClass td,
|
|
||||||
.ExternalClass div {
|
|
||||||
line-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.apple-link a {
|
.apple-link a {
|
||||||
color: inherit !important;
|
color: inherit !important;
|
||||||
font-family: inherit !important;
|
font-family: inherit !important;
|
||||||
|
@ -127,16 +149,15 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="" style="font-family: sans-serif; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.4; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; background-color: #f6f6f6; margin: 0; padding: 0;">
|
<body class="" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;font-size: 14px;line-height: 1.4;margin: 0;padding: 0;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
|
||||||
<table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: #f6f6f6;" width="100%" bgcolor="#f6f6f6">
|
<table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%; background-color: #1f1f1f; color: #fff;">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top"> </td>
|
<td class="container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;display: block;max-width: 1042px;padding: 10px;width: 1042px;margin: 0 auto !important;">
|
||||||
<td class="container" style="font-family: sans-serif; font-size: 14px; vertical-align: top; display: block; Margin: 0 auto !important; max-width: 580px; padding: 10px; width: 580px;" width="580" valign="top">
|
<div class="content" style="box-sizing: border-box; display: block; margin: 0 auto; max-width: 1037px; padding: 10px;">
|
||||||
<div class="content" style="box-sizing: border-box; display: block; Margin: 0 auto; max-width: 580px; padding: 10px;">
|
|
||||||
|
|
||||||
<!-- START CENTERED WHITE CONTAINER -->
|
<!-- START CENTERED WHITE CONTAINER -->
|
||||||
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Ombi Recently Added</span>
|
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Ombi Recently Added</span>
|
||||||
<table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #fff; border-radius: 3px;" width="100%">
|
<table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; border-radius: 3px;" width="100%">
|
||||||
|
|
||||||
<!-- START MAIN CONTENT AREA -->
|
<!-- START MAIN CONTENT AREA -->
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -144,23 +165,24 @@
|
||||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
|
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<img src="{@LOGO}" width="400px" text-align="center" />
|
<img src="{@LOGO}" width="400px" text-align="center"/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<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;">{@INTRO}</p>
|
<p style="color: #fff; font-family: sans-serif; font-size: 20px; font-weight: normal; margin: 0; Margin-bottom: 15px; text-align: center;">{@INTRO}</p>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{@RECENTLYADDED}
|
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{@RECENTLYADDED}
|
||||||
|
|
||||||
<!-- END MAIN CONTENT AREA -->
|
<!-- END MAIN CONTENT AREA -->
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -39,7 +39,7 @@ namespace Ombi.Notifications.Agents
|
||||||
|
|
||||||
protected override bool ValidateConfiguration(MobileNotificationSettings settings)
|
protected override bool ValidateConfiguration(MobileNotificationSettings settings)
|
||||||
{
|
{
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task NewRequest(NotificationOptions model, MobileNotificationSettings settings)
|
protected override async Task NewRequest(NotificationOptions model, MobileNotificationSettings settings)
|
||||||
|
@ -211,7 +211,7 @@ namespace Ombi.Notifications.Agents
|
||||||
|
|
||||||
protected async Task Send(List<string> playerIds, NotificationMessage model, MobileNotificationSettings settings)
|
protected async Task Send(List<string> playerIds, NotificationMessage model, MobileNotificationSettings settings)
|
||||||
{
|
{
|
||||||
if (!playerIds.Any())
|
if (playerIds == null || !playerIds.Any())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
43
src/Ombi.Schedule.Tests/NewsletterTests.cs
Normal file
43
src/Ombi.Schedule.Tests/NewsletterTests.cs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Ombi.Core.Settings;
|
||||||
|
using Ombi.Schedule.Jobs.Ombi;
|
||||||
|
using Ombi.Settings.Settings.Models;
|
||||||
|
using Ombi.Settings.Settings.Models.Notifications;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
|
||||||
|
namespace Ombi.Schedule.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class NewsletterTests
|
||||||
|
{
|
||||||
|
[TestCaseSource(nameof(EpisodeListData))]
|
||||||
|
public string BuildEpisodeListTest(List<int> episodes)
|
||||||
|
{
|
||||||
|
var emailSettings = new Mock<ISettingsService<EmailNotificationSettings>>();
|
||||||
|
var customziation = new Mock<ISettingsService<CustomizationSettings>>();
|
||||||
|
var newsletterSettings = new Mock<ISettingsService<NewsletterSettings>>();
|
||||||
|
var newsletter = new NewsletterJob(null, null, null, null, null, null, customziation.Object, emailSettings.Object, null, null, newsletterSettings.Object, null);
|
||||||
|
|
||||||
|
var ep = new List<int>();
|
||||||
|
foreach (var i in episodes)
|
||||||
|
{
|
||||||
|
ep.Add(i);
|
||||||
|
}
|
||||||
|
var result = newsletter.BuildEpisodeList(ep);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<TestCaseData> EpisodeListData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
yield return new TestCaseData(new List<int>{1,2,3,4,5,6}).Returns("1-6").SetName("Simple 1-6");
|
||||||
|
yield return new TestCaseData(new List<int>{1,2,3,4,5,6,8,9}).Returns("1-6, 8-9").SetName("Simple 1-6, 8-9");
|
||||||
|
yield return new TestCaseData(new List<int>{1,99,101,555,468,469}).Returns("1, 99, 101, 555, 468-469").SetName("More Complex");
|
||||||
|
yield return new TestCaseData(new List<int>{1}).Returns("1").SetName("Single Episode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,10 +8,10 @@
|
||||||
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" />
|
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.2" />
|
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.2" />
|
||||||
<PackageReference Include="Moq" Version="4.7.99" />
|
<PackageReference Include="Moq" Version="4.7.99" />
|
||||||
<PackageReference Include="Nunit" Version="3.8.1" />
|
<PackageReference Include="Nunit" Version="3.10.1" />
|
||||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
|
<PackageReference Include="NUnit.ConsoleRunner" Version="3.8.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
|
||||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.6.1"></packagereference>
|
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.7.0"></packagereference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace Ombi.Schedule
|
||||||
IOmbiAutomaticUpdater updater, IEmbyContentSync embySync, IPlexUserImporter userImporter,
|
IOmbiAutomaticUpdater updater, IEmbyContentSync embySync, IPlexUserImporter userImporter,
|
||||||
IEmbyUserImporter embyUserImporter, ISonarrSync cache, ICouchPotatoSync cpCache,
|
IEmbyUserImporter embyUserImporter, ISonarrSync cache, ICouchPotatoSync cpCache,
|
||||||
ISettingsService<JobSettings> jobsettings, ISickRageSync srSync, IRefreshMetadata refresh,
|
ISettingsService<JobSettings> jobsettings, ISickRageSync srSync, IRefreshMetadata refresh,
|
||||||
INewsletterJob newsletter)
|
INewsletterJob newsletter, IPlexRecentlyAddedSync recentlyAddedPlex)
|
||||||
{
|
{
|
||||||
_plexContentSync = plexContentSync;
|
_plexContentSync = plexContentSync;
|
||||||
_radarrSync = radarrSync;
|
_radarrSync = radarrSync;
|
||||||
|
@ -33,9 +33,11 @@ namespace Ombi.Schedule
|
||||||
_srSync = srSync;
|
_srSync = srSync;
|
||||||
_refreshMetadata = refresh;
|
_refreshMetadata = refresh;
|
||||||
_newsletter = newsletter;
|
_newsletter = newsletter;
|
||||||
|
_plexRecentlyAddedSync = recentlyAddedPlex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly IPlexContentSync _plexContentSync;
|
private readonly IPlexContentSync _plexContentSync;
|
||||||
|
private readonly IPlexRecentlyAddedSync _plexRecentlyAddedSync;
|
||||||
private readonly IRadarrSync _radarrSync;
|
private readonly IRadarrSync _radarrSync;
|
||||||
private readonly IOmbiAutomaticUpdater _updater;
|
private readonly IOmbiAutomaticUpdater _updater;
|
||||||
private readonly IPlexUserImporter _plexUserImporter;
|
private readonly IPlexUserImporter _plexUserImporter;
|
||||||
|
@ -56,7 +58,7 @@ namespace Ombi.Schedule
|
||||||
RecurringJob.AddOrUpdate(() => _sonarrSync.Start(), JobSettingsHelper.Sonarr(s));
|
RecurringJob.AddOrUpdate(() => _sonarrSync.Start(), JobSettingsHelper.Sonarr(s));
|
||||||
RecurringJob.AddOrUpdate(() => _radarrSync.CacheContent(), JobSettingsHelper.Radarr(s));
|
RecurringJob.AddOrUpdate(() => _radarrSync.CacheContent(), JobSettingsHelper.Radarr(s));
|
||||||
RecurringJob.AddOrUpdate(() => _plexContentSync.CacheContent(false), JobSettingsHelper.PlexContent(s));
|
RecurringJob.AddOrUpdate(() => _plexContentSync.CacheContent(false), JobSettingsHelper.PlexContent(s));
|
||||||
RecurringJob.AddOrUpdate(() => _plexContentSync.CacheContent(true), JobSettingsHelper.PlexRecentlyAdded(s));
|
RecurringJob.AddOrUpdate(() => _plexRecentlyAddedSync.Start(), JobSettingsHelper.PlexRecentlyAdded(s));
|
||||||
RecurringJob.AddOrUpdate(() => _cpCache.Start(), JobSettingsHelper.CouchPotato(s));
|
RecurringJob.AddOrUpdate(() => _cpCache.Start(), JobSettingsHelper.CouchPotato(s));
|
||||||
RecurringJob.AddOrUpdate(() => _srSync.Start(), JobSettingsHelper.SickRageSync(s));
|
RecurringJob.AddOrUpdate(() => _srSync.Start(), JobSettingsHelper.SickRageSync(s));
|
||||||
RecurringJob.AddOrUpdate(() => _refreshMetadata.Start(), JobSettingsHelper.RefreshMetadata(s));
|
RecurringJob.AddOrUpdate(() => _refreshMetadata.Start(), JobSettingsHelper.RefreshMetadata(s));
|
||||||
|
|
|
@ -9,6 +9,7 @@ using Ombi.Api.Emby.Models.Movie;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Core.Settings.Models.External;
|
using Ombi.Core.Settings.Models.External;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
|
using Ombi.Schedule.Jobs.Ombi;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
@ -19,13 +20,14 @@ namespace Ombi.Schedule.Jobs.Emby
|
||||||
public class EmbyContentSync : IEmbyContentSync
|
public class EmbyContentSync : IEmbyContentSync
|
||||||
{
|
{
|
||||||
public EmbyContentSync(ISettingsService<EmbySettings> settings, IEmbyApi api, ILogger<EmbyContentSync> logger,
|
public EmbyContentSync(ISettingsService<EmbySettings> settings, IEmbyApi api, ILogger<EmbyContentSync> logger,
|
||||||
IEmbyContentRepository repo, IEmbyEpisodeSync epSync)
|
IEmbyContentRepository repo, IEmbyEpisodeSync epSync, IRefreshMetadata metadata)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
_api = api;
|
_api = api;
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
_episodeSync = epSync;
|
_episodeSync = epSync;
|
||||||
|
_metadata = metadata;
|
||||||
_settings.ClearCache();
|
_settings.ClearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +36,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
||||||
private readonly IEmbyApi _api;
|
private readonly IEmbyApi _api;
|
||||||
private readonly IEmbyContentRepository _repo;
|
private readonly IEmbyContentRepository _repo;
|
||||||
private readonly IEmbyEpisodeSync _episodeSync;
|
private readonly IEmbyEpisodeSync _episodeSync;
|
||||||
|
private readonly IRefreshMetadata _metadata;
|
||||||
|
|
||||||
|
|
||||||
public async Task Start()
|
public async Task Start()
|
||||||
|
@ -56,6 +59,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
||||||
|
|
||||||
// Episodes
|
// Episodes
|
||||||
BackgroundJob.Enqueue(() => _episodeSync.Start());
|
BackgroundJob.Enqueue(() => _episodeSync.Start());
|
||||||
|
BackgroundJob.Enqueue(() => _metadata.Start());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,43 +4,79 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
{
|
{
|
||||||
public abstract class HtmlTemplateGenerator
|
public abstract class HtmlTemplateGenerator
|
||||||
{
|
{
|
||||||
protected virtual void AddParagraph(StringBuilder stringBuilder, string text, int fontSize = 14, string fontWeight = "normal")
|
protected virtual void AddBackgroundInsideTable(StringBuilder sb, string url)
|
||||||
{
|
{
|
||||||
stringBuilder.AppendFormat("<p style=\"font-family: sans-serif; font-size: {1}px; font-weight: {2}; margin: 0; Margin-bottom: 15px;\">{0}</p>", text, fontSize, fontWeight);
|
sb.Append("<td align=\"center\" valign=\"top\" class=\"media-card\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 12px; vertical-align: top; padding: 3px; width: 502px; min-width: 500px; max-width: 500px; height: 235px; \">");
|
||||||
|
sb.AppendFormat("<table class=\"card-bg\" style=\"background-image: url({0}); border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: #1f1f1f; background-position: center; background-size: cover; background-repeat: no-repeat; background-clip: padding-box; border: 2px solid rgba(255,118,27,.4); \">", url);
|
||||||
|
sb.Append("<tr>");
|
||||||
|
sb.Append("<td>");
|
||||||
|
sb.Append("<table class=\"bg-tint\" style=\"background-color: rgba(0, 0, 0, .6); position: absolute; width: 490px; height: 239px; \">");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void AddImageInsideTable(StringBuilder sb, string url, int size = 400)
|
protected virtual void AddPosterInsideTable(StringBuilder sb, string url)
|
||||||
{
|
{
|
||||||
sb.Append("<tr>");
|
sb.Append("<tr>");
|
||||||
sb.Append("<td align=\"center\">");
|
sb.Append("<td class=\"poster-container\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; width: 150px; min-width: 15px; height: 225px; \">");
|
||||||
sb.Append($"<img src=\"{url}\" width=\"{size}px\" text-align=\"center\" />");
|
sb.AppendFormat("<table class=\"poster-img\" style=\"background-image: url({0}); border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: transparent; background-position: center; background-size: cover; background-repeat: no-repeat; background-clip: padding-box; border: 1px solid rgba(255,255,255,.1); \">", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void AddMediaServerUrl(StringBuilder sb, string mediaurl, string url)
|
||||||
|
{
|
||||||
|
sb.Append("<tr>");
|
||||||
|
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; \">");
|
||||||
|
sb.AppendFormat("<a href=\"{0}\" target=\"_blank\">", mediaurl);
|
||||||
|
sb.AppendFormat("<img class=\"poster-overlay\" src=\"{0}\" width=\"150\" height=\"225\" style=\"border: none;-ms-interpolation-mode: bicubic; max-width: 100%;display: block; visibility: hidden; \">", url);
|
||||||
|
sb.Append("</a>");
|
||||||
|
sb.Append("</td>");
|
||||||
|
sb.Append("</tr>");
|
||||||
|
sb.Append("</table>");
|
||||||
|
sb.Append("</td>");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void AddInfoTable(StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append(
|
||||||
|
"<td class=\"movie-info\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; padding-left: 4px; text-align: left; height: 227px; \">");
|
||||||
|
sb.Append("<table style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; height: 100%; \">");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void AddTitle(StringBuilder sb, string url, string title)
|
||||||
|
{
|
||||||
|
sb.Append("<tr>");
|
||||||
|
sb.Append("<td class=\"title\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 0.9rem; vertical-align: top; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; line-height: 1.2rem; padding: 5px; \">");
|
||||||
|
sb.AppendFormat("<a href=\"{0}\" target=\"_blank\">", url);
|
||||||
|
sb.AppendFormat("<h1 style=\"white-space: normal; line-height: 1;\" >{0}</h1>", title);
|
||||||
|
sb.Append("</a>");
|
||||||
sb.Append("</td>");
|
sb.Append("</td>");
|
||||||
sb.Append("</tr>");
|
sb.Append("</tr>");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Href(StringBuilder sb, string url)
|
protected virtual void AddParagraph(StringBuilder sb, string text)
|
||||||
{
|
{
|
||||||
sb.AppendFormat("<a href=\"{0}\">", url);
|
sb.Append("<tr class=\"description\">");
|
||||||
|
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 0.75rem; vertical-align: top; padding: 5px; height: 100%; \">");
|
||||||
|
sb.AppendFormat("<p style=\"color: #fff; font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-weight: 400; margin: 0; max-width: 325px; text-align: justify; \">{0}</p>", text);
|
||||||
|
sb.Append("</td>");
|
||||||
|
sb.Append("</tr>");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void TableData(StringBuilder sb)
|
protected virtual void AddTvParagraph(StringBuilder sb, string episodes, string summary)
|
||||||
{
|
{
|
||||||
sb.Append(
|
sb.Append("<tr class=\"description\">");
|
||||||
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
|
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 0.75rem; vertical-align: top; padding: 5px; height: 100%; \">");
|
||||||
|
sb.AppendFormat("<p style=\"color: #fff; font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-weight: 400; margin: 0; max-width: 325px; margin-bottom: 10px; \">{0}</p>", episodes);
|
||||||
|
sb.AppendFormat("<div style=\"color: #fff; font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-weight: 400; margin: 0; max-width: 325px; overflow: hidden; text-align: justify; \">{0}</div>", summary);
|
||||||
|
sb.Append("</td>");
|
||||||
|
sb.Append("</tr>");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void EndTag(StringBuilder sb, string tag)
|
protected virtual void AddGenres(StringBuilder sb, string text)
|
||||||
{
|
{
|
||||||
sb.AppendFormat("</{0}>", tag);
|
sb.Append("<tr class=\"meta\">");
|
||||||
|
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; max-width: 265px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; \">");
|
||||||
|
sb.AppendFormat("<span style=\"display: inline-block; min-width: 10px; padding: 3px 7px; font-size: 11px; line-height: 1; text-align: center; white-space: nowrap; vertical-align: middle; background-color: rgba(255, 118, 27, 0.5); color: #fff; border-radius: 2px; text-overflow: ellipsis; overflow: hidden; \">{0}</span>", text);
|
||||||
|
sb.Append("</td>");
|
||||||
|
sb.Append("</tr>");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Header(StringBuilder sb, int size, string text, string fontWeight = "normal")
|
|
||||||
{
|
|
||||||
sb.AppendFormat(
|
|
||||||
"<h{0} style=\"font-family: sans-serif; font-weight: {2}; margin: 0; Margin-bottom: 15px;\">{1}</h{0}>",
|
|
||||||
size, text, fontWeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -98,14 +98,14 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode);
|
addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode);
|
||||||
|
|
||||||
// Filter out the ones that we haven't sent yet
|
// Filter out the ones that we haven't sent yet
|
||||||
var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId)));
|
var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && x.HasTheMovieDb && !addedPlexMovieLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId)));
|
||||||
var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId)));
|
var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && x.HasTheMovieDb && !addedEmbyMoviesLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId)));
|
||||||
_log.LogInformation("Plex Movies to send: {0}", plexContentMoviesToSend.Count());
|
_log.LogInformation("Plex Movies to send: {0}", plexContentMoviesToSend.Count());
|
||||||
_log.LogInformation("Emby Movies to send: {0}", embyContentMoviesToSend.Count());
|
_log.LogInformation("Emby Movies to send: {0}", embyContentMoviesToSend.Count());
|
||||||
|
|
||||||
var plexEpisodesToSend =
|
var plexEpisodesToSend =
|
||||||
FilterPlexEpisodes(_plex.GetAllEpisodes().Include(x => x.Series).AsNoTracking(), addedPlexEpisodesLogIds);
|
FilterPlexEpisodes(_plex.GetAllEpisodes().Include(x => x.Series).Where(x => x.Series.HasTvDb).AsNoTracking(), addedPlexEpisodesLogIds);
|
||||||
var embyEpisodesToSend = FilterEmbyEpisodes(_emby.GetAllEpisodes().Include(x => x.Series).AsNoTracking(),
|
var embyEpisodesToSend = FilterEmbyEpisodes(_emby.GetAllEpisodes().Include(x => x.Series).Where(x => x.Series.HasTvDb).AsNoTracking(),
|
||||||
addedEmbyEpisodesLogIds);
|
addedEmbyEpisodesLogIds);
|
||||||
|
|
||||||
_log.LogInformation("Plex Episodes to send: {0}", plexEpisodesToSend.Count());
|
_log.LogInformation("Plex Episodes to send: {0}", plexEpisodesToSend.Count());
|
||||||
|
@ -114,7 +114,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
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 = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10).ToHashSet();
|
var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10).ToHashSet();
|
||||||
var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet();
|
var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet();
|
||||||
body = await BuildHtml(plexm, embym, plext, embyt, settings);
|
body = await BuildHtml(plexm, embym, plext, embyt, settings);
|
||||||
|
@ -306,16 +306,38 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
var embyMovies = embyContentToSend.Where(x => x.Type == EmbyMediaType.Movie);
|
var embyMovies = embyContentToSend.Where(x => x.Type == EmbyMediaType.Movie);
|
||||||
if ((plexMovies.Any() || embyMovies.Any()) && !settings.DisableMovies)
|
if ((plexMovies.Any() || embyMovies.Any()) && !settings.DisableMovies)
|
||||||
{
|
{
|
||||||
sb.Append("<h1>New Movies:</h1><br /><br />");
|
sb.Append("<h1 style=\"text-align: center;\">New Movies</h1><br /><br />");
|
||||||
|
sb.Append(
|
||||||
|
"<table class=\"movies-table\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100 %; \">");
|
||||||
|
sb.Append("<tr>");
|
||||||
|
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; \">");
|
||||||
|
sb.Append("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100 %; \">");
|
||||||
|
sb.Append("<tr>");
|
||||||
await ProcessPlexMovies(plexMovies, sb);
|
await ProcessPlexMovies(plexMovies, sb);
|
||||||
await ProcessEmbyMovies(embyMovies, sb);
|
await ProcessEmbyMovies(embyMovies, sb);
|
||||||
|
sb.Append("</tr>");
|
||||||
|
sb.Append("</table>");
|
||||||
|
sb.Append("</td>");
|
||||||
|
sb.Append("</tr>");
|
||||||
|
sb.Append("</table>");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((plexEpisodes.Any() || embyEp.Any()) && !settings.DisableTv)
|
if ((plexEpisodes.Any() || embyEp.Any()) && !settings.DisableTv)
|
||||||
{
|
{
|
||||||
sb.Append("<h1>New Episodes:</h1><br /><br />");
|
sb.Append("<br /><br /><h1 style=\"text-align: center;\">New TV</h1><br /><br />");
|
||||||
|
sb.Append(
|
||||||
|
"<table class=\"tv-table\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100 %; \">");
|
||||||
|
sb.Append("<tr>");
|
||||||
|
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; \">");
|
||||||
|
sb.Append("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100 %; \">");
|
||||||
|
sb.Append("<tr>");
|
||||||
await ProcessPlexTv(plexEpisodes, sb);
|
await ProcessPlexTv(plexEpisodes, sb);
|
||||||
await ProcessEmbyTv(embyEp, sb);
|
await ProcessEmbyTv(embyEp, sb);
|
||||||
|
sb.Append("</tr>");
|
||||||
|
sb.Append("</table>");
|
||||||
|
sb.Append("</td>");
|
||||||
|
sb.Append("</tr>");
|
||||||
|
sb.Append("</table>");
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
|
@ -323,8 +345,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
|
|
||||||
private async Task ProcessPlexMovies(IQueryable<PlexServerContent> plexContentToSend, StringBuilder sb)
|
private async Task ProcessPlexMovies(IQueryable<PlexServerContent> plexContentToSend, StringBuilder sb)
|
||||||
{
|
{
|
||||||
sb.Append(
|
int count = 0;
|
||||||
"<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%\">");
|
|
||||||
var ordered = plexContentToSend.OrderByDescending(x => x.AddedAt);
|
var ordered = plexContentToSend.OrderByDescending(x => x.AddedAt);
|
||||||
foreach (var content in ordered)
|
foreach (var content in ordered)
|
||||||
{
|
{
|
||||||
|
@ -334,13 +355,15 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId);
|
var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId);
|
||||||
|
var mediaurl = content.Url;
|
||||||
if (info == null)
|
if (info == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
CreateMovieHtmlContent(sb, info);
|
CreateMovieHtmlContent(sb, info, mediaurl);
|
||||||
|
count += 1;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -350,13 +373,19 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
{
|
{
|
||||||
EndLoopHtml(sb);
|
EndLoopHtml(sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (count == 2)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
sb.Append("</tr>");
|
||||||
|
sb.Append("<tr>");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessEmbyMovies(IQueryable<EmbyContent> embyContent, StringBuilder sb)
|
private async Task ProcessEmbyMovies(IQueryable<EmbyContent> embyContent, StringBuilder sb)
|
||||||
{
|
{
|
||||||
sb.Append(
|
int count = 0;
|
||||||
"<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%\">");
|
|
||||||
var ordered = embyContent.OrderByDescending(x => x.AddedAt);
|
var ordered = embyContent.OrderByDescending(x => x.AddedAt);
|
||||||
foreach (var content in ordered)
|
foreach (var content in ordered)
|
||||||
{
|
{
|
||||||
|
@ -374,6 +403,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
theMovieDbId = result.id.ToString();
|
theMovieDbId = result.id.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mediaurl = content.Url;
|
||||||
var info = await _movieApi.GetMovieInformationWithExtraInfo(StringHelper.IntParseLinq(theMovieDbId));
|
var info = await _movieApi.GetMovieInformationWithExtraInfo(StringHelper.IntParseLinq(theMovieDbId));
|
||||||
if (info == null)
|
if (info == null)
|
||||||
{
|
{
|
||||||
|
@ -381,7 +411,8 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
CreateMovieHtmlContent(sb, info);
|
CreateMovieHtmlContent(sb, info, mediaurl);
|
||||||
|
count += 1;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -391,17 +422,24 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
{
|
{
|
||||||
EndLoopHtml(sb);
|
EndLoopHtml(sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (count == 2)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
sb.Append("</tr>");
|
||||||
|
sb.Append("<tr>");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateMovieHtmlContent(StringBuilder sb, MovieResponseDto info)
|
private void CreateMovieHtmlContent(StringBuilder sb, MovieResponseDto info, string mediaurl)
|
||||||
{
|
{
|
||||||
AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/original{info.PosterPath}");
|
AddBackgroundInsideTable(sb, $"https://image.tmdb.org/t/p/w1280/{info.BackdropPath}");
|
||||||
|
AddPosterInsideTable(sb, $"https://image.tmdb.org/t/p/original{info.PosterPath}");
|
||||||
|
|
||||||
sb.Append("<tr>");
|
AddMediaServerUrl(sb, mediaurl, $"https://image.tmdb.org/t/p/original{info.PosterPath}");
|
||||||
TableData(sb);
|
AddInfoTable(sb);
|
||||||
|
|
||||||
Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/");
|
|
||||||
var releaseDate = string.Empty;
|
var releaseDate = string.Empty;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -411,16 +449,22 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
{
|
{
|
||||||
// Swallow, couldn't parse the date
|
// Swallow, couldn't parse the date
|
||||||
}
|
}
|
||||||
Header(sb, 3, $"{info.Title} {releaseDate}");
|
|
||||||
EndTag(sb, "a");
|
AddTitle(sb, $"https://www.imdb.com/title/{info.ImdbId}/", $"{info.Title} {releaseDate}");
|
||||||
|
|
||||||
|
var summary = info.Overview;
|
||||||
|
if (summary.Length > 280)
|
||||||
|
{
|
||||||
|
summary = summary.Remove(280);
|
||||||
|
summary = summary + "...</p>";
|
||||||
|
}
|
||||||
|
AddParagraph(sb, summary);
|
||||||
|
|
||||||
if (info.Genres.Any())
|
if (info.Genres.Any())
|
||||||
{
|
{
|
||||||
AddParagraph(sb,
|
AddGenres(sb,
|
||||||
$"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}");
|
$"Genres: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}");
|
||||||
}
|
}
|
||||||
|
|
||||||
AddParagraph(sb, info.Overview);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessPlexTv(HashSet<PlexEpisode> plexContent, StringBuilder sb)
|
private async Task ProcessPlexTv(HashSet<PlexEpisode> plexContent, StringBuilder sb)
|
||||||
|
@ -444,9 +488,8 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
var orderedTv = series.OrderByDescending(x => x.AddedAt);
|
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)
|
foreach (var t in orderedTv)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -489,17 +532,30 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
{
|
{
|
||||||
banner = banner.Replace("http", "https"); // Always use the Https banners
|
banner = banner.Replace("http", "https"); // Always use the Https banners
|
||||||
}
|
}
|
||||||
AddImageInsideTable(sb, banner);
|
|
||||||
|
|
||||||
sb.Append("<tr>");
|
var tvInfo = await _movieApi.GetTVInfo(t.TheMovieDbId);
|
||||||
sb.Append(
|
if (tvInfo != null && tvInfo.backdrop_path.HasValue())
|
||||||
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
|
{
|
||||||
|
|
||||||
var title = $"{t.Title} ({t.ReleaseYear})";
|
AddBackgroundInsideTable(sb, $"https://image.tmdb.org/t/p/w500{tvInfo.backdrop_path}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddBackgroundInsideTable(sb, $"https://image.tmdb.org/t/p/w1280/");
|
||||||
|
}
|
||||||
|
AddPosterInsideTable(sb, banner);
|
||||||
|
AddMediaServerUrl(sb, t.Url, banner);
|
||||||
|
AddInfoTable(sb);
|
||||||
|
|
||||||
Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/");
|
var title = "";
|
||||||
Header(sb, 3, title);
|
if (!String.IsNullOrEmpty(info.premiered) && info.premiered.Length > 4)
|
||||||
EndTag(sb, "a");
|
{
|
||||||
|
title = $"{t.Title} ({info.premiered.Remove(4)})";
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
title = $"{t.Title}";
|
||||||
|
}
|
||||||
|
AddTitle(sb, $"https://www.imdb.com/title/{info.externals.imdb}/", title);
|
||||||
|
|
||||||
// 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,
|
||||||
|
@ -511,32 +567,29 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
);
|
);
|
||||||
|
|
||||||
// Group the episodes
|
// Group the episodes
|
||||||
|
var finalsb = new StringBuilder();
|
||||||
foreach (var epInformation in results.OrderBy(x => x.SeasonNumber))
|
foreach (var epInformation in results.OrderBy(x => x.SeasonNumber))
|
||||||
{
|
{
|
||||||
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
||||||
var epSb = new StringBuilder();
|
var episodeString = BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber));
|
||||||
for (var i = 0; i < orderedEpisodes.Count; i++)
|
finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString}");
|
||||||
{
|
finalsb.Append("<br />");
|
||||||
var ep = orderedEpisodes[i];
|
|
||||||
if (i < orderedEpisodes.Count - 1)
|
|
||||||
{
|
|
||||||
epSb.Append($"{ep.EpisodeNumber},");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
epSb.Append($"{ep.EpisodeNumber}");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
AddParagraph(sb, $"Season: {epInformation.SeasonNumber}, Episode: {epSb}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var summary = info.summary;
|
||||||
|
if (summary.Length > 280)
|
||||||
|
{
|
||||||
|
summary = summary.Remove(280);
|
||||||
|
summary = summary + "...</p>";
|
||||||
|
}
|
||||||
|
AddTvParagraph(sb, finalsb.ToString(), summary);
|
||||||
|
|
||||||
if (info.genres.Any())
|
if (info.genres.Any())
|
||||||
{
|
{
|
||||||
AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
|
AddGenres(sb, $"Genres: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
|
||||||
}
|
}
|
||||||
|
count += 1;
|
||||||
|
|
||||||
AddParagraph(sb, info.summary);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -546,9 +599,57 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
{
|
{
|
||||||
EndLoopHtml(sb);
|
EndLoopHtml(sb);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
sb.Append("</table><br /><br />");
|
|
||||||
|
|
||||||
|
if (count == 2)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
sb.Append("</tr>");
|
||||||
|
sb.Append("<tr>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BuildEpisodeList(IEnumerable<int> orderedEpisodes)
|
||||||
|
{
|
||||||
|
var epSb = new StringBuilder();
|
||||||
|
var previousEpisodes = new List<int>();
|
||||||
|
var previousEpisode = -1;
|
||||||
|
foreach (var ep in orderedEpisodes)
|
||||||
|
{
|
||||||
|
if (ep - 1 == previousEpisode)
|
||||||
|
{
|
||||||
|
// This is the next one
|
||||||
|
previousEpisodes.Add(ep);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (previousEpisodes.Count > 1)
|
||||||
|
{
|
||||||
|
// End it
|
||||||
|
epSb.Append($"{previousEpisodes.First()}-{previousEpisodes.Last()}, ");
|
||||||
|
}
|
||||||
|
else if (previousEpisodes.Count == 1)
|
||||||
|
{
|
||||||
|
epSb.Append($"{previousEpisodes.FirstOrDefault()}, ");
|
||||||
|
}
|
||||||
|
// New one
|
||||||
|
previousEpisodes.Clear();
|
||||||
|
previousEpisodes.Add(ep);
|
||||||
|
}
|
||||||
|
previousEpisode = ep;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousEpisodes.Count > 1)
|
||||||
|
{
|
||||||
|
// Got some left over
|
||||||
|
epSb.Append($"{previousEpisodes.First()}-{previousEpisodes.Last()}");
|
||||||
|
}
|
||||||
|
else if(previousEpisodes.Count == 1)
|
||||||
|
{
|
||||||
|
epSb.Append(previousEpisodes.FirstOrDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
return epSb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessEmbyTv(HashSet<EmbyEpisode> embyContent, StringBuilder sb)
|
private async Task ProcessEmbyTv(HashSet<EmbyEpisode> embyContent, StringBuilder sb)
|
||||||
|
@ -570,9 +671,9 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
series.Add(episode.Series);
|
series.Add(episode.Series);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
var orderedTv = series.OrderByDescending(x => x.AddedAt);
|
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)
|
foreach (var t in orderedTv)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -581,26 +682,44 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int.TryParse(t.TvDbId, out var tvdbId);
|
int.TryParse(t.TvDbId, out var tvdbId);
|
||||||
var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId);
|
var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId);
|
||||||
if (info == null)
|
if (info == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var banner = info.image?.original;
|
var banner = info.image?.original;
|
||||||
if (!string.IsNullOrEmpty(banner))
|
if (!string.IsNullOrEmpty(banner))
|
||||||
{
|
{
|
||||||
banner = banner.Replace("http", "https"); // Always use the Https banners
|
banner = banner.Replace("http", "https"); // Always use the Https banners
|
||||||
}
|
}
|
||||||
AddImageInsideTable(sb, banner);
|
|
||||||
|
|
||||||
sb.Append("<tr>");
|
var tvInfo = await _movieApi.GetTVInfo(t.TheMovieDbId);
|
||||||
sb.Append(
|
if (tvInfo != null && tvInfo.backdrop_path.HasValue())
|
||||||
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
|
{
|
||||||
|
|
||||||
Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/");
|
AddBackgroundInsideTable(sb, $"https://image.tmdb.org/t/p/w500{tvInfo.backdrop_path}");
|
||||||
Header(sb, 3, t.Title);
|
}
|
||||||
EndTag(sb, "a");
|
else
|
||||||
|
{
|
||||||
|
AddBackgroundInsideTable(sb, $"https://image.tmdb.org/t/p/w1280/");
|
||||||
|
}
|
||||||
|
AddPosterInsideTable(sb, banner);
|
||||||
|
AddMediaServerUrl(sb, t.Url, banner);
|
||||||
|
AddInfoTable(sb);
|
||||||
|
|
||||||
|
var title = "";
|
||||||
|
if (!String.IsNullOrEmpty(info.premiered) && info.premiered.Length > 4)
|
||||||
|
{
|
||||||
|
title = $"{t.Title} ({info.premiered.Remove(4)})";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
title = $"{t.Title}";
|
||||||
|
}
|
||||||
|
AddTitle(sb, $"https://www.imdb.com/title/{info.externals.imdb}/", title);
|
||||||
|
|
||||||
// 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,
|
||||||
|
@ -612,32 +731,29 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
);
|
);
|
||||||
|
|
||||||
// Group the episodes
|
// Group the episodes
|
||||||
|
var finalsb = new StringBuilder();
|
||||||
foreach (var epInformation in results.OrderBy(x => x.SeasonNumber))
|
foreach (var epInformation in results.OrderBy(x => x.SeasonNumber))
|
||||||
{
|
{
|
||||||
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
||||||
var epSb = new StringBuilder();
|
var episodeString = BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber));
|
||||||
for (var i = 0; i < orderedEpisodes.Count; i++)
|
finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString}");
|
||||||
{
|
finalsb.Append("<br />");
|
||||||
var ep = orderedEpisodes[i];
|
|
||||||
if (i < orderedEpisodes.Count - 1)
|
|
||||||
{
|
|
||||||
epSb.Append($"{ep.EpisodeNumber},");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
epSb.Append($"{ep.EpisodeNumber}");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
AddParagraph(sb, $"Season: {epInformation.SeasonNumber}, Episode: {epSb}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var summary = info.summary;
|
||||||
|
if (summary.Length > 280)
|
||||||
|
{
|
||||||
|
summary = summary.Remove(280);
|
||||||
|
summary = summary + "...</p>";
|
||||||
|
}
|
||||||
|
AddTvParagraph(sb, finalsb.ToString(), summary);
|
||||||
|
|
||||||
if (info.genres.Any())
|
if (info.genres.Any())
|
||||||
{
|
{
|
||||||
AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
|
AddGenres(sb, $"Genres: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
|
||||||
}
|
}
|
||||||
|
count += 1;
|
||||||
|
|
||||||
AddParagraph(sb, info.summary);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -647,19 +763,28 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
{
|
{
|
||||||
EndLoopHtml(sb);
|
EndLoopHtml(sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (count == 2)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
sb.Append("</tr>");
|
||||||
|
sb.Append("<tr>");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sb.Append("</table><br /><br />");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EndLoopHtml(StringBuilder sb)
|
private void EndLoopHtml(StringBuilder sb)
|
||||||
{
|
{
|
||||||
//NOTE: BR have to be in TD's as per html spec or it will be put outside of the table...
|
//NOTE: BR have to be in TD's as per html spec or it will be put outside of the table...
|
||||||
//Source: http://stackoverflow.com/questions/6588638/phantom-br-tag-rendered-by-browsers-prior-to-table-tag
|
//Source: http://stackoverflow.com/questions/6588638/phantom-br-tag-rendered-by-browsers-prior-to-table-tag
|
||||||
sb.Append("<hr />");
|
sb.Append("</table>");
|
||||||
sb.Append("<br />");
|
|
||||||
sb.Append("<br />");
|
|
||||||
sb.Append("</td>");
|
sb.Append("</td>");
|
||||||
sb.Append("</tr>");
|
sb.Append("</tr>");
|
||||||
|
sb.Append("</table>");
|
||||||
|
sb.Append("</td>");
|
||||||
|
sb.Append("</tr>");
|
||||||
|
sb.Append("</table>");
|
||||||
|
sb.Append("</td>");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool ValidateConfiguration(EmailNotificationSettings settings)
|
protected bool ValidateConfiguration(EmailNotificationSettings settings)
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ombi.Schedule.Jobs.Plex
|
||||||
|
{
|
||||||
|
public interface IPlexRecentlyAddedSync : IBaseJob
|
||||||
|
{
|
||||||
|
void Start();
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,7 @@ using Ombi.Api.Plex.Models;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Core.Settings.Models.External;
|
using Ombi.Core.Settings.Models.External;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
|
using Ombi.Schedule.Jobs.Ombi;
|
||||||
using Ombi.Schedule.Jobs.Plex.Interfaces;
|
using Ombi.Schedule.Jobs.Plex.Interfaces;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
|
@ -46,13 +47,14 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
public class PlexContentSync : IPlexContentSync
|
public class PlexContentSync : IPlexContentSync
|
||||||
{
|
{
|
||||||
public PlexContentSync(ISettingsService<PlexSettings> plex, IPlexApi plexApi, ILogger<PlexContentSync> logger, IPlexContentRepository repo,
|
public PlexContentSync(ISettingsService<PlexSettings> plex, IPlexApi plexApi, ILogger<PlexContentSync> logger, IPlexContentRepository repo,
|
||||||
IPlexEpisodeSync epsiodeSync)
|
IPlexEpisodeSync epsiodeSync, IRefreshMetadata metadataRefresh)
|
||||||
{
|
{
|
||||||
Plex = plex;
|
Plex = plex;
|
||||||
PlexApi = plexApi;
|
PlexApi = plexApi;
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
Repo = repo;
|
Repo = repo;
|
||||||
EpisodeSync = epsiodeSync;
|
EpisodeSync = epsiodeSync;
|
||||||
|
Metadata = metadataRefresh;
|
||||||
plex.ClearCache();
|
plex.ClearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +63,7 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
private ILogger<PlexContentSync> Logger { get; }
|
private ILogger<PlexContentSync> Logger { get; }
|
||||||
private IPlexContentRepository Repo { get; }
|
private IPlexContentRepository Repo { get; }
|
||||||
private IPlexEpisodeSync EpisodeSync { get; }
|
private IPlexEpisodeSync EpisodeSync { get; }
|
||||||
|
private IRefreshMetadata Metadata { get; }
|
||||||
|
|
||||||
public async Task CacheContent(bool recentlyAddedSearch = false)
|
public async Task CacheContent(bool recentlyAddedSearch = false)
|
||||||
{
|
{
|
||||||
|
@ -87,6 +90,7 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
|
|
||||||
Logger.LogInformation("Starting EP Cacher");
|
Logger.LogInformation("Starting EP Cacher");
|
||||||
BackgroundJob.Enqueue(() => EpisodeSync.Start());
|
BackgroundJob.Enqueue(() => EpisodeSync.Start());
|
||||||
|
BackgroundJob.Enqueue(() => Metadata.Start());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StartTheCache(PlexSettings plexSettings, bool recentlyAddedSearch)
|
private async Task StartTheCache(PlexSettings plexSettings, bool recentlyAddedSearch)
|
||||||
|
@ -115,187 +119,30 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
var contentToAdd = new HashSet<PlexServerContent>();
|
var contentToAdd = new HashSet<PlexServerContent>();
|
||||||
foreach (var content in allContent)
|
foreach (var content in allContent)
|
||||||
{
|
{
|
||||||
|
if (content.viewGroup.Equals(PlexMediaType.Episode.ToString(), StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Found some episodes, this must be a recently added sync");
|
||||||
|
foreach (var epInfo in content.Metadata)
|
||||||
|
{
|
||||||
|
var grandParentKey = epInfo.grandparentRatingKey;
|
||||||
|
// Lookup the rating key
|
||||||
|
var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri, grandParentKey);
|
||||||
|
var show = showMetadata.MediaContainer.Metadata.FirstOrDefault();
|
||||||
|
if(show == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ProcessTvShow(servers, show, contentToAdd, recentlyAddedSearch);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase))
|
if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
// Process Shows
|
// Process Shows
|
||||||
Logger.LogInformation("Processing TV Shows");
|
Logger.LogInformation("Processing TV Shows");
|
||||||
foreach (var show in content.Metadata ?? new Metadata[] { })
|
foreach (var show in content.Metadata ?? new Metadata[] { })
|
||||||
{
|
{
|
||||||
var seasonList = await PlexApi.GetSeasons(servers.PlexAuthToken, servers.FullUri,
|
await ProcessTvShow(servers, show, contentToAdd, recentlyAddedSearch);
|
||||||
show.ratingKey);
|
|
||||||
var seasonsContent = new List<PlexSeasonsContent>();
|
|
||||||
foreach (var season in seasonList.MediaContainer.Metadata)
|
|
||||||
{
|
|
||||||
seasonsContent.Add(new PlexSeasonsContent
|
|
||||||
{
|
|
||||||
ParentKey = season.parentRatingKey,
|
|
||||||
SeasonKey = season.ratingKey,
|
|
||||||
SeasonNumber = season.index,
|
|
||||||
PlexContentId = show.ratingKey
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do we already have this item?
|
|
||||||
// Let's try and match
|
|
||||||
var existingContent = await Repo.GetFirstContentByCustom(x => x.Title == show.title
|
|
||||||
&& x.ReleaseYear == show.year.ToString()
|
|
||||||
&& x.Type == PlexMediaTypeEntity.Show);
|
|
||||||
|
|
||||||
// Just double check the rating key, since this is our unique constraint
|
|
||||||
var existingKey = await Repo.GetByKey(show.ratingKey);
|
|
||||||
|
|
||||||
if (existingKey != null)
|
|
||||||
{
|
|
||||||
// Damn son.
|
|
||||||
// Let's check if they match up
|
|
||||||
var doesMatch = show.title.Equals(existingKey.Title,
|
|
||||||
StringComparison.CurrentCulture);
|
|
||||||
if (!doesMatch)
|
|
||||||
{
|
|
||||||
// Something fucked up on Plex at somepoint... Damn, rebuild of lib maybe?
|
|
||||||
// Lets delete the matching key
|
|
||||||
await Repo.Delete(existingKey);
|
|
||||||
existingKey = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingContent != null)
|
|
||||||
{
|
|
||||||
// Just check the key
|
|
||||||
if (existingKey != null)
|
|
||||||
{
|
|
||||||
// The rating key is all good!
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// This means the rating key has changed somehow.
|
|
||||||
// Should probably delete this and get the new one
|
|
||||||
var oldKey = existingContent.Key;
|
|
||||||
Repo.DeleteWithoutSave(existingContent);
|
|
||||||
|
|
||||||
// Because we have changed the rating key, we need to change all children too
|
|
||||||
var episodeToChange = Repo.GetAllEpisodes().Where(x => x.GrandparentKey == oldKey);
|
|
||||||
if (episodeToChange.Any())
|
|
||||||
{
|
|
||||||
foreach (var e in episodeToChange)
|
|
||||||
{
|
|
||||||
Repo.DeleteWithoutSave(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Repo.SaveChangesAsync();
|
|
||||||
existingContent = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The ratingKey keeps changing...
|
|
||||||
//var existingContent = await Repo.GetByKey(show.ratingKey);
|
|
||||||
if (existingContent != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Logger.LogInformation("We already have show {0} checking for new seasons",
|
|
||||||
existingContent.Title);
|
|
||||||
// Ok so we have it, let's check if there are any new seasons
|
|
||||||
var itemAdded = false;
|
|
||||||
foreach (var season in seasonsContent)
|
|
||||||
{
|
|
||||||
var seasonExists =
|
|
||||||
existingContent.Seasons.FirstOrDefault(x => x.SeasonKey == season.SeasonKey);
|
|
||||||
|
|
||||||
if (seasonExists != null)
|
|
||||||
{
|
|
||||||
// We already have this season
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
existingContent.Seasons.Add(season);
|
|
||||||
itemAdded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemAdded) await Repo.Update(existingContent);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogError(LoggingEvents.PlexContentCacher, e,
|
|
||||||
"Exception when adding new seasons to title {0}", existingContent.Title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Logger.LogInformation("New show {0}, so add it", show.title);
|
|
||||||
|
|
||||||
// Get the show metadata... This sucks since the `metadata` var contains all information about the show
|
|
||||||
// But it does not contain the `guid` property that we need to pull out thetvdb id...
|
|
||||||
var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri,
|
|
||||||
show.ratingKey);
|
|
||||||
var providerIds =
|
|
||||||
PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault()
|
|
||||||
.guid);
|
|
||||||
|
|
||||||
var item = new PlexServerContent
|
|
||||||
{
|
|
||||||
AddedAt = DateTime.Now,
|
|
||||||
Key = show.ratingKey,
|
|
||||||
ReleaseYear = show.year.ToString(),
|
|
||||||
Type = PlexMediaTypeEntity.Show,
|
|
||||||
Title = show.title,
|
|
||||||
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, show.ratingKey),
|
|
||||||
Seasons = new List<PlexSeasonsContent>()
|
|
||||||
};
|
|
||||||
if (providerIds.Type == ProviderType.ImdbId)
|
|
||||||
{
|
|
||||||
item.ImdbId = providerIds.ImdbId;
|
|
||||||
}
|
|
||||||
if (providerIds.Type == ProviderType.TheMovieDbId)
|
|
||||||
{
|
|
||||||
item.TheMovieDbId = providerIds.TheMovieDb;
|
|
||||||
}
|
|
||||||
if (providerIds.Type == ProviderType.TvDbId)
|
|
||||||
{
|
|
||||||
item.TvDbId = providerIds.TheTvDb;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's just double check to make sure we do not have it now we have some id's
|
|
||||||
var existingImdb = false;
|
|
||||||
var existingMovieDbId = false;
|
|
||||||
var existingTvDbId = false;
|
|
||||||
if (item.ImdbId.HasValue())
|
|
||||||
{
|
|
||||||
existingImdb = await Repo.GetAll().AnyAsync(x =>
|
|
||||||
x.ImdbId == item.ImdbId && x.Type == PlexMediaTypeEntity.Show);
|
|
||||||
}
|
|
||||||
if (item.TheMovieDbId.HasValue())
|
|
||||||
{
|
|
||||||
existingMovieDbId = await Repo.GetAll().AnyAsync(x =>
|
|
||||||
x.TheMovieDbId == item.TheMovieDbId && x.Type == PlexMediaTypeEntity.Show);
|
|
||||||
}
|
|
||||||
if (item.TvDbId.HasValue())
|
|
||||||
{
|
|
||||||
existingTvDbId = await Repo.GetAll().AnyAsync(x =>
|
|
||||||
x.TvDbId == item.TvDbId && x.Type == PlexMediaTypeEntity.Show);
|
|
||||||
}
|
|
||||||
if (existingImdb || existingTvDbId || existingMovieDbId)
|
|
||||||
{
|
|
||||||
// We already have it!
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
item.Seasons.ToList().AddRange(seasonsContent);
|
|
||||||
|
|
||||||
contentToAdd.Add(item);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogError(LoggingEvents.PlexContentCacher, e, "Exception when adding tv show {0}",
|
|
||||||
show.title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (contentToAdd.Count > 500)
|
|
||||||
{
|
|
||||||
await Repo.AddRange(contentToAdd);
|
|
||||||
contentToAdd.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (content.viewGroup.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase))
|
if (content.viewGroup.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
@ -382,6 +229,193 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ProcessTvShow(PlexServers servers, Metadata show, HashSet<PlexServerContent> contentToAdd, bool recentlyAdded)
|
||||||
|
{
|
||||||
|
var seasonList = await PlexApi.GetSeasons(servers.PlexAuthToken, servers.FullUri,
|
||||||
|
show.ratingKey);
|
||||||
|
var seasonsContent = new List<PlexSeasonsContent>();
|
||||||
|
foreach (var season in seasonList.MediaContainer.Metadata)
|
||||||
|
{
|
||||||
|
seasonsContent.Add(new PlexSeasonsContent
|
||||||
|
{
|
||||||
|
ParentKey = season.parentRatingKey,
|
||||||
|
SeasonKey = season.ratingKey,
|
||||||
|
SeasonNumber = season.index,
|
||||||
|
PlexContentId = show.ratingKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do we already have this item?
|
||||||
|
// Let's try and match
|
||||||
|
var existingContent = await Repo.GetFirstContentByCustom(x => x.Title == show.title
|
||||||
|
&& x.ReleaseYear == show.year.ToString()
|
||||||
|
&& x.Type == PlexMediaTypeEntity.Show);
|
||||||
|
|
||||||
|
// Just double check the rating key, since this is our unique constraint
|
||||||
|
var existingKey = await Repo.GetByKey(show.ratingKey);
|
||||||
|
|
||||||
|
if (existingKey != null)
|
||||||
|
{
|
||||||
|
// Damn son.
|
||||||
|
// Let's check if they match up
|
||||||
|
var doesMatch = show.title.Equals(existingKey.Title,
|
||||||
|
StringComparison.CurrentCulture);
|
||||||
|
if (!doesMatch)
|
||||||
|
{
|
||||||
|
// Something fucked up on Plex at somepoint... Damn, rebuild of lib maybe?
|
||||||
|
// Lets delete the matching key
|
||||||
|
await Repo.Delete(existingKey);
|
||||||
|
existingKey = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingContent != null)
|
||||||
|
{
|
||||||
|
// Just check the key
|
||||||
|
if (existingKey != null)
|
||||||
|
{
|
||||||
|
// The rating key is all good!
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This means the rating key has changed somehow.
|
||||||
|
// Should probably delete this and get the new one
|
||||||
|
var oldKey = existingContent.Key;
|
||||||
|
Repo.DeleteWithoutSave(existingContent);
|
||||||
|
|
||||||
|
// Because we have changed the rating key, we need to change all children too
|
||||||
|
var episodeToChange = Repo.GetAllEpisodes().Where(x => x.GrandparentKey == oldKey);
|
||||||
|
if (episodeToChange.Any())
|
||||||
|
{
|
||||||
|
foreach (var e in episodeToChange)
|
||||||
|
{
|
||||||
|
Repo.DeleteWithoutSave(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Repo.SaveChangesAsync();
|
||||||
|
existingContent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ratingKey keeps changing...
|
||||||
|
//var existingContent = await Repo.GetByKey(show.ratingKey);
|
||||||
|
if (existingContent != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger.LogInformation("We already have show {0} checking for new seasons",
|
||||||
|
existingContent.Title);
|
||||||
|
// Ok so we have it, let's check if there are any new seasons
|
||||||
|
var itemAdded = false;
|
||||||
|
foreach (var season in seasonsContent)
|
||||||
|
{
|
||||||
|
var seasonExists =
|
||||||
|
existingContent.Seasons.FirstOrDefault(x => x.SeasonKey == season.SeasonKey);
|
||||||
|
|
||||||
|
if (seasonExists != null)
|
||||||
|
{
|
||||||
|
// We already have this season
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
existingContent.Seasons.Add(season);
|
||||||
|
itemAdded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemAdded) await Repo.Update(existingContent);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError(LoggingEvents.PlexContentCacher, e,
|
||||||
|
"Exception when adding new seasons to title {0}", existingContent.Title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger.LogInformation("New show {0}, so add it", show.title);
|
||||||
|
|
||||||
|
// Get the show metadata... This sucks since the `metadata` var contains all information about the show
|
||||||
|
// But it does not contain the `guid` property that we need to pull out thetvdb id...
|
||||||
|
var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri,
|
||||||
|
show.ratingKey);
|
||||||
|
var providerIds =
|
||||||
|
PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault()
|
||||||
|
.guid);
|
||||||
|
|
||||||
|
var item = new PlexServerContent
|
||||||
|
{
|
||||||
|
AddedAt = DateTime.Now,
|
||||||
|
Key = show.ratingKey,
|
||||||
|
ReleaseYear = show.year.ToString(),
|
||||||
|
Type = PlexMediaTypeEntity.Show,
|
||||||
|
Title = show.title,
|
||||||
|
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, show.ratingKey),
|
||||||
|
Seasons = new List<PlexSeasonsContent>()
|
||||||
|
};
|
||||||
|
if (providerIds.Type == ProviderType.ImdbId)
|
||||||
|
{
|
||||||
|
item.ImdbId = providerIds.ImdbId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (providerIds.Type == ProviderType.TheMovieDbId)
|
||||||
|
{
|
||||||
|
item.TheMovieDbId = providerIds.TheMovieDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (providerIds.Type == ProviderType.TvDbId)
|
||||||
|
{
|
||||||
|
item.TvDbId = providerIds.TheTvDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's just double check to make sure we do not have it now we have some id's
|
||||||
|
var existingImdb = false;
|
||||||
|
var existingMovieDbId = false;
|
||||||
|
var existingTvDbId = false;
|
||||||
|
if (item.ImdbId.HasValue())
|
||||||
|
{
|
||||||
|
existingImdb = await Repo.GetAll().AnyAsync(x =>
|
||||||
|
x.ImdbId == item.ImdbId && x.Type == PlexMediaTypeEntity.Show);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.TheMovieDbId.HasValue())
|
||||||
|
{
|
||||||
|
existingMovieDbId = await Repo.GetAll().AnyAsync(x =>
|
||||||
|
x.TheMovieDbId == item.TheMovieDbId && x.Type == PlexMediaTypeEntity.Show);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.TvDbId.HasValue())
|
||||||
|
{
|
||||||
|
existingTvDbId = await Repo.GetAll().AnyAsync(x =>
|
||||||
|
x.TvDbId == item.TvDbId && x.Type == PlexMediaTypeEntity.Show);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingImdb || existingTvDbId || existingMovieDbId)
|
||||||
|
{
|
||||||
|
// We already have it!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.Seasons.ToList().AddRange(seasonsContent);
|
||||||
|
|
||||||
|
contentToAdd.Add(item);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError(LoggingEvents.PlexContentCacher, e, "Exception when adding tv show {0}",
|
||||||
|
show.title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentToAdd.Count > 500 || recentlyAdded)
|
||||||
|
{
|
||||||
|
await Repo.AddRange(contentToAdd);
|
||||||
|
contentToAdd.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all the library sections.
|
/// Gets all the library sections.
|
||||||
/// If the user has specified only certain libraries then we will only look for those
|
/// If the user has specified only certain libraries then we will only look for those
|
||||||
|
|
|
@ -50,12 +50,13 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
await Cache(server);
|
await Cache(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
BackgroundJob.Enqueue(() => _availabilityChecker.Start());
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_log.LogError(LoggingEvents.Cacher, e, "Caching Episodes Failed");
|
_log.LogError(LoggingEvents.Cacher, e, "Caching Episodes Failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BackgroundJob.Enqueue(() => _availabilityChecker.Start());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Cache(PlexServers settings)
|
private async Task Cache(PlexServers settings)
|
||||||
|
|
|
@ -31,6 +31,7 @@ namespace Ombi.Schedule.Jobs
|
||||||
public enum PlexMediaType
|
public enum PlexMediaType
|
||||||
{
|
{
|
||||||
Movie = 0,
|
Movie = 0,
|
||||||
Show = 1
|
Show = 1,
|
||||||
|
Episode = 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
40
src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs
Normal file
40
src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Hangfire;
|
||||||
|
|
||||||
|
namespace Ombi.Schedule.Jobs.Plex
|
||||||
|
{
|
||||||
|
public class PlexRecentlyAddedSync : IPlexRecentlyAddedSync
|
||||||
|
{
|
||||||
|
public PlexRecentlyAddedSync(IPlexContentSync sync)
|
||||||
|
{
|
||||||
|
_sync = sync;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly IPlexContentSync _sync;
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
BackgroundJob.Enqueue(() => _sync.CacheContent(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _disposed;
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_sync?.Dispose();
|
||||||
|
}
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,7 +21,7 @@ namespace Ombi.Settings.Settings.Models
|
||||||
}
|
}
|
||||||
public static string PlexContent(JobSettings s)
|
public static string PlexContent(JobSettings s)
|
||||||
{
|
{
|
||||||
return Get(s.PlexContentSync, Cron.HourInterval(6));
|
return Get(s.PlexContentSync, Cron.Daily(2));
|
||||||
}
|
}
|
||||||
public static string PlexRecentlyAdded(JobSettings s)
|
public static string PlexRecentlyAdded(JobSettings s)
|
||||||
{
|
{
|
||||||
|
@ -50,7 +50,7 @@ namespace Ombi.Settings.Settings.Models
|
||||||
}
|
}
|
||||||
public static string RefreshMetadata(JobSettings s)
|
public static string RefreshMetadata(JobSettings s)
|
||||||
{
|
{
|
||||||
return Get(s.RefreshMetadata, Cron.Daily(3));
|
return Get(s.RefreshMetadata, Cron.DayInterval(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Get(string settings, string defaultCron)
|
private static string Get(string settings, string defaultCron)
|
||||||
|
|
|
@ -51,6 +51,15 @@ namespace Ombi.Store.Entities
|
||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
|
|
||||||
public ICollection<EmbyEpisode> Episodes { get; set; }
|
public ICollection<EmbyEpisode> Episodes { get; set; }
|
||||||
|
|
||||||
|
[NotMapped]
|
||||||
|
public bool HasImdb => !string.IsNullOrEmpty(ImdbId);
|
||||||
|
|
||||||
|
[NotMapped]
|
||||||
|
public bool HasTvDb => !string.IsNullOrEmpty(TvDbId);
|
||||||
|
|
||||||
|
[NotMapped]
|
||||||
|
public bool HasTheMovieDb => !string.IsNullOrEmpty(TheMovieDbId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum EmbyMediaType
|
public enum EmbyMediaType
|
||||||
|
|
|
@ -1,132 +1,134 @@
|
||||||
|
|
||||||
using System.Collections.Generic;
|
//using System.Collections.Generic;
|
||||||
using System.Linq;
|
//using System.Linq;
|
||||||
using System.Threading;
|
//using System.Threading;
|
||||||
using System.Threading.Tasks;
|
//using System.Threading.Tasks;
|
||||||
using AutoMapper;
|
//using AutoMapper;
|
||||||
using Microsoft.AspNetCore.Http;
|
//using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features.Authentication;
|
//using Microsoft.AspNetCore.Http.Features.Authentication;
|
||||||
using Microsoft.AspNetCore.Identity;
|
//using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
//using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
//using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
//using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
//using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Options;
|
//using Microsoft.Extensions.Options;
|
||||||
using Moq;
|
//using Moq;
|
||||||
using NUnit.Framework;
|
//using NUnit.Framework;
|
||||||
using Ombi.Api.Emby;
|
//using Ombi.Api.Emby;
|
||||||
using Ombi.Api.Plex;
|
//using Ombi.Api.Plex;
|
||||||
using Ombi.Config;
|
//using Ombi.Config;
|
||||||
using Ombi.Controllers;
|
//using Ombi.Controllers;
|
||||||
using Ombi.Core.Authentication;
|
//using Ombi.Core.Authentication;
|
||||||
using Ombi.Core.Settings;
|
//using Ombi.Core.Settings;
|
||||||
using Ombi.Core.Settings.Models.External;
|
//using Ombi.Core.Settings.Models.External;
|
||||||
using Ombi.Models;
|
//using Ombi.Models;
|
||||||
using Ombi.Notifications;
|
//using Ombi.Notifications;
|
||||||
using Ombi.Schedule.Jobs.Ombi;
|
//using Ombi.Schedule.Jobs.Ombi;
|
||||||
using Ombi.Settings.Settings.Models;
|
//using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Settings.Settings.Models.Notifications;
|
//using Ombi.Settings.Settings.Models.Notifications;
|
||||||
using Ombi.Store.Context;
|
//using Ombi.Store.Context;
|
||||||
using Ombi.Store.Entities;
|
//using Ombi.Store.Entities;
|
||||||
|
|
||||||
namespace Ombi.Tests
|
//namespace Ombi.Tests
|
||||||
{
|
//{
|
||||||
[TestFixture]
|
// [TestFixture]
|
||||||
[Ignore("Need to sort out the DB, looks like it's using the real one...")]
|
// [Ignore("Need to sort out the DB, looks like it's using the real one...")]
|
||||||
public class IdentityControllerTests
|
// public class IdentityControllerTests
|
||||||
{
|
// {
|
||||||
[SetUp]
|
// [SetUp]
|
||||||
public void Setup()
|
// public void Setup()
|
||||||
{
|
// {
|
||||||
_plexApi = new Mock<IPlexApi>();
|
// _plexApi = new Mock<IPlexApi>();
|
||||||
_embyApi = new Mock<IEmbyApi>();
|
// _embyApi = new Mock<IEmbyApi>();
|
||||||
_mapper = new Mock<IMapper>();
|
// _mapper = new Mock<IMapper>();
|
||||||
_emailProvider = new Mock<IEmailProvider>();
|
// _emailProvider = new Mock<IEmailProvider>();
|
||||||
_emailSettings = new Mock<ISettingsService<EmailNotificationSettings>>();
|
// _emailSettings = new Mock<ISettingsService<EmailNotificationSettings>>();
|
||||||
_customizationSettings = new Mock<ISettingsService<CustomizationSettings>>();
|
// _customizationSettings = new Mock<ISettingsService<CustomizationSettings>>();
|
||||||
_welcomeEmail = new Mock<IWelcomeEmail>();
|
// _welcomeEmail = new Mock<IWelcomeEmail>();
|
||||||
_embySettings = new Mock<ISettingsService<EmbySettings>>();
|
// _embySettings = new Mock<ISettingsService<EmbySettings>>();
|
||||||
_plexSettings = new Mock<ISettingsService<PlexSettings>>();
|
// _plexSettings = new Mock<ISettingsService<PlexSettings>>();
|
||||||
|
|
||||||
var services = new ServiceCollection();
|
// var services = new ServiceCollection();
|
||||||
services.AddEntityFrameworkInMemoryDatabase()
|
// services.AddEntityFrameworkInMemoryDatabase()
|
||||||
.AddDbContext<OmbiContext>();
|
// .AddDbContext<OmbiContext>();
|
||||||
services.AddIdentity<OmbiUser, IdentityRole>()
|
// services.AddIdentity<OmbiUser, IdentityRole>()
|
||||||
.AddEntityFrameworkStores<OmbiContext>().AddUserManager<OmbiUserManager>();
|
// .AddEntityFrameworkStores<OmbiContext>().AddUserManager<OmbiUserManager>();
|
||||||
|
|
||||||
services.AddTransient(x => _plexApi.Object);
|
// services.AddTransient(x => _plexApi.Object);
|
||||||
services.AddTransient(x => _embyApi.Object);
|
// services.AddTransient(x => _embyApi.Object);
|
||||||
services.AddTransient(x => _customizationSettings.Object);
|
// services.AddTransient(x => _customizationSettings.Object);
|
||||||
services.AddTransient(x => _welcomeEmail.Object);
|
// services.AddTransient(x => _welcomeEmail.Object);
|
||||||
services.AddTransient(x => _emailSettings.Object);
|
// services.AddTransient(x => _emailSettings.Object);
|
||||||
services.AddTransient(x => _emailProvider.Object);
|
// services.AddTransient(x => _emailProvider.Object);
|
||||||
services.AddTransient(x => _mapper.Object);
|
// services.AddTransient(x => _mapper.Object);
|
||||||
services.AddTransient(x => _embySettings.Object);
|
// services.AddTransient(x => _embySettings.Object);
|
||||||
services.AddTransient(x => _plexSettings.Object);
|
// services.AddTransient(x => _plexSettings.Object);
|
||||||
|
|
||||||
services.Configure<IdentityOptions>(options =>
|
// services.Configure<IdentityOptions>(options =>
|
||||||
{
|
// {
|
||||||
options.Password.RequireDigit = false;
|
// options.Password.RequireDigit = false;
|
||||||
options.Password.RequiredLength = 1;
|
// options.Password.RequiredLength = 1;
|
||||||
options.Password.RequireLowercase = false;
|
// options.Password.RequireLowercase = false;
|
||||||
options.Password.RequireNonAlphanumeric = false;
|
// options.Password.RequireNonAlphanumeric = false;
|
||||||
options.Password.RequireUppercase = false;
|
// options.Password.RequireUppercase = false;
|
||||||
options.User.AllowedUserNameCharacters = string.Empty;
|
// options.User.AllowedUserNameCharacters = string.Empty;
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Taken from https://github.com/aspnet/MusicStore/blob/dev/test/MusicStore.Test/ManageControllerTest.cs (and modified)
|
// // Taken from https://github.com/aspnet/MusicStore/blob/dev/test/MusicStore.Test/ManageControllerTest.cs (and modified)
|
||||||
var context = new DefaultHttpContext();
|
// var context = new DefaultHttpContext();
|
||||||
context.Features.Set<IHttpAuthenticationFeature>(new HttpAuthenticationFeature());
|
// context.Features.Set<IHttpAuthenticationFeature>(new HttpAuthenticationFeature());
|
||||||
services.AddSingleton<IHttpContextAccessor>(h => new HttpContextAccessor { HttpContext = context });
|
// services.AddSingleton<IHttpContextAccessor>(h => new HttpContextAccessor { HttpContext = context });
|
||||||
_serviceProvider = services.BuildServiceProvider();
|
// _serviceProvider = services.BuildServiceProvider();
|
||||||
_userManager = _serviceProvider.GetRequiredService<OmbiUserManager>();
|
// _userManager = _serviceProvider.GetRequiredService<OmbiUserManager>();
|
||||||
|
|
||||||
Controller = new IdentityController(_userManager, _mapper.Object, _serviceProvider.GetService<RoleManager<IdentityRole>>(), _emailProvider.Object,
|
// Controller = new IdentityController(_userManager, _mapper.Object,
|
||||||
_emailSettings.Object, _customizationSettings.Object,_welcomeEmail.Object, null, null, null, null, null, null, null, null);
|
// _serviceProvider.GetService<RoleManager<IdentityRole>>(), _emailProvider.Object,
|
||||||
}
|
// _emailSettings.Object, _customizationSettings.Object, _welcomeEmail.Object, null, null, null, null,
|
||||||
|
// null, null, null, null, null);
|
||||||
|
// }
|
||||||
|
|
||||||
private OmbiUserManager _userManager;
|
// private OmbiUserManager _userManager;
|
||||||
private Mock<IEmailProvider> _emailProvider;
|
// private Mock<IEmailProvider> _emailProvider;
|
||||||
private Mock<ISettingsService<EmailNotificationSettings>> _emailSettings;
|
// private Mock<ISettingsService<EmailNotificationSettings>> _emailSettings;
|
||||||
private Mock<ISettingsService<CustomizationSettings>> _customizationSettings;
|
// private Mock<ISettingsService<CustomizationSettings>> _customizationSettings;
|
||||||
private Mock<ISettingsService<EmbySettings>> _embySettings;
|
// private Mock<ISettingsService<EmbySettings>> _embySettings;
|
||||||
private Mock<ISettingsService<PlexSettings>> _plexSettings;
|
// private Mock<ISettingsService<PlexSettings>> _plexSettings;
|
||||||
private Mock<IWelcomeEmail> _welcomeEmail;
|
// private Mock<IWelcomeEmail> _welcomeEmail;
|
||||||
private Mock<IMapper> _mapper;
|
// private Mock<IMapper> _mapper;
|
||||||
private Mock<IPlexApi> _plexApi;
|
// private Mock<IPlexApi> _plexApi;
|
||||||
private Mock<IEmbyApi> _embyApi;
|
// private Mock<IEmbyApi> _embyApi;
|
||||||
private ServiceProvider _serviceProvider;
|
// private ServiceProvider _serviceProvider;
|
||||||
|
|
||||||
private IdentityController Controller { get; set; }
|
// private IdentityController Controller { get; set; }
|
||||||
|
|
||||||
[Test]
|
// [Test]
|
||||||
public async Task CreateWizardUser_Should_CreateUser_WhenThereAreNoOtherUsers()
|
// public async Task CreateWizardUser_Should_CreateUser_WhenThereAreNoOtherUsers()
|
||||||
{
|
// {
|
||||||
var model = new CreateUserWizardModel()
|
// var model = new CreateUserWizardModel()
|
||||||
{
|
// {
|
||||||
Password = "a",
|
// Password = "a",
|
||||||
Username = "b"
|
// Username = "b"
|
||||||
};
|
// };
|
||||||
|
|
||||||
var result = await Controller.CreateWizardUser(model);
|
// var result = await Controller.CreateWizardUser(model);
|
||||||
|
|
||||||
Assert.That(result, Is.True);
|
// Assert.That(result, Is.True);
|
||||||
}
|
// }
|
||||||
|
|
||||||
[Test]
|
// [Test]
|
||||||
public async Task CreateWizardUser_ShouldNot_CreateUser_WhenThereAreOtherUsers()
|
// public async Task CreateWizardUser_ShouldNot_CreateUser_WhenThereAreOtherUsers()
|
||||||
{
|
// {
|
||||||
var um = _serviceProvider.GetService<OmbiUserManager>();
|
// var um = _serviceProvider.GetService<OmbiUserManager>();
|
||||||
var r = await um.CreateAsync(new OmbiUser { UserName = "aaaa",UserType = UserType.LocalUser}, "bbb");
|
// var r = await um.CreateAsync(new OmbiUser { UserName = "aaaa",UserType = UserType.LocalUser}, "bbb");
|
||||||
var model = new CreateUserWizardModel
|
// var model = new CreateUserWizardModel
|
||||||
{
|
// {
|
||||||
Password = "a",
|
// Password = "a",
|
||||||
Username = "b"
|
// Username = "b"
|
||||||
};
|
// };
|
||||||
|
|
||||||
var result = await Controller.CreateWizardUser(model);
|
// var result = await Controller.CreateWizardUser(model);
|
||||||
|
|
||||||
Assert.That(result, Is.False);
|
// Assert.That(result, Is.False);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
|
@ -18,5 +18,6 @@ namespace Ombi.Api.TheMovieDb
|
||||||
Task<List<MovieSearchResult>> SimilarMovies(int movieId);
|
Task<List<MovieSearchResult>> SimilarMovies(int movieId);
|
||||||
Task<FindResult> Find(string externalId, ExternalSource source);
|
Task<FindResult> Find(string externalId, ExternalSource source);
|
||||||
Task<TvExternals> GetTvExternals(int theMovieDbId);
|
Task<TvExternals> GetTvExternals(int theMovieDbId);
|
||||||
|
Task<TvInfo> GetTVInfo(string themoviedbid);
|
||||||
}
|
}
|
||||||
}
|
}
|
74
src/Ombi.TheMovieDbApi/Models/TvInfo.cs
Normal file
74
src/Ombi.TheMovieDbApi/Models/TvInfo.cs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
namespace Ombi.Api.TheMovieDb.Models
|
||||||
|
{
|
||||||
|
public class TvInfo
|
||||||
|
{
|
||||||
|
public string backdrop_path { get; set; }
|
||||||
|
public Created_By[] created_by { get; set; }
|
||||||
|
public int[] episode_run_time { get; set; }
|
||||||
|
public string first_air_date { get; set; }
|
||||||
|
public Genre[] genres { get; set; }
|
||||||
|
public string homepage { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
public bool in_production { get; set; }
|
||||||
|
public string[] languages { get; set; }
|
||||||
|
public string last_air_date { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public Network[] networks { get; set; }
|
||||||
|
public int number_of_episodes { get; set; }
|
||||||
|
public int number_of_seasons { get; set; }
|
||||||
|
public string[] origin_country { get; set; }
|
||||||
|
public string original_language { get; set; }
|
||||||
|
public string original_name { get; set; }
|
||||||
|
public string overview { get; set; }
|
||||||
|
public float popularity { get; set; }
|
||||||
|
public string poster_path { get; set; }
|
||||||
|
public Production_Companies[] production_companies { get; set; }
|
||||||
|
public Season[] seasons { get; set; }
|
||||||
|
public string status { get; set; }
|
||||||
|
public string type { get; set; }
|
||||||
|
public float vote_average { get; set; }
|
||||||
|
public int vote_count { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Created_By
|
||||||
|
{
|
||||||
|
public int id { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public int gender { get; set; }
|
||||||
|
public string profile_path { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Genre
|
||||||
|
{
|
||||||
|
public int id { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Network
|
||||||
|
{
|
||||||
|
public string name { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
public string logo_path { get; set; }
|
||||||
|
public string origin_country { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Production_Companies
|
||||||
|
{
|
||||||
|
public int id { get; set; }
|
||||||
|
public string logo_path { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public string origin_country { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Season
|
||||||
|
{
|
||||||
|
public string air_date { get; set; }
|
||||||
|
public int episode_count { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public string overview { get; set; }
|
||||||
|
public string poster_path { get; set; }
|
||||||
|
public int season_number { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -129,6 +129,15 @@ namespace Ombi.Api.TheMovieDb
|
||||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<TvInfo> GetTVInfo(string themoviedbid)
|
||||||
|
{
|
||||||
|
var request = new Request($"/tv/{themoviedbid}", BaseUri, HttpMethod.Get);
|
||||||
|
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||||
|
AddRetry(request);
|
||||||
|
|
||||||
|
return await Api.Request<TvInfo>(request);
|
||||||
|
}
|
||||||
private static void AddRetry(Request request)
|
private static void AddRetry(Request request)
|
||||||
{
|
{
|
||||||
request.Retry = true;
|
request.Retry = true;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="{{ user.name && (hasRole('Admin') || hasRole('PowerUser')) ? 'adminUser' : 'user'}}">
|
<div [ngClass]="user.name && roleClass()">
|
||||||
<p-growl [value]="notificationService.messages" [life]="3000"></p-growl>
|
<p-growl [value]="notificationService.messages" [life]="3000"></p-growl>
|
||||||
<nav *ngIf="showNav" class="navbar navbar-default navbar-fixed-top">
|
<nav *ngIf="showNav" class="navbar navbar-default navbar-fixed-top">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
|
@ -72,7 +72,7 @@
|
||||||
|
|
||||||
|
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
<li *ngIf="hasRole('Admin') " [routerLinkActive]="['active']">
|
<li *ngIf="hasRole('Admin') " id="settings" [routerLinkActive]="['active']">
|
||||||
<a [routerLink]="['/Settings/About']">
|
<a [routerLink]="['/Settings/About']">
|
||||||
|
|
||||||
<i *ngIf="!updateAvailable" class="fa fa-cog"></i>
|
<i *ngIf="!updateAvailable" class="fa fa-cog"></i>
|
||||||
|
|
|
@ -73,6 +73,15 @@ export class AppComponent implements OnInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public roleClass() {
|
||||||
|
if (this.user.roles.some(r => r === "Admin")) {
|
||||||
|
return "adminUser";
|
||||||
|
} else if (this.user.roles.some(r => r === "PowerUser")) {
|
||||||
|
return "powerUser";
|
||||||
|
}
|
||||||
|
return "user";
|
||||||
|
}
|
||||||
|
|
||||||
public hasRole(role: string): boolean {
|
public hasRole(role: string): boolean {
|
||||||
return this.user.roles.some(r => r === role);
|
return this.user.roles.some(r => r === role);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,11 @@ export interface IMovieRequests extends IFullBaseRequest {
|
||||||
qualityOverrideTitle: string;
|
qualityOverrideTitle: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IFilterResult<T> {
|
||||||
|
total: number;
|
||||||
|
collection: T[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface IMovieUpdateModel {
|
export interface IMovieUpdateModel {
|
||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
|
@ -103,6 +108,8 @@ export interface IMovieRequestModel {
|
||||||
export interface IFilter {
|
export interface IFilter {
|
||||||
availabilityFilter: FilterType;
|
availabilityFilter: FilterType;
|
||||||
statusFilter: FilterType;
|
statusFilter: FilterType;
|
||||||
|
position: number;
|
||||||
|
count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum FilterType {
|
export enum FilterType {
|
||||||
|
|
|
@ -5,12 +5,25 @@
|
||||||
<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">
|
<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>
|
<div class="issue-status">
|
||||||
<h3 *ngIf="!issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}: {{issue.userReported.userName}}</h3>
|
<span *ngIf="issue.status === IssueStatus.Pending" id="pendingLabel" class="label label-warning">{{IssueStatus[issue.status]}}</span>
|
||||||
<h3 *ngIf="issue.subject">{{'Issues.Subject' | translate}}: {{issue.subject}}</h3>
|
<span *ngIf="issue.status === IssueStatus.InProgress" id="inprogressLabel" class="label label-info">{{IssueStatus[issue.status]}}</span>
|
||||||
|
<span *ngIf="issue.status === IssueStatus.Resolved" id="resolvedLabel" class="label label-success">{{IssueStatus[issue.status]}}</span>
|
||||||
|
</div>
|
||||||
|
<span class="label label-success">{{issue.issueCategory.value}}</span>
|
||||||
|
<br>
|
||||||
|
<span class="reported-by">
|
||||||
|
<h3 *ngIf="issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}:</h3>
|
||||||
|
<h3 *ngIf="!issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}:</h3>
|
||||||
|
</span>
|
||||||
|
<span class="reported-user">
|
||||||
|
<h3 *ngIf="issue.userReported?.alias">{{issue.userReported.alias}}</h3>
|
||||||
|
<h3 *ngIf="!issue.userReported?.alias">{{issue.userReported.userName}}</h3>
|
||||||
|
</span>
|
||||||
|
<br>
|
||||||
|
<span class="subject-category"><h3 *ngIf="issue.subject">{{'Issues.Subject' | translate}}:</h3></span>
|
||||||
|
<span class="subject"><h3 *ngIf="issue.subject">{{issue.subject}}</h3></span>
|
||||||
<br>
|
<br>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="description" class="control-label" [translate]="'Issues.Description'"></label>
|
<label for="description" class="control-label" [translate]="'Issues.Description'"></label>
|
||||||
|
|
|
@ -37,7 +37,7 @@ export class MovieRequestsComponent implements OnInit {
|
||||||
public filterType = FilterType;
|
public filterType = FilterType;
|
||||||
|
|
||||||
public order: string = "requestedDate";
|
public order: string = "requestedDate";
|
||||||
public reverse = false;
|
public reverse = true;
|
||||||
|
|
||||||
public totalMovies: number = 100;
|
public totalMovies: number = 100;
|
||||||
private currentlyLoaded: number;
|
private currentlyLoaded: number;
|
||||||
|
@ -72,7 +72,10 @@ export class MovieRequestsComponent implements OnInit {
|
||||||
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||||
this.filter = {
|
this.filter = {
|
||||||
availabilityFilter: FilterType.None,
|
availabilityFilter: FilterType.None,
|
||||||
statusFilter: FilterType.None};
|
statusFilter: FilterType.None,
|
||||||
|
count: this.amountToLoad,
|
||||||
|
position: 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public paginate(event: IPagenator) {
|
public paginate(event: IPagenator) {
|
||||||
|
@ -174,8 +177,9 @@ export class MovieRequestsComponent implements OnInit {
|
||||||
this.filter.availabilityFilter = filter;
|
this.filter.availabilityFilter = filter;
|
||||||
this.requestService.filterMovies(this.filter)
|
this.requestService.filterMovies(this.filter)
|
||||||
.subscribe(x => {
|
.subscribe(x => {
|
||||||
this.setOverrides(x);
|
this.totalMovies = x.total;
|
||||||
this.movieRequests = x;
|
this.setOverrides(x.collection);
|
||||||
|
this.movieRequests = x.collection;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,8 +188,9 @@ export class MovieRequestsComponent implements OnInit {
|
||||||
this.filter.statusFilter = filter;
|
this.filter.statusFilter = filter;
|
||||||
this.requestService.filterMovies(this.filter)
|
this.requestService.filterMovies(this.filter)
|
||||||
.subscribe(x => {
|
.subscribe(x => {
|
||||||
this.setOverrides(x);
|
this.totalMovies = x.total;
|
||||||
this.movieRequests = x;
|
this.setOverrides(x.collection);
|
||||||
|
this.movieRequests = x.collection;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,7 +228,8 @@ export class MovieRequestsComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadRequests(amountToLoad: number, currentlyLoaded: number) {
|
private loadRequests(amountToLoad: number, currentlyLoaded: number) {
|
||||||
this.requestService.getMovieRequests(amountToLoad, currentlyLoaded + 1)
|
if(this.filter.availabilityFilter === FilterType.None && this.filter.statusFilter === FilterType.None) {
|
||||||
|
this.requestService.getMovieRequests(amountToLoad, currentlyLoaded + 1)
|
||||||
.subscribe(x => {
|
.subscribe(x => {
|
||||||
this.setOverrides(x);
|
this.setOverrides(x);
|
||||||
if(!this.movieRequests) {
|
if(!this.movieRequests) {
|
||||||
|
@ -232,6 +238,16 @@ export class MovieRequestsComponent implements OnInit {
|
||||||
this.movieRequests = x;
|
this.movieRequests = x;
|
||||||
this.currentlyLoaded = currentlyLoaded + amountToLoad;
|
this.currentlyLoaded = currentlyLoaded + amountToLoad;
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
this.filter.position = currentlyLoaded;
|
||||||
|
this.requestService.filterMovies(this.filter)
|
||||||
|
.subscribe(x => {
|
||||||
|
this.setOverrides(x.collection);
|
||||||
|
this.totalMovies = x.total;
|
||||||
|
this.movieRequests = x.collection;
|
||||||
|
this.currentlyLoaded = currentlyLoaded + amountToLoad;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateRequest(request: IMovieRequests) {
|
private updateRequest(request: IMovieRequests) {
|
||||||
|
|
|
@ -85,7 +85,7 @@
|
||||||
<br/>
|
<br/>
|
||||||
<div *ngIf="result.available">
|
<div *ngIf="result.available">
|
||||||
<a *ngIf="result.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.plexUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Plex</a>
|
<a *ngIf="result.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.plexUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Plex</a>
|
||||||
<a *ngIf="result.embyUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.embyUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Emby</a>
|
<a *ngIf="result.embyUrl" style="text-align: right" id="embybtn" class="btn btn-sm btn-success-outline" href="{{result.embyUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Emby</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown" *ngIf="result.available && issueCategories && issuesEnabled">
|
<div class="dropdown" *ngIf="result.available && issueCategories && issuesEnabled">
|
||||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||||
|
|
|
@ -129,7 +129,7 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="node.data.embyUrl && node.data.available">
|
<div *ngIf="node.data.embyUrl && node.data.available">
|
||||||
<a style="text-align: right" class="btn btn-sm btn-success-outline" href="{{node.data.embyUrl}}"
|
<a style="text-align: right" id="embybtn" class="btn btn-sm btn-success-outline" href="{{node.data.embyUrl}}"
|
||||||
target="_blank">
|
target="_blank">
|
||||||
<i class="fa fa-eye"></i> {{ 'Search.ViewOnEmby' | translate }}
|
<i class="fa fa-eye"></i> {{ 'Search.ViewOnEmby' | translate }}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -6,7 +6,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, IFilterResult, IMovieRequestModel, IMovieRequests, IMovieUpdateModel, ITvRequests,ITvUpdateModel } from "../interfaces";
|
||||||
import { ITvRequestViewModel } from "../interfaces";
|
import { ITvRequestViewModel } from "../interfaces";
|
||||||
import { ServiceHelpers } from "./service.helpers";
|
import { ServiceHelpers } from "./service.helpers";
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ export class RequestService extends ServiceHelpers {
|
||||||
public deleteChild(child: IChildRequests): Observable<boolean> {
|
public deleteChild(child: IChildRequests): Observable<boolean> {
|
||||||
return this.http.delete<boolean>(`${this.url}tv/child/${child.id}`, {headers: this.headers});
|
return this.http.delete<boolean>(`${this.url}tv/child/${child.id}`, {headers: this.headers});
|
||||||
}
|
}
|
||||||
public filterMovies(filter: IFilter): Observable<IMovieRequests[]> {
|
public filterMovies(filter: IFilter): Observable<IFilterResult<IMovieRequests>> {
|
||||||
return this.http.post<IMovieRequests[]>(`${this.url}movie/filter`, JSON.stringify(filter), {headers: this.headers});
|
return this.http.post<IFilterResult<IMovieRequests>>(`${this.url}movie/filter`, JSON.stringify(filter), {headers: this.headers});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,14 +41,6 @@
|
||||||
<small *ngIf="form.get('automaticUpdater').hasError('required')" class="error-text">The Automatic Update is required</small>
|
<small *ngIf="form.get('automaticUpdater').hasError('required')" class="error-text">The Automatic Update is required</small>
|
||||||
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('automaticUpdater')?.value)">Test</button>
|
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('automaticUpdater')?.value)">Test</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<div>
|
|
||||||
<button type="submit" [disabled]="form.invalid" class="btn btn-primary-outline">Submit</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -92,8 +84,12 @@
|
||||||
<small *ngIf="form.get('newsletter').hasError('required')" class="error-text">The Newsletter is required</small>
|
<small *ngIf="form.get('newsletter').hasError('required')" class="error-text">The Newsletter is required</small>
|
||||||
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('newsletter')?.value)">Test</button>
|
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('newsletter')?.value)">Test</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div>
|
||||||
|
<button type="submit" [disabled]="form.invalid" class="btn btn-primary-outline">Submit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -30,7 +30,14 @@ export class MobileComponent implements OnInit {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.mobileService.getUserDeviceList().subscribe(x => this.userList = x);
|
this.mobileService.getUserDeviceList().subscribe(x => {
|
||||||
|
if(x.length <= 0) {
|
||||||
|
this.userList = [];
|
||||||
|
this.userList.push({username:"None",devices:0});
|
||||||
|
} else {
|
||||||
|
this.userList = x;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSubmit(form: FormGroup) {
|
public onSubmit(form: FormGroup) {
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
<i class="fa fa-bell-o" aria-hidden="true"></i> Notifications <span class="caret"></span>
|
<i class="fa fa-bell-o" aria-hidden="true"></i> Notifications <span class="caret"></span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
|
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Mobile']">Mobile</a></li>
|
||||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Email']">Email</a></li>
|
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Email']">Email</a></li>
|
||||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/MassEmail']">Mass Email</a></li>
|
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/MassEmail']">Mass Email</a></li>
|
||||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Newsletter']">Newsletter</a></li>
|
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Newsletter']">Newsletter</a></li>
|
||||||
|
|
|
@ -7,4 +7,19 @@ export class SettingsMenuComponent {
|
||||||
public ignore(event: any): void {
|
public ignore(event: any): void {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ngOnInit() {
|
||||||
|
const element = document.getElementById("settings");
|
||||||
|
if (element != null) {
|
||||||
|
element.classList.add("active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy() {
|
||||||
|
const element = document.getElementById("settings");
|
||||||
|
if (element != null) {
|
||||||
|
element.classList.remove("active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -963,3 +963,14 @@ a > h4:hover {
|
||||||
background-color: $primary-colour-outline $i;
|
background-color: $primary-colour-outline $i;
|
||||||
color: black $i;
|
color: black $i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#themeContent {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reported-by,
|
||||||
|
.reported-user,
|
||||||
|
.subject-category,
|
||||||
|
.subject {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
|
@ -352,7 +352,7 @@ namespace Ombi.Controllers
|
||||||
/// <param name="vm"></param>
|
/// <param name="vm"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpPost("movie/filter")]
|
[HttpPost("movie/filter")]
|
||||||
public async Task<IEnumerable<MovieRequests>> Filter([FromBody] FilterViewModel vm)
|
public async Task<FilterResult<MovieRequests>> Filter([FromBody] FilterViewModel vm)
|
||||||
{
|
{
|
||||||
return await MovieRequestEngine.Filter(vm);
|
return await MovieRequestEngine.Filter(vm);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue