mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-20 13:23:20 -07:00
Merge branch 'master' of https://github.com/tidusjar/ombi
This commit is contained in:
commit
a003095540
162 changed files with 5215 additions and 676 deletions
78
CHANGELOG.md
78
CHANGELOG.md
|
@ -1,5 +1,83 @@
|
|||
# Changelog
|
||||
|
||||
## v3.0.3020 (2018-03-13)
|
||||
|
||||
### **New Features**
|
||||
|
||||
- Added the Recently Added Newsletter! You are welcome. [tidusjar]
|
||||
|
||||
- Added a new scrollbar to Ombi. [tidusjar]
|
||||
|
||||
- Added the ability to automatically generate the API Key on startup if it does not exist #2070. [tidusjar]
|
||||
|
||||
- Updated npm dependancies. [Jamie]
|
||||
|
||||
- Update README.md. [Jamie]
|
||||
|
||||
- Update README.md. [Jamie]
|
||||
|
||||
- Update ISSUE_TEMPLATE.md. [Jamie]
|
||||
|
||||
- Update appveyor.yml. [Jamie]
|
||||
|
||||
- Added recently added stuff. [Jamie]
|
||||
|
||||
- Added the recently added engine with some basic methods. [Jamie]
|
||||
|
||||
- Added the ability to refresh out backend metadata (#2078) [Jamie]
|
||||
|
||||
### **Fixes**
|
||||
|
||||
- Specific favicons for different platforms. [louis-lau]
|
||||
|
||||
- MovieDbId was switched to string fron number so accomodated for change. [Anojh]
|
||||
|
||||
- Removing duplicate functions. [Anojh Thayaparan]
|
||||
|
||||
- Conflict resolving and adopting Jamie's new method. [Anojh]
|
||||
|
||||
- Wrote new calls to just get poster and bg. [Anojh]
|
||||
|
||||
- Fix for issue #1907, which is to add content poster and bg to issue details page. [Anojh]
|
||||
|
||||
- Dynamic Background Animation. [Anojh]
|
||||
|
||||
- Improved the message for #2037. [tidusjar]
|
||||
|
||||
- Improved the way we use the notification variables, we have now split out the Username and Alias (Requested User is depricated but not removed) [tidusjar]
|
||||
|
||||
- Removed redundant timers. [Anojh]
|
||||
|
||||
- More optimizations by reducing requests. [Anojh]
|
||||
|
||||
- Improved version. [Anojh]
|
||||
|
||||
- Dynamic Background Animation. [Anojh]
|
||||
|
||||
- Fixed #2055 and #1903. [Jamie]
|
||||
|
||||
- Small changes to the auto updater, let's see how this works. [Jamie]
|
||||
|
||||
- Fixed build. [Jamie]
|
||||
|
||||
- Fixed the update check for the master build. [Jamie]
|
||||
|
||||
- Fixed build. [Jamie]
|
||||
|
||||
- Fixed #2074 and #2079. [Jamie]
|
||||
|
||||
|
||||
## v3.0.3030 (2018-03-14)
|
||||
|
||||
### **New Features**
|
||||
|
||||
- Updated the .Net core dependancies #2072. [Jamie]
|
||||
|
||||
### **Fixes**
|
||||
|
||||
- Delete Ombi.testdb. [Jamie]
|
||||
|
||||
|
||||
## v3.0.3020 (2018-03-13)
|
||||
|
||||
### **Fixes**
|
||||
|
|
|
@ -12,9 +12,6 @@ ____
|
|||
___
|
||||
|
||||
|
||||
[](https://forums.ombi.io/viewforum.php?f=10) [](https://forums.ombi.io/posting.php?mode=post&f=20)
|
||||
|
||||
|
||||
| Service | Stable | Develop |
|
||||
|----------|:---------------------------:|:----------------------------:|
|
||||
| AppVeyor | [](https://ci.appveyor.com/project/tidusjar/requestplex/branch/master) | [](https://ci.appveyor.com/project/tidusjar/requestplex/branch/develop) |
|
||||
|
|
|
@ -32,9 +32,9 @@ namespace Ombi.Api.FanartTv
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<MovieResult> GetMovieImages(int theMovieDbId, string token)
|
||||
public async Task<MovieResult> GetMovieImages(string movieOrImdbId, string token)
|
||||
{
|
||||
var request = new Request($"movies/{theMovieDbId}", Endpoint, HttpMethod.Get);
|
||||
var request = new Request($"movies/{movieOrImdbId}", Endpoint, HttpMethod.Get);
|
||||
request.AddHeader("api-key", token);
|
||||
|
||||
return await Api.Request<MovieResult>(request);
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace Ombi.Api.FanartTv
|
|||
{
|
||||
public interface IFanartTvApi
|
||||
{
|
||||
Task<MovieResult> GetMovieImages(int theMovieDbId, string token);
|
||||
Task<MovieResult> GetMovieImages(string movieOrImdbId, string token);
|
||||
Task<TvResult> GetTvImages(int tvdbId, string token);
|
||||
}
|
||||
}
|
|
@ -5,6 +5,6 @@ namespace Ombi.Api.Mattermost
|
|||
{
|
||||
public interface IMattermostApi
|
||||
{
|
||||
Task<string> PushAsync(string webhook, MattermostBody message);
|
||||
Task PushAsync(string webhook, MattermostMessage message);
|
||||
}
|
||||
}
|
|
@ -14,14 +14,10 @@ namespace Ombi.Api.Mattermost
|
|||
|
||||
private readonly IApi _api;
|
||||
|
||||
public async Task<string> PushAsync(string webhook, MattermostBody message)
|
||||
public async Task PushAsync(string webhook, MattermostMessage message)
|
||||
{
|
||||
var request = new Request(string.Empty, webhook, HttpMethod.Post);
|
||||
|
||||
request.AddJsonBody(message);
|
||||
|
||||
var result = await _api.RequestContent(request);
|
||||
return result;
|
||||
var client = new MatterhookClient(webhook);
|
||||
await client.PostAsync(_api, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
168
src/Ombi.Api.Mattermost/Models/MattermostClient.cs
Normal file
168
src/Ombi.Api.Mattermost/Models/MattermostClient.cs
Normal file
|
@ -0,0 +1,168 @@
|
|||
///
|
||||
///
|
||||
///
|
||||
/// Code taken from https://github.com/PromoFaux/Matterhook.NET.MatterhookClient
|
||||
///
|
||||
///
|
||||
///
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Ombi.Api.Mattermost.Models
|
||||
{
|
||||
public class MatterhookClient
|
||||
{
|
||||
private readonly Uri _webhookUrl;
|
||||
private readonly HttpClient _httpClient = new HttpClient();
|
||||
|
||||
/// <summary>
|
||||
/// Create a new Mattermost Client
|
||||
/// </summary>
|
||||
/// <param name="webhookUrl">The URL of your Mattermost Webhook</param>
|
||||
/// <param name="timeoutSeconds">Timeout Value (Default 100)</param>
|
||||
public MatterhookClient(string webhookUrl, int timeoutSeconds = 100)
|
||||
{
|
||||
if (!Uri.TryCreate(webhookUrl, UriKind.Absolute, out _webhookUrl))
|
||||
throw new ArgumentException("Mattermost URL invalid");
|
||||
|
||||
_httpClient.Timeout = new TimeSpan(0, 0, 0, timeoutSeconds);
|
||||
}
|
||||
|
||||
public MattermostMessage CloneMessage(MattermostMessage inMsg)
|
||||
{
|
||||
var outMsg = new MattermostMessage
|
||||
{
|
||||
Text = "",
|
||||
Channel = inMsg.Channel,
|
||||
Username = inMsg.Username,
|
||||
IconUrl = inMsg.IconUrl
|
||||
};
|
||||
|
||||
return outMsg;
|
||||
}
|
||||
|
||||
private static MattermostAttachment CloneAttachment(MattermostAttachment inAtt)
|
||||
{
|
||||
var outAtt = new MattermostAttachment
|
||||
{
|
||||
AuthorIcon = inAtt.AuthorIcon,
|
||||
AuthorLink = inAtt.AuthorLink,
|
||||
AuthorName = inAtt.AuthorName,
|
||||
Color = inAtt.Color,
|
||||
Fallback = inAtt.Fallback,
|
||||
Fields = inAtt.Fields,
|
||||
ImageUrl = inAtt.ImageUrl,
|
||||
Pretext = inAtt.Pretext,
|
||||
ThumbUrl = inAtt.ThumbUrl,
|
||||
Title = inAtt.Title,
|
||||
TitleLink = inAtt.TitleLink,
|
||||
Text = ""
|
||||
};
|
||||
return outAtt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Post Message to Mattermost server. Messages will be automatically split if total text length > 4000
|
||||
/// </summary>
|
||||
/// <param name="api"></param>
|
||||
/// <param name="inMessage">The messsage you wish to send</param>
|
||||
/// <returns></returns>
|
||||
public async Task PostAsync(IApi api, MattermostMessage inMessage)
|
||||
{
|
||||
try
|
||||
{
|
||||
var outMessages = new List<MattermostMessage>();
|
||||
|
||||
var msgCount = 0;
|
||||
|
||||
var lines = new string[] { };
|
||||
if (inMessage.Text != null)
|
||||
{
|
||||
lines = inMessage.Text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
|
||||
}
|
||||
|
||||
//start with one cloned inMessage in the list
|
||||
outMessages.Add(CloneMessage(inMessage));
|
||||
|
||||
//add text from original. If we go over 3800, we'll split it to a new inMessage.
|
||||
foreach (var line in lines)
|
||||
{
|
||||
|
||||
if (line.Length + outMessages[msgCount].Text.Length > 3800)
|
||||
{
|
||||
|
||||
msgCount += 1;
|
||||
outMessages.Add(CloneMessage(inMessage));
|
||||
}
|
||||
|
||||
outMessages[msgCount].Text += $"{line}\r\n";
|
||||
}
|
||||
|
||||
//Length of text on the last (or first if only one) inMessage.
|
||||
var lenMessageText = outMessages[msgCount].Text.Length;
|
||||
|
||||
//does our original have attachments?
|
||||
if (inMessage.Attachments?.Any() ?? false)
|
||||
{
|
||||
outMessages[msgCount].Attachments = new List<MattermostAttachment>();
|
||||
|
||||
//loop through them in a similar fashion to the inMessage text above.
|
||||
foreach (var att in inMessage.Attachments)
|
||||
{
|
||||
//add this attachment to the outgoing message
|
||||
outMessages[msgCount].Attachments.Add(CloneAttachment(att));
|
||||
//get a count of attachments on this message, and subtract one so we know the index of the current new attachment
|
||||
var attIndex = outMessages[msgCount].Attachments.Count - 1;
|
||||
|
||||
//Get the text lines
|
||||
lines = att.Text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
//Get the total length of all attachments on the current outgoing message
|
||||
var lenAllAttsText = outMessages[msgCount].Attachments.Sum(a => a.Text.Length);
|
||||
|
||||
if (lenMessageText + lenAllAttsText + line.Length > 3800)
|
||||
{
|
||||
msgCount += 1;
|
||||
attIndex = 0;
|
||||
outMessages.Add(CloneMessage(inMessage));
|
||||
outMessages[msgCount].Attachments = new List<MattermostAttachment> { CloneAttachment(att) };
|
||||
}
|
||||
|
||||
outMessages[msgCount].Attachments[attIndex].Text += $"{line}\r\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (outMessages.Count > 1)
|
||||
{
|
||||
var num = 1;
|
||||
foreach (var msg in outMessages)
|
||||
{
|
||||
msg.Text = $"`({num}/{msgCount + 1}): ` " + msg.Text;
|
||||
num++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var msg in outMessages)
|
||||
{
|
||||
var request = new Request("", _webhookUrl.ToString(), HttpMethod.Post);
|
||||
request.AddJsonBody(msg);
|
||||
await api.Request(request);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
181
src/Ombi.Api.Mattermost/Models/MattermostMessage.cs
Normal file
181
src/Ombi.Api.Mattermost/Models/MattermostMessage.cs
Normal file
|
@ -0,0 +1,181 @@
|
|||
///
|
||||
///
|
||||
///
|
||||
/// Code taken from https://github.com/PromoFaux/Matterhook.NET.MatterhookClient
|
||||
///
|
||||
///
|
||||
///
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Ombi.Api.Mattermost.Models
|
||||
|
||||
{
|
||||
public class MattermostMessage
|
||||
{
|
||||
|
||||
//https://docs.mattermost.com/developer/webhooks-incoming.html
|
||||
|
||||
/// <summary>
|
||||
/// Channel to post to
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "channel")]
|
||||
public string Channel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Username for bot
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "username")]
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bot/User Icon
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "icon_url")]
|
||||
public Uri IconUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Message body. Supports Markdown
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "text")]
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Richtext attachments
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "attachments")]
|
||||
public List<MattermostAttachment> Attachments { get; set; }
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://docs.mattermost.com/developer/message-attachments.html#message-attachments
|
||||
/// </summary>
|
||||
public class MattermostAttachment
|
||||
{
|
||||
//https://docs.mattermost.com/developer/message-attachments.html#attachment-options
|
||||
#region AttachmentOptions
|
||||
|
||||
/// <summary>
|
||||
/// A required plain-text summary of the post. This is used in notifications, and in clients that don’t support formatted text (eg IRC).
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "fallback")]
|
||||
public string Fallback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A hex color code that will be used as the left border color for the attachment. If not specified, it will default to match the left hand sidebar header background color.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "color")]
|
||||
public string Color { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional text that should appear above the formatted data
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "pretext")]
|
||||
public string Pretext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The text to be included in the attachment. It can be formatted using Markdown. If it includes more than 300 characters or more than 5 line breaks, the message will be collapsed and a “Show More” link will be added to expand the message.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "text")]
|
||||
public string Text { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
//https://docs.mattermost.com/developer/message-attachments.html#author-details
|
||||
#region AuthorDetails
|
||||
|
||||
/// <summary>
|
||||
/// An optional name used to identify the author. It will be included in a small section at the top of the attachment.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "author_name")]
|
||||
public string AuthorName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional URL used to hyperlink the author_name. If no author_name is specified, this field does nothing.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "author_link")]
|
||||
public Uri AuthorLink { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional URL used to display a 16x16 pixel icon beside the author_name.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "author_icon")]
|
||||
public Uri AuthorIcon { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
//https://docs.mattermost.com/developer/message-attachments.html#titles
|
||||
#region Titles
|
||||
|
||||
/// <summary>
|
||||
/// An optional title displayed below the author information in the attachment.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional URL used to hyperlink the title. If no title is specified, this field does nothing.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "title_link")]
|
||||
public Uri TitleLink { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Fields can be included as an optional array within attachments, and are used to display information in a table format inside the attachment.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "fields")]
|
||||
public List<MattermostField> Fields { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
//https://docs.mattermost.com/developer/message-attachments.html#images
|
||||
#region Images
|
||||
|
||||
/// <summary>
|
||||
/// An optional URL to an image file (GIF, JPEG, PNG, or BMP) that is displayed inside a message attachment.
|
||||
/// Large images are resized to a maximum width of 400px or a maximum height of 300px, while still maintaining the original aspect ratio.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "image_url")]
|
||||
public Uri ImageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional URL to an image file(GIF, JPEG, PNG, or BMP) that is displayed as a 75x75 pixel thumbnail on the right side of an attachment.
|
||||
/// We recommend using an image that is already 75x75 pixels, but larger images will be scaled down with the aspect ratio maintained.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "thumb_url")]
|
||||
public Uri ThumbUrl { get; set; }
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://docs.mattermost.com/developer/message-attachments.html#fieldshttps://docs.mattermost.com/developer/message-attachments.html#fields
|
||||
/// </summary>
|
||||
public class MattermostField
|
||||
{
|
||||
/// <summary>
|
||||
/// A title shown in the table above the value.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The text value of the field. It can be formatted using Markdown.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "value")]
|
||||
public string Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optionally set to “True” or “False” to indicate whether the value is short enough to be displayed beside other values.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "short")]
|
||||
public bool Short { get; set; }
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
using System.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -6,6 +9,7 @@ using System.Xml.Serialization;
|
|||
using Newtonsoft.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Helpers;
|
||||
using Polly;
|
||||
|
||||
namespace Ombi.Api
|
||||
{
|
||||
|
@ -36,6 +40,30 @@ namespace Ombi.Api
|
|||
if (!httpResponseMessage.IsSuccessStatusCode)
|
||||
{
|
||||
LogError(request, httpResponseMessage);
|
||||
if (request.Retry)
|
||||
{
|
||||
|
||||
var result = Policy
|
||||
.Handle<HttpRequestException>()
|
||||
.OrResult<HttpResponseMessage>(r => request.StatusCodeToRetry.Contains(r.StatusCode))
|
||||
.WaitAndRetryAsync(new[]
|
||||
{
|
||||
TimeSpan.FromSeconds(10),
|
||||
}, (exception, timeSpan, context) =>
|
||||
{
|
||||
|
||||
Logger.LogError(LoggingEvents.Api,
|
||||
$"Retrying RequestUri: {request.FullUri} Because we got Status Code: {exception?.Result?.StatusCode}");
|
||||
});
|
||||
|
||||
httpResponseMessage = await result.ExecuteAsync(async () =>
|
||||
{
|
||||
using (var req = await httpRequestMessage.Clone())
|
||||
{
|
||||
return await _client.SendAsync(req);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// do something with the response
|
||||
|
|
45
src/Ombi.Api/HttpRequestExtnesions.cs
Normal file
45
src/Ombi.Api/HttpRequestExtnesions.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Api
|
||||
{
|
||||
public static class HttpRequestExtnesions
|
||||
{
|
||||
public static async Task<HttpRequestMessage> Clone(this HttpRequestMessage request)
|
||||
{
|
||||
var clone = new HttpRequestMessage(request.Method, request.RequestUri)
|
||||
{
|
||||
Content = await request.Content.Clone(),
|
||||
Version = request.Version
|
||||
};
|
||||
foreach (KeyValuePair<string, object> prop in request.Properties)
|
||||
{
|
||||
clone.Properties.Add(prop);
|
||||
}
|
||||
foreach (KeyValuePair<string, IEnumerable<string>> header in request.Headers)
|
||||
{
|
||||
clone.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
public static async Task<HttpContent> Clone(this HttpContent content)
|
||||
{
|
||||
if (content == null) return null;
|
||||
|
||||
var ms = new MemoryStream();
|
||||
await content.CopyToAsync(ms);
|
||||
ms.Position = 0;
|
||||
|
||||
var clone = new StreamContent(ms);
|
||||
foreach (KeyValuePair<string, IEnumerable<string>> header in content.Headers)
|
||||
{
|
||||
clone.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,8 +9,9 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
<PackageReference Include="Polly" Version="5.8.0" />
|
||||
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
|
||||
|
@ -25,6 +26,9 @@ namespace Ombi.Api
|
|||
public string BaseUrl { get; }
|
||||
public HttpMethod HttpMethod { get; }
|
||||
|
||||
public bool Retry { get; set; }
|
||||
public List<HttpStatusCode> StatusCodeToRetry { get; set; } = new List<HttpStatusCode>();
|
||||
|
||||
public Action<string> OnBeforeDeserialization { get; set; }
|
||||
|
||||
private string FullUrl
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<PackageReference Include="Nunit" Version="3.8.1" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.0.0"></packagereference>
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.6.1"></packagereference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
16
src/Ombi.Core/Engine/IRecentlyAddedEngine.cs
Normal file
16
src/Ombi.Core/Engine/IRecentlyAddedEngine.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Core.Models;
|
||||
|
||||
namespace Ombi.Core.Engine
|
||||
{
|
||||
public interface IRecentlyAddedEngine
|
||||
{
|
||||
IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies();
|
||||
IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies(DateTime from, DateTime to);
|
||||
IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(DateTime from, DateTime to, bool groupBySeason);
|
||||
IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(bool groupBySeason);
|
||||
Task<bool> UpdateRecentlyAddedDatabase();
|
||||
}
|
||||
}
|
|
@ -54,7 +54,7 @@ namespace Ombi.Core.Engine
|
|||
{
|
||||
Result = false,
|
||||
Message = "There was an issue adding this movie!",
|
||||
ErrorMessage = $"TheMovieDb didn't have any information for ID {model.TheMovieDbId}"
|
||||
ErrorMessage = $"Please try again later"
|
||||
};
|
||||
}
|
||||
var fullMovieName =
|
||||
|
|
224
src/Ombi.Core/Engine/RecentlyAddedEngine.cs
Normal file
224
src/Ombi.Core/Engine/RecentlyAddedEngine.cs
Normal file
|
@ -0,0 +1,224 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Models;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using RecentlyAddedType = Ombi.Store.Entities.RecentlyAddedType;
|
||||
|
||||
namespace Ombi.Core.Engine
|
||||
{
|
||||
public class RecentlyAddedEngine : IRecentlyAddedEngine
|
||||
{
|
||||
public RecentlyAddedEngine(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository<RecentlyAddedLog> recentlyAdded)
|
||||
{
|
||||
_plex = plex;
|
||||
_emby = emby;
|
||||
_recentlyAddedLog = recentlyAdded;
|
||||
}
|
||||
|
||||
private readonly IPlexContentRepository _plex;
|
||||
private readonly IEmbyContentRepository _emby;
|
||||
private readonly IRepository<RecentlyAddedLog> _recentlyAddedLog;
|
||||
|
||||
public IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies(DateTime from, DateTime to)
|
||||
{
|
||||
var plexMovies = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Movie && x.AddedAt > from && x.AddedAt < to);
|
||||
var embyMovies = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Movie && x.AddedAt > from && x.AddedAt < to);
|
||||
|
||||
return GetRecentlyAddedMovies(plexMovies, embyMovies).Take(30);
|
||||
}
|
||||
|
||||
public IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies()
|
||||
{
|
||||
var plexMovies = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Movie);
|
||||
var embyMovies = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Movie);
|
||||
return GetRecentlyAddedMovies(plexMovies, embyMovies);
|
||||
}
|
||||
|
||||
public IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(DateTime from, DateTime to, bool groupBySeason)
|
||||
{
|
||||
var plexTv = _plex.GetAll().Include(x => x.Seasons).Include(x => x.Episodes).Where(x => x.Type == PlexMediaTypeEntity.Show && x.AddedAt > from && x.AddedAt < to);
|
||||
var embyTv = _emby.GetAll().Include(x => x.Episodes).Where(x => x.Type == EmbyMediaType.Series && x.AddedAt > from && x.AddedAt < to);
|
||||
|
||||
return GetRecentlyAddedTv(plexTv, embyTv, groupBySeason).Take(30);
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(bool groupBySeason)
|
||||
{
|
||||
var plexTv = _plex.GetAll().Include(x => x.Seasons).Include(x => x.Episodes).Where(x => x.Type == PlexMediaTypeEntity.Show);
|
||||
var embyTv = _emby.GetAll().Include(x => x.Episodes).Where(x => x.Type == EmbyMediaType.Series);
|
||||
|
||||
return GetRecentlyAddedTv(plexTv, embyTv, groupBySeason);
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateRecentlyAddedDatabase()
|
||||
{
|
||||
var plexContent = _plex.GetAll().Include(x => x.Episodes);
|
||||
var embyContent = _emby.GetAll().Include(x => x.Episodes);
|
||||
var recentlyAddedLog = new HashSet<RecentlyAddedLog>();
|
||||
foreach (var p in plexContent)
|
||||
{
|
||||
if (p.Type == PlexMediaTypeEntity.Movie)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Plex,
|
||||
ContentId = p.Id,
|
||||
ContentType = ContentType.Parent
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the episodes
|
||||
foreach (var ep in p.Episodes)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Plex,
|
||||
ContentId = ep.Id,
|
||||
ContentType = ContentType.Episode
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var e in embyContent)
|
||||
{
|
||||
if (e.Type == EmbyMediaType.Movie)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Emby,
|
||||
ContentId = e.Id,
|
||||
ContentType = ContentType.Parent
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the episodes
|
||||
foreach (var ep in e.Episodes)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Emby,
|
||||
ContentId = ep.Id,
|
||||
ContentType = ContentType.Episode
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
await _recentlyAddedLog.AddRange(recentlyAddedLog);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(IQueryable<PlexServerContent> plexTv, IQueryable<EmbyContent> embyTv,
|
||||
bool groupBySeason)
|
||||
{
|
||||
var model = new HashSet<RecentlyAddedTvModel>();
|
||||
TransformPlexShows(plexTv, model);
|
||||
TransformEmbyShows(embyTv, model);
|
||||
|
||||
if (groupBySeason)
|
||||
{
|
||||
return model.DistinctBy(x => x.SeasonNumber);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
private IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies(IQueryable<PlexServerContent> plexMovies, IQueryable<EmbyContent> embyMovies)
|
||||
{
|
||||
var model = new HashSet<RecentlyAddedMovieModel>();
|
||||
TransformPlexMovies(plexMovies, model);
|
||||
TransformEmbyMovies(embyMovies, model);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
private static void TransformEmbyMovies(IQueryable<EmbyContent> embyMovies, HashSet<RecentlyAddedMovieModel> model)
|
||||
{
|
||||
foreach (var emby in embyMovies)
|
||||
{
|
||||
model.Add(new RecentlyAddedMovieModel
|
||||
{
|
||||
Id = emby.Id,
|
||||
ImdbId = emby.ProviderId,
|
||||
AddedAt = emby.AddedAt,
|
||||
Title = emby.Title,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void TransformPlexMovies(IQueryable<PlexServerContent> plexMovies, HashSet<RecentlyAddedMovieModel> model)
|
||||
{
|
||||
foreach (var plex in plexMovies)
|
||||
{
|
||||
model.Add(new RecentlyAddedMovieModel
|
||||
{
|
||||
Id = plex.Id,
|
||||
ImdbId = plex.ImdbId,
|
||||
TheMovieDbId = plex.TheMovieDbId,
|
||||
AddedAt = plex.AddedAt,
|
||||
Title = plex.Title,
|
||||
Quality = plex.Quality,
|
||||
ReleaseYear = plex.ReleaseYear
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void TransformPlexShows(IQueryable<PlexServerContent> plexShows, HashSet<RecentlyAddedTvModel> model)
|
||||
{
|
||||
foreach (var plex in plexShows)
|
||||
{
|
||||
foreach (var season in plex.Seasons)
|
||||
{
|
||||
foreach (var episode in plex.Episodes)
|
||||
{
|
||||
model.Add(new RecentlyAddedTvModel
|
||||
{
|
||||
Id = plex.Id,
|
||||
ImdbId = plex.ImdbId,
|
||||
TheMovieDbId = plex.TheMovieDbId,
|
||||
AddedAt = plex.AddedAt,
|
||||
Title = plex.Title,
|
||||
Quality = plex.Quality,
|
||||
ReleaseYear = plex.ReleaseYear,
|
||||
TvDbId = plex.TvDbId,
|
||||
EpisodeNumber = episode.EpisodeNumber,
|
||||
SeasonNumber = season.SeasonNumber
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void TransformEmbyShows(IQueryable<EmbyContent> embyShows, HashSet<RecentlyAddedTvModel> model)
|
||||
{
|
||||
foreach (var emby in embyShows)
|
||||
{
|
||||
foreach (var episode in emby.Episodes)
|
||||
{
|
||||
model.Add(new RecentlyAddedTvModel
|
||||
{
|
||||
Id = emby.Id,
|
||||
ImdbId = emby.ProviderId,
|
||||
AddedAt = emby.AddedAt,
|
||||
Title = emby.Title,
|
||||
EpisodeNumber = episode.EpisodeNumber,
|
||||
SeasonNumber = episode.SeasonNumber
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
src/Ombi.Core/Models/RecentlyAddedMovieModel.cs
Normal file
23
src/Ombi.Core/Models/RecentlyAddedMovieModel.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace Ombi.Core.Models
|
||||
{
|
||||
public class RecentlyAddedMovieModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Overview { get; set; }
|
||||
public string ImdbId { get; set; }
|
||||
public string TvDbId { get; set; }
|
||||
public string TheMovieDbId { get; set; }
|
||||
public string ReleaseYear { get; set; }
|
||||
public DateTime AddedAt { get; set; }
|
||||
public string Quality { get; set; }
|
||||
}
|
||||
|
||||
public enum RecentlyAddedType
|
||||
{
|
||||
Plex,
|
||||
Emby
|
||||
}
|
||||
}
|
19
src/Ombi.Core/Models/RecentlyAddedTvModel.cs
Normal file
19
src/Ombi.Core/Models/RecentlyAddedTvModel.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace Ombi.Core.Models
|
||||
{
|
||||
public class RecentlyAddedTvModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; } // Series Title
|
||||
public string Overview { get; set; }
|
||||
public string ImdbId { get; set; }
|
||||
public string TvDbId { get; set; }
|
||||
public string TheMovieDbId { get; set; }
|
||||
public string ReleaseYear { get; set; }
|
||||
public DateTime AddedAt { get; set; }
|
||||
public string Quality { get; set; }
|
||||
public int SeasonNumber { get; set; }
|
||||
public int EpisodeNumber { get; set; }
|
||||
}
|
||||
}
|
23
src/Ombi.Core/Models/UI/NewsletterNotificationViewModel.cs
Normal file
23
src/Ombi.Core/Models/UI/NewsletterNotificationViewModel.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Core.Models.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// The view model for the notification settings page
|
||||
/// </summary>
|
||||
/// <seealso cref="NewsletterNotificationViewModel" />
|
||||
public class NewsletterNotificationViewModel : NewsletterSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the notification templates.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The notification templates.
|
||||
/// </value>
|
||||
public NotificationTemplates NotificationTemplate { get; set; }
|
||||
|
||||
}
|
||||
}
|
|
@ -12,9 +12,9 @@
|
|||
<PackageReference Include="AutoMapper" Version="6.1.1" />
|
||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.0.1" />
|
||||
<PackageReference Include="Hangfire" Version="1.6.17" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.5" />
|
||||
<PackageReference Include="MiniProfiler.AspNetCore" Version="4.0.0-alpha6-79" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
|
||||
|
|
|
@ -79,6 +79,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<ITvSearchEngine, TvSearchEngine>();
|
||||
services.AddTransient<IRuleEvaluator, RuleEvaluator>();
|
||||
services.AddTransient<IMovieSender, MovieSender>();
|
||||
services.AddTransient<IRecentlyAddedEngine, RecentlyAddedEngine>();
|
||||
services.AddTransient<ITvSender, TvSender>();
|
||||
services.AddTransient<IMassEmailSender, MassEmailSender>();
|
||||
}
|
||||
|
@ -172,6 +173,8 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<ICouchPotatoSync, CouchPotatoSync>();
|
||||
services.AddTransient<IProcessProvider, ProcessProvider>();
|
||||
services.AddTransient<ISickRageSync, SickRageSync>();
|
||||
services.AddTransient<IRefreshMetadata, RefreshMetadata>();
|
||||
services.AddTransient<INewsletterJob, NewsletterJob>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -13,5 +13,6 @@
|
|||
WelcomeEmail = 8,
|
||||
IssueResolved = 9,
|
||||
IssueComment = 10,
|
||||
Newsletter = 11,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="EasyCrypto" Version="3.3.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
<PackageReference Include="Nito.AsyncEx" Version="5.0.0-pre-05" />
|
||||
<PackageReference Include="System.Security.Claims" Version="4.3.0" />
|
||||
|
|
|
@ -9,5 +9,6 @@
|
|||
public const string RequestTv = nameof(RequestTv);
|
||||
public const string RequestMovie = nameof(RequestMovie);
|
||||
public const string Disabled = nameof(Disabled);
|
||||
public const string RecievesNewsletter = nameof(RecievesNewsletter);
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ namespace Ombi.Mapping.Profiles
|
|||
CreateMap<TelegramNotificationsViewModel, TelegramSettings>().ReverseMap();
|
||||
CreateMap<UpdateSettingsViewModel, UpdateSettings>().ReverseMap();
|
||||
CreateMap<MobileNotificationsViewModel, MobileNotificationSettings>().ReverseMap();
|
||||
CreateMap<NewsletterNotificationViewModel, NewsletterSettings>().ReverseMap();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,19 +4,26 @@ using System.Text;
|
|||
|
||||
namespace Ombi.Notifications.Templates
|
||||
{
|
||||
public class EmailBasicTemplate : IEmailBasicTemplate
|
||||
public class EmailBasicTemplate : TemplateBase, IEmailBasicTemplate
|
||||
{
|
||||
public string TemplateLocation
|
||||
public override string TemplateLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_templateLocation))
|
||||
{
|
||||
#if DEBUG
|
||||
return Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp2.0", "Templates", "BasicTemplate.html");
|
||||
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp2.0", "Templates",
|
||||
"BasicTemplate.html");
|
||||
#else
|
||||
return Path.Combine(Directory.GetCurrentDirectory(), "Templates","BasicTemplate.html");
|
||||
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "Templates","BasicTemplate.html");
|
||||
#endif
|
||||
}
|
||||
return _templateLocation;
|
||||
}
|
||||
}
|
||||
|
||||
private string _templateLocation;
|
||||
|
||||
private const string SubjectKey = "{@SUBJECT}";
|
||||
private const string BodyKey = "{@BODY}";
|
||||
|
@ -31,7 +38,7 @@ namespace Ombi.Notifications.Templates
|
|||
sb.Replace(BodyKey, body);
|
||||
sb.Replace(DateKey, DateTime.Now.ToString("f"));
|
||||
sb.Replace(Poster, string.IsNullOrEmpty(imgsrc) ? string.Empty : $"<tr><td align=\"center\"><img src=\"{imgsrc}\" alt=\"Poster\" width=\"400px\" text-align=\"center\"/></td></tr>");
|
||||
sb.Replace(Logo, string.IsNullOrEmpty(logo) ? "http://i.imgur.com/qQsN78U.png" : logo);
|
||||
sb.Replace(Logo, string.IsNullOrEmpty(logo) ? OmbiLogo : logo);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
|
7
src/Ombi.Notifications.Templates/INewsletterTemplate.cs
Normal file
7
src/Ombi.Notifications.Templates/INewsletterTemplate.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Ombi.Notifications.Templates
|
||||
{
|
||||
public interface INewsletterTemplate
|
||||
{
|
||||
string LoadTemplate(string subject, string intro, string tableHtml, string logo);
|
||||
}
|
||||
}
|
46
src/Ombi.Notifications.Templates/NewsletterTemplate.cs
Normal file
46
src/Ombi.Notifications.Templates/NewsletterTemplate.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Ombi.Notifications.Templates
|
||||
{
|
||||
public class NewsletterTemplate : TemplateBase, INewsletterTemplate
|
||||
{
|
||||
public override string TemplateLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_templateLocation))
|
||||
{
|
||||
#if DEBUG
|
||||
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp2.0", "Templates", "NewsletterTemplate.html");
|
||||
#else
|
||||
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "Templates", "NewsletterTemplate.html");
|
||||
#endif
|
||||
}
|
||||
return _templateLocation;
|
||||
}
|
||||
}
|
||||
|
||||
private string _templateLocation;
|
||||
|
||||
private const string SubjectKey = "{@SUBJECT}";
|
||||
private const string DateKey = "{@DATENOW}";
|
||||
private const string Logo = "{@LOGO}";
|
||||
private const string TableLocation = "{@RECENTLYADDED}";
|
||||
private const string IntroText = "{@INTRO}";
|
||||
|
||||
|
||||
public string LoadTemplate(string subject, string intro, string tableHtml, string logo)
|
||||
{
|
||||
var sb = new StringBuilder(File.ReadAllText(TemplateLocation));
|
||||
sb.Replace(SubjectKey, subject);
|
||||
sb.Replace(TableLocation, tableHtml);
|
||||
sb.Replace(IntroText, intro);
|
||||
sb.Replace(DateKey, DateTime.Now.ToString("f"));
|
||||
sb.Replace(Logo, string.IsNullOrEmpty(logo) ? OmbiLogo : logo);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,9 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Templates\NewsletterTemplate.html">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Templates\BasicTemplate.html">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
|
8
src/Ombi.Notifications.Templates/TemplateBase.cs
Normal file
8
src/Ombi.Notifications.Templates/TemplateBase.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ombi.Notifications.Templates
|
||||
{
|
||||
public abstract class TemplateBase
|
||||
{
|
||||
public abstract string TemplateLocation { get; }
|
||||
public virtual string OmbiLogo => "http://i.imgur.com/qQsN78U.png";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Ombi</title>
|
||||
<style media="all" type="text/css">
|
||||
@media all {
|
||||
.btn-primary table td:hover {
|
||||
background-color: #34495e !important;
|
||||
}
|
||||
|
||||
.btn-primary a:hover {
|
||||
background-color: #34495e !important;
|
||||
border-color: #34495e !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media all {
|
||||
.btn-secondary a:hover {
|
||||
border-color: #34495e !important;
|
||||
color: #34495e !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 620px) {
|
||||
table[class=body] h1 {
|
||||
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 {
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .header {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] .main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 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 {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</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;">
|
||||
<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">
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top"> </td>
|
||||
<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: 580px; padding: 10px;">
|
||||
|
||||
<!-- 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>
|
||||
<table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #fff; border-radius: 3px;" width="100%">
|
||||
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tr>
|
||||
<td class="wrapper" style="font-family: sans-serif; font-size: 14px; vertical-align: top; box-sizing: border-box; padding: 20px;" valign="top">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="{@LOGO}" width="400px" text-align="center" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">
|
||||
<br />
|
||||
<br />
|
||||
<p style="font-family: sans-serif; font-size: 20px; font-weight: normal; margin: 0; Margin-bottom: 15px;">{@INTRO}</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{@RECENTLYADDED}
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
<div class="footer" style="clear: both; padding-top: 10px; text-align: center; 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>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-top: 10px; padding-bottom: 10px; font-size: 12px; color: #999999; text-align: center;" valign="top" align="center">
|
||||
Powered by <a href="https://github.com/tidusjar/Ombi" style="color: #999999; font-size: 12px; text-align: center; text-decoration: underline;">Ombi</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- END FOOTER -->
|
||||
<!-- END CENTERED WHITE CONTAINER -->
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
|
@ -8,7 +8,7 @@
|
|||
<PackageReference Include="Nunit" Version="3.8.1" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.0.0"></packagereference>
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.6.1"></packagereference>
|
||||
<PackageReference Include="Moq" Version="4.7.99" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -59,10 +59,18 @@ namespace Ombi.Notifications.Agents
|
|||
Message = parsed.Message,
|
||||
};
|
||||
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
AddOtherInformation(model, notification, parsed);
|
||||
//notification.Other.Add("overview", model.RequestType == RequestType.Movie ? base.MovieRequest.Overview : TvRequest.);
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
private void AddOtherInformation(NotificationOptions model, NotificationMessage notification,
|
||||
NotificationMessageContent parsed)
|
||||
{
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
notification.Other.Add("title", model.RequestType == RequestType.Movie ? MovieRequest.Title : TvRequest.Title);
|
||||
}
|
||||
|
||||
protected override async Task NewIssue(NotificationOptions model, MattermostNotificationSettings settings)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Mattermost, NotificationType.Issue, model);
|
||||
|
@ -75,7 +83,7 @@ namespace Ombi.Notifications.Agents
|
|||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
AddOtherInformation(model, notification, parsed);
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
|
@ -91,7 +99,7 @@ namespace Ombi.Notifications.Agents
|
|||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
AddOtherInformation(model, notification, parsed);
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
|
@ -107,7 +115,7 @@ namespace Ombi.Notifications.Agents
|
|||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
AddOtherInformation(model, notification, parsed);
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
|
@ -149,7 +157,7 @@ namespace Ombi.Notifications.Agents
|
|||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
AddOtherInformation(model, notification, parsed);
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
|
@ -166,7 +174,7 @@ namespace Ombi.Notifications.Agents
|
|||
Message = parsed.Message,
|
||||
};
|
||||
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
AddOtherInformation(model, notification, parsed);
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
|
@ -182,7 +190,7 @@ namespace Ombi.Notifications.Agents
|
|||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
notification.Other.Add("image", parsed.Image);
|
||||
AddOtherInformation(model, notification, parsed);
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
|
@ -190,12 +198,20 @@ namespace Ombi.Notifications.Agents
|
|||
{
|
||||
try
|
||||
{
|
||||
var body = new MattermostBody
|
||||
var body = new MattermostMessage
|
||||
{
|
||||
username = string.IsNullOrEmpty(settings.Username) ? "Ombi" : settings.Username,
|
||||
channel = settings.Channel,
|
||||
text = model.Message,
|
||||
icon_url = settings.IconUrl
|
||||
Username = string.IsNullOrEmpty(settings.Username) ? "Ombi" : settings.Username,
|
||||
Channel = settings.Channel,
|
||||
Text = model.Message,
|
||||
IconUrl = new Uri(settings.IconUrl),
|
||||
Attachments = new List<MattermostAttachment>
|
||||
{
|
||||
new MattermostAttachment
|
||||
{
|
||||
Title = model.Other.ContainsKey("title") ? model.Other["title"] : string.Empty,
|
||||
ImageUrl = model.Other.ContainsKey("image") ? new Uri(model.Other["image"]) : null,
|
||||
}
|
||||
}
|
||||
};
|
||||
await Api.PushAsync(settings.WebhookUrl, body);
|
||||
}
|
||||
|
|
|
@ -107,9 +107,13 @@ namespace Ombi.Notifications
|
|||
var body = new BodyBuilder
|
||||
{
|
||||
HtmlBody = model.Message,
|
||||
TextBody = model.Other["PlainTextBody"]
|
||||
};
|
||||
|
||||
if (model.Other.ContainsKey("PlainTextBody"))
|
||||
{
|
||||
body.TextBody = model.Other["PlainTextBody"];
|
||||
}
|
||||
|
||||
var message = new MimeMessage
|
||||
{
|
||||
Body = body.ToMessageBody(),
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Notifications.Models;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
using Ombi.Store.Repository.Requests;
|
||||
|
||||
namespace Ombi.Notifications
|
||||
{
|
||||
|
@ -25,9 +28,9 @@ namespace Ombi.Notifications
|
|||
}
|
||||
ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty;
|
||||
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
|
||||
RequestedUser = string.IsNullOrEmpty(req?.RequestedUser?.Alias)
|
||||
? req?.RequestedUser?.UserName
|
||||
: req?.RequestedUser?.Alias;
|
||||
RequestedUser = req?.RequestedUser?.UserName;
|
||||
UserName = req?.RequestedUser?.UserName;
|
||||
Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName;
|
||||
Title = title;
|
||||
RequestedDate = req?.RequestedDate.ToString("D");
|
||||
Type = req?.RequestType.ToString();
|
||||
|
@ -38,6 +41,15 @@ namespace Ombi.Notifications
|
|||
AdditionalInformation = opts?.AdditionalInformation ?? string.Empty;
|
||||
}
|
||||
|
||||
public void SetupNewsletter(CustomizationSettings s, OmbiUser username)
|
||||
{
|
||||
ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty;
|
||||
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
|
||||
RequestedUser = username.UserName;
|
||||
UserName = username.UserName;
|
||||
Alias = username.Alias.HasValue() ? username.Alias : username.UserName;
|
||||
}
|
||||
|
||||
public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s)
|
||||
{
|
||||
LoadIssues(opts);
|
||||
|
@ -52,9 +64,9 @@ namespace Ombi.Notifications
|
|||
}
|
||||
ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty;
|
||||
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
|
||||
RequestedUser = string.IsNullOrEmpty(req?.RequestedUser.Alias)
|
||||
? req?.RequestedUser.UserName
|
||||
: req?.RequestedUser.Alias;
|
||||
RequestedUser = req?.RequestedUser?.UserName;
|
||||
UserName = req?.RequestedUser?.UserName;
|
||||
Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName;
|
||||
Title = title;
|
||||
RequestedDate = req?.RequestedDate.ToString("D");
|
||||
Type = req?.RequestType.ToString();
|
||||
|
@ -64,6 +76,40 @@ namespace Ombi.Notifications
|
|||
$"https://image.tmdb.org/t/p/w300{req?.ParentRequest.PosterPath}" : req?.ParentRequest.PosterPath;
|
||||
AdditionalInformation = opts.AdditionalInformation;
|
||||
// DO Episode and Season Lists
|
||||
|
||||
var episodes = req?.SeasonRequests?.SelectMany(x => x.Episodes) ?? new List<EpisodeRequests>();
|
||||
var seasons = req?.SeasonRequests?.OrderBy(x => x.SeasonNumber).ToList() ?? new List<SeasonRequests>();
|
||||
var orderedEpisodes = episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
||||
var epSb = new StringBuilder();
|
||||
var seasonSb = new StringBuilder();
|
||||
for (var i = 0; i < orderedEpisodes.Count; i++)
|
||||
{
|
||||
var ep = orderedEpisodes[i];
|
||||
if (i < orderedEpisodes.Count - 1)
|
||||
{
|
||||
epSb.Append($"{ep.EpisodeNumber},");
|
||||
}
|
||||
else
|
||||
{
|
||||
epSb.Append($"{ep.EpisodeNumber}");
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < seasons.Count; i++)
|
||||
{
|
||||
var ep = seasons[i];
|
||||
if (i < seasons.Count - 1)
|
||||
{
|
||||
seasonSb.Append($"{ep.SeasonNumber},");
|
||||
}
|
||||
else
|
||||
{
|
||||
seasonSb.Append($"{ep.SeasonNumber}");
|
||||
}
|
||||
}
|
||||
|
||||
EpisodesList = epSb.ToString();
|
||||
SeasonsList = seasonSb.ToString();
|
||||
}
|
||||
|
||||
public void Setup(OmbiUser user, CustomizationSettings s)
|
||||
|
@ -81,13 +127,14 @@ namespace Ombi.Notifications
|
|||
IssueStatus = opts.Substitutes.TryGetValue("IssueStatus", out val) ? val : string.Empty;
|
||||
IssueSubject = opts.Substitutes.TryGetValue("IssueSubject", out val) ? val : string.Empty;
|
||||
NewIssueComment = opts.Substitutes.TryGetValue("NewIssueComment", out val) ? val : string.Empty;
|
||||
RequestedUser = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty;
|
||||
UserName = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty;
|
||||
}
|
||||
|
||||
// User Defined
|
||||
public string RequestedUser { get; set; }
|
||||
public string UserName => RequestedUser;
|
||||
public string IssueUser => RequestedUser;
|
||||
public string UserName { get; set; }
|
||||
public string IssueUser => UserName;
|
||||
public string Alias { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
public string RequestedDate { get; set; }
|
||||
|
@ -137,6 +184,7 @@ namespace Ombi.Notifications
|
|||
{nameof(NewIssueComment),NewIssueComment},
|
||||
{nameof(IssueUser),IssueUser},
|
||||
{nameof(UserName),UserName},
|
||||
{nameof(Alias),Alias},
|
||||
};
|
||||
}
|
||||
}
|
|
@ -6,12 +6,12 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.2" />
|
||||
<PackageReference Include="Moq" Version="4.7.99" />
|
||||
<PackageReference Include="Nunit" Version="3.8.1" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.0.0"></packagereference>
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.6.1"></packagereference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -17,46 +17,53 @@ namespace Ombi.Schedule
|
|||
public JobSetup(IPlexContentSync plexContentSync, IRadarrSync radarrSync,
|
||||
IOmbiAutomaticUpdater updater, IEmbyContentSync embySync, IPlexUserImporter userImporter,
|
||||
IEmbyUserImporter embyUserImporter, ISonarrSync cache, ICouchPotatoSync cpCache,
|
||||
ISettingsService<JobSettings> jobsettings, ISickRageSync srSync)
|
||||
ISettingsService<JobSettings> jobsettings, ISickRageSync srSync, IRefreshMetadata refresh,
|
||||
INewsletterJob newsletter)
|
||||
{
|
||||
PlexContentSync = plexContentSync;
|
||||
RadarrSync = radarrSync;
|
||||
Updater = updater;
|
||||
EmbyContentSync = embySync;
|
||||
PlexUserImporter = userImporter;
|
||||
EmbyUserImporter = embyUserImporter;
|
||||
SonarrSync = cache;
|
||||
CpCache = cpCache;
|
||||
JobSettings = jobsettings;
|
||||
SrSync = srSync;
|
||||
_plexContentSync = plexContentSync;
|
||||
_radarrSync = radarrSync;
|
||||
_updater = updater;
|
||||
_embyContentSync = embySync;
|
||||
_plexUserImporter = userImporter;
|
||||
_embyUserImporter = embyUserImporter;
|
||||
_sonarrSync = cache;
|
||||
_cpCache = cpCache;
|
||||
_jobSettings = jobsettings;
|
||||
_srSync = srSync;
|
||||
_refreshMetadata = refresh;
|
||||
_newsletter = newsletter;
|
||||
}
|
||||
|
||||
private IPlexContentSync PlexContentSync { get; }
|
||||
private IRadarrSync RadarrSync { get; }
|
||||
private IOmbiAutomaticUpdater Updater { get; }
|
||||
private IPlexUserImporter PlexUserImporter { get; }
|
||||
private IEmbyContentSync EmbyContentSync { get; }
|
||||
private IEmbyUserImporter EmbyUserImporter { get; }
|
||||
private ISonarrSync SonarrSync { get; }
|
||||
private ICouchPotatoSync CpCache { get; }
|
||||
private ISickRageSync SrSync { get; }
|
||||
private ISettingsService<JobSettings> JobSettings { get; set; }
|
||||
private readonly IPlexContentSync _plexContentSync;
|
||||
private readonly IRadarrSync _radarrSync;
|
||||
private readonly IOmbiAutomaticUpdater _updater;
|
||||
private readonly IPlexUserImporter _plexUserImporter;
|
||||
private readonly IEmbyContentSync _embyContentSync;
|
||||
private readonly IEmbyUserImporter _embyUserImporter;
|
||||
private readonly ISonarrSync _sonarrSync;
|
||||
private readonly ICouchPotatoSync _cpCache;
|
||||
private readonly ISickRageSync _srSync;
|
||||
private readonly ISettingsService<JobSettings> _jobSettings;
|
||||
private readonly IRefreshMetadata _refreshMetadata;
|
||||
private readonly INewsletterJob _newsletter;
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
var s = JobSettings.GetSettings();
|
||||
var s = _jobSettings.GetSettings();
|
||||
|
||||
RecurringJob.AddOrUpdate(() => EmbyContentSync.Start(), JobSettingsHelper.EmbyContent(s));
|
||||
RecurringJob.AddOrUpdate(() => SonarrSync.Start(), JobSettingsHelper.Sonarr(s));
|
||||
RecurringJob.AddOrUpdate(() => RadarrSync.CacheContent(), JobSettingsHelper.Radarr(s));
|
||||
RecurringJob.AddOrUpdate(() => PlexContentSync.CacheContent(), JobSettingsHelper.PlexContent(s));
|
||||
RecurringJob.AddOrUpdate(() => CpCache.Start(), JobSettingsHelper.CouchPotato(s));
|
||||
RecurringJob.AddOrUpdate(() => SrSync.Start(), JobSettingsHelper.SickRageSync(s));
|
||||
RecurringJob.AddOrUpdate(() => _embyContentSync.Start(), JobSettingsHelper.EmbyContent(s));
|
||||
RecurringJob.AddOrUpdate(() => _sonarrSync.Start(), JobSettingsHelper.Sonarr(s));
|
||||
RecurringJob.AddOrUpdate(() => _radarrSync.CacheContent(), JobSettingsHelper.Radarr(s));
|
||||
RecurringJob.AddOrUpdate(() => _plexContentSync.CacheContent(), JobSettingsHelper.PlexContent(s));
|
||||
RecurringJob.AddOrUpdate(() => _cpCache.Start(), JobSettingsHelper.CouchPotato(s));
|
||||
RecurringJob.AddOrUpdate(() => _srSync.Start(), JobSettingsHelper.SickRageSync(s));
|
||||
RecurringJob.AddOrUpdate(() => _refreshMetadata.Start(), JobSettingsHelper.RefreshMetadata(s));
|
||||
|
||||
RecurringJob.AddOrUpdate(() => Updater.Update(null), JobSettingsHelper.Updater(s));
|
||||
RecurringJob.AddOrUpdate(() => _updater.Update(null), JobSettingsHelper.Updater(s));
|
||||
|
||||
RecurringJob.AddOrUpdate(() => EmbyUserImporter.Start(), JobSettingsHelper.UserImporter(s));
|
||||
RecurringJob.AddOrUpdate(() => PlexUserImporter.Start(), JobSettingsHelper.UserImporter(s));
|
||||
RecurringJob.AddOrUpdate(() => _embyUserImporter.Start(), JobSettingsHelper.UserImporter(s));
|
||||
RecurringJob.AddOrUpdate(() => _plexUserImporter.Start(), JobSettingsHelper.UserImporter(s));
|
||||
RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
46
src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs
Normal file
46
src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System.Text;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Ombi
|
||||
{
|
||||
public abstract class HtmlTemplateGenerator
|
||||
{
|
||||
protected virtual void AddParagraph(StringBuilder stringBuilder, string text, int fontSize = 14, string fontWeight = "normal")
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
protected virtual void AddImageInsideTable(StringBuilder sb, string url, int size = 400)
|
||||
{
|
||||
sb.Append("<tr>");
|
||||
sb.Append("<td align=\"center\">");
|
||||
sb.Append($"<img src=\"{url}\" width=\"{size}px\" text-align=\"center\" />");
|
||||
sb.Append("</td>");
|
||||
sb.Append("</tr>");
|
||||
}
|
||||
|
||||
protected virtual void Href(StringBuilder sb, string url)
|
||||
{
|
||||
sb.AppendFormat("<a href=\"{0}\">", url);
|
||||
}
|
||||
|
||||
protected virtual void TableData(StringBuilder sb)
|
||||
{
|
||||
sb.Append(
|
||||
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
|
||||
}
|
||||
|
||||
protected virtual void EndTag(StringBuilder sb, string tag)
|
||||
{
|
||||
sb.AppendFormat("</{0}>", tag);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
11
src/Ombi.Schedule/Jobs/Ombi/INewsletterJob.cs
Normal file
11
src/Ombi.Schedule/Jobs/Ombi/INewsletterJob.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System.Threading.Tasks;
|
||||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Ombi
|
||||
{
|
||||
public interface INewsletterJob : IBaseJob
|
||||
{
|
||||
Task Start();
|
||||
Task Start(NewsletterSettings settings, bool test);
|
||||
}
|
||||
}
|
9
src/Ombi.Schedule/Jobs/Ombi/IRefreshMetadata.cs
Normal file
9
src/Ombi.Schedule/Jobs/Ombi/IRefreshMetadata.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Ombi
|
||||
{
|
||||
public interface IRefreshMetadata : IBaseJob
|
||||
{
|
||||
Task Start();
|
||||
}
|
||||
}
|
636
src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs
Normal file
636
src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs
Normal file
|
@ -0,0 +1,636 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Api.TheMovieDb;
|
||||
using Ombi.Api.TheMovieDb.Models;
|
||||
using Ombi.Api.TvMaze;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Notifications;
|
||||
using Ombi.Notifications.Models;
|
||||
using Ombi.Notifications.Templates;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Ombi
|
||||
{
|
||||
public class NewsletterJob : HtmlTemplateGenerator, INewsletterJob
|
||||
{
|
||||
public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository<RecentlyAddedLog> addedLog,
|
||||
IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService<CustomizationSettings> custom,
|
||||
ISettingsService<EmailNotificationSettings> emailSettings, INotificationTemplatesRepository templateRepo,
|
||||
UserManager<OmbiUser> um, ISettingsService<NewsletterSettings> newsletter)
|
||||
{
|
||||
_plex = plex;
|
||||
_emby = emby;
|
||||
_recentlyAddedLog = addedLog;
|
||||
_movieApi = movieApi;
|
||||
_tvApi = tvApi;
|
||||
_email = email;
|
||||
_customizationSettings = custom;
|
||||
_templateRepo = templateRepo;
|
||||
_emailSettings = emailSettings;
|
||||
_newsletterSettings = newsletter;
|
||||
_userManager = um;
|
||||
_emailSettings.ClearCache();
|
||||
_customizationSettings.ClearCache();
|
||||
_newsletterSettings.ClearCache();
|
||||
}
|
||||
|
||||
private readonly IPlexContentRepository _plex;
|
||||
private readonly IEmbyContentRepository _emby;
|
||||
private readonly IRepository<RecentlyAddedLog> _recentlyAddedLog;
|
||||
private readonly IMovieDbApi _movieApi;
|
||||
private readonly ITvMazeApi _tvApi;
|
||||
private readonly IEmailProvider _email;
|
||||
private readonly ISettingsService<CustomizationSettings> _customizationSettings;
|
||||
private readonly INotificationTemplatesRepository _templateRepo;
|
||||
private readonly ISettingsService<EmailNotificationSettings> _emailSettings;
|
||||
private readonly ISettingsService<NewsletterSettings> _newsletterSettings;
|
||||
private readonly UserManager<OmbiUser> _userManager;
|
||||
|
||||
public async Task Start(NewsletterSettings settings, bool test)
|
||||
{
|
||||
if (!settings.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var template = await _templateRepo.GetTemplate(NotificationAgent.Email, NotificationType.Newsletter);
|
||||
if (!template.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var emailSettings = await _emailSettings.GetSettingsAsync();
|
||||
if (!ValidateConfiguration(emailSettings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var customization = await _customizationSettings.GetSettingsAsync();
|
||||
|
||||
// Get the Content
|
||||
var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking();
|
||||
var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking();
|
||||
|
||||
var addedLog = _recentlyAddedLog.GetAll();
|
||||
var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId);
|
||||
var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId);
|
||||
|
||||
var addedPlexEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode).Select(x => x.ContentId);
|
||||
var addedEmbyEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode).Select(x => x.ContentId);
|
||||
|
||||
// Filter out the ones that we haven't sent yet
|
||||
var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.Contains(x.Id));
|
||||
var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(x.Id));
|
||||
|
||||
var plexEpisodesToSend = _plex.GetAllEpisodes().Include(x => x.Series).Where(x => !addedPlexEpisodesLogIds.Contains(x.Id)).AsNoTracking();
|
||||
var embyEpisodesToSend = _emby.GetAllEpisodes().Include(x => x.Series).Where(x => !addedEmbyEpisodesLogIds.Contains(x.Id)).AsNoTracking();
|
||||
|
||||
var body = string.Empty;
|
||||
if (test)
|
||||
{
|
||||
var plexm = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie).OrderByDescending(x => x.AddedAt).Take(10);
|
||||
var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie).OrderByDescending(x => x.AddedAt).Take(10);
|
||||
var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10);
|
||||
var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10);
|
||||
body = await BuildHtml(plexm, embym, plext, embyt);
|
||||
}
|
||||
else
|
||||
{
|
||||
body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend);
|
||||
if (body.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!test)
|
||||
{
|
||||
// Get the users to send it to
|
||||
var users = await _userManager.GetUsersInRoleAsync(OmbiRoles.RecievesNewsletter);
|
||||
if (!users.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var emailTasks = new List<Task>();
|
||||
foreach (var user in users)
|
||||
{
|
||||
if (user.Email.IsNullOrEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var messageContent = ParseTemplate(template, customization, user);
|
||||
var email = new NewsletterTemplate();
|
||||
|
||||
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo);
|
||||
|
||||
emailTasks.Add(_email.Send(
|
||||
new NotificationMessage { Message = html, Subject = messageContent.Subject, To = user.Email },
|
||||
emailSettings));
|
||||
}
|
||||
|
||||
// Now add all of this to the Recently Added log
|
||||
var recentlyAddedLog = new HashSet<RecentlyAddedLog>();
|
||||
foreach (var p in plexContentMoviesToSend)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Plex,
|
||||
ContentType = ContentType.Parent,
|
||||
ContentId = p.Id
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
foreach (var p in plexEpisodesToSend)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Plex,
|
||||
ContentType = ContentType.Episode,
|
||||
ContentId = p.Id
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var e in embyContentMoviesToSend)
|
||||
{
|
||||
if (e.Type == EmbyMediaType.Movie)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Emby,
|
||||
ContentType = ContentType.Parent,
|
||||
ContentId = e.Id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var p in embyEpisodesToSend)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Emby,
|
||||
ContentType = ContentType.Episode,
|
||||
ContentId = p.Id
|
||||
});
|
||||
}
|
||||
await _recentlyAddedLog.AddRange(recentlyAddedLog);
|
||||
|
||||
await Task.WhenAll(emailTasks.ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
var admins = await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin);
|
||||
foreach (var a in admins)
|
||||
{
|
||||
if (a.Email.IsNullOrEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var messageContent = ParseTemplate(template, customization, a);
|
||||
|
||||
var email = new NewsletterTemplate();
|
||||
|
||||
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo);
|
||||
|
||||
await _email.Send(
|
||||
new NotificationMessage { Message = html, Subject = messageContent.Subject, To = a.Email },
|
||||
emailSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Start()
|
||||
{
|
||||
var newsletterSettings = await _newsletterSettings.GetSettingsAsync();
|
||||
await Start(newsletterSettings, false);
|
||||
}
|
||||
|
||||
private NotificationMessageContent ParseTemplate(NotificationTemplates template, CustomizationSettings settings, OmbiUser username)
|
||||
{
|
||||
var resolver = new NotificationMessageResolver();
|
||||
var curlys = new NotificationMessageCurlys();
|
||||
|
||||
curlys.SetupNewsletter(settings, username);
|
||||
|
||||
return resolver.ParseMessage(template, curlys);
|
||||
}
|
||||
|
||||
private async Task<string> BuildHtml(IQueryable<PlexServerContent> plexContentToSend, IQueryable<EmbyContent> embyContentToSend, IQueryable<PlexEpisode> plexEpisodes, IQueryable<EmbyEpisode> embyEp)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var plexMovies = plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Movie);
|
||||
var embyMovies = embyContentToSend.Where(x => x.Type == EmbyMediaType.Movie);
|
||||
if (plexMovies.Any() || embyMovies.Any())
|
||||
{
|
||||
sb.Append("<h1>New Movies:</h1><br /><br />");
|
||||
await ProcessPlexMovies(plexMovies, sb);
|
||||
await ProcessEmbyMovies(embyMovies, sb);
|
||||
}
|
||||
|
||||
if (plexEpisodes.Any() || embyEp.Any())
|
||||
{
|
||||
sb.Append("<h1>New Episodes:</h1><br /><br />");
|
||||
await ProcessPlexTv(plexEpisodes, sb);
|
||||
await ProcessEmbyTv(embyEp, sb);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private async Task ProcessPlexMovies(IQueryable<PlexServerContent> plexContentToSend, StringBuilder sb)
|
||||
{
|
||||
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%\">");
|
||||
var ordered = plexContentToSend.OrderByDescending(x => x.AddedAt);
|
||||
foreach (var content in ordered)
|
||||
{
|
||||
if (content.TheMovieDbId.IsNullOrEmpty())
|
||||
{
|
||||
// Maybe we should try the ImdbId?
|
||||
if (content.ImdbId.HasValue())
|
||||
{
|
||||
var findResult = await _movieApi.Find(content.ImdbId, ExternalSource.imdb_id);
|
||||
|
||||
var movieId = findResult.movie_results?[0]?.id ?? 0;
|
||||
content.TheMovieDbId = movieId.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
int.TryParse(content.TheMovieDbId, out var movieDbId);
|
||||
var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId);
|
||||
if (info == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
try
|
||||
{
|
||||
CreateMovieHtmlContent(sb, info);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndLoopHtml(sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessEmbyMovies(IQueryable<EmbyContent> embyContent, StringBuilder sb)
|
||||
{
|
||||
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%\">");
|
||||
var ordered = embyContent.OrderByDescending(x => x.AddedAt);
|
||||
foreach (var content in ordered)
|
||||
{
|
||||
int.TryParse(content.ProviderId, out var movieDbId);
|
||||
var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId);
|
||||
if (info == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
try
|
||||
{
|
||||
CreateMovieHtmlContent(sb, info);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndLoopHtml(sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateMovieHtmlContent(StringBuilder sb, MovieResponseDto info)
|
||||
{
|
||||
AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/original{info.PosterPath}");
|
||||
|
||||
sb.Append("<tr>");
|
||||
TableData(sb);
|
||||
|
||||
Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/");
|
||||
var releaseDate = string.Empty;
|
||||
try
|
||||
{
|
||||
releaseDate = $"({DateTime.Parse(info.ReleaseDate).Year})";
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Swallow, couldn't parse the date
|
||||
}
|
||||
Header(sb, 3, $"{info.Title} {releaseDate}");
|
||||
EndTag(sb, "a");
|
||||
|
||||
if (info.Genres.Any())
|
||||
{
|
||||
AddParagraph(sb,
|
||||
$"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}");
|
||||
}
|
||||
|
||||
AddParagraph(sb, info.Overview);
|
||||
}
|
||||
|
||||
private async Task ProcessPlexTv(IQueryable<PlexEpisode> plexContent, StringBuilder sb)
|
||||
{
|
||||
var series = new List<PlexServerContent>();
|
||||
foreach (var plexEpisode in plexContent)
|
||||
{
|
||||
var alreadyAdded = series.FirstOrDefault(x => x.Key == plexEpisode.Series.Key);
|
||||
if (alreadyAdded != null)
|
||||
{
|
||||
var episodeExists = alreadyAdded.Episodes.Any(x => x.Key == plexEpisode.Key);
|
||||
if (!episodeExists)
|
||||
{
|
||||
alreadyAdded.Episodes.Add(plexEpisode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
plexEpisode.Series.Episodes = new List<PlexEpisode> { plexEpisode };
|
||||
series.Add(plexEpisode.Series);
|
||||
}
|
||||
}
|
||||
|
||||
var orderedTv = series.OrderByDescending(x => x.AddedAt);
|
||||
sb.Append(
|
||||
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
|
||||
foreach (var t in orderedTv)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!t.HasTvDb)
|
||||
{
|
||||
// We may need to use themoviedb for the imdbid or their own id to get info
|
||||
if (t.HasTheMovieDb)
|
||||
{
|
||||
int.TryParse(t.TheMovieDbId, out var movieId);
|
||||
var externals = await _movieApi.GetTvExternals(movieId);
|
||||
if (externals == null || externals.tvdb_id <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
t.TvDbId = externals.tvdb_id.ToString();
|
||||
}
|
||||
// WE could check the below but we need to get the moviedb and then perform the above, let the metadata job figure this out.
|
||||
//else if(t.HasImdb)
|
||||
//{
|
||||
// // Check the imdbid
|
||||
// var externals = await _movieApi.Find(t.ImdbId, ExternalSource.imdb_id);
|
||||
// if (externals?.tv_results == null || externals.tv_results.Length <= 0)
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
// t.TvDbId = externals.tv_results.FirstOrDefault()..ToString();
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
int.TryParse(t.TvDbId, out var tvdbId);
|
||||
var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId);
|
||||
if (info == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var banner = info.image?.original;
|
||||
if (!string.IsNullOrEmpty(banner))
|
||||
{
|
||||
banner = banner.Replace("http", "https"); // Always use the Https banners
|
||||
}
|
||||
AddImageInsideTable(sb, banner);
|
||||
|
||||
sb.Append("<tr>");
|
||||
sb.Append(
|
||||
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
|
||||
|
||||
var title = $"{t.Title} ({t.ReleaseYear})";
|
||||
|
||||
Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/");
|
||||
Header(sb, 3, title);
|
||||
EndTag(sb, "a");
|
||||
|
||||
// Group by the season number
|
||||
var results = t.Episodes.GroupBy(p => p.SeasonNumber,
|
||||
(key, g) => new
|
||||
{
|
||||
SeasonNumber = key,
|
||||
Episodes = g.ToList()
|
||||
}
|
||||
);
|
||||
|
||||
// Group the episodes
|
||||
foreach (var epInformation in results.OrderBy(x => x.SeasonNumber))
|
||||
{
|
||||
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
||||
var epSb = new StringBuilder();
|
||||
for (var i = 0; i < orderedEpisodes.Count; i++)
|
||||
{
|
||||
var ep = orderedEpisodes[i];
|
||||
if (i < orderedEpisodes.Count - 1)
|
||||
{
|
||||
epSb.Append($"{ep.EpisodeNumber},");
|
||||
}
|
||||
else
|
||||
{
|
||||
epSb.Append($"{ep.EpisodeNumber}");
|
||||
}
|
||||
|
||||
}
|
||||
AddParagraph(sb, $"Season: {epInformation.SeasonNumber}, Episode: {epSb}");
|
||||
}
|
||||
|
||||
if (info.genres.Any())
|
||||
{
|
||||
AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
|
||||
}
|
||||
|
||||
AddParagraph(sb, info.summary);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//Log.Error(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndLoopHtml(sb);
|
||||
}
|
||||
}
|
||||
sb.Append("</table><br /><br />");
|
||||
|
||||
}
|
||||
|
||||
private async Task ProcessEmbyTv(IQueryable<EmbyEpisode> embyContent, StringBuilder sb)
|
||||
{
|
||||
var series = new List<EmbyContent>();
|
||||
foreach (var episode in embyContent)
|
||||
{
|
||||
var alreadyAdded = series.FirstOrDefault(x => x.EmbyId == episode.Series.EmbyId);
|
||||
if (alreadyAdded != null)
|
||||
{
|
||||
alreadyAdded.Episodes.Add(episode);
|
||||
}
|
||||
else
|
||||
{
|
||||
episode.Series.Episodes = new List<EmbyEpisode>
|
||||
{
|
||||
episode
|
||||
};
|
||||
series.Add(episode.Series);
|
||||
}
|
||||
}
|
||||
var orderedTv = series.OrderByDescending(x => x.AddedAt);
|
||||
sb.Append(
|
||||
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
|
||||
foreach (var t in orderedTv)
|
||||
{
|
||||
try
|
||||
{
|
||||
int.TryParse(t.ProviderId, out var tvdbId);
|
||||
var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId);
|
||||
if (info == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var banner = info.image?.original;
|
||||
if (!string.IsNullOrEmpty(banner))
|
||||
{
|
||||
banner = banner.Replace("http", "https"); // Always use the Https banners
|
||||
}
|
||||
AddImageInsideTable(sb, banner);
|
||||
|
||||
sb.Append("<tr>");
|
||||
sb.Append(
|
||||
"<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}/");
|
||||
Header(sb, 3, t.Title);
|
||||
EndTag(sb, "a");
|
||||
|
||||
// Group by the season number
|
||||
var results = t.Episodes?.GroupBy(p => p.SeasonNumber,
|
||||
(key, g) => new
|
||||
{
|
||||
SeasonNumber = key,
|
||||
Episodes = g.ToList()
|
||||
}
|
||||
);
|
||||
|
||||
// Group the episodes
|
||||
foreach (var epInformation in results.OrderBy(x => x.SeasonNumber))
|
||||
{
|
||||
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
||||
var epSb = new StringBuilder();
|
||||
for (var i = 0; i < orderedEpisodes.Count; i++)
|
||||
{
|
||||
var ep = orderedEpisodes[i];
|
||||
if (i < orderedEpisodes.Count - 1)
|
||||
{
|
||||
epSb.Append($"{ep.EpisodeNumber},");
|
||||
}
|
||||
else
|
||||
{
|
||||
epSb.Append($"{ep.EpisodeNumber}");
|
||||
}
|
||||
|
||||
}
|
||||
AddParagraph(sb, $"Season: {epInformation.SeasonNumber}, Episode: {epSb}");
|
||||
}
|
||||
|
||||
if (info.genres.Any())
|
||||
{
|
||||
AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
|
||||
}
|
||||
|
||||
AddParagraph(sb, info.summary);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//Log.Error(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndLoopHtml(sb);
|
||||
}
|
||||
}
|
||||
sb.Append("</table><br /><br />");
|
||||
}
|
||||
|
||||
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...
|
||||
//Source: http://stackoverflow.com/questions/6588638/phantom-br-tag-rendered-by-browsers-prior-to-table-tag
|
||||
sb.Append("<hr />");
|
||||
sb.Append("<br />");
|
||||
sb.Append("<br />");
|
||||
sb.Append("</td>");
|
||||
sb.Append("</tr>");
|
||||
}
|
||||
|
||||
protected bool ValidateConfiguration(EmailNotificationSettings settings)
|
||||
{
|
||||
if (!settings.Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (settings.Authentication)
|
||||
{
|
||||
if (string.IsNullOrEmpty(settings.Username) || string.IsNullOrEmpty(settings.Password))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (string.IsNullOrEmpty(settings.Host) || string.IsNullOrEmpty(settings.AdminEmail) || string.IsNullOrEmpty(settings.Port.ToString()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_plex?.Dispose();
|
||||
_emby?.Dispose();
|
||||
_newsletterSettings?.Dispose();
|
||||
_customizationSettings?.Dispose();
|
||||
_emailSettings.Dispose();
|
||||
_recentlyAddedLog.Dispose();
|
||||
_templateRepo?.Dispose();
|
||||
_userManager?.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
244
src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs
Normal file
244
src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs
Normal file
|
@ -0,0 +1,244 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Api.TheMovieDb;
|
||||
using Ombi.Api.TheMovieDb.Models;
|
||||
using Ombi.Api.TvMaze;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using Ombi.Store.Repository.Requests;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Ombi
|
||||
{
|
||||
public class RefreshMetadata : IRefreshMetadata
|
||||
{
|
||||
public RefreshMetadata(IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo,
|
||||
ILogger<RefreshMetadata> log, ITvMazeApi tvApi, ISettingsService<PlexSettings> plexSettings,
|
||||
IMovieDbApi movieApi)
|
||||
{
|
||||
_plexRepo = plexRepo;
|
||||
_embyRepo = embyRepo;
|
||||
_log = log;
|
||||
_movieApi = movieApi;
|
||||
_tvApi = tvApi;
|
||||
_plexSettings = plexSettings;
|
||||
}
|
||||
|
||||
private readonly IPlexContentRepository _plexRepo;
|
||||
private readonly IEmbyContentRepository _embyRepo;
|
||||
private readonly ILogger _log;
|
||||
private readonly IMovieDbApi _movieApi;
|
||||
private readonly ITvMazeApi _tvApi;
|
||||
private readonly ISettingsService<PlexSettings> _plexSettings;
|
||||
|
||||
public async Task Start()
|
||||
{
|
||||
_log.LogInformation("Starting the Metadata refresh");
|
||||
try
|
||||
{
|
||||
var settings = await _plexSettings.GetSettingsAsync();
|
||||
if (settings.Enable)
|
||||
{
|
||||
await StartPlex();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.LogError(e, "Exception when refreshing the Plex Metadata");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartPlex()
|
||||
{
|
||||
await StartPlexMovies();
|
||||
|
||||
// Now Tv
|
||||
await StartPlexTv();
|
||||
}
|
||||
|
||||
private async Task StartPlexTv()
|
||||
{
|
||||
var allTv = _plexRepo.GetAll().Where(x =>
|
||||
x.Type == PlexMediaTypeEntity.Show && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue() || !x.TvDbId.HasValue()));
|
||||
var tvCount = 0;
|
||||
foreach (var show in allTv)
|
||||
{
|
||||
var hasImdb = show.ImdbId.HasValue();
|
||||
var hasTheMovieDb = show.TheMovieDbId.HasValue();
|
||||
var hasTvDbId = show.TvDbId.HasValue();
|
||||
|
||||
if (!hasTheMovieDb)
|
||||
{
|
||||
var id = await GetTheMovieDbId(hasTvDbId, hasImdb, show.TvDbId, show.ImdbId, show.Title);
|
||||
show.TheMovieDbId = id;
|
||||
}
|
||||
|
||||
if (!hasImdb)
|
||||
{
|
||||
var id = await GetImdbId(hasTheMovieDb, hasTvDbId, show.Title, show.TheMovieDbId, show.TvDbId);
|
||||
show.ImdbId = id;
|
||||
_plexRepo.UpdateWithoutSave(show);
|
||||
}
|
||||
|
||||
if (!hasTvDbId)
|
||||
{
|
||||
var id = await GetTvDbId(hasTheMovieDb, hasImdb, show.TheMovieDbId, show.ImdbId, show.Title);
|
||||
show.TvDbId = id;
|
||||
_plexRepo.UpdateWithoutSave(show);
|
||||
}
|
||||
tvCount++;
|
||||
if (tvCount >= 20)
|
||||
{
|
||||
await _plexRepo.SaveChangesAsync();
|
||||
tvCount = 0;
|
||||
}
|
||||
}
|
||||
await _plexRepo.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async Task StartPlexMovies()
|
||||
{
|
||||
var allMovies = _plexRepo.GetAll().Where(x =>
|
||||
x.Type == PlexMediaTypeEntity.Movie && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue()));
|
||||
int movieCount = 0;
|
||||
foreach (var movie in allMovies)
|
||||
{
|
||||
var hasImdb = movie.ImdbId.HasValue();
|
||||
var hasTheMovieDb = movie.TheMovieDbId.HasValue();
|
||||
// Movies don't really use TheTvDb
|
||||
|
||||
if (!hasImdb)
|
||||
{
|
||||
var imdbId = await GetImdbId(hasTheMovieDb, false, movie.Title, movie.TheMovieDbId, string.Empty);
|
||||
movie.ImdbId = imdbId;
|
||||
_plexRepo.UpdateWithoutSave(movie);
|
||||
}
|
||||
if (!hasTheMovieDb)
|
||||
{
|
||||
var id = await GetTheMovieDbId(false, hasImdb, string.Empty, movie.ImdbId, movie.Title);
|
||||
movie.TheMovieDbId = id;
|
||||
_plexRepo.UpdateWithoutSave(movie);
|
||||
}
|
||||
movieCount++;
|
||||
if (movieCount >= 20)
|
||||
{
|
||||
await _plexRepo.SaveChangesAsync();
|
||||
movieCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
await _plexRepo.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async Task<string> GetTheMovieDbId(bool hasTvDbId, bool hasImdb, string tvdbID, string imdbId, string title)
|
||||
{
|
||||
_log.LogInformation("The Media item {0} does not have a TheMovieDbId, searching for TheMovieDbId", title);
|
||||
FindResult result = null;
|
||||
var hasResult = false;
|
||||
if (hasTvDbId)
|
||||
{
|
||||
result = await _movieApi.Find(tvdbID, ExternalSource.tvdb_id);
|
||||
hasResult = result?.tv_results?.Length > 0;
|
||||
|
||||
_log.LogInformation("Setting Show {0} because we have TvDbId, result: {1}", title, hasResult);
|
||||
}
|
||||
if (hasImdb && !hasResult)
|
||||
{
|
||||
result = await _movieApi.Find(imdbId, ExternalSource.imdb_id);
|
||||
hasResult = result?.tv_results?.Length > 0;
|
||||
|
||||
_log.LogInformation("Setting Show {0} because we have ImdbId, result: {1}", title, hasResult);
|
||||
}
|
||||
if (hasResult)
|
||||
{
|
||||
return result.tv_results?[0]?.id.ToString() ?? string.Empty;
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private async Task<string> GetImdbId(bool hasTheMovieDb, bool hasTvDbId, string title, string theMovieDbId, string tvDbId)
|
||||
{
|
||||
_log.LogInformation("The media item {0} does not have a ImdbId, searching for ImdbId", title);
|
||||
// Looks like TV Maze does not provide the moviedb id, neither does the TV endpoint on TheMovieDb
|
||||
if (hasTheMovieDb)
|
||||
{
|
||||
_log.LogInformation("The show {0} has TheMovieDbId but not ImdbId, searching for ImdbId", title);
|
||||
if (int.TryParse(theMovieDbId, out var id))
|
||||
{
|
||||
var result = await _movieApi.GetTvExternals(id);
|
||||
|
||||
return result.imdb_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasTvDbId)
|
||||
{
|
||||
_log.LogInformation("The show {0} has tvdbid but not ImdbId, searching for ImdbId", title);
|
||||
if (int.TryParse(tvDbId, out var id))
|
||||
{
|
||||
var result = await _tvApi.ShowLookupByTheTvDbId(id);
|
||||
return result?.externals?.imdb;
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
||||
private async Task<string> GetTvDbId(bool hasTheMovieDb, bool hasImdb, string theMovieDbId, string imdbId, string title)
|
||||
{
|
||||
_log.LogInformation("The media item {0} does not have a TvDbId, searching for TvDbId", title);
|
||||
if (hasTheMovieDb)
|
||||
{
|
||||
_log.LogInformation("The show {0} has theMovieDBId but not ImdbId, searching for ImdbId", title);
|
||||
if (int.TryParse(theMovieDbId, out var id))
|
||||
{
|
||||
var result = await _movieApi.GetTvExternals(id);
|
||||
|
||||
return result.tvdb_id.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
if (hasImdb)
|
||||
{
|
||||
_log.LogInformation("The show {0} has ImdbId but not ImdbId, searching for ImdbId", title);
|
||||
var result = await _movieApi.Find(imdbId, ExternalSource.imdb_id);
|
||||
if (result?.tv_results?.Length > 0)
|
||||
{
|
||||
var movieId = result.tv_results?[0]?.id ?? 0;
|
||||
|
||||
var externalResult = await _movieApi.GetTvExternals(movieId);
|
||||
|
||||
return externalResult.imdb_id;
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
||||
private bool _disposed;
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_plexRepo?.Dispose();
|
||||
_embyRepo?.Dispose();
|
||||
_plexSettings?.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,8 +32,10 @@
|
|||
<ProjectReference Include="..\Ombi.Api.Service\Ombi.Api.Service.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.SickRage\Ombi.Api.SickRage.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Sonarr\Ombi.Api.Sonarr.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" />
|
||||
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -46,7 +46,8 @@ namespace Ombi.Schedule.Processor
|
|||
if (masterBranch)
|
||||
{
|
||||
latestRelease = doc.DocumentNode.Descendants("h2")
|
||||
.FirstOrDefault(x => x.InnerText != "(unreleased)");
|
||||
.FirstOrDefault(x => x.InnerText == "(unreleased)");
|
||||
// TODO: Change this to InnterText != "(unreleased)" once we go live and it's not a prerelease
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -78,9 +79,9 @@ namespace Ombi.Schedule.Processor
|
|||
Downloads = new List<Downloads>()
|
||||
};
|
||||
|
||||
var releaseTag = latestRelease.InnerText.Substring(0, 6);
|
||||
if (masterBranch)
|
||||
{
|
||||
var releaseTag = latestRelease.InnerText.Substring(0, 9);
|
||||
await GetGitubRelease(release, releaseTag);
|
||||
}
|
||||
else
|
||||
|
@ -147,7 +148,7 @@ namespace Ombi.Schedule.Processor
|
|||
var builds = await _api.Request<AppveyorBranchResult>(request);
|
||||
var jobId = builds.build.jobs.FirstOrDefault()?.jobId ?? string.Empty;
|
||||
|
||||
if (builds.build.finished == DateTime.MinValue)
|
||||
if (builds.build.finished == DateTime.MinValue || builds.build.status.Equals("failed"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ namespace Ombi.Settings.Settings.Models
|
|||
{
|
||||
public string ApplicationName { get; set; }
|
||||
public string ApplicationUrl { get; set; }
|
||||
public bool Mobile { get; set; }
|
||||
public string CustomCssLink { get; set; }
|
||||
public bool EnableCustomDonations { get; set; }
|
||||
public string CustomDonationUrl { get; set; }
|
||||
|
@ -17,6 +18,7 @@ namespace Ombi.Settings.Settings.Models
|
|||
|
||||
public string PresetThemeName { get; set; }
|
||||
public string PresetThemeContent { get; set; }
|
||||
public bool RecentlyAddedPage { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public string PresetThemeVersion
|
||||
|
|
|
@ -10,5 +10,7 @@
|
|||
public string AutomaticUpdater { get; set; }
|
||||
public string UserImporter { get; set; }
|
||||
public string SickRageSync { get; set; }
|
||||
public string RefreshMetadata { get; set; }
|
||||
public string Newsletter { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using Ombi.Helpers;
|
||||
using System;
|
||||
using Ombi.Helpers;
|
||||
|
||||
namespace Ombi.Settings.Settings.Models
|
||||
{
|
||||
|
@ -35,10 +36,18 @@ namespace Ombi.Settings.Settings.Models
|
|||
{
|
||||
return Get(s.UserImporter, Cron.Daily());
|
||||
}
|
||||
public static string Newsletter(JobSettings s)
|
||||
{
|
||||
return Get(s.Newsletter, Cron.Weekly(DayOfWeek.Friday, 12));
|
||||
}
|
||||
public static string SickRageSync(JobSettings s)
|
||||
{
|
||||
return Get(s.SickRageSync, Cron.Hourly(35));
|
||||
}
|
||||
public static string RefreshMetadata(JobSettings s)
|
||||
{
|
||||
return Get(s.RefreshMetadata, Cron.Daily(3));
|
||||
}
|
||||
|
||||
|
||||
private static string Get(string settings, string defaultCron)
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
namespace Ombi.Settings.Settings.Models.Notifications
|
||||
{
|
||||
public class NewsletterSettings : Settings
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
}
|
|
@ -41,5 +41,6 @@ namespace Ombi.Store.Context
|
|||
DbSet<SickRageCache> SickRageCache { get; set; }
|
||||
DbSet<SickRageEpisodeCache> SickRageEpisodeCache { get; set; }
|
||||
DbSet<RequestLog> RequestLogs { get; set; }
|
||||
DbSet<RecentlyAddedLog> RecentlyAddedLogs { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Helpers;
|
||||
|
@ -37,6 +38,7 @@ namespace Ombi.Store.Context
|
|||
public DbSet<IssueCategory> IssueCategories { get; set; }
|
||||
public DbSet<IssueComments> IssueComments { get; set; }
|
||||
public DbSet<RequestLog> RequestLogs { get; set; }
|
||||
public DbSet<RecentlyAddedLog> RecentlyAddedLogs { get; set; }
|
||||
|
||||
|
||||
public DbSet<Audit> Audit { get; set; }
|
||||
|
@ -55,7 +57,7 @@ namespace Ombi.Store.Context
|
|||
{
|
||||
i.StoragePath = string.Empty;
|
||||
}
|
||||
optionsBuilder.UseSqlite($"Data Source={Path.Combine(i.StoragePath,"Ombi.db")}");
|
||||
optionsBuilder.UseSqlite($"Data Source={Path.Combine(i.StoragePath, "Ombi.db")}");
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
|
@ -113,6 +115,15 @@ namespace Ombi.Store.Context
|
|||
// VACUUM;
|
||||
Database.ExecuteSqlCommand("VACUUM;");
|
||||
|
||||
// Make sure we have the roles
|
||||
var roles = Roles.Where(x => x.Name == OmbiRoles.RecievesNewsletter);
|
||||
if (!roles.Any())
|
||||
{
|
||||
Roles.Add(new IdentityRole(OmbiRoles.RecievesNewsletter)
|
||||
{
|
||||
NormalizedName = OmbiRoles.RecievesNewsletter.ToUpper()
|
||||
});
|
||||
}
|
||||
//Check if templates exist
|
||||
var templates = NotificationTemplates.ToList();
|
||||
|
||||
|
@ -135,7 +146,7 @@ namespace Ombi.Store.Context
|
|||
notificationToAdd = new NotificationTemplates
|
||||
{
|
||||
NotificationType = notificationType,
|
||||
Message = "Hello! The user '{RequestedUser}' has requested the {Type} '{Title}'! Please log in to approve this request. Request Date: {RequestedDate}",
|
||||
Message = "Hello! The user '{UserName}' has requested the {Type} '{Title}'! Please log in to approve this request. Request Date: {RequestedDate}",
|
||||
Subject = "{ApplicationName}: New {Type} request for {Title}!",
|
||||
Agent = agent,
|
||||
Enabled = true,
|
||||
|
@ -145,7 +156,7 @@ namespace Ombi.Store.Context
|
|||
notificationToAdd = new NotificationTemplates
|
||||
{
|
||||
NotificationType = notificationType,
|
||||
Message = "Hello! The user '{IssueUser}' has reported a new issue for the title {Title}! </br> {IssueCategory} - {IssueSubject} : {IssueDescription}",
|
||||
Message = "Hello! The user '{UserName}' has reported a new issue for the title {Title}! </br> {IssueCategory} - {IssueSubject} : {IssueDescription}",
|
||||
Subject = "{ApplicationName}: New issue for {Title}!",
|
||||
Agent = agent,
|
||||
Enabled = true,
|
||||
|
@ -155,7 +166,7 @@ namespace Ombi.Store.Context
|
|||
notificationToAdd = new NotificationTemplates
|
||||
{
|
||||
NotificationType = notificationType,
|
||||
Message = "Hello! You requested {Title} on {ApplicationName}! This is now available! :)",
|
||||
Message = "Hello! You {Title} on {ApplicationName}! This is now available! :)",
|
||||
Subject = "{ApplicationName}: {Title} is now available!",
|
||||
Agent = agent,
|
||||
Enabled = true,
|
||||
|
@ -199,7 +210,7 @@ namespace Ombi.Store.Context
|
|||
notificationToAdd = new NotificationTemplates
|
||||
{
|
||||
NotificationType = notificationType,
|
||||
Message = "Hello {IssueUser} Your issue for {Title} has now been resolved.",
|
||||
Message = "Hello {UserName} Your issue for {Title} has now been resolved.",
|
||||
Subject = "{ApplicationName}: Issue has been resolved for {Title}!",
|
||||
Agent = agent,
|
||||
Enabled = true,
|
||||
|
@ -218,6 +229,16 @@ namespace Ombi.Store.Context
|
|||
break;
|
||||
case NotificationType.AdminNote:
|
||||
continue;
|
||||
case NotificationType.Newsletter:
|
||||
notificationToAdd = new NotificationTemplates
|
||||
{
|
||||
NotificationType = notificationType,
|
||||
Message = "Here is a list of Movies and TV Shows that have recently been added!",
|
||||
Subject = "{ApplicationName}: Recently Added Content!",
|
||||
Agent = agent,
|
||||
Enabled = true,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
|
|
@ -56,6 +56,15 @@ namespace Ombi.Store.Entities
|
|||
public int Key { get; set; }
|
||||
public DateTime AddedAt { get; set; }
|
||||
public string Quality { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public bool HasImdb => !string.IsNullOrEmpty(ImdbId);
|
||||
|
||||
[NotMapped]
|
||||
public bool HasTvDb => !string.IsNullOrEmpty(TvDbId);
|
||||
|
||||
[NotMapped]
|
||||
public bool HasTheMovieDb => !string.IsNullOrEmpty(TheMovieDbId);
|
||||
}
|
||||
|
||||
[Table("PlexSeasonsContent")]
|
||||
|
|
26
src/Ombi.Store/Entities/RecentlyAddedLog.cs
Normal file
26
src/Ombi.Store/Entities/RecentlyAddedLog.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Ombi.Store.Entities
|
||||
{
|
||||
[Table("RecentlyAddedLog")]
|
||||
public class RecentlyAddedLog : Entity
|
||||
{
|
||||
public RecentlyAddedType Type { get; set; }
|
||||
public ContentType ContentType { get; set; }
|
||||
public int ContentId { get; set; } // This is dependant on the type
|
||||
public DateTime AddedAt { get; set; }
|
||||
}
|
||||
|
||||
public enum RecentlyAddedType
|
||||
{
|
||||
Plex = 0,
|
||||
Emby = 1
|
||||
}
|
||||
|
||||
public enum ContentType
|
||||
{
|
||||
Parent = 0,
|
||||
Episode = 1
|
||||
}
|
||||
}
|
936
src/Ombi.Store/Migrations/20180322204610_RecentlyAddedLog.Designer.cs
generated
Normal file
936
src/Ombi.Store/Migrations/20180322204610_RecentlyAddedLog.Designer.cs
generated
Normal file
|
@ -0,0 +1,936 @@
|
|||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Context;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
using System;
|
||||
|
||||
namespace Ombi.Store.Migrations
|
||||
{
|
||||
[DbContext(typeof(OmbiContext))]
|
||||
[Migration("20180322204610_RecentlyAddedLog")]
|
||||
partial class RecentlyAddedLog
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.0.2-rtm-10011");
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("ProviderKey");
|
||||
|
||||
b.Property<string>("ProviderDisplayName");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("RoleId");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("Type");
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ApplicationConfiguration");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Audit", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("AuditArea");
|
||||
|
||||
b.Property<int>("AuditType");
|
||||
|
||||
b.Property<DateTime>("DateTime");
|
||||
|
||||
b.Property<string>("Description");
|
||||
|
||||
b.Property<string>("User");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Audit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("TheMovieDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("CouchPotatoCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("AddedAt");
|
||||
|
||||
b.Property<string>("EmbyId")
|
||||
.IsRequired();
|
||||
|
||||
b.Property<string>("ProviderId");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<int>("Type");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("EmbyContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("AddedAt");
|
||||
|
||||
b.Property<string>("EmbyId");
|
||||
|
||||
b.Property<int>("EpisodeNumber");
|
||||
|
||||
b.Property<string>("ParentId");
|
||||
|
||||
b.Property<string>("ProviderId");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentId");
|
||||
|
||||
b.ToTable("EmbyEpisode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Content");
|
||||
|
||||
b.Property<string>("SettingsName");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("GlobalSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("Agent");
|
||||
|
||||
b.Property<bool>("Enabled");
|
||||
|
||||
b.Property<string>("Message");
|
||||
|
||||
b.Property<int>("NotificationType");
|
||||
|
||||
b.Property<string>("Subject");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("NotificationTemplates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("AddedAt");
|
||||
|
||||
b.Property<string>("PlayerId");
|
||||
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("NotificationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("AccessFailedCount");
|
||||
|
||||
b.Property<string>("Alias");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<bool>("EmailConfirmed");
|
||||
|
||||
b.Property<string>("EmbyConnectUserId");
|
||||
|
||||
b.Property<int?>("EpisodeRequestLimit");
|
||||
|
||||
b.Property<DateTime?>("LastLoggedIn");
|
||||
|
||||
b.Property<bool>("LockoutEnabled");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd");
|
||||
|
||||
b.Property<int?>("MovieRequestLimit");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("PasswordHash");
|
||||
|
||||
b.Property<string>("PhoneNumber");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed");
|
||||
|
||||
b.Property<string>("ProviderUserId");
|
||||
|
||||
b.Property<string>("SecurityStamp");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled");
|
||||
|
||||
b.Property<string>("UserAccessToken");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<int>("UserType");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("EpisodeNumber");
|
||||
|
||||
b.Property<int>("GrandparentKey");
|
||||
|
||||
b.Property<int>("Key");
|
||||
|
||||
b.Property<int>("ParentKey");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GrandparentKey");
|
||||
|
||||
b.ToTable("PlexEpisode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("ParentKey");
|
||||
|
||||
b.Property<int>("PlexContentId");
|
||||
|
||||
b.Property<int?>("PlexServerContentId");
|
||||
|
||||
b.Property<int>("SeasonKey");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PlexServerContentId");
|
||||
|
||||
b.ToTable("PlexSeasonsContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("AddedAt");
|
||||
|
||||
b.Property<string>("ImdbId");
|
||||
|
||||
b.Property<int>("Key");
|
||||
|
||||
b.Property<string>("Quality");
|
||||
|
||||
b.Property<string>("ReleaseYear");
|
||||
|
||||
b.Property<string>("TheMovieDbId");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<string>("TvDbId");
|
||||
|
||||
b.Property<int>("Type");
|
||||
|
||||
b.Property<string>("Url");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PlexServerContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<bool>("HasFile");
|
||||
|
||||
b.Property<int>("TheMovieDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("RadarrCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("AddedAt");
|
||||
|
||||
b.Property<int>("ContentId");
|
||||
|
||||
b.Property<int>("ContentType");
|
||||
|
||||
b.Property<int>("Type");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("RecentlyAddedLog");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<bool>("Approved");
|
||||
|
||||
b.Property<bool>("Available");
|
||||
|
||||
b.Property<bool?>("Denied");
|
||||
|
||||
b.Property<string>("DeniedReason");
|
||||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<int>("ParentRequestId");
|
||||
|
||||
b.Property<int>("RequestType");
|
||||
|
||||
b.Property<DateTime>("RequestedDate");
|
||||
|
||||
b.Property<string>("RequestedUserId");
|
||||
|
||||
b.Property<int>("SeriesType");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentRequestId");
|
||||
|
||||
b.HasIndex("RequestedUserId");
|
||||
|
||||
b.ToTable("ChildRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("IssueCategory");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Comment");
|
||||
|
||||
b.Property<DateTime>("Date");
|
||||
|
||||
b.Property<int?>("IssuesId");
|
||||
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IssuesId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("IssueComments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Description");
|
||||
|
||||
b.Property<int>("IssueCategoryId");
|
||||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<string>("ProviderId");
|
||||
|
||||
b.Property<int?>("RequestId");
|
||||
|
||||
b.Property<int>("RequestType");
|
||||
|
||||
b.Property<DateTime?>("ResovledDate");
|
||||
|
||||
b.Property<int>("Status");
|
||||
|
||||
b.Property<string>("Subject");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<string>("UserReportedId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IssueCategoryId");
|
||||
|
||||
b.HasIndex("IssueId");
|
||||
|
||||
b.HasIndex("UserReportedId");
|
||||
|
||||
b.ToTable("Issues");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<bool>("Approved");
|
||||
|
||||
b.Property<bool>("Available");
|
||||
|
||||
b.Property<string>("Background");
|
||||
|
||||
b.Property<bool?>("Denied");
|
||||
|
||||
b.Property<string>("DeniedReason");
|
||||
|
||||
b.Property<DateTime?>("DigitalReleaseDate");
|
||||
|
||||
b.Property<string>("ImdbId");
|
||||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<string>("Overview");
|
||||
|
||||
b.Property<string>("PosterPath");
|
||||
|
||||
b.Property<int>("QualityOverride");
|
||||
|
||||
b.Property<DateTime>("ReleaseDate");
|
||||
|
||||
b.Property<int>("RequestType");
|
||||
|
||||
b.Property<DateTime>("RequestedDate");
|
||||
|
||||
b.Property<string>("RequestedUserId");
|
||||
|
||||
b.Property<int>("RootPathOverride");
|
||||
|
||||
b.Property<string>("Status");
|
||||
|
||||
b.Property<int>("TheMovieDbId");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RequestedUserId");
|
||||
|
||||
b.ToTable("MovieRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("EpisodeCount");
|
||||
|
||||
b.Property<DateTime>("RequestDate");
|
||||
|
||||
b.Property<int>("RequestId");
|
||||
|
||||
b.Property<int>("RequestType");
|
||||
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("RequestLog");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ImdbId");
|
||||
|
||||
b.Property<string>("Overview");
|
||||
|
||||
b.Property<string>("PosterPath");
|
||||
|
||||
b.Property<int?>("QualityOverride");
|
||||
|
||||
b.Property<DateTime>("ReleaseDate");
|
||||
|
||||
b.Property<int?>("RootFolder");
|
||||
|
||||
b.Property<string>("Status");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<int>("TvDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("TvRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("TvDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SickRageCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("EpisodeNumber");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.Property<int>("TvDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SickRageEpisodeCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("TvDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SonarrCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("EpisodeNumber");
|
||||
|
||||
b.Property<bool>("HasFile");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.Property<int>("TvDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SonarrEpisodeCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Token");
|
||||
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Tokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("AirDate");
|
||||
|
||||
b.Property<bool>("Approved");
|
||||
|
||||
b.Property<bool>("Available");
|
||||
|
||||
b.Property<int>("EpisodeNumber");
|
||||
|
||||
b.Property<bool>("Requested");
|
||||
|
||||
b.Property<int>("SeasonId");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<string>("Url");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SeasonId");
|
||||
|
||||
b.ToTable("EpisodeRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("ChildRequestId");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChildRequestId");
|
||||
|
||||
b.ToTable("SeasonRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("ParentId")
|
||||
.HasPrincipalKey("EmbyId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
|
||||
.WithMany("NotificationUserIds")
|
||||
.HasForeignKey("UserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series")
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("GrandparentKey")
|
||||
.HasPrincipalKey("Key")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.PlexServerContent")
|
||||
.WithMany("Seasons")
|
||||
.HasForeignKey("PlexServerContentId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest")
|
||||
.WithMany("ChildRequests")
|
||||
.HasForeignKey("ParentRequestId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("RequestedUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues")
|
||||
.WithMany("Comments")
|
||||
.HasForeignKey("IssuesId");
|
||||
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory")
|
||||
.WithMany()
|
||||
.HasForeignKey("IssueCategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests")
|
||||
.WithMany("Issues")
|
||||
.HasForeignKey("IssueId");
|
||||
|
||||
b.HasOne("Ombi.Store.Entities.Requests.MovieRequests")
|
||||
.WithMany("Issues")
|
||||
.HasForeignKey("IssueId");
|
||||
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserReportedId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("RequestedUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season")
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("SeasonId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest")
|
||||
.WithMany("SeasonRequests")
|
||||
.HasForeignKey("ChildRequestId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
34
src/Ombi.Store/Migrations/20180322204610_RecentlyAddedLog.cs
Normal file
34
src/Ombi.Store/Migrations/20180322204610_RecentlyAddedLog.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ombi.Store.Migrations
|
||||
{
|
||||
public partial class RecentlyAddedLog : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RecentlyAddedLog",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
AddedAt = table.Column<DateTime>(nullable: false),
|
||||
ContentId = table.Column<int>(nullable: false),
|
||||
ContentType = table.Column<int>(nullable: false),
|
||||
Type = table.Column<int>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RecentlyAddedLog", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "RecentlyAddedLog");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ namespace Ombi.Store.Migrations
|
|||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
|
||||
.HasAnnotation("ProductVersion", "2.0.2-rtm-10011");
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
|
@ -430,6 +430,24 @@ namespace Ombi.Store.Migrations
|
|||
b.ToTable("RadarrCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("AddedAt");
|
||||
|
||||
b.Property<int>("ContentId");
|
||||
|
||||
b.Property<int>("ContentType");
|
||||
|
||||
b.Property<int>("Type");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("RecentlyAddedLog");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.9" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -45,9 +45,9 @@ namespace Ombi.Store.Repository
|
|||
|
||||
private IOmbiContext Db { get; }
|
||||
|
||||
public async Task<IEnumerable<EmbyContent>> GetAll()
|
||||
public IQueryable<EmbyContent> GetAll()
|
||||
{
|
||||
return await Db.EmbyContent.ToListAsync();
|
||||
return Db.EmbyContent.AsQueryable();
|
||||
}
|
||||
|
||||
public async Task AddRange(IEnumerable<EmbyContent> content)
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace Ombi.Store.Repository
|
|||
Task<bool> ContentExists(string providerId);
|
||||
IQueryable<EmbyContent> Get();
|
||||
Task<EmbyContent> Get(string providerId);
|
||||
Task<IEnumerable<EmbyContent>> GetAll();
|
||||
IQueryable<EmbyContent> GetAll();
|
||||
Task<EmbyContent> GetByEmbyId(string embyId);
|
||||
Task Update(EmbyContent existingContent);
|
||||
IQueryable<EmbyEpisode> GetAllEpisodes();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Helpers;
|
||||
|
@ -6,11 +7,11 @@ using Ombi.Store.Entities;
|
|||
|
||||
namespace Ombi.Store.Repository
|
||||
{
|
||||
public interface INotificationTemplatesRepository
|
||||
public interface INotificationTemplatesRepository : IDisposable
|
||||
{
|
||||
IQueryable<NotificationTemplates> All();
|
||||
Task<IEnumerable<NotificationTemplates>> GetAllTemplates();
|
||||
Task<IEnumerable<NotificationTemplates>> GetAllTemplates(NotificationAgent agent);
|
||||
IQueryable<NotificationTemplates> GetAllTemplates();
|
||||
IQueryable<NotificationTemplates> GetAllTemplates(NotificationAgent agent);
|
||||
Task<NotificationTemplates> Insert(NotificationTemplates entity);
|
||||
Task Update(NotificationTemplates template);
|
||||
Task UpdateRange(IEnumerable<NotificationTemplates> template);
|
||||
|
|
|
@ -22,5 +22,7 @@ namespace Ombi.Store.Repository
|
|||
Task DeleteEpisode(PlexEpisode content);
|
||||
void DeleteWithoutSave(PlexServerContent content);
|
||||
void DeleteWithoutSave(PlexEpisode content);
|
||||
Task UpdateRange(IEnumerable<PlexServerContent> existingContent);
|
||||
void UpdateWithoutSave(PlexServerContent existingContent);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Query;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
|
@ -24,5 +25,6 @@ namespace Ombi.Store.Repository
|
|||
where TEntity : class;
|
||||
|
||||
Task ExecuteSql(string sql);
|
||||
DbSet<T> _db { get; }
|
||||
}
|
||||
}
|
|
@ -23,14 +23,14 @@ namespace Ombi.Store.Repository
|
|||
return Db.NotificationTemplates.AsQueryable();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<NotificationTemplates>> GetAllTemplates()
|
||||
public IQueryable<NotificationTemplates> GetAllTemplates()
|
||||
{
|
||||
return await Db.NotificationTemplates.ToListAsync();
|
||||
return Db.NotificationTemplates;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<NotificationTemplates>> GetAllTemplates(NotificationAgent agent)
|
||||
public IQueryable<NotificationTemplates> GetAllTemplates(NotificationAgent agent)
|
||||
{
|
||||
return await Db.NotificationTemplates.Where(x => x.Agent == agent).ToListAsync();
|
||||
return Db.NotificationTemplates.Where(x => x.Agent == agent);
|
||||
}
|
||||
|
||||
public async Task<NotificationTemplates> GetTemplate(NotificationAgent agent, NotificationType type)
|
||||
|
@ -40,6 +40,11 @@ namespace Ombi.Store.Repository
|
|||
|
||||
public async Task Update(NotificationTemplates template)
|
||||
{
|
||||
if (Db.Entry(template).State == EntityState.Detached)
|
||||
{
|
||||
Db.Attach(template);
|
||||
Db.Entry(template).State = EntityState.Modified;
|
||||
}
|
||||
await Db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
|
@ -60,5 +65,26 @@ namespace Ombi.Store.Repository
|
|||
await Db.SaveChangesAsync().ConfigureAwait(false);
|
||||
return settings.Entity;
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
// Protected implementation of Dispose pattern.
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Db?.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -97,6 +97,16 @@ namespace Ombi.Store.Repository
|
|||
Db.PlexServerContent.Update(existingContent);
|
||||
await Db.SaveChangesAsync();
|
||||
}
|
||||
public void UpdateWithoutSave(PlexServerContent existingContent)
|
||||
{
|
||||
Db.PlexServerContent.Update(existingContent);
|
||||
}
|
||||
|
||||
public async Task UpdateRange(IEnumerable<PlexServerContent> existingContent)
|
||||
{
|
||||
Db.PlexServerContent.UpdateRange(existingContent);
|
||||
await Db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public IQueryable<PlexEpisode> GetAllEpisodes()
|
||||
{
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace Ombi.Store.Repository
|
|||
_ctx = ctx;
|
||||
_db = _ctx.Set<T>();
|
||||
}
|
||||
private readonly DbSet<T> _db;
|
||||
public DbSet<T> _db { get; }
|
||||
private readonly IOmbiContext _ctx;
|
||||
|
||||
public async Task<T> Find(object key)
|
||||
|
|
|
@ -7,13 +7,12 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.2" />
|
||||
<PackageReference Include="Moq" Version="4.7.99" />
|
||||
<PackageReference Include="Nunit" Version="3.8.1" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.0.0"></packagereference>
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.6.1"></packagereference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -15,5 +15,7 @@ namespace Ombi.Api.TheMovieDb
|
|||
Task<List<MovieSearchResult>> TopRated();
|
||||
Task<List<MovieSearchResult>> Upcoming();
|
||||
Task<List<MovieSearchResult>> SimilarMovies(int movieId);
|
||||
Task<FindResult> Find(string externalId, ExternalSource source);
|
||||
Task<TvExternals> GetTvExternals(int theMovieDbId);
|
||||
}
|
||||
}
|
52
src/Ombi.TheMovieDbApi/Models/FindResult.cs
Normal file
52
src/Ombi.TheMovieDbApi/Models/FindResult.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
namespace Ombi.Api.TheMovieDb.Models
|
||||
{
|
||||
public class FindResult
|
||||
{
|
||||
public Movie_Results[] movie_results { get; set; }
|
||||
public object[] person_results { get; set; }
|
||||
public TvResults[] tv_results { get; set; }
|
||||
public object[] tv_episode_results { get; set; }
|
||||
public object[] tv_season_results { get; set; }
|
||||
}
|
||||
|
||||
public class Movie_Results
|
||||
{
|
||||
public bool adult { get; set; }
|
||||
public string backdrop_path { get; set; }
|
||||
public int[] genre_ids { get; set; }
|
||||
public int id { get; set; }
|
||||
public string original_language { get; set; }
|
||||
public string original_title { get; set; }
|
||||
public string overview { get; set; }
|
||||
public string poster_path { get; set; }
|
||||
public string release_date { get; set; }
|
||||
public string title { get; set; }
|
||||
public bool video { get; set; }
|
||||
public float vote_average { get; set; }
|
||||
public int vote_count { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class TvResults
|
||||
{
|
||||
public string original_name { get; set; }
|
||||
public int id { get; set; }
|
||||
public string name { get; set; }
|
||||
public int vote_count { get; set; }
|
||||
public float vote_average { get; set; }
|
||||
public string first_air_date { get; set; }
|
||||
public string poster_path { get; set; }
|
||||
public int[] genre_ids { get; set; }
|
||||
public string original_language { get; set; }
|
||||
public string backdrop_path { get; set; }
|
||||
public string overview { get; set; }
|
||||
public string[] origin_country { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public enum ExternalSource
|
||||
{
|
||||
imdb_id,
|
||||
tvdb_id
|
||||
}
|
||||
}
|
16
src/Ombi.TheMovieDbApi/Models/TvExternals.cs
Normal file
16
src/Ombi.TheMovieDbApi/Models/TvExternals.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
namespace Ombi.Api.TheMovieDb.Models
|
||||
{
|
||||
public class TvExternals
|
||||
{
|
||||
public string imdb_id { get; set; }
|
||||
public string freebase_mid { get; set; }
|
||||
public string freebase_id { get; set; }
|
||||
public int tvdb_id { get; set; }
|
||||
public int tvrage_id { get; set; }
|
||||
public string facebook_id { get; set; }
|
||||
public object instagram_id { get; set; }
|
||||
public object twitter_id { get; set; }
|
||||
public int id { get; set; }
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using AutoMapper;
|
||||
|
@ -25,15 +26,37 @@ namespace Ombi.Api.TheMovieDb
|
|||
{
|
||||
var request = new Request($"movie/{movieId}", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
AddRetry(request);
|
||||
|
||||
var result = await Api.Request<MovieResponse>(request);
|
||||
return Mapper.Map<MovieResponseDto>(result);
|
||||
}
|
||||
|
||||
public async Task<FindResult> Find(string externalId, ExternalSource source)
|
||||
{
|
||||
var request = new Request($"find/{externalId}", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
AddRetry(request);
|
||||
|
||||
request.AddQueryString("external_source", source.ToString());
|
||||
|
||||
return await Api.Request<FindResult>(request);
|
||||
}
|
||||
|
||||
public async Task<TvExternals> GetTvExternals(int theMovieDbId)
|
||||
{
|
||||
var request = new Request($"/tv/{theMovieDbId}/external_ids", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
AddRetry(request);
|
||||
|
||||
return await Api.Request<TvExternals>(request);
|
||||
}
|
||||
|
||||
public async Task<List<MovieSearchResult>> SimilarMovies(int movieId)
|
||||
{
|
||||
var request = new Request($"movie/{movieId}/similar", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
AddRetry(request);
|
||||
|
||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||
|
@ -44,6 +67,7 @@ namespace Ombi.Api.TheMovieDb
|
|||
var request = new Request($"movie/{movieId}", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("append_to_response", "videos,release_dates");
|
||||
AddRetry(request);
|
||||
var result = await Api.Request<MovieResponse>(request);
|
||||
return Mapper.Map<MovieResponseDto>(result);
|
||||
}
|
||||
|
@ -53,6 +77,7 @@ namespace Ombi.Api.TheMovieDb
|
|||
var request = new Request($"search/movie", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("query", searchTerm);
|
||||
AddRetry(request);
|
||||
|
||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||
|
@ -62,6 +87,7 @@ namespace Ombi.Api.TheMovieDb
|
|||
{
|
||||
var request = new Request($"movie/popular", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
AddRetry(request);
|
||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||
}
|
||||
|
@ -70,6 +96,7 @@ namespace Ombi.Api.TheMovieDb
|
|||
{
|
||||
var request = new Request($"movie/top_rated", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
AddRetry(request);
|
||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||
}
|
||||
|
@ -78,6 +105,7 @@ namespace Ombi.Api.TheMovieDb
|
|||
{
|
||||
var request = new Request($"movie/upcoming", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
AddRetry(request);
|
||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||
}
|
||||
|
@ -86,9 +114,14 @@ namespace Ombi.Api.TheMovieDb
|
|||
{
|
||||
var request = new Request($"movie/now_playing", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
AddRetry(request);
|
||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||
}
|
||||
|
||||
private static void AddRetry(Request request)
|
||||
{
|
||||
request.Retry = true;
|
||||
request.StatusCodeToRetry.Add((HttpStatusCode)429);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,14 +12,14 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.1.1-beta" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" />
|
||||
<PackageReference Include="Serilog" Version="2.6.0-dev-00892" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="3.2.0" />
|
||||
|
|
12
src/Ombi/ClientApp/app/animations/fadeinout.ts
Normal file
12
src/Ombi/ClientApp/app/animations/fadeinout.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { animate, style, transition, trigger } from "@angular/animations";
|
||||
import { AnimationEntryMetadata } from "@angular/core";
|
||||
|
||||
export const fadeInOutAnimation: AnimationEntryMetadata = trigger("fadeInOut", [
|
||||
transition(":enter", [ // :enter is alias to 'void => *'
|
||||
style({ opacity: 0 }),
|
||||
animate(1000, style({ opacity: 1 })),
|
||||
]),
|
||||
transition(":leave", [ // :leave is alias to '* => void'
|
||||
animate(1000, style({ opacity: 0 })),
|
||||
]),
|
||||
]);
|
|
@ -34,6 +34,14 @@
|
|||
<i class="fa fa-th-list"></i> {{ 'NavigationBar.Requests' | translate }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div *ngIf="customizationSettings">
|
||||
<ul *ngIf="customizationSettings.recentlyAddedPage" class="nav navbar-nav">
|
||||
<li id="RecentlyAdded" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/recentlyadded']">
|
||||
<i class="fa fa-check"></i> {{ 'NavigationBar.RecentlyAdded' | translate }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul *ngIf="issuesEnabled" class="nav navbar-nav">
|
||||
<li id="Issues" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/issues']">
|
||||
|
@ -46,6 +54,7 @@
|
|||
<i class="fa fa-user"></i> {{ 'NavigationBar.UserManagement' | translate }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul *ngIf="hasRole('Admin') || hasRole('PowerUser')" class="nav navbar-nav">
|
||||
<li>
|
||||
<a href="https://www.paypal.me/PlexRequestsNet" target="_blank" pTooltip="{{ 'NavigationBar.DonateTooltip' | translate }}">
|
||||
|
@ -84,7 +93,7 @@
|
|||
<a [routerLink]="['/usermanagement/updatedetails']">
|
||||
<i class="fa fa-key"></i>{{ 'NavigationBar.UpdateDetails' | translate }}</a>
|
||||
</li>
|
||||
<li *ngIf="showMobileLink" [routerLinkActive]="['active']">
|
||||
<li *ngIf="customizationSettings?.mobile" [routerLinkActive]="['active']">
|
||||
<a href="#" (click)="openMobileApp($event)">
|
||||
<i class="fa fa-mobile"></i>{{ 'NavigationBar.OpenMobileApp' | translate }}</a>
|
||||
</li>
|
||||
|
|
|
@ -23,7 +23,6 @@ export class AppComponent implements OnInit {
|
|||
public updateAvailable: boolean;
|
||||
public currentUrl: string;
|
||||
public userAccessToken: string;
|
||||
public showMobileLink = false;
|
||||
|
||||
private checkedForUpdate: boolean;
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ const routes: Routes = [
|
|||
{ loadChildren: "./usermanagement/usermanagement.module#UserManagementModule", path: "usermanagement" },
|
||||
{ loadChildren: "./requests/requests.module#RequestsModule", path: "requests" },
|
||||
{ loadChildren: "./search/search.module#SearchModule", path: "search" },
|
||||
{ loadChildren: "./recentlyAdded/recentlyAdded.module#RecentlyAddedModule", path: "recentlyadded" },
|
||||
];
|
||||
|
||||
// AoT requires an exported function for factories
|
||||
|
|
|
@ -46,6 +46,7 @@ export enum NotificationType {
|
|||
WelcomeEmail = 8,
|
||||
IssueResolved = 9,
|
||||
IssueComment = 10,
|
||||
Newsletter = 11,
|
||||
}
|
||||
|
||||
export interface IDiscordNotifcationSettings extends INotificationSettings {
|
||||
|
@ -54,6 +55,10 @@ export interface IDiscordNotifcationSettings extends INotificationSettings {
|
|||
notificationTemplates: INotificationTemplates[];
|
||||
}
|
||||
|
||||
export interface INewsletterNotificationSettings extends INotificationSettings {
|
||||
notificationTemplate: INotificationTemplates;
|
||||
}
|
||||
|
||||
export interface ITelegramNotifcationSettings extends INotificationSettings {
|
||||
botApi: string;
|
||||
chatId: string;
|
||||
|
|
29
src/Ombi/ClientApp/app/interfaces/IRecentlyAdded.ts
Normal file
29
src/Ombi/ClientApp/app/interfaces/IRecentlyAdded.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
export interface IRecentlyAddedMovies {
|
||||
id: number;
|
||||
title: string;
|
||||
overview: string;
|
||||
imdbId: string;
|
||||
theMovieDbId: string;
|
||||
releaseYear: string;
|
||||
addedAt: Date;
|
||||
quality: string;
|
||||
|
||||
// For UI only
|
||||
posterPath: string;
|
||||
}
|
||||
|
||||
export interface IRecentlyAddedTvShows extends IRecentlyAddedMovies {
|
||||
seasonNumber: number;
|
||||
episodeNumber: number;
|
||||
tvDbId: number;
|
||||
}
|
||||
|
||||
export interface IRecentlyAddedRangeModel {
|
||||
from: Date;
|
||||
to: Date;
|
||||
}
|
||||
|
||||
export enum RecentlyAddedType {
|
||||
Plex,
|
||||
Emby,
|
||||
}
|
|
@ -97,6 +97,7 @@ export interface ICustomizationSettings extends ISettings {
|
|||
applicationName: string;
|
||||
applicationUrl: string;
|
||||
logo: string;
|
||||
mobile: boolean;
|
||||
customCssLink: string;
|
||||
enableCustomDonations: boolean;
|
||||
customDonationUrl: string;
|
||||
|
@ -106,6 +107,7 @@ export interface ICustomizationSettings extends ISettings {
|
|||
presetThemeContent: string;
|
||||
presetThemeDisplayName: string;
|
||||
presetThemeVersion: string;
|
||||
recentlyAddedPage: boolean;
|
||||
}
|
||||
|
||||
export interface IThemes {
|
||||
|
@ -124,6 +126,8 @@ export interface IJobSettings {
|
|||
automaticUpdater: string;
|
||||
userImporter: string;
|
||||
sickRageSync: string;
|
||||
refreshMetadata: string;
|
||||
newsletter: string;
|
||||
}
|
||||
|
||||
export interface IIssueSettings extends ISettings {
|
||||
|
@ -192,3 +196,18 @@ export interface IDogNzbSettings extends ISettings {
|
|||
export interface IIssueCategory extends ISettings {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface ICronTestModel {
|
||||
success: boolean;
|
||||
message: string;
|
||||
schedule: Date[];
|
||||
}
|
||||
|
||||
export interface ICronViewModelBody {
|
||||
expression: string;
|
||||
}
|
||||
|
||||
export interface IJobSettingsViewModel {
|
||||
result: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
|
|
@ -13,3 +13,4 @@ export * from "./ISettings";
|
|||
export * from "./ISonarr";
|
||||
export * from "./IUser";
|
||||
export * from "./IIssues";
|
||||
export * from "./IRecentlyAdded";
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
<div *ngIf="issue">
|
||||
<div class="myBg backdrop" [style.background-image]="backgroundPath"></div>
|
||||
<div class="tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
|
||||
<h1>{{issue.title}} </h1>
|
||||
<div class="col-md-6">
|
||||
<img class="img-responsive poster" src="{{posterPath}}" alt="poster">
|
||||
<span class="label label-info">{{IssueStatus[issue.status]}}</span>
|
||||
<span class="label label-success">{{issue.issueCategory.value}}</span>
|
||||
|
||||
<h3 *ngIf="issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}: {{issue.userReported.alias}}</h3>
|
||||
<h3 *ngIf="!issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}: {{issue.userReported.userName}}</h3>
|
||||
<h3 *ngIf="issue.subject">{{'Issues.Subject' | translate}}: {{issue.subject}}</h3>
|
||||
<br>
|
||||
<br>
|
||||
<div class="form-group">
|
||||
<label for="description" class="control-label" [translate]="'Issues.Description'"></label>
|
||||
<div>
|
||||
|
@ -26,7 +29,8 @@
|
|||
<div class="panel-heading top-bar">
|
||||
<div class="col-md-8 col-xs-8">
|
||||
<h3 class="panel-title">
|
||||
<span class="glyphicon glyphicon-comment"></span> {{'Issues.Comments' | translate}}</h3>
|
||||
<span class="glyphicon glyphicon-comment"></span> {{'Issues.Comments' | translate}}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -51,8 +55,7 @@
|
|||
</div>
|
||||
<div class="panel-footer">
|
||||
<div class="input-group">
|
||||
<input id="btn-input" type="text" class="form-control input-sm chat_input" [(ngModel)]="newComment.comment" [attr.placeholder]="'Issues.WriteMessagePlaceholder' | translate"
|
||||
/>
|
||||
<input id="btn-input" type="text" class="form-control input-sm chat_input" [(ngModel)]="newComment.comment" [attr.placeholder]="'Issues.WriteMessagePlaceholder' | translate" />
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-primary btn-sm" id="btn-chat" (click)="addComment()" [translate]="'Issues.SendMessageButton'"></button>
|
||||
</span>
|
||||
|
|
|
@ -64,6 +64,15 @@ body{
|
|||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
.myBg {
|
||||
z-index: -1;
|
||||
}
|
||||
.tint {
|
||||
z-index: -1;
|
||||
}
|
||||
img-responsive poster {
|
||||
display:block;
|
||||
}
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
|
|
@ -2,8 +2,9 @@ import { Component, OnInit } from "@angular/core";
|
|||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { AuthService } from "../auth/auth.service";
|
||||
import { IssuesService, NotificationService, SettingsService } from "../services";
|
||||
import { ImageService, IssuesService, NotificationService, SettingsService } from "../services";
|
||||
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { IIssues, IIssuesChat, IIssueSettings, INewIssueComments, IssueStatus } from "../interfaces";
|
||||
|
||||
@Component({
|
||||
|
@ -22,6 +23,8 @@ export class IssueDetailsComponent implements OnInit {
|
|||
public IssueStatus = IssueStatus;
|
||||
public isAdmin: boolean;
|
||||
public settings: IIssueSettings;
|
||||
public backgroundPath: any;
|
||||
public posterPath: any;
|
||||
|
||||
private issueId: number;
|
||||
|
||||
|
@ -29,7 +32,9 @@ export class IssueDetailsComponent implements OnInit {
|
|||
private route: ActivatedRoute,
|
||||
private authService: AuthService,
|
||||
private settingsService: SettingsService,
|
||||
private notificationService: NotificationService) {
|
||||
private notificationService: NotificationService,
|
||||
private imageService: ImageService,
|
||||
private sanitizer: DomSanitizer) {
|
||||
this.route.params
|
||||
.subscribe((params: any) => {
|
||||
this.issueId = parseInt(params.id);
|
||||
|
@ -56,8 +61,8 @@ export class IssueDetailsComponent implements OnInit {
|
|||
providerId: x.providerId,
|
||||
userReported: x.userReported,
|
||||
};
|
||||
this.setBackground(x);
|
||||
});
|
||||
|
||||
this.loadComments();
|
||||
}
|
||||
|
||||
|
@ -85,4 +90,26 @@ export class IssueDetailsComponent implements OnInit {
|
|||
private loadComments() {
|
||||
this.issueService.getComments(this.issueId).subscribe(x => this.comments = x);
|
||||
}
|
||||
|
||||
private setBackground(issue: any) {
|
||||
if (issue.requestType === 1) {
|
||||
this.imageService.getMovieBackground(issue.providerId).subscribe(x => {
|
||||
this.backgroundPath = this.sanitizer.bypassSecurityTrustStyle
|
||||
("url(" + x + ")");
|
||||
});
|
||||
this.imageService.getMoviePoster(issue.providerId).subscribe(x => {
|
||||
this.posterPath = x.toString();
|
||||
});
|
||||
|
||||
} else {
|
||||
this.imageService.getTvBackground(Number(issue.providerId)).subscribe(x => {
|
||||
this.backgroundPath = this.sanitizer.bypassSecurityTrustStyle
|
||||
("url(" + x + ")");
|
||||
});
|
||||
this.imageService.getTvPoster(Number(issue.providerId)).subscribe(x => {
|
||||
this.posterPath = x.toString();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
|||
import { OrderModule } from "ngx-order-pipe";
|
||||
import { PaginatorModule, SharedModule, TabViewModule } from "primeng/primeng";
|
||||
|
||||
import { IdentityService } from "../services";
|
||||
import { IdentityService, SearchService } from "../services";
|
||||
|
||||
import { AuthGuard } from "../auth/auth.guard";
|
||||
|
||||
|
@ -43,6 +43,7 @@ const routes: Routes = [
|
|||
],
|
||||
providers: [
|
||||
IdentityService,
|
||||
SearchService,
|
||||
],
|
||||
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div *ngIf="landingPageSettings && customizationSettings">
|
||||
<div *ngIf="background" class="bg" [style.background-image]="background"></div>
|
||||
<div *ngIf="background" @fadeInOut class="bg" [style.background-image]="background"></div>
|
||||
|
||||
<div class="centered col-md-12">
|
||||
<div class="row">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { PlatformLocation } from "@angular/common";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
|
||||
import { IMediaServerStatus } from "../interfaces";
|
||||
import { ICustomizationSettings, ILandingPageSettings } from "../interfaces";
|
||||
|
@ -9,17 +9,21 @@ import { SettingsService } from "../services";
|
|||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { ImageService } from "../services";
|
||||
|
||||
import { fadeInOutAnimation } from "../animations/fadeinout";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./landingpage.component.html",
|
||||
animations: [fadeInOutAnimation],
|
||||
styleUrls: ["./landingpage.component.scss"],
|
||||
})
|
||||
export class LandingPageComponent implements OnInit {
|
||||
export class LandingPageComponent implements OnDestroy, OnInit {
|
||||
|
||||
public customizationSettings: ICustomizationSettings;
|
||||
public landingPageSettings: ILandingPageSettings;
|
||||
public background: any;
|
||||
public mediaServerStatus: IMediaServerStatus;
|
||||
public baseUrl: string;
|
||||
private timer: any;
|
||||
|
||||
constructor(private settingsService: SettingsService,
|
||||
private images: ImageService, private sanitizer: DomSanitizer, private landingPageService: LandingPageService,
|
||||
|
@ -31,6 +35,9 @@ export class LandingPageComponent implements OnInit {
|
|||
this.images.getRandomBackground().subscribe(x => {
|
||||
this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%), url(" + x.url + ")");
|
||||
});
|
||||
this.timer = setInterval(() => {
|
||||
this.cycleBackground();
|
||||
}, 10000);
|
||||
|
||||
const base = this.location.getBaseHrefFromDOM();
|
||||
if (base.length > 1) {
|
||||
|
@ -41,4 +48,18 @@ export class LandingPageComponent implements OnInit {
|
|||
this.mediaServerStatus = x;
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
|
||||
public cycleBackground() {
|
||||
this.images.getRandomBackground().subscribe(x => {
|
||||
this.background = "";
|
||||
});
|
||||
this.images.getRandomBackground().subscribe(x => {
|
||||
this.background = this.sanitizer
|
||||
.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%), url(" + x.url + ")");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ include the remember me checkbox
|
|||
-->
|
||||
<div *ngIf="form && customizationSettings">
|
||||
|
||||
<div *ngIf="background" class="bg" [style.background-image]="background"></div>
|
||||
<div *ngIf="background" @fadeInOut class="bg" [style.background-image]="background"></div>
|
||||
<div class="container" id="login">
|
||||
<div class="card card-container">
|
||||
<!-- <img class="profile-img-card" src="//lh3.googleusercontent.com/-6V8xOA6M7BA/AAAAAAAAAAI/AAAAAAAAAAA/rzlHcD0KYwo/photo.jpg?sz=120" alt="" /> -->
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
|
@ -13,11 +13,14 @@ import { StatusService } from "../services";
|
|||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { ImageService } from "../services";
|
||||
|
||||
import { fadeInOutAnimation } from "../animations/fadeinout";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./login.component.html",
|
||||
animations: [fadeInOutAnimation],
|
||||
styleUrls: ["./login.component.scss"],
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
export class LoginComponent implements OnDestroy, OnInit {
|
||||
|
||||
public form: FormGroup;
|
||||
public customizationSettings: ICustomizationSettings;
|
||||
|
@ -25,6 +28,7 @@ export class LoginComponent implements OnInit {
|
|||
public background: any;
|
||||
public landingFlag: boolean;
|
||||
public baseUrl: string;
|
||||
private timer: any;
|
||||
|
||||
private errorBody: string;
|
||||
private errorValidation: string;
|
||||
|
@ -67,6 +71,10 @@ export class LoginComponent implements OnInit {
|
|||
this.images.getRandomBackground().subscribe(x => {
|
||||
this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%),url(" + x.url + ")");
|
||||
});
|
||||
this.timer = setInterval(() => {
|
||||
this.cycleBackground();
|
||||
}, 10000);
|
||||
|
||||
const base = this.location.getBaseHrefFromDOM();
|
||||
if (base.length > 1) {
|
||||
this.baseUrl = base;
|
||||
|
@ -102,4 +110,19 @@ export class LoginComponent implements OnInit {
|
|||
}, err => this.notify.error(this.errorBody));
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
|
||||
private cycleBackground() {
|
||||
this.images.getRandomBackground().subscribe(x => {
|
||||
this.background = "";
|
||||
});
|
||||
this.images.getRandomBackground().subscribe(x => {
|
||||
this.background = this.sanitizer
|
||||
.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%), url(" + x.url + ")");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<h1>Recently Added</h1>
|
||||
<input type="checkbox" [(ngModel)]="groupTv" (click)="change()" />
|
||||
<hr />
|
||||
<p-calendar [(ngModel)]="range" showButtonBar="true" selectionMode="range" (onClose)="close()"></p-calendar>
|
||||
<hr />
|
||||
<style>
|
||||
.img-conatiner {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Bottom left text */
|
||||
.bottom-left {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
</style>
|
||||
<ngu-carousel [inputs]="carouselTile">
|
||||
<ngu-tile NguCarouselItem *ngFor="let movie of movies">
|
||||
<div class="img-container">
|
||||
<img class="img-responsive poster" src="{{movie.posterPath}}" style="width: 300px" alt="poster">
|
||||
<div class="bottom-left"> {{movie.title}}</div>
|
||||
</div>
|
||||
|
||||
|
||||
</ngu-tile>
|
||||
|
||||
<button NguCarouselPrev class='leftRs'><i class="fa fa-arrow-left"></i></button>
|
||||
<button NguCarouselNext class='rightRs'><i class="fa fa-arrow-right"></i></button>
|
||||
</ngu-carousel>
|
||||
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
<ngu-carousel [inputs]="carouselTile">
|
||||
<ngu-tile NguCarouselItem *ngFor="let t of tv">
|
||||
<img class="img-responsive poster" src="{{t.posterPath}}" style="width: 300px" alt="poster">
|
||||
<b>{{t.title}}</b>
|
||||
<br>
|
||||
<b>Season: {{t.seasonNumber}}</b>
|
||||
<br>
|
||||
<b>Episode: {{t.episodeNumber}}</b>
|
||||
</ngu-tile>
|
||||
|
||||
<button NguCarouselPrev class='leftRs'><i class="fa fa-arrow-left"></i></button>
|
||||
<button NguCarouselNext class='rightRs'><i class="fa fa-arrow-right"></i></button>
|
||||
</ngu-carousel>
|
127
src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts
Normal file
127
src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts
Normal file
|
@ -0,0 +1,127 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { NguCarousel } from "@ngu/carousel";
|
||||
|
||||
import { ImageService, RecentlyAddedService } from "../services";
|
||||
import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "./../interfaces";
|
||||
|
||||
@Component({
|
||||
templateUrl: "recentlyAdded.component.html",
|
||||
styles: [`
|
||||
.leftRs {
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, .3);
|
||||
border-radius: 100%;
|
||||
left: 0;
|
||||
background: #df691a;
|
||||
}
|
||||
|
||||
.rightRs {
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, .3);
|
||||
border-radius: 100%;
|
||||
right: 0;
|
||||
background: #df691a;
|
||||
}
|
||||
`],
|
||||
})
|
||||
|
||||
export class RecentlyAddedComponent implements OnInit {
|
||||
public movies: IRecentlyAddedMovies[];
|
||||
public tv: IRecentlyAddedTvShows[];
|
||||
public range: Date[];
|
||||
|
||||
public groupTv: boolean = false;
|
||||
|
||||
// https://github.com/sheikalthaf/ngu-carousel
|
||||
public carouselTile: NguCarousel;
|
||||
|
||||
constructor(private recentlyAddedService: RecentlyAddedService,
|
||||
private imageService: ImageService) {}
|
||||
|
||||
public ngOnInit() {
|
||||
this.getMovies();
|
||||
this.getShows();
|
||||
|
||||
this.carouselTile = {
|
||||
grid: {xs: 2, sm: 3, md: 3, lg: 5, all: 0},
|
||||
slide: 2,
|
||||
speed: 400,
|
||||
animation: "lazy",
|
||||
point: {
|
||||
visible: true,
|
||||
},
|
||||
load: 2,
|
||||
touch: true,
|
||||
easing: "ease",
|
||||
};
|
||||
}
|
||||
|
||||
public close() {
|
||||
if(this.range.length < 2) {
|
||||
return;
|
||||
}
|
||||
if(!this.range[1]) {
|
||||
// If we do not have a second date then just set it to now
|
||||
this.range[1] = new Date();
|
||||
}
|
||||
this.getMovies();
|
||||
}
|
||||
|
||||
public change() {
|
||||
this.getShows();
|
||||
}
|
||||
|
||||
private getShows() {
|
||||
if(this.groupTv) {
|
||||
this.recentlyAddedService.getRecentlyAddedTvGrouped().subscribe(x => {
|
||||
this.tv = x;
|
||||
|
||||
this.tv.forEach((t) => {
|
||||
this.imageService.getTvPoster(t.tvDbId).subscribe(p => {
|
||||
t.posterPath = p;
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.recentlyAddedService.getRecentlyAddedTv().subscribe(x => {
|
||||
this.tv = x;
|
||||
|
||||
this.tv.forEach((t) => {
|
||||
this.imageService.getTvPoster(t.tvDbId).subscribe(p => {
|
||||
t.posterPath = p;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getMovies() {
|
||||
this.recentlyAddedService.getRecentlyAddedMovies().subscribe(x => {
|
||||
this.movies = x;
|
||||
|
||||
this.movies.forEach((movie) => {
|
||||
if(movie.theMovieDbId) {
|
||||
this.imageService.getMoviePoster(movie.theMovieDbId).subscribe(p => {
|
||||
movie.posterPath = p;
|
||||
});
|
||||
} else if(movie.imdbId) {
|
||||
this.imageService.getMoviePoster(movie.imdbId).subscribe(p => {
|
||||
movie.posterPath = p;
|
||||
});
|
||||
} else {
|
||||
movie.posterPath = "";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
47
src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.module.ts
Normal file
47
src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.module.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
||||
import { OrderModule } from "ngx-order-pipe";
|
||||
import { CalendarModule, PaginatorModule, SharedModule, TabViewModule } from "primeng/primeng";
|
||||
|
||||
import { IdentityService, ImageService, RecentlyAddedService } from "../services";
|
||||
|
||||
import { AuthGuard } from "../auth/auth.guard";
|
||||
|
||||
import { SharedModule as OmbiShared } from "../shared/shared.module";
|
||||
|
||||
import { RecentlyAddedComponent } from "./recentlyAdded.component";
|
||||
|
||||
import { NguCarouselModule } from "@ngu/carousel";
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "", component: RecentlyAddedComponent, canActivate: [AuthGuard] },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
NgbModule.forRoot(),
|
||||
SharedModule,
|
||||
OrderModule,
|
||||
OmbiShared,
|
||||
PaginatorModule,
|
||||
TabViewModule,
|
||||
CalendarModule,
|
||||
NguCarouselModule,
|
||||
],
|
||||
declarations: [
|
||||
RecentlyAddedComponent,
|
||||
],
|
||||
exports: [
|
||||
RouterModule,
|
||||
],
|
||||
providers: [
|
||||
IdentityService,
|
||||
RecentlyAddedService,
|
||||
ImageService,
|
||||
],
|
||||
|
||||
})
|
||||
export class RecentlyAddedModule { }
|
|
@ -207,7 +207,7 @@
|
|||
|
||||
|
||||
<issue-report [movie]="true" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequest?.title"
|
||||
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]=""></issue-report>
|
||||
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]="issueProviderId"></issue-report>
|
||||
|
||||
|
||||
<p-sidebar [(visible)]="filterDisplay" styleClass="ui-sidebar-md side-back side-small">
|
||||
|
|
|
@ -105,4 +105,4 @@
|
|||
|
||||
|
||||
<issue-report [movie]="false" [visible]="issuesBarVisible" [title]="issueRequest?.title"
|
||||
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" (visibleChange)="issuesBarVisible = $event;"></issue-report>
|
||||
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]="issueProviderId" (visibleChange)="issuesBarVisible = $event;"></issue-report>
|
|
@ -105,6 +105,7 @@ export class TvRequestChildrenComponent {
|
|||
this.issueRequest = req;
|
||||
this.issueCategorySelected = catId;
|
||||
this.issuesBarVisible = true;
|
||||
this.issueProviderId = req.id.toString();
|
||||
}
|
||||
|
||||
private removeRequestFromUi(key: IChildRequests) {
|
||||
|
|
|
@ -129,7 +129,6 @@ export class TvSearchComponent implements OnInit {
|
|||
|
||||
public getExtraInfo() {
|
||||
this.tvResults.forEach((val, index) => {
|
||||
|
||||
this.imageService.getTvBanner(val.data.id).subscribe(x => {
|
||||
|
||||
val.data.background = this.sanitizer.
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
IEmailNotificationSettings,
|
||||
IEmbyServer,
|
||||
IMattermostNotifcationSettings,
|
||||
INewsletterNotificationSettings,
|
||||
IPlexServer,
|
||||
IPushbulletNotificationSettings,
|
||||
IPushoverNotificationSettings,
|
||||
|
@ -78,4 +79,7 @@ export class TesterService extends ServiceHelpers {
|
|||
public sickrageTest(settings: ISickRageSettings): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}sickrage`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
public newsletterTest(settings: INewsletterNotificationSettings): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}newsletter`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,4 +20,21 @@ export class ImageService extends ServiceHelpers {
|
|||
public getTvBanner(tvdbid: number): Observable<string> {
|
||||
return this.http.get<string>(`${this.url}tv/${tvdbid}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getMoviePoster(movieDbId: string): Observable<string> {
|
||||
return this.http.get<string>(`${this.url}poster/movie/${movieDbId}`, { headers: this.headers });
|
||||
}
|
||||
|
||||
public getTvPoster(tvdbid: number): Observable<string> {
|
||||
return this.http.get<string>(`${this.url}poster/tv/${tvdbid}`, { headers: this.headers });
|
||||
}
|
||||
|
||||
public getMovieBackground(movieDbId: string): Observable<string> {
|
||||
return this.http.get<string>(`${this.url}background/movie/${movieDbId}`, { headers: this.headers });
|
||||
}
|
||||
|
||||
public getTvBackground(tvdbid: number): Observable<string> {
|
||||
return this.http.get<string>(`${this.url}background/tv/${tvdbid}`, { headers: this.headers });
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export * from "./applications";
|
||||
export * from "./applications";
|
||||
export * from "./helpers";
|
||||
export * from "./identity.service";
|
||||
export * from "./image.service";
|
||||
|
@ -13,3 +13,4 @@ export * from "./job.service";
|
|||
export * from "./issues.service";
|
||||
export * from "./mobile.service";
|
||||
export * from "./notificationMessage.service";
|
||||
export * from "./recentlyAdded.service";
|
||||
|
|
|
@ -38,4 +38,8 @@ export class JobService extends ServiceHelpers {
|
|||
public runEmbyCacher(): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}embycontentcacher/`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public runNewsletter(): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}newsletter/`, {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue