mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-16 02:02:55 -07:00
merge
This commit is contained in:
commit
8febad53b0
47 changed files with 632 additions and 139 deletions
|
@ -59,6 +59,7 @@ We integrate with the following applications:
|
|||
Supported notifications:
|
||||
* SMTP Notifications (Email)
|
||||
* Discord
|
||||
* Gotify
|
||||
* Slack
|
||||
* Pushbullet
|
||||
* Pushover
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#addin "Cake.Gulp"
|
||||
#addin "SharpZipLib"
|
||||
#addin nuget:?package=Cake.Compression&version=0.1.4
|
||||
#addin "Cake.Incubator"
|
||||
#addin "Cake.Incubator&version=3.1.0"
|
||||
#addin "Cake.Yarn"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
|
36
src/Ombi.Api.Gotify/GotifyApi.cs
Normal file
36
src/Ombi.Api.Gotify/GotifyApi.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Api.Gotify
|
||||
{
|
||||
public class GotifyApi : IGotifyApi
|
||||
{
|
||||
public GotifyApi(IApi api)
|
||||
{
|
||||
_api = api;
|
||||
}
|
||||
|
||||
private readonly IApi _api;
|
||||
|
||||
public async Task PushAsync(string baseUrl, string accessToken, string subject, string body, sbyte priority)
|
||||
{
|
||||
var request = new Request("/message", baseUrl, HttpMethod.Post);
|
||||
request.AddQueryString("token", accessToken);
|
||||
|
||||
request.AddHeader("Access-Token", accessToken);
|
||||
request.ApplicationJsonContentType();
|
||||
|
||||
|
||||
var jsonBody = new
|
||||
{
|
||||
message = body,
|
||||
title = subject,
|
||||
priority = priority
|
||||
};
|
||||
|
||||
request.AddJsonBody(jsonBody);
|
||||
|
||||
await _api.Request(request);
|
||||
}
|
||||
}
|
||||
}
|
9
src/Ombi.Api.Gotify/IGotifyApi.cs
Normal file
9
src/Ombi.Api.Gotify/IGotifyApi.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Api.Gotify
|
||||
{
|
||||
public interface IGotifyApi
|
||||
{
|
||||
Task PushAsync(string endpoint, string accessToken, string subject, string body, sbyte priority);
|
||||
}
|
||||
}
|
15
src/Ombi.Api.Gotify/Ombi.Api.Gotify.csproj
Normal file
15
src/Ombi.Api.Gotify/Ombi.Api.Gotify.csproj
Normal file
|
@ -0,0 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<AssemblyVersion>3.0.0.0</AssemblyVersion>
|
||||
<FileVersion>3.0.0.0</FileVersion>
|
||||
<Version></Version>
|
||||
<PackageVersion></PackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -4,6 +4,7 @@ using Moq;
|
|||
using Ombi.Core.Rule.Rules.Request;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
using NUnit.Framework;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Helpers;
|
||||
|
||||
namespace Ombi.Core.Tests.Rule.Request
|
||||
|
@ -16,7 +17,7 @@ namespace Ombi.Core.Tests.Rule.Request
|
|||
{
|
||||
|
||||
PrincipalMock = new Mock<IPrincipal>();
|
||||
Rule = new AutoApproveRule(PrincipalMock.Object);
|
||||
Rule = new AutoApproveRule(PrincipalMock.Object, null);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
|||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Ombi.Core.Rule.Rules;
|
||||
using Ombi.Core.Rule.Rules.Request;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
|
||||
|
@ -15,7 +16,7 @@ namespace Ombi.Core.Tests.Rule.Request
|
|||
{
|
||||
|
||||
PrincipalMock = new Mock<IPrincipal>();
|
||||
Rule = new CanRequestRule(PrincipalMock.Object);
|
||||
Rule = new CanRequestRule(PrincipalMock.Object, null);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace Ombi.Core.Tests.Rule.Search
|
|||
public void Setup()
|
||||
{
|
||||
ContextMock = new Mock<IEmbyContentRepository>();
|
||||
Rule = new EmbyAvailabilityRule(ContextMock.Object);
|
||||
Rule = new EmbyAvailabilityRule(ContextMock.Object, null);
|
||||
}
|
||||
|
||||
private EmbyAvailabilityRule Rule { get; set; }
|
||||
|
|
|
@ -32,14 +32,13 @@ namespace Ombi.Core.Engine
|
|||
{
|
||||
public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user,
|
||||
INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager,
|
||||
ITvSender sender, IAuditRepository audit, IRepository<RequestLog> rl, ISettingsService<OmbiSettings> settings, ICacheService cache,
|
||||
ITvSender sender, IRepository<RequestLog> rl, ISettingsService<OmbiSettings> settings, ICacheService cache,
|
||||
IRepository<RequestSubscription> sub) : base(user, requestService, rule, manager, cache, settings, sub)
|
||||
{
|
||||
TvApi = tvApi;
|
||||
MovieDbApi = movApi;
|
||||
NotificationHelper = helper;
|
||||
TvSender = sender;
|
||||
Audit = audit;
|
||||
_requestLog = rl;
|
||||
}
|
||||
|
||||
|
@ -47,7 +46,6 @@ namespace Ombi.Core.Engine
|
|||
private ITvMazeApi TvApi { get; }
|
||||
private IMovieDbApi MovieDbApi { get; }
|
||||
private ITvSender TvSender { get; }
|
||||
private IAuditRepository Audit { get; }
|
||||
private readonly IRepository<RequestLog> _requestLog;
|
||||
|
||||
public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv)
|
||||
|
@ -85,8 +83,6 @@ namespace Ombi.Core.Engine
|
|||
}
|
||||
}
|
||||
|
||||
await Audit.Record(AuditType.Added, AuditArea.TvRequest, $"Added Request {tvBuilder.ChildRequest.Title}", Username);
|
||||
|
||||
var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.TvDbId);
|
||||
if (existingRequest != null)
|
||||
{
|
||||
|
@ -408,7 +404,6 @@ namespace Ombi.Core.Engine
|
|||
|
||||
public async Task<TvRequests> UpdateTvRequest(TvRequests request)
|
||||
{
|
||||
await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username);
|
||||
var allRequests = TvRepository.Get();
|
||||
var results = await allRequests.FirstOrDefaultAsync(x => x.Id == request.Id);
|
||||
|
||||
|
@ -451,7 +446,6 @@ namespace Ombi.Core.Engine
|
|||
if (request.Approved)
|
||||
{
|
||||
NotificationHelper.Notify(request, NotificationType.RequestApproved);
|
||||
await Audit.Record(AuditType.Approved, AuditArea.TvRequest, $"Approved Request {request.Title}", Username);
|
||||
// Autosend
|
||||
await TvSender.Send(request);
|
||||
}
|
||||
|
@ -483,9 +477,7 @@ namespace Ombi.Core.Engine
|
|||
|
||||
public async Task<ChildRequests> UpdateChildRequest(ChildRequests request)
|
||||
{
|
||||
await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username);
|
||||
|
||||
await TvRepository.UpdateChild(request);
|
||||
await TvRepository.UpdateChild(request);
|
||||
return request;
|
||||
}
|
||||
|
||||
|
@ -503,16 +495,14 @@ namespace Ombi.Core.Engine
|
|||
// Delete the parent
|
||||
TvRepository.Db.TvRequests.Remove(parent);
|
||||
}
|
||||
await Audit.Record(AuditType.Deleted, AuditArea.TvRequest, $"Deleting Request {request.Title}", Username);
|
||||
|
||||
|
||||
await TvRepository.Db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task RemoveTvRequest(int requestId)
|
||||
{
|
||||
var request = await TvRepository.Get().FirstOrDefaultAsync(x => x.Id == requestId);
|
||||
await Audit.Record(AuditType.Deleted, AuditArea.TvRequest, $"Deleting Request {request.Title}", Username);
|
||||
await TvRepository.Delete(request);
|
||||
await TvRepository.Delete(request);
|
||||
}
|
||||
|
||||
public async Task<bool> UserHasRequest(string userId)
|
||||
|
|
23
src/Ombi.Core/Models/UI/GotifyNotificationViewModel.cs
Normal file
23
src/Ombi.Core/Models/UI/GotifyNotificationViewModel.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="GotifyNotificationSettings" />
|
||||
public class GotifyNotificationViewModel : GotifySettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the notification templates.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The notification templates.
|
||||
/// </value>
|
||||
public List<NotificationTemplates> NotificationTemplates { get; set; }
|
||||
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Core.Models.Requests;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Helpers;
|
||||
|
@ -10,28 +12,31 @@ namespace Ombi.Core.Rule.Rules.Request
|
|||
{
|
||||
public class AutoApproveRule : BaseRequestRule, IRules<BaseRequest>
|
||||
{
|
||||
public AutoApproveRule(IPrincipal principal)
|
||||
public AutoApproveRule(IPrincipal principal, OmbiUserManager um)
|
||||
{
|
||||
User = principal;
|
||||
_manager = um;
|
||||
}
|
||||
|
||||
private IPrincipal User { get; }
|
||||
private readonly OmbiUserManager _manager;
|
||||
|
||||
public Task<RuleResult> Execute(BaseRequest obj)
|
||||
public async Task<RuleResult> Execute(BaseRequest obj)
|
||||
{
|
||||
if (User.IsInRole(OmbiRoles.Admin))
|
||||
var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
|
||||
if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin))
|
||||
{
|
||||
obj.Approved = true;
|
||||
return Task.FromResult(Success());
|
||||
return Success();
|
||||
}
|
||||
|
||||
if (obj.RequestType == RequestType.Movie && User.IsInRole(OmbiRoles.AutoApproveMovie))
|
||||
if (obj.RequestType == RequestType.Movie && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie))
|
||||
obj.Approved = true;
|
||||
if (obj.RequestType == RequestType.TvShow && User.IsInRole(OmbiRoles.AutoApproveTv))
|
||||
if (obj.RequestType == RequestType.TvShow && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveTv))
|
||||
obj.Approved = true;
|
||||
if (obj.RequestType == RequestType.Album && User.IsInRole(OmbiRoles.AutoApproveMusic))
|
||||
if (obj.RequestType == RequestType.Album && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMusic))
|
||||
obj.Approved = true;
|
||||
return Task.FromResult(Success()); // We don't really care, we just don't set the obj to approve
|
||||
return Success(); // We don't really care, we just don't set the obj to approve
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +1,52 @@
|
|||
using Ombi.Store.Entities;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
|
||||
namespace Ombi.Core.Rule.Rules
|
||||
namespace Ombi.Core.Rule.Rules.Request
|
||||
{
|
||||
public class CanRequestRule : BaseRequestRule, IRules<BaseRequest>
|
||||
{
|
||||
public CanRequestRule(IPrincipal principal)
|
||||
public CanRequestRule(IPrincipal principal, OmbiUserManager manager)
|
||||
{
|
||||
User = principal;
|
||||
_manager = manager;
|
||||
}
|
||||
|
||||
private IPrincipal User { get; }
|
||||
private readonly OmbiUserManager _manager;
|
||||
|
||||
public Task<RuleResult> Execute(BaseRequest obj)
|
||||
public async Task<RuleResult> Execute(BaseRequest obj)
|
||||
{
|
||||
if (User.IsInRole(OmbiRoles.Admin))
|
||||
return Task.FromResult(Success());
|
||||
var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
|
||||
if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin))
|
||||
return Success();
|
||||
|
||||
if (obj.RequestType == RequestType.Movie)
|
||||
{
|
||||
if (User.IsInRole(OmbiRoles.RequestMovie) || User.IsInRole(OmbiRoles.AutoApproveMovie))
|
||||
return Task.FromResult(Success());
|
||||
return Task.FromResult(Fail("You do not have permissions to Request a Movie"));
|
||||
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMovie) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie))
|
||||
return Success();
|
||||
return Fail("You do not have permissions to Request a Movie");
|
||||
}
|
||||
|
||||
if (obj.RequestType == RequestType.TvShow)
|
||||
{
|
||||
if (User.IsInRole(OmbiRoles.RequestTv) || User.IsInRole(OmbiRoles.AutoApproveTv))
|
||||
return Task.FromResult(Success());
|
||||
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestTv) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveTv))
|
||||
return Success();
|
||||
}
|
||||
|
||||
if (obj.RequestType == RequestType.Album)
|
||||
{
|
||||
if (User.IsInRole(OmbiRoles.RequestMusic) || User.IsInRole(OmbiRoles.AutoApproveMusic))
|
||||
return Task.FromResult(Success());
|
||||
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMusic) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMusic))
|
||||
return Success();
|
||||
}
|
||||
|
||||
return Task.FromResult(Fail("You do not have permissions to Request a TV Show"));
|
||||
return Fail("You do not have permissions to Request a TV Show");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ using System.Threading.Tasks;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Models.Search;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
|
@ -11,12 +13,14 @@ namespace Ombi.Core.Rule.Rules.Search
|
|||
{
|
||||
public class EmbyAvailabilityRule : BaseSearchRule, IRules<SearchViewModel>
|
||||
{
|
||||
public EmbyAvailabilityRule(IEmbyContentRepository repo)
|
||||
public EmbyAvailabilityRule(IEmbyContentRepository repo, ISettingsService<EmbySettings> s)
|
||||
{
|
||||
EmbyContentRepository = repo;
|
||||
EmbySettings = s;
|
||||
}
|
||||
|
||||
private IEmbyContentRepository EmbyContentRepository { get; }
|
||||
private ISettingsService<EmbySettings> EmbySettings { get; }
|
||||
|
||||
public async Task<RuleResult> Execute(SearchViewModel obj)
|
||||
{
|
||||
|
@ -60,7 +64,16 @@ namespace Ombi.Core.Rule.Rules.Search
|
|||
if (item != null)
|
||||
{
|
||||
obj.Available = true;
|
||||
obj.EmbyUrl = item.Url;
|
||||
var s = await EmbySettings.GetSettingsAsync();
|
||||
var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null);
|
||||
if ((server?.ServerHostname ?? string.Empty).HasValue())
|
||||
{
|
||||
obj.EmbyUrl = $"{server.ServerHostname}#!/itemdetails.html?id={item.EmbyId}";
|
||||
}
|
||||
else
|
||||
{
|
||||
obj.EmbyUrl = $"https://app.emby.media/#!/itemdetails.html?id={item.EmbyId}";
|
||||
}
|
||||
|
||||
if (obj.Type == RequestType.TvShow)
|
||||
{
|
||||
|
|
|
@ -32,6 +32,7 @@ using Ombi.Api.CouchPotato;
|
|||
using Ombi.Api.DogNzb;
|
||||
using Ombi.Api.FanartTv;
|
||||
using Ombi.Api.Github;
|
||||
using Ombi.Api.Gotify;
|
||||
using Ombi.Api.Lidarr;
|
||||
using Ombi.Api.Mattermost;
|
||||
using Ombi.Api.Notifications;
|
||||
|
@ -128,6 +129,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<IOmbiService, OmbiService>();
|
||||
services.AddTransient<IFanartTvApi, FanartTvApi>();
|
||||
services.AddTransient<IPushoverApi, PushoverApi>();
|
||||
services.AddTransient<IGotifyApi, GotifyApi>();
|
||||
services.AddTransient<IMattermostApi, MattermostApi>();
|
||||
services.AddTransient<ICouchPotatoApi, CouchPotatoApi>();
|
||||
services.AddTransient<IDogNzbApi, DogNzbApi>();
|
||||
|
@ -140,28 +142,28 @@ namespace Ombi.DependencyInjection
|
|||
}
|
||||
|
||||
public static void RegisterStore(this IServiceCollection services) {
|
||||
services.AddEntityFrameworkSqlite().AddDbContext<OmbiContext>();
|
||||
services.AddEntityFrameworkSqlite().AddDbContext<SettingsContext>();
|
||||
services.AddEntityFrameworkSqlite().AddDbContext<ExternalContext>();
|
||||
services.AddDbContext<OmbiContext>();
|
||||
services.AddDbContext<SettingsContext>();
|
||||
services.AddDbContext<ExternalContext>();
|
||||
|
||||
services.AddScoped<IOmbiContext, OmbiContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
|
||||
services.AddScoped<ISettingsContext, SettingsContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
|
||||
services.AddScoped<IExternalContext, ExternalContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
|
||||
services.AddTransient<ISettingsRepository, SettingsJsonRepository>();
|
||||
services.AddTransient<ISettingsResolver, SettingsResolver>();
|
||||
services.AddTransient<IPlexContentRepository, PlexServerContentRepository>();
|
||||
services.AddTransient<IEmbyContentRepository, EmbyContentRepository>();
|
||||
services.AddTransient<INotificationTemplatesRepository, NotificationTemplatesRepository>();
|
||||
services.AddScoped<ISettingsRepository, SettingsJsonRepository>();
|
||||
services.AddScoped<ISettingsResolver, SettingsResolver>();
|
||||
services.AddScoped<IPlexContentRepository, PlexServerContentRepository>();
|
||||
services.AddScoped<IEmbyContentRepository, EmbyContentRepository>();
|
||||
services.AddScoped<INotificationTemplatesRepository, NotificationTemplatesRepository>();
|
||||
|
||||
services.AddTransient<ITvRequestRepository, TvRequestRepository>();
|
||||
services.AddTransient<IMovieRequestRepository, MovieRequestRepository>();
|
||||
services.AddTransient<IMusicRequestRepository, MusicRequestRepository>();
|
||||
services.AddTransient<IAuditRepository, AuditRepository>();
|
||||
services.AddTransient<IApplicationConfigRepository, ApplicationConfigRepository>();
|
||||
services.AddTransient<ITokenRepository, TokenRepository>();
|
||||
services.AddTransient(typeof(ISettingsService<>), typeof(SettingsService<>));
|
||||
services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
|
||||
services.AddTransient(typeof(IExternalRepository<>), typeof(ExternalRepository<>));
|
||||
services.AddScoped<ITvRequestRepository, TvRequestRepository>();
|
||||
services.AddScoped<IMovieRequestRepository, MovieRequestRepository>();
|
||||
services.AddScoped<IMusicRequestRepository, MusicRequestRepository>();
|
||||
services.AddScoped<IAuditRepository, AuditRepository>();
|
||||
services.AddScoped<IApplicationConfigRepository, ApplicationConfigRepository>();
|
||||
services.AddScoped<ITokenRepository, TokenRepository>();
|
||||
services.AddScoped(typeof(ISettingsService<>), typeof(SettingsService<>));
|
||||
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
|
||||
services.AddScoped(typeof(IExternalRepository<>), typeof(ExternalRepository<>));
|
||||
}
|
||||
public static void RegisterServices(this IServiceCollection services)
|
||||
{
|
||||
|
@ -169,7 +171,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<INotificationService, NotificationService>();
|
||||
services.AddTransient<IEmailProvider, GenericEmailProvider>();
|
||||
services.AddTransient<INotificationHelper, NotificationHelper>();
|
||||
services.AddTransient<ICacheService, CacheService>();
|
||||
services.AddSingleton<ICacheService, CacheService>();
|
||||
|
||||
services.AddTransient<IDiscordNotification, DiscordNotification>();
|
||||
services.AddTransient<IEmailNotification, EmailNotification>();
|
||||
|
@ -178,6 +180,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<ISlackNotification, SlackNotification>();
|
||||
services.AddTransient<IMattermostNotification, MattermostNotification>();
|
||||
services.AddTransient<IPushoverNotification, PushoverNotification>();
|
||||
services.AddTransient<IGotifyNotification, GotifyNotification>();
|
||||
services.AddTransient<ITelegramNotification, TelegramNotification>();
|
||||
services.AddTransient<IMobileNotification, MobileNotification>();
|
||||
services.AddTransient<IChangeLogProcessor, ChangeLogProcessor>();
|
||||
|
|
|
@ -32,6 +32,7 @@ namespace Ombi.Helpers
|
|||
public static EventId MattermostNotification => new EventId(4004);
|
||||
public static EventId PushoverNotification => new EventId(4005);
|
||||
public static EventId TelegramNotifcation => new EventId(4006);
|
||||
public static EventId GotifyNotification => new EventId(4007);
|
||||
|
||||
public static EventId TvSender => new EventId(5000);
|
||||
public static EventId SonarrSender => new EventId(5001);
|
||||
|
|
|
@ -10,5 +10,6 @@
|
|||
Slack = 5,
|
||||
Mattermost = 6,
|
||||
Mobile = 7,
|
||||
Gotify = 8,
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ namespace Ombi.Mapping.Profiles
|
|||
CreateMap<UpdateSettingsViewModel, UpdateSettings>().ReverseMap();
|
||||
CreateMap<MobileNotificationsViewModel, MobileNotificationSettings>().ReverseMap();
|
||||
CreateMap<NewsletterNotificationViewModel, NewsletterSettings>().ReverseMap();
|
||||
CreateMap<GotifyNotificationViewModel, GotifySettings>().ReverseMap();
|
||||
}
|
||||
}
|
||||
}
|
116
src/Ombi.Notifications/Agents/GotifyNotification.cs
Normal file
116
src/Ombi.Notifications/Agents/GotifyNotification.cs
Normal file
|
@ -0,0 +1,116 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Api.Gotify;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Notifications.Models;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using Ombi.Store.Repository.Requests;
|
||||
|
||||
namespace Ombi.Notifications.Agents
|
||||
{
|
||||
public class GotifyNotification : BaseNotification<GotifySettings>, IGotifyNotification
|
||||
{
|
||||
public GotifyNotification(IGotifyApi api, ISettingsService<GotifySettings> sn, ILogger<GotifyNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
|
||||
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
|
||||
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref)
|
||||
{
|
||||
Api = api;
|
||||
Logger = log;
|
||||
}
|
||||
|
||||
public override string NotificationName => "GotifyNotification";
|
||||
|
||||
private IGotifyApi Api { get; }
|
||||
private ILogger<GotifyNotification> Logger { get; }
|
||||
|
||||
protected override bool ValidateConfiguration(GotifySettings settings)
|
||||
{
|
||||
return settings.Enabled && !string.IsNullOrEmpty(settings.BaseUrl) && !string.IsNullOrEmpty(settings.ApplicationToken);
|
||||
}
|
||||
|
||||
protected override async Task NewRequest(NotificationOptions model, GotifySettings settings)
|
||||
{
|
||||
await Run(model, settings, NotificationType.NewRequest);
|
||||
}
|
||||
|
||||
|
||||
protected override async Task NewIssue(NotificationOptions model, GotifySettings settings)
|
||||
{
|
||||
await Run(model, settings, NotificationType.Issue);
|
||||
}
|
||||
|
||||
protected override async Task IssueComment(NotificationOptions model, GotifySettings settings)
|
||||
{
|
||||
await Run(model, settings, NotificationType.IssueComment);
|
||||
}
|
||||
|
||||
protected override async Task IssueResolved(NotificationOptions model, GotifySettings settings)
|
||||
{
|
||||
await Run(model, settings, NotificationType.IssueResolved);
|
||||
}
|
||||
|
||||
protected override async Task AddedToRequestQueue(NotificationOptions model, GotifySettings settings)
|
||||
{
|
||||
await Run(model, settings, NotificationType.ItemAddedToFaultQueue);
|
||||
}
|
||||
|
||||
protected override async Task RequestDeclined(NotificationOptions model, GotifySettings settings)
|
||||
{
|
||||
await Run(model, settings, NotificationType.RequestDeclined);
|
||||
}
|
||||
|
||||
protected override async Task RequestApproved(NotificationOptions model, GotifySettings settings)
|
||||
{
|
||||
await Run(model, settings, NotificationType.RequestApproved);
|
||||
}
|
||||
|
||||
protected override async Task AvailableRequest(NotificationOptions model, GotifySettings settings)
|
||||
{
|
||||
await Run(model, settings, NotificationType.RequestAvailable);
|
||||
}
|
||||
|
||||
protected override async Task Send(NotificationMessage model, GotifySettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Api.PushAsync(settings.BaseUrl, settings.ApplicationToken, model.Subject, model.Message, settings.Priority);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError(LoggingEvents.GotifyNotification, e, "Failed to send Gotify notification");
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task Test(NotificationOptions model, GotifySettings settings)
|
||||
{
|
||||
var message = $"This is a test from Ombi, if you can see this then we have successfully pushed a notification!";
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
private async Task Run(NotificationOptions model, GotifySettings settings, NotificationType type)
|
||||
{
|
||||
var parsed = await LoadTemplate(NotificationAgent.Gotify, type, model);
|
||||
if (parsed.Disabled)
|
||||
{
|
||||
Logger.LogInformation($"Template {type} is disabled for {NotificationAgent.Gotify}");
|
||||
return;
|
||||
}
|
||||
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = parsed.Message,
|
||||
};
|
||||
|
||||
await Send(notification, settings);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace Ombi.Notifications.Agents
|
||||
{
|
||||
public interface IGotifyNotification : INotification
|
||||
{
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ using EnsureThat;
|
|||
using MailKit.Net.Smtp;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MimeKit;
|
||||
using MimeKit.Utils;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Notifications.Models;
|
||||
|
@ -37,6 +38,15 @@ namespace Ombi.Notifications
|
|||
|
||||
var customization = await CustomizationSettings.GetSettingsAsync();
|
||||
var html = email.LoadTemplate(model.Subject, model.Message, null, customization.Logo);
|
||||
|
||||
var messageId = MimeUtils.GenerateMessageId();
|
||||
if (customization.ApplicationUrl.HasValue())
|
||||
{
|
||||
if (Uri.TryCreate(customization.ApplicationUrl, UriKind.RelativeOrAbsolute, out var url))
|
||||
{
|
||||
messageId = MimeUtils.GenerateMessageId(url.IdnHost);
|
||||
}
|
||||
}
|
||||
|
||||
var textBody = string.Empty;
|
||||
|
||||
|
@ -50,7 +60,8 @@ namespace Ombi.Notifications
|
|||
var message = new MimeMessage
|
||||
{
|
||||
Body = body.ToMessageBody(),
|
||||
Subject = model.Subject
|
||||
Subject = model.Subject,
|
||||
MessageId = messageId
|
||||
};
|
||||
message.From.Add(new MailboxAddress(string.IsNullOrEmpty(settings.SenderName) ? settings.SenderAddress : settings.SenderName, settings.SenderAddress));
|
||||
message.To.Add(new MailboxAddress(model.To, model.To));
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ombi.Api.Discord\Ombi.Api.Discord.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Gotify\Ombi.Api.Gotify.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Mattermost\Ombi.Api.Mattermost.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Notifications\Ombi.Api.Notifications.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Pushbullet\Ombi.Api.Pushbullet.csproj" />
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
namespace Ombi.Settings.Settings.Models.Notifications
|
||||
{
|
||||
public class GotifySettings : Settings
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public string BaseUrl { get; set; }
|
||||
public string ApplicationToken { get; set; }
|
||||
public sbyte Priority { get; set; } = 4;
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ namespace Ombi.Store.Context
|
|||
{
|
||||
if (_created) return;
|
||||
|
||||
|
||||
_created = true;
|
||||
Database.SetCommandTimeout(60);
|
||||
Database.Migrate();
|
||||
|
|
|
@ -5,7 +5,7 @@ using System.Linq.Expressions;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Query;
|
||||
using Nito.AsyncEx;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Context;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
|
@ -20,7 +20,6 @@ namespace Ombi.Store.Repository
|
|||
}
|
||||
public DbSet<T> _db { get; }
|
||||
private readonly U _ctx;
|
||||
private readonly AsyncLock _mutex = new AsyncLock();
|
||||
|
||||
public async Task<T> Find(object key)
|
||||
{
|
||||
|
@ -32,7 +31,7 @@ namespace Ombi.Store.Repository
|
|||
return _db.AsQueryable();
|
||||
}
|
||||
|
||||
public async Task<T> FirstOrDefaultAsync(Expression<Func<T,bool>> predicate)
|
||||
public async Task<T> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate)
|
||||
{
|
||||
return await _db.FirstOrDefaultAsync(predicate);
|
||||
}
|
||||
|
@ -82,14 +81,11 @@ namespace Ombi.Store.Repository
|
|||
await _ctx.Database.ExecuteSqlCommandAsync(sql);
|
||||
}
|
||||
|
||||
private async Task<int> InternalSaveChanges()
|
||||
protected async Task<int> InternalSaveChanges()
|
||||
{
|
||||
using (await _mutex.LockAsync())
|
||||
{
|
||||
return await _ctx.SaveChangesAsync();
|
||||
}
|
||||
return await _ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private bool _disposed;
|
||||
// Protected implementation of Dispose pattern.
|
||||
|
@ -102,7 +98,7 @@ namespace Ombi.Store.Repository
|
|||
{
|
||||
_ctx?.Dispose();
|
||||
}
|
||||
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ namespace Ombi.Store.Repository
|
|||
public async Task Update(EmbyContent existingContent)
|
||||
{
|
||||
Db.EmbyContent.Update(existingContent);
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
public IQueryable<EmbyEpisode> GetAllEpisodes()
|
||||
|
@ -83,7 +83,7 @@ namespace Ombi.Store.Repository
|
|||
public async Task<EmbyEpisode> Add(EmbyEpisode content)
|
||||
{
|
||||
await Db.EmbyEpisode.AddAsync(content);
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
return content;
|
||||
}
|
||||
public async Task<EmbyEpisode> GetEpisodeByEmbyId(string key)
|
||||
|
@ -94,12 +94,13 @@ namespace Ombi.Store.Repository
|
|||
public async Task AddRange(IEnumerable<EmbyEpisode> content)
|
||||
{
|
||||
Db.EmbyEpisode.AddRange(content);
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
public void UpdateWithoutSave(EmbyContent existingContent)
|
||||
{
|
||||
Db.EmbyContent.Update(existingContent);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -45,7 +45,7 @@ namespace Ombi.Store.Repository
|
|||
Db.Attach(template);
|
||||
Db.Entry(template).State = EntityState.Modified;
|
||||
}
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
public async Task UpdateRange(IEnumerable<NotificationTemplates> templates)
|
||||
|
@ -56,16 +56,21 @@ namespace Ombi.Store.Repository
|
|||
Db.Attach(t);
|
||||
Db.Entry(t).State = EntityState.Modified;
|
||||
}
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
public async Task<NotificationTemplates> Insert(NotificationTemplates entity)
|
||||
{
|
||||
var settings = await Db.NotificationTemplates.AddAsync(entity).ConfigureAwait(false);
|
||||
await Db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await InternalSaveChanges().ConfigureAwait(false);
|
||||
return settings.Entity;
|
||||
}
|
||||
|
||||
private async Task<int> InternalSaveChanges()
|
||||
{
|
||||
return await Db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
// Protected implementation of Dispose pattern.
|
||||
protected virtual void Dispose(bool disposing)
|
||||
|
|
|
@ -96,7 +96,7 @@ namespace Ombi.Store.Repository
|
|||
public async Task Update(PlexServerContent existingContent)
|
||||
{
|
||||
Db.PlexServerContent.Update(existingContent);
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
public void UpdateWithoutSave(PlexServerContent existingContent)
|
||||
{
|
||||
|
@ -106,7 +106,7 @@ namespace Ombi.Store.Repository
|
|||
public async Task UpdateRange(IEnumerable<PlexServerContent> existingContent)
|
||||
{
|
||||
Db.PlexServerContent.UpdateRange(existingContent);
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
public IQueryable<PlexEpisode> GetAllEpisodes()
|
||||
|
@ -127,14 +127,14 @@ namespace Ombi.Store.Repository
|
|||
public async Task<PlexEpisode> Add(PlexEpisode content)
|
||||
{
|
||||
await Db.PlexEpisode.AddAsync(content);
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
return content;
|
||||
}
|
||||
|
||||
public async Task DeleteEpisode(PlexEpisode content)
|
||||
{
|
||||
Db.PlexEpisode.Remove(content);
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
public async Task<PlexEpisode> GetEpisodeByKey(int key)
|
||||
|
@ -144,7 +144,7 @@ namespace Ombi.Store.Repository
|
|||
public async Task AddRange(IEnumerable<PlexEpisode> content)
|
||||
{
|
||||
Db.PlexEpisode.AddRange(content);
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Context;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
|
||||
|
@ -70,12 +71,12 @@ namespace Ombi.Store.Repository.Requests
|
|||
Db.MovieRequests.Attach(request);
|
||||
Db.Update(request);
|
||||
}
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
public async Task Save()
|
||||
{
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Context;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
|
||||
|
@ -61,12 +62,12 @@ namespace Ombi.Store.Repository.Requests
|
|||
Db.AlbumRequests.Attach(request);
|
||||
Db.Update(request);
|
||||
}
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
public async Task Save()
|
||||
{
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Context;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
|
||||
|
@ -101,20 +102,20 @@ namespace Ombi.Store.Repository.Requests
|
|||
|
||||
public async Task Save()
|
||||
{
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
public async Task<TvRequests> Add(TvRequests request)
|
||||
{
|
||||
await Db.TvRequests.AddAsync(request);
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
return request;
|
||||
}
|
||||
|
||||
public async Task<ChildRequests> AddChild(ChildRequests request)
|
||||
{
|
||||
await Db.ChildRequests.AddAsync(request);
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
|
||||
return request;
|
||||
}
|
||||
|
@ -122,33 +123,38 @@ namespace Ombi.Store.Repository.Requests
|
|||
public async Task Delete(TvRequests request)
|
||||
{
|
||||
Db.TvRequests.Remove(request);
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
public async Task DeleteChild(ChildRequests request)
|
||||
{
|
||||
Db.ChildRequests.Remove(request);
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
public async Task DeleteChildRange(IEnumerable<ChildRequests> request)
|
||||
{
|
||||
Db.ChildRequests.RemoveRange(request);
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
public async Task Update(TvRequests request)
|
||||
{
|
||||
Db.Update(request);
|
||||
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
public async Task UpdateChild(ChildRequests request)
|
||||
{
|
||||
Db.Update(request);
|
||||
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
private async Task<int> InternalSaveChanges()
|
||||
{
|
||||
return await Db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -62,14 +62,14 @@ namespace Ombi.Store.Repository
|
|||
{
|
||||
//_cache.Remove(GetName(entity.SettingsName));
|
||||
Db.Settings.Remove(entity);
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(GlobalSettings entity)
|
||||
{
|
||||
//_cache.Remove(GetName(entity.SettingsName));
|
||||
Db.Update(entity);
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
public void Delete(GlobalSettings entity)
|
||||
|
@ -91,6 +91,11 @@ namespace Ombi.Store.Repository
|
|||
return $"{entity}Json";
|
||||
}
|
||||
|
||||
private async Task<int> InternalSaveChanges()
|
||||
{
|
||||
return await Db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@ using Ombi.Store.Entities;
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Helpers;
|
||||
|
||||
namespace Ombi.Store.Repository
|
||||
{
|
||||
|
@ -19,12 +20,16 @@ namespace Ombi.Store.Repository
|
|||
public async Task CreateToken(Tokens token)
|
||||
{
|
||||
await Db.Tokens.AddAsync(token);
|
||||
await Db.SaveChangesAsync();
|
||||
await InternalSaveChanges();
|
||||
}
|
||||
|
||||
public IQueryable<Tokens> GetToken(string tokenId)
|
||||
{
|
||||
return Db.Tokens.Where(x => x.Token == tokenId);
|
||||
}
|
||||
private async Task<int> InternalSaveChanges()
|
||||
{
|
||||
return await Db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,6 +98,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Lidarr", "Ombi.Api
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Helpers.Tests", "Ombi.Helpers.Tests\Ombi.Helpers.Tests.csproj", "{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Gotify", "Ombi.Api.Gotify\Ombi.Api.Gotify.csproj", "{105EA346-766E-45B8-928B-DE6991DCB7EB}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Settings.Tests", "Ombi.Settings.Tests\Ombi.Settings.Tests.csproj", "{F3969B69-3B07-4884-A7AB-0BAB8B84DF94}"
|
||||
EndProject
|
||||
Global
|
||||
|
@ -258,6 +260,10 @@ Global
|
|||
{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{105EA346-766E-45B8-928B-DE6991DCB7EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{105EA346-766E-45B8-928B-DE6991DCB7EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{105EA346-766E-45B8-928B-DE6991DCB7EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{105EA346-766E-45B8-928B-DE6991DCB7EB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F3969B69-3B07-4884-A7AB-0BAB8B84DF94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F3969B69-3B07-4884-A7AB-0BAB8B84DF94}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F3969B69-3B07-4884-A7AB-0BAB8B84DF94}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
@ -299,6 +305,7 @@ Global
|
|||
{10D1FE9D-9124-42B7-B1E1-CEB99B832618} = {9293CA11-360A-4C20-A674-B9E794431BF5}
|
||||
{4FA21A20-92F4-462C-B929-2C517A88CC56} = {9293CA11-360A-4C20-A674-B9E794431BF5}
|
||||
{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
|
||||
{105EA346-766E-45B8-928B-DE6991DCB7EB} = {9293CA11-360A-4C20-A674-B9E794431BF5}
|
||||
{F3969B69-3B07-4884-A7AB-0BAB8B84DF94} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
|
3
src/Ombi/.vscode/settings.json
vendored
3
src/Ombi/.vscode/settings.json
vendored
|
@ -9,5 +9,6 @@
|
|||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||
"cSpell.words": [
|
||||
"usermanagement"
|
||||
]
|
||||
],
|
||||
"discord.enabled": true
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
|
||||
<settings-menu></settings-menu>
|
||||
<div *ngIf="form">
|
||||
<fieldset>
|
||||
<legend>Gotify Notifications</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">
|
||||
<label for="baseUrl" class="control-label">Base URL</label>
|
||||
|
||||
<input type="text" class="form-control form-control-custom " id="baseUrl" name="baseUrl" [ngClass]="{'form-error': form.get('baseUrl').hasError('required')}" formControlName="baseUrl" pTooltip="Enter the URL of your gotify server.">
|
||||
<small *ngIf="form.get('baseUrl').hasError('required')" class="error-text">The Base URL is required</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="applicationToken" class="control-label">Application Token</label>
|
||||
|
||||
<input type="text" class="form-control form-control-custom " id="applicationToken" name="applicationToken" [ngClass]="{'form-error': form.get('applicationToken').hasError('required')}" formControlName="applicationToken" pTooltip="Enter your Application token from Gotify.">
|
||||
<small *ngIf="form.get('applicationToken').hasError('required')" class="error-text">The Application Token is required</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="priority" class="control-label">Priority</label>
|
||||
<div>
|
||||
<select class="form-control form-control-custom " id="priority" name="priority" formControlName="priority" pTooltip="The priority you want your gotify notifications sent as.">
|
||||
<option value="4">Normal</option>
|
||||
<option value="8">High</option>
|
||||
<option value="2">Low</option>
|
||||
<option value="0">Lowest</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="button" (click)="test(form)" class="btn btn-primary-outline">
|
||||
Test
|
||||
<div id="spinner"></div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-6">
|
||||
<notification-templates [templates]="templates" [showSubject]="false"></notification-templates>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
|
@ -0,0 +1,68 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||
|
||||
import { IGotifyNotificationSettings, INotificationTemplates, NotificationType } from "../../interfaces";
|
||||
import { TesterService } from "../../services";
|
||||
import { NotificationService } from "../../services";
|
||||
import { SettingsService } from "../../services";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./gotify.component.html",
|
||||
})
|
||||
export class GotifyComponent implements OnInit {
|
||||
public NotificationType = NotificationType;
|
||||
public templates: INotificationTemplates[];
|
||||
public form: FormGroup;
|
||||
|
||||
constructor(private settingsService: SettingsService,
|
||||
private notificationService: NotificationService,
|
||||
private fb: FormBuilder,
|
||||
private testerService: TesterService) { }
|
||||
|
||||
public ngOnInit() {
|
||||
this.settingsService.getGotifyNotificationSettings().subscribe(x => {
|
||||
this.templates = x.notificationTemplates;
|
||||
|
||||
this.form = this.fb.group({
|
||||
enabled: [x.enabled],
|
||||
baseUrl: [x.baseUrl, [Validators.required]],
|
||||
applicationToken: [x.applicationToken, [Validators.required]],
|
||||
priority: [x.priority],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public onSubmit(form: FormGroup) {
|
||||
if (form.invalid) {
|
||||
this.notificationService.error("Please check your entered values");
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = <IGotifyNotificationSettings> form.value;
|
||||
settings.notificationTemplates = this.templates;
|
||||
|
||||
this.settingsService.saveGotifyNotificationSettings(settings).subscribe(x => {
|
||||
if (x) {
|
||||
this.notificationService.success("Successfully saved the Gotify settings");
|
||||
} else {
|
||||
this.notificationService.success("There was an error when saving the Gotify settings");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public test(form: FormGroup) {
|
||||
if (form.invalid) {
|
||||
this.notificationService.error("Please check your entered values");
|
||||
return;
|
||||
}
|
||||
|
||||
this.testerService.gotifyTest(form.value).subscribe(x => {
|
||||
if (x) {
|
||||
this.notificationService.success("Successfully sent a Gotify message");
|
||||
} else {
|
||||
this.notificationService.error("There was an error when sending the Gotify message. Please check your settings");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -93,6 +93,14 @@ export interface IPushoverNotificationSettings extends INotificationSettings {
|
|||
sound: string;
|
||||
}
|
||||
|
||||
export interface IGotifyNotificationSettings extends INotificationSettings {
|
||||
accessToken: string;
|
||||
notificationTemplates: INotificationTemplates[];
|
||||
baseUrl: string;
|
||||
applicationToken: string;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
export interface IMattermostNotifcationSettings extends INotificationSettings {
|
||||
webhookUrl: string;
|
||||
username: string;
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
<i class="fa fa-check"></i> {{ 'Common.Available' | translate }}</button>
|
||||
</div>
|
||||
<div *ngIf="!result.fullyAvailable">
|
||||
<div *ngIf="result.requested || result.approved; then requestedBtn else notRequestedBtn"></div>
|
||||
<div *ngIf="result.requested || result.approved || result.monitored; then requestedBtn else notRequestedBtn"></div>
|
||||
<ng-template #requestedBtn>
|
||||
<button style="text-align: right" class="btn btn-primary-outline disabled" [disabled]>
|
||||
<i class="fa fa-check"></i> {{ 'Common.Requested' | translate }}</button>
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
IDiscordNotifcationSettings,
|
||||
IEmailNotificationSettings,
|
||||
IEmbyServer,
|
||||
IGotifyNotificationSettings,
|
||||
ILidarrSettings,
|
||||
IMattermostNotifcationSettings,
|
||||
IMobileNotificationTestSettings,
|
||||
|
@ -40,7 +41,11 @@ export class TesterService extends ServiceHelpers {
|
|||
}
|
||||
|
||||
public pushoverTest(settings: IPushoverNotificationSettings): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}pushover`, JSON.stringify(settings), {headers: this.headers});
|
||||
return this.http.post<boolean>(`${this.url}pushover`, JSON.stringify(settings), { headers: this.headers });
|
||||
}
|
||||
|
||||
public gotifyTest(settings: IGotifyNotificationSettings): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}gotify`, JSON.stringify(settings), { headers: this.headers });
|
||||
}
|
||||
|
||||
public mattermostTest(settings: IMattermostNotifcationSettings): Observable<boolean> {
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
IDogNzbSettings,
|
||||
IEmailNotificationSettings,
|
||||
IEmbySettings,
|
||||
IGotifyNotificationSettings,
|
||||
IIssueSettings,
|
||||
IJobSettings,
|
||||
IJobSettingsViewModel,
|
||||
|
@ -182,6 +183,14 @@ export class SettingsService extends ServiceHelpers {
|
|||
.post<boolean>(`${this.url}/notifications/pushover`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
|
||||
public getGotifyNotificationSettings(): Observable<IGotifyNotificationSettings> {
|
||||
return this.http.get<IGotifyNotificationSettings>(`${this.url}/notifications/gotify`, { headers: this.headers });
|
||||
}
|
||||
public saveGotifyNotificationSettings(settings: IGotifyNotificationSettings): Observable<boolean> {
|
||||
return this.http
|
||||
.post<boolean>(`${this.url}/notifications/gotify`, JSON.stringify(settings), { headers: this.headers });
|
||||
}
|
||||
|
||||
public getSlackNotificationSettings(): Observable<ISlackNotificationSettings> {
|
||||
return this.http.get<ISlackNotificationSettings>(`${this.url}/notifications/slack`, {headers: this.headers});
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import { LidarrComponent } from "./lidarr/lidarr.component";
|
|||
import { MassEmailComponent } from "./massemail/massemail.component";
|
||||
import { DiscordComponent } from "./notifications/discord.component";
|
||||
import { EmailNotificationComponent } from "./notifications/emailnotification.component";
|
||||
import { GotifyComponent } from "./notifications/gotify.component";
|
||||
import { MattermostComponent } from "./notifications/mattermost.component";
|
||||
import { MobileComponent } from "./notifications/mobile.component";
|
||||
import { NewsletterComponent } from "./notifications/newsletter.component";
|
||||
|
@ -65,6 +66,7 @@ const routes: Routes = [
|
|||
{ path: "Slack", component: SlackComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Pushover", component: PushoverComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Pushbullet", component: PushbulletComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Gotify", component: GotifyComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Mattermost", component: MattermostComponent, canActivate: [AuthGuard] },
|
||||
{ path: "UserManagement", component: UserManagementComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Update", component: UpdateComponent, canActivate: [AuthGuard] },
|
||||
|
@ -121,6 +123,7 @@ const routes: Routes = [
|
|||
PushoverComponent,
|
||||
MattermostComponent,
|
||||
PushbulletComponent,
|
||||
GotifyComponent,
|
||||
UserManagementComponent,
|
||||
UpdateComponent,
|
||||
AboutComponent,
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
<button mat-menu-item [routerLink]="['/Settings/Pushover']">Pushover</button>
|
||||
<button mat-menu-item [routerLink]="['/Settings/Mattermost']">Mattermost</button>
|
||||
<button mat-menu-item [routerLink]="['/Settings/Telegram']">Telegram</button>
|
||||
<button mat-menu-item [routerLink]="['/Settings/Gotify']">Gotify</button>
|
||||
</mat-menu>
|
||||
|
||||
<button mat-button [matMenuTriggerFor]="systemMenu"><i class="fa fa-tachometer" aria-hidden="true"></i> System</button>
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace Ombi.Controllers.V1.External
|
|||
IPushbulletNotification pushbullet, ISlackNotification slack, IPushoverNotification po, IMattermostNotification mm,
|
||||
IPlexApi plex, IEmbyApi emby, IRadarrApi radarr, ISonarrApi sonarr, ILogger<TesterController> log, IEmailProvider provider,
|
||||
ICouchPotatoApi cpApi, ITelegramNotification telegram, ISickRageApi srApi, INewsletterJob newsletter, IMobileNotification mobileNotification,
|
||||
ILidarrApi lidarrApi)
|
||||
ILidarrApi lidarrApi, IGotifyNotification gotifyNotification)
|
||||
{
|
||||
Service = service;
|
||||
DiscordNotification = notification;
|
||||
|
@ -61,6 +61,7 @@ namespace Ombi.Controllers.V1.External
|
|||
Newsletter = newsletter;
|
||||
MobileNotification = mobileNotification;
|
||||
LidarrApi = lidarrApi;
|
||||
GotifyNotification = gotifyNotification;
|
||||
}
|
||||
|
||||
private INotificationService Service { get; }
|
||||
|
@ -69,6 +70,7 @@ namespace Ombi.Controllers.V1.External
|
|||
private IPushbulletNotification PushbulletNotification { get; }
|
||||
private ISlackNotification SlackNotification { get; }
|
||||
private IPushoverNotification PushoverNotification { get; }
|
||||
private IGotifyNotification GotifyNotification { get; }
|
||||
private IMattermostNotification MattermostNotification { get; }
|
||||
private IPlexApi PlexApi { get; }
|
||||
private IRadarrApi RadarrApi { get; }
|
||||
|
@ -155,6 +157,30 @@ namespace Ombi.Controllers.V1.External
|
|||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a test message to Gotify using the provided settings
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("gotify")]
|
||||
public bool Gotify([FromBody] GotifySettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
settings.Enabled = true;
|
||||
GotifyNotification.NotifyAsync(
|
||||
new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.LogError(LoggingEvents.Api, e, "Could not test Gotify");
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a test message to mattermost using the provided settings
|
||||
/// </summary>
|
||||
|
|
|
@ -968,6 +968,40 @@ namespace Ombi.Controllers.V1
|
|||
return model;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the gotify notification settings.
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("notifications/gotify")]
|
||||
public async Task<bool> GotifyNotificationSettings([FromBody] GotifyNotificationViewModel model)
|
||||
{
|
||||
// Save the email settings
|
||||
var settings = Mapper.Map<GotifySettings>(model);
|
||||
var result = await Save(settings);
|
||||
|
||||
// Save the templates
|
||||
await TemplateRepository.UpdateRange(model.NotificationTemplates);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the gotify Notification Settings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("notifications/gotify")]
|
||||
public async Task<GotifyNotificationViewModel> GotifyNotificationSettings()
|
||||
{
|
||||
var settings = await Get<GotifySettings>();
|
||||
var model = Mapper.Map<GotifyNotificationViewModel>(settings);
|
||||
|
||||
// Lookup to see if we have any templates saved
|
||||
model.NotificationTemplates = BuildTemplates(NotificationAgent.Gotify);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the Newsletter notification settings.
|
||||
/// </summary>
|
||||
|
|
|
@ -49,6 +49,7 @@ namespace Ombi
|
|||
demoInstance.Demo = demo;
|
||||
instance.StoragePath = storagePath ?? string.Empty;
|
||||
// Check if we need to migrate the settings
|
||||
DeleteSchedules();
|
||||
CheckAndMigrate();
|
||||
var ctx = new SettingsContext();
|
||||
var config = ctx.ApplicationConfigurations.ToList();
|
||||
|
@ -97,6 +98,20 @@ namespace Ombi
|
|||
CreateWebHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
private static void DeleteSchedules()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists("Schedules.db"))
|
||||
{
|
||||
File.Delete("Schedules.db");
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is to remove the Settings from the Ombi.db to the "new"
|
||||
/// OmbiSettings.db
|
||||
|
@ -115,7 +130,7 @@ namespace Ombi
|
|||
|
||||
try
|
||||
{
|
||||
if (ombi.Settings.Any())
|
||||
if (ombi.Settings.Any() && !settings.Settings.Any())
|
||||
{
|
||||
// OK migrate it!
|
||||
var allSettings = ombi.Settings.ToList();
|
||||
|
@ -125,7 +140,7 @@ namespace Ombi
|
|||
|
||||
// Check for any application settings
|
||||
|
||||
if (ombi.ApplicationConfigurations.Any())
|
||||
if (ombi.ApplicationConfigurations.Any() && !settings.ApplicationConfigurations.Any())
|
||||
{
|
||||
// OK migrate it!
|
||||
var allSettings = ombi.ApplicationConfigurations.ToList();
|
||||
|
|
|
@ -61,9 +61,6 @@ namespace Ombi
|
|||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public IServiceProvider ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// Add framework services.
|
||||
services.AddDbContext<OmbiContext>();
|
||||
|
||||
services.AddIdentity<OmbiUser, IdentityRole>()
|
||||
.AddEntityFrameworkStores<OmbiContext>()
|
||||
.AddDefaultTokenProviders()
|
||||
|
|
47
src/Ombi/package-lock.json
generated
47
src/Ombi/package-lock.json
generated
|
@ -1766,9 +1766,9 @@
|
|||
}
|
||||
},
|
||||
"bootstrap": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.0.tgz",
|
||||
"integrity": "sha512-F1yTDO9OHKH0xjl03DsOe8Nu1OWBIeAugGMhy3UTIYDdbbIPesQIhCEbj+HEr6wqlwweGAlP8F3OBC6kEuhFuw=="
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz",
|
||||
"integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA=="
|
||||
},
|
||||
"bootswatch": {
|
||||
"version": "3.4.0",
|
||||
|
@ -4190,8 +4190,7 @@
|
|||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
|
@ -4209,13 +4208,11 @@
|
|||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
@ -4228,18 +4225,15 @@
|
|||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
|
@ -4342,8 +4336,7 @@
|
|||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
|
@ -4353,7 +4346,6 @@
|
|||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
|
@ -4366,20 +4358,17 @@
|
|||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.3.5",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
|
@ -4396,7 +4385,6 @@
|
|||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
|
@ -4469,8 +4457,7 @@
|
|||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
|
@ -4480,7 +4467,6 @@
|
|||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
|
@ -4556,8 +4542,7 @@
|
|||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
|
@ -4587,7 +4572,6 @@
|
|||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
|
@ -4605,7 +4589,6 @@
|
|||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
|
@ -4644,13 +4627,11 @@
|
|||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.3",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue