Pretty much finished the actual newsletter. Still need to work on the UI !wip

pull/2089/head
Jamie 7 years ago
parent 9767380f83
commit 1528cdfc03

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models;
namespace Ombi.Core.Engine
@ -10,5 +11,6 @@ namespace Ombi.Core.Engine
IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies(DateTime from, DateTime to);
IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(DateTime from, DateTime to, bool groupBySeason);
IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(bool groupBySeason);
Task<bool> UpdateRecentlyAddedDatabase();
}
}

@ -1,27 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
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)
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)
{
@ -55,6 +56,67 @@ namespace Ombi.Core.Engine
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
});
}
else
{
// Add the episodes
foreach (var ep in p.Episodes)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex,
ContentId = ep.Id
});
}
}
}
foreach (var e in embyContent)
{
if (e.Type == EmbyMediaType.Movie)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Emby,
ContentId = e.Id
});
}
else
{
// Add the episodes
foreach (var ep in e.Episodes)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex,
ContentId = ep.Id
});
}
}
}
await _recentlyAddedLog.AddRange(recentlyAddedLog);
return true;
}
private IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(IQueryable<PlexServerContent> plexTv, IQueryable<EmbyContent> embyTv,
bool groupBySeason)
{

@ -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; }
}
}

@ -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);
}
}

@ -38,6 +38,13 @@ namespace Ombi.Notifications
AdditionalInformation = opts?.AdditionalInformation ?? string.Empty;
}
public void SetupNewsletter(CustomizationSettings s, string username)
{
ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty;
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
RequestedUser = username;
}
public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s)
{
LoadIssues(opts);

@ -1,7 +1,9 @@
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;
@ -22,7 +24,8 @@ namespace Ombi.Schedule.Jobs.Ombi
{
public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository<RecentlyAddedLog> addedLog,
IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService<CustomizationSettings> custom,
ISettingsService<EmailNotificationSettings> emailSettings, INotificationTemplatesRepository templateRepo)
ISettingsService<EmailNotificationSettings> emailSettings, INotificationTemplatesRepository templateRepo,
UserManager<OmbiUser> um, ISettingsService<NewsletterSettings> newsletter)
{
_plex = plex;
_emby = emby;
@ -33,6 +36,8 @@ namespace Ombi.Schedule.Jobs.Ombi
_customizationSettings = custom;
_templateRepo = templateRepo;
_emailSettings = emailSettings;
_newsletterSettings = newsletter;
_userManager = um;
}
private readonly IPlexContentRepository _plex;
@ -44,9 +49,16 @@ namespace Ombi.Schedule.Jobs.Ombi
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()
{
var newsletterSettings = await _newsletterSettings.GetSettingsAsync();
if (!newsletterSettings.Enabled)
{
return;
}
var template = await _templateRepo.GetTemplate(NotificationAgent.Email, NotificationType.Newsletter);
if (!template.Enabled)
{
@ -59,40 +71,146 @@ namespace Ombi.Schedule.Jobs.Ombi
return;
}
var customization = await _customizationSettings.GetSettingsAsync();
// Get the Content
var plexContent = _plex.GetAll().Include(x => x.Episodes);
var embyContent = _emby.GetAll().Include(x => x.Episodes);
var addedLog = _recentlyAddedLog.GetAll();
var addedPlexLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex).Select(x => x.ContentId);
var addedEmbyLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby).Select(x => x.ContentId);
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 plexContentToSend = plexContent.Where(x => !addedPlexLogIds.Contains(x.Id));
var embyContentToSend = embyContent.Where(x => !addedEmbyLogIds.Contains(x.Id));
var plexContentMoviesToSend = plexContent.Where(x => !addedPlexMovieLogIds.Contains(x.Id));
var embyContentMoviesToSend = embyContent.Where(x => !addedEmbyMoviesLogIds.Contains(x.Id));
var plexContentTvToSend = plexContent.Where(x => x.Episodes.Any(e => !addedPlexEpisodesLogIds.Contains(e.Id)));
var embyContentTvToSend = embyContent.Where(x => x.Episodes.Any(e => !addedEmbyEpisodesLogIds.Contains(e.Id)));
var plexContentToSend = plexContentMoviesToSend.Union(plexContentTvToSend);
var embyContentToSend = embyContentMoviesToSend.Union(embyContentTvToSend);
var body = await BuildHtml(plexContentToSend, embyContentToSend);
// 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 html = LoadTemplate(body, template, customization, user.Alias);
emailTasks.Add(_email.Send(new NotificationMessage { Message = html, Subject = template.Subject, To = user.Email }, emailSettings));
}
// Now add all of this to the Recently Added log
var recentlyAddedLog = new HashSet<RecentlyAddedLog>();
foreach (var p in plexContentMoviesToSend)
{
if (p.Type == PlexMediaTypeEntity.Movie)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex,
ContentId = p.Id
});
}
else
{
// Add the episodes
foreach (var ep in p.Episodes)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex,
ContentId = ep.Id
});
}
}
}
foreach (var e in embyContentMoviesToSend)
{
if (e.Type == EmbyMediaType.Movie)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Emby,
ContentId = e.Id
});
}
else
{
// Add the episodes
foreach (var ep in e.Episodes)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex,
ContentId = ep.Id
});
}
}
}
await _recentlyAddedLog.AddRange(recentlyAddedLog);
await Task.WhenAll(emailTasks.ToArray());
}
private string LoadTemplate(string body, NotificationTemplates template, CustomizationSettings settings, string username)
{
var email = new NewsletterTemplate();
var customization = await _customizationSettings.GetSettingsAsync();
var resolver = new NotificationMessageResolver();
var curlys = new NotificationMessageCurlys();
curlys.SetupNewsletter(settings, username);
var html = email.LoadTemplate(template.Subject, template.Message, body, customization.Logo);
var parsed = resolver.ParseMessage(template, curlys);
await _email.Send(new NotificationMessage {Message = html, Subject = template.Subject, To = "tidusjar@gmail.com"}, emailSettings);
var html = email.LoadTemplate(parsed.Subject, parsed.Message, body, settings.Logo);
return html;
}
private async Task<string> BuildHtml(IQueryable<PlexServerContent> plexContentToSend, IQueryable<EmbyContent> embyContentToSend)
{
var sb = new StringBuilder();
sb.Append("<h1>New Movies:</h1><br /><br />");
await ProcessPlexMovies(plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Movie), sb);
await ProcessEmbyMovies(embyContentToSend.Where(x => x.Type == EmbyMediaType.Movie), sb);
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);
}
sb.Append("<h1>New Episodes:</h1><br /><br />");
await ProcessPlexTv(plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Show), sb);
await ProcessEmbyMovies(embyContentToSend.Where(x => x.Type == EmbyMediaType.Series), sb);
var plexTv = plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Show);
var embyTv = embyContentToSend.Where(x => x.Type == EmbyMediaType.Series);
if (plexTv.Any() || embyTv.Any())
{
sb.Append("<h1>New Episodes:</h1><br /><br />");
await ProcessPlexTv(plexTv, sb);
await ProcessEmbyMovies(embyTv, sb);
}
return sb.ToString();
}
@ -330,7 +448,7 @@ namespace Ombi.Schedule.Jobs.Ombi
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");
@ -426,6 +544,12 @@ namespace Ombi.Schedule.Jobs.Ombi
{
_plex?.Dispose();
_emby?.Dispose();
_newsletterSettings?.Dispose();
_customizationSettings?.Dispose();
_emailSettings.Dispose();
_recentlyAddedLog.Dispose();
_templateRepo?.Dispose();
_userManager?.Dispose();
}
_disposed = true;
}

@ -0,0 +1,7 @@
namespace Ombi.Settings.Settings.Models.Notifications
{
public class NewsletterSettings : Settings
{
public bool Enabled { get; set; }
}
}

@ -7,13 +7,20 @@ namespace Ombi.Store.Entities
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,
Emby
Plex = 0,
Emby = 1
}
public enum ContentType
{
Parent = 0,
Episode = 1
}
}

@ -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,7 +7,7 @@ using Ombi.Store.Entities;
namespace Ombi.Store.Repository
{
public interface INotificationTemplatesRepository
public interface INotificationTemplatesRepository : IDisposable
{
IQueryable<NotificationTemplates> All();
Task<IEnumerable<NotificationTemplates>> GetAllTemplates();

@ -60,5 +60,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);
}
}
}

@ -54,6 +54,10 @@ export interface IDiscordNotifcationSettings extends INotificationSettings {
notificationTemplates: INotificationTemplates[];
}
export interface INewsletterNotificationSettings extends INotificationSettings {
notificationTemplate: INotificationTemplates;
}
export interface ITelegramNotifcationSettings extends INotificationSettings {
botApi: string;
chatId: string;

@ -20,6 +20,7 @@ import {
ILandingPageSettings,
IMattermostNotifcationSettings,
IMobileNotifcationSettings,
INewsletterNotificationSettings,
IOmbiSettings,
IPlexSettings,
IPushbulletNotificationSettings,
@ -265,4 +266,17 @@ export class SettingsService extends ServiceHelpers {
return this.http
.post<boolean>(`${this.url}/issues`, JSON.stringify(settings), {headers: this.headers});
}
public getNewsletterSettings(): Observable<INewsletterNotificationSettings> {
return this.http.get<INewsletterNotificationSettings>(`${this.url}/notifications/newsletter`, {headers: this.headers});
}
public updateNewsletterDatabase(): Observable<boolean> {
return this.http.post<boolean>(`${this.url}/notifications/newsletterdatabase`, {headers: this.headers});
}
public saveNewsletterSettings(settings: INewsletterNotificationSettings): Observable<boolean> {
return this.http
.post<boolean>(`${this.url}/notifications/newsletter`, JSON.stringify(settings), {headers: this.headers});
}
}

@ -0,0 +1,52 @@
<settings-menu></settings-menu>
<div *ngIf="form">
<fieldset>
<legend>Newsletter</legend>
<div class="col-md-6">
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="enable" formControlName="enabled">
<label for="enable">Enabled</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="enabled" [(ngModel)]="template.enabled" ng-checked="template.enabled">
<label for="enabled">Enable</label>
</div>
</div>
<div class="form-group" *ngIf="showSubject">
<label class="control-label">Subject</label>
<div>
<input type="text" class="form-control form-control-custom" [(ngModel)]="template.subject" value="{{template.subject}}">
</div>
</div>
<div class="form-group">
<label class="control-label">Message</label>
<div>
<textarea type="text" class="form-control form-control-custom" [(ngModel)]="template.message" value="{{template.message}}"></textarea>
</div>
</div>
<div class="form-group">
<div>
<button [disabled]="form.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
<button [disabled]="form.invalid" type="button" (click)="updateDatabase()" class="btn btn-info-outline" tooltipPosition="top" pTooltip="I reccomend running this with a fresh Ombi install, this will set all the current *found* content to have been sent via Newsletter,
if you do not do this then eventhing that Ombi has found in your libraries will go out on the first email!">Update Database</button>
</div>
</div>
</form>
</div>
<div class="col-md-6">
</div>
</fieldset>
</div>

@ -0,0 +1,71 @@
import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup } from "@angular/forms";
import { INewsletterNotificationSettings, INotificationTemplates, NotificationType } from "../../interfaces";
import { TesterService } from "../../services";
import { NotificationService } from "../../services";
import { SettingsService } from "../../services";
@Component({
templateUrl: "./newsletter.component.html",
})
export class NewsletterComponent implements OnInit {
public NotificationType = NotificationType;
public template: INotificationTemplates;
public form: FormGroup;
constructor(private settingsService: SettingsService,
private notificationService: NotificationService,
private fb: FormBuilder,
private testerService: TesterService) { }
public ngOnInit() {
this.settingsService.getNewsletterSettings().subscribe(x => {
this.template = x.notificationTemplate;
this.form = this.fb.group({
enabled: [x.enabled],
});
});
}
public updateDatabase() {
this.settingsService.updateNewsletterDatabase().subscribe();
}
public onSubmit(form: FormGroup) {
if (form.invalid) {
this.notificationService.error("Please check your entered values");
return;
}
const settings = <INewsletterNotificationSettings>form.value;
settings.notificationTemplate = this.template;
this.settingsService.saveNewsletterSettings(settings).subscribe(x => {
if (x) {
this.notificationService.success("Successfully saved the Newsletter settings");
} else {
this.notificationService.error("There was an error when saving the Newsletter settings");
}
});
}
public test(form: FormGroup) {
if (form.invalid) {
this.notificationService.error("Please check your entered values");
return;
}
this.testerService.discordTest(form.value).subscribe(x => {
if (x) {
this.notificationService.success("Successfully sent a Discord message, please check the discord channel");
} else {
this.notificationService.error("There was an error when sending the Discord message. Please check your settings");
}
});
}
}

@ -32,6 +32,7 @@ using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Api.Github;
using Ombi.Core.Engine;
namespace Ombi.Controllers
{
@ -60,7 +61,8 @@ namespace Ombi.Controllers
IEmbyApi embyApi,
IRadarrSync radarrSync,
ICacheService memCache,
IGithubApi githubApi)
IGithubApi githubApi,
IRecentlyAddedEngine engine)
{
SettingsResolver = resolver;
Mapper = mapper;
@ -78,6 +80,7 @@ namespace Ombi.Controllers
private readonly IRadarrSync _radarrSync;
private readonly ICacheService _cache;
private readonly IGithubApi _githubApi;
private readonly IRecentlyAddedEngine _recentlyAdded;
/// <summary>
/// Gets the Ombi settings.
@ -865,13 +868,53 @@ namespace Ombi.Controllers
return model;
}
/// <summary>
/// Saves the Newsletter notification settings.
/// </summary>
/// <param name="model">The model.</param>
/// <returns></returns>
[HttpPost("notifications/newsletter")]
public async Task<bool> NewsletterSettings([FromBody] NewsletterNotificationViewModel model)
{
// Save the email settings
var settings = Mapper.Map<NewsletterSettings>(model);
var result = await Save(settings);
// Save the templates
await TemplateRepository.Update(model.NotificationTemplate);
return result;
}
[ApiExplorerSettings(IgnoreApi = true)]
[HttpPost("notifications/newsletterdatabase")]
public async Task<bool> UpdateNewsletterDatabase()
{
return await _recentlyAdded.UpdateRecentlyAddedDatabase();
}
/// <summary>
/// Gets the Newsletter Notification Settings.
/// </summary>
/// <returns></returns>
[HttpGet("notifications/newsletter")]
public async Task<NewsletterNotificationViewModel> NewsletterSettings()
{
var settings = await Get<NewsletterSettings>();
var model = Mapper.Map<NewsletterNotificationViewModel>(settings);
// Lookup to see if we have any templates saved
var templates = await BuildTemplates(NotificationAgent.Email);
model.NotificationTemplate = templates.FirstOrDefault(x => x.NotificationType == NotificationType.Newsletter);
return model;
}
private async Task<List<NotificationTemplates>> BuildTemplates(NotificationAgent agent)
{
var templates = await TemplateRepository.GetAllTemplates(agent);
return templates.OrderBy(x => x.NotificationType.ToString()).ToList();
}
private async Task<T> Get<T>()
{
var settings = SettingsResolver.Resolve<T>();

Loading…
Cancel
Save