mirror of
https://github.com/Radarr/Radarr.git
synced 2024-11-09 12:32:31 +01:00
New: Add Webhook support to sonarr
Add Form type url (type=url input field) Add isValidUrl input type validation Only allow absolute urls when checking if a url is valid String => string as per comments that sonarr is standarizing on the lowercase primative Remove this before function calls Refactored everything so OnGrab is supported Don't double submit the webhook Wrappers around Series, EpisodeFile, Episode so the entire data structure isn't exposed Add Braces as per style guide Series.ID and Series.TvdbId should be integers Reorder webhook payload as per style guide Upgrade to use ongrab as json instead of string Add method selection to webhook settings include episode directly in download event QualityVersion should be an int and not a string (don't convert it int=>string) Remove the list of episodes Add season number to episode data structure Code Review Fixes: * Remove episodefile from payload, move everything to episode * Change episode to a list convert to var as per code review / style guide Down with internals Everything now uses webhookpayload. None of that payload.Message stuff {"EventType":"Test","Series":{"Id":1,"Title":"Test Title","Path":"C:\\testpath","TvdbId":1234},"Episodes":[{"Id":123,"EpisodeNumber":1,"SeasonNumber":1,"Title":"Test title","AirDate":null,"AirDateUtc":null,"Quality":null,"QualityVersion":0,"ReleaseGroup":null,"SceneName":null}]} Remove logger and processProvider Remove unused constructor
This commit is contained in:
parent
187064101c
commit
c5b25bcfee
32
src/NzbDrone.Common/Extensions/UrlExtensions.cs
Normal file
32
src/NzbDrone.Common/Extensions/UrlExtensions.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Common.Extensions
|
||||||
|
{
|
||||||
|
public static class UrlExtensions
|
||||||
|
{
|
||||||
|
public static bool IsValidUrl(this string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri uri;
|
||||||
|
if (!Uri.TryCreate(path, UriKind.Absolute, out uri))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uri.IsWellFormedOriginalString())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -138,6 +138,7 @@
|
|||||||
<Compile Include="Extensions\Int64Extensions.cs" />
|
<Compile Include="Extensions\Int64Extensions.cs" />
|
||||||
<Compile Include="Extensions\ObjectExtensions.cs" />
|
<Compile Include="Extensions\ObjectExtensions.cs" />
|
||||||
<Compile Include="Extensions\StreamExtensions.cs" />
|
<Compile Include="Extensions\StreamExtensions.cs" />
|
||||||
|
<Compile Include="Extensions\UrlExtensions.cs" />
|
||||||
<Compile Include="Extensions\XmlExtentions.cs" />
|
<Compile Include="Extensions\XmlExtentions.cs" />
|
||||||
<Compile Include="HashUtil.cs" />
|
<Compile Include="HashUtil.cs" />
|
||||||
<Compile Include="Http\CurlHttpClient.cs" />
|
<Compile Include="Http\CurlHttpClient.cs" />
|
||||||
|
@ -28,6 +28,7 @@ public enum FieldType
|
|||||||
Path,
|
Path,
|
||||||
Hidden,
|
Hidden,
|
||||||
Tag,
|
Tag,
|
||||||
Action
|
Action,
|
||||||
|
Url
|
||||||
}
|
}
|
||||||
}
|
}
|
55
src/NzbDrone.Core/Notifications/Webhook/Webhook.cs
Normal file
55
src/NzbDrone.Core/Notifications/Webhook/Webhook.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Webhook
|
||||||
|
{
|
||||||
|
public class Webhook : NotificationBase<WebhookSettings>
|
||||||
|
{
|
||||||
|
private readonly IWebhookService _service;
|
||||||
|
|
||||||
|
public Webhook(IWebhookService service)
|
||||||
|
{
|
||||||
|
_service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Link
|
||||||
|
{
|
||||||
|
get { return "https://github.com/Sonarr/Sonarr/wiki/Webhook"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnGrab(GrabMessage message)
|
||||||
|
{
|
||||||
|
_service.OnGrab(message.Series, message.Episode, message.Quality, Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnDownload(DownloadMessage message)
|
||||||
|
{
|
||||||
|
_service.OnDownload(message.Series, message.EpisodeFile, Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRename(Series series)
|
||||||
|
{
|
||||||
|
_service.OnRename(series, Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return "Webhook";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ValidationResult Test()
|
||||||
|
{
|
||||||
|
var failures = new List<ValidationFailure>();
|
||||||
|
|
||||||
|
failures.AddIfNotNull(_service.Test(Settings));
|
||||||
|
|
||||||
|
return new ValidationResult(failures);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
src/NzbDrone.Core/Notifications/Webhook/WebhookEpisode.cs
Normal file
32
src/NzbDrone.Core/Notifications/Webhook/WebhookEpisode.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Webhook
|
||||||
|
{
|
||||||
|
public class WebhookEpisode
|
||||||
|
{
|
||||||
|
public WebhookEpisode() { }
|
||||||
|
|
||||||
|
public WebhookEpisode(Episode episode)
|
||||||
|
{
|
||||||
|
Id = episode.Id;
|
||||||
|
SeasonNumber = episode.SeasonNumber;
|
||||||
|
EpisodeNumber = episode.EpisodeNumber;
|
||||||
|
Title = episode.Title;
|
||||||
|
AirDate = episode.AirDate;
|
||||||
|
AirDateUtc = episode.AirDateUtc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int EpisodeNumber { get; set; }
|
||||||
|
public int SeasonNumber { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string AirDate { get; set; }
|
||||||
|
public DateTime? AirDateUtc { get; set; }
|
||||||
|
|
||||||
|
public string Quality { get; set; }
|
||||||
|
public int QualityVersion { get; set; }
|
||||||
|
public string ReleaseGroup { get; set; }
|
||||||
|
public string SceneName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
16
src/NzbDrone.Core/Notifications/Webhook/WebhookException.cs
Normal file
16
src/NzbDrone.Core/Notifications/Webhook/WebhookException.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using NzbDrone.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Webhook
|
||||||
|
{
|
||||||
|
public class WebhookException : NzbDroneException
|
||||||
|
{
|
||||||
|
public WebhookException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebhookException(string message, Exception innerException, params object[] args) : base(message, innerException, args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/NzbDrone.Core/Notifications/Webhook/WebhookMethod.cs
Normal file
13
src/NzbDrone.Core/Notifications/Webhook/WebhookMethod.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Webhook
|
||||||
|
{
|
||||||
|
public enum WebhookMethod
|
||||||
|
{
|
||||||
|
POST = RestSharp.Method.POST,
|
||||||
|
PUT = RestSharp.Method.PUT
|
||||||
|
}
|
||||||
|
}
|
11
src/NzbDrone.Core/Notifications/Webhook/WebhookPayload.cs
Normal file
11
src/NzbDrone.Core/Notifications/Webhook/WebhookPayload.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Webhook
|
||||||
|
{
|
||||||
|
public class WebhookPayload
|
||||||
|
{
|
||||||
|
public string EventType { get; set; }
|
||||||
|
public WebhookSeries Series { get; set; }
|
||||||
|
public List<WebhookEpisode> Episodes { get; set; }
|
||||||
|
}
|
||||||
|
}
|
22
src/NzbDrone.Core/Notifications/Webhook/WebhookSeries.cs
Normal file
22
src/NzbDrone.Core/Notifications/Webhook/WebhookSeries.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Webhook
|
||||||
|
{
|
||||||
|
public class WebhookSeries
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Path { get; set; }
|
||||||
|
public int TvdbId { get; set; }
|
||||||
|
|
||||||
|
public WebhookSeries() { }
|
||||||
|
|
||||||
|
public WebhookSeries(Series series)
|
||||||
|
{
|
||||||
|
Id = series.Id;
|
||||||
|
Title = series.Title;
|
||||||
|
Path = series.Path;
|
||||||
|
TvdbId = series.TvdbId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
120
src/NzbDrone.Core/Notifications/Webhook/WebhookService.cs
Normal file
120
src/NzbDrone.Core/Notifications/Webhook/WebhookService.cs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
using FluentValidation.Results;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Processes;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
using NzbDrone.Core.Rest;
|
||||||
|
using RestSharp;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Webhook
|
||||||
|
{
|
||||||
|
public interface IWebhookService
|
||||||
|
{
|
||||||
|
void OnDownload(Series series, EpisodeFile episodeFile, WebhookSettings settings);
|
||||||
|
void OnRename(Series series, WebhookSettings settings);
|
||||||
|
void OnGrab(Series series, RemoteEpisode episode, QualityModel quality, WebhookSettings settings);
|
||||||
|
ValidationFailure Test(WebhookSettings settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WebhookService : IWebhookService
|
||||||
|
{
|
||||||
|
public void OnDownload(Series series, EpisodeFile episodeFile, WebhookSettings settings)
|
||||||
|
{
|
||||||
|
var payload = new WebhookPayload
|
||||||
|
{
|
||||||
|
EventType = "Download",
|
||||||
|
Series = new WebhookSeries(series),
|
||||||
|
Episodes = episodeFile.Episodes.Value.ConvertAll(x => new WebhookEpisode(x) {
|
||||||
|
Quality = episodeFile.Quality.Quality.Name,
|
||||||
|
QualityVersion = episodeFile.Quality.Revision.Version,
|
||||||
|
ReleaseGroup = episodeFile.ReleaseGroup,
|
||||||
|
SceneName = episodeFile.SceneName
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
NotifyWebhook(payload, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnRename(Series series, WebhookSettings settings)
|
||||||
|
{
|
||||||
|
var payload = new WebhookPayload
|
||||||
|
{
|
||||||
|
EventType = "Rename",
|
||||||
|
Series = new WebhookSeries(series)
|
||||||
|
};
|
||||||
|
|
||||||
|
NotifyWebhook(payload, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnGrab(Series series, RemoteEpisode episode, QualityModel quality, WebhookSettings settings)
|
||||||
|
{
|
||||||
|
var payload = new WebhookPayload
|
||||||
|
{
|
||||||
|
EventType = "Grab",
|
||||||
|
Series = new WebhookSeries(series),
|
||||||
|
Episodes = episode.Episodes.ConvertAll(x => new WebhookEpisode(x)
|
||||||
|
{
|
||||||
|
Quality = quality.Quality.Name,
|
||||||
|
QualityVersion = quality.Revision.Version,
|
||||||
|
ReleaseGroup = episode.ParsedEpisodeInfo.ReleaseGroup
|
||||||
|
})
|
||||||
|
};
|
||||||
|
NotifyWebhook(payload, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NotifyWebhook(WebhookPayload body, WebhookSettings settings)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
var client = RestClientFactory.BuildClient(settings.Url);
|
||||||
|
var request = new RestRequest((Method) settings.Method);
|
||||||
|
request.RequestFormat = DataFormat.Json;
|
||||||
|
request.AddBody(body);
|
||||||
|
client.ExecuteAndValidate(request);
|
||||||
|
}
|
||||||
|
catch (RestException ex)
|
||||||
|
{
|
||||||
|
throw new WebhookException("Unable to post to webhook: {0}", ex, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationFailure Test(WebhookSettings settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
NotifyWebhook(
|
||||||
|
new WebhookPayload
|
||||||
|
{
|
||||||
|
EventType = "Test",
|
||||||
|
Series = new WebhookSeries()
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Title = "Test Title",
|
||||||
|
Path = "C:\\testpath",
|
||||||
|
TvdbId = 1234
|
||||||
|
},
|
||||||
|
Episodes = new List<WebhookEpisode>() {
|
||||||
|
new WebhookEpisode()
|
||||||
|
{
|
||||||
|
Id = 123,
|
||||||
|
EpisodeNumber = 1,
|
||||||
|
SeasonNumber = 1,
|
||||||
|
Title = "Test title"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
settings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (WebhookException ex)
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationFailure("Url", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
src/NzbDrone.Core/Notifications/Webhook/WebhookSettings.cs
Normal file
38
src/NzbDrone.Core/Notifications/Webhook/WebhookSettings.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
using NzbDrone.Core.Validation.Paths;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Webhook
|
||||||
|
{
|
||||||
|
public class WebhookSettingsValidator : AbstractValidator<WebhookSettings>
|
||||||
|
{
|
||||||
|
public WebhookSettingsValidator()
|
||||||
|
{
|
||||||
|
RuleFor(c => c.Url).IsValidUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WebhookSettings : IProviderConfig
|
||||||
|
{
|
||||||
|
private static readonly WebhookSettingsValidator Validator = new WebhookSettingsValidator();
|
||||||
|
|
||||||
|
public WebhookSettings()
|
||||||
|
{
|
||||||
|
Method = Convert.ToInt32(WebhookMethod.POST);
|
||||||
|
}
|
||||||
|
|
||||||
|
[FieldDefinition(0, Label = "URL", Type = FieldType.Url)]
|
||||||
|
public string Url { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(1, Label = "Method", Type = FieldType.Select, SelectOptions = typeof(WebhookMethod), HelpText = "Which HTTP method to use submit to the Webservice")]
|
||||||
|
public Int32 Method { get; set; }
|
||||||
|
|
||||||
|
public NzbDroneValidationResult Validate()
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -746,6 +746,14 @@
|
|||||||
<Compile Include="Notifications\Synology\SynologyIndexerProxy.cs" />
|
<Compile Include="Notifications\Synology\SynologyIndexerProxy.cs" />
|
||||||
<Compile Include="Notifications\Synology\SynologyIndexerSettings.cs" />
|
<Compile Include="Notifications\Synology\SynologyIndexerSettings.cs" />
|
||||||
<Compile Include="Notifications\Twitter\TwitterException.cs" />
|
<Compile Include="Notifications\Twitter\TwitterException.cs" />
|
||||||
|
<Compile Include="Notifications\Webhook\WebhookEpisode.cs" />
|
||||||
|
<Compile Include="Notifications\Webhook\WebhookException.cs" />
|
||||||
|
<Compile Include="Notifications\Webhook\WebhookMethod.cs" />
|
||||||
|
<Compile Include="Notifications\Webhook\WebhookPayload.cs" />
|
||||||
|
<Compile Include="Notifications\Webhook\WebhookSeries.cs" />
|
||||||
|
<Compile Include="Notifications\Webhook\WebhookService.cs" />
|
||||||
|
<Compile Include="Notifications\Webhook\WebhookSettings.cs" />
|
||||||
|
<Compile Include="Notifications\Webhook\Webhook.cs" />
|
||||||
<Compile Include="Organizer\NamingConfigRepository.cs" />
|
<Compile Include="Organizer\NamingConfigRepository.cs" />
|
||||||
<Compile Include="Notifications\Twitter\Twitter.cs" />
|
<Compile Include="Notifications\Twitter\Twitter.cs" />
|
||||||
<Compile Include="Notifications\Twitter\TwitterService.cs" />
|
<Compile Include="Notifications\Twitter\TwitterService.cs" />
|
||||||
@ -992,6 +1000,7 @@
|
|||||||
<Compile Include="Validation\Paths\SeriesExistsValidator.cs" />
|
<Compile Include="Validation\Paths\SeriesExistsValidator.cs" />
|
||||||
<Compile Include="Validation\Paths\SeriesPathValidator.cs" />
|
<Compile Include="Validation\Paths\SeriesPathValidator.cs" />
|
||||||
<Compile Include="Validation\RuleBuilderExtensions.cs" />
|
<Compile Include="Validation\RuleBuilderExtensions.cs" />
|
||||||
|
<Compile Include="Validation\UrlValidator.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client">
|
<BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client">
|
||||||
|
28
src/NzbDrone.Core/Validation/UrlValidator.cs
Normal file
28
src/NzbDrone.Core/Validation/UrlValidator.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using FluentValidation.Validators;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Validation
|
||||||
|
{
|
||||||
|
public static class UrlValidation
|
||||||
|
{
|
||||||
|
public static IRuleBuilderOptions<T, string> IsValidUrl<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||||
|
{
|
||||||
|
return ruleBuilder.SetValidator(new UrlValidator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UrlValidator : PropertyValidator
|
||||||
|
{
|
||||||
|
public UrlValidator()
|
||||||
|
: base("Invalid Url")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsValid(PropertyValidatorContext context)
|
||||||
|
{
|
||||||
|
if (context.PropertyValue == null) return false;
|
||||||
|
return context.PropertyValue.ToString().IsValidUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,10 @@ var _fieldBuilder = function(field) {
|
|||||||
return _templateRenderer.call(field, 'Form/HiddenTemplate');
|
return _templateRenderer.call(field, 'Form/HiddenTemplate');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (field.type === 'url') {
|
||||||
|
return _templateRenderer.call(field, 'Form/UrlTemplate');
|
||||||
|
}
|
||||||
|
|
||||||
if (field.type === 'password') {
|
if (field.type === 'password') {
|
||||||
return _templateRenderer.call(field, 'Form/PasswordTemplate');
|
return _templateRenderer.call(field, 'Form/PasswordTemplate');
|
||||||
}
|
}
|
||||||
|
8
src/UI/Form/UrlTemplate.hbs
Normal file
8
src/UI/Form/UrlTemplate.hbs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<div class="form-group {{#if advanced}}advanced-setting{{/if}}">
|
||||||
|
<label class="col-sm-3 control-label">{{label}}</label>
|
||||||
|
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<input type="url" name="fields.{{order}}.value" validation-name="{{name}}" spellcheck="false" class="form-control"/>
|
||||||
|
</div>
|
||||||
|
{{> FormHelpPartial}}
|
||||||
|
</div>
|
Loading…
Reference in New Issue
Block a user