parent
7480ebea85
commit
742c0d02bc
@ -0,0 +1,224 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
{
|
||||
public class FlareSolverr : HttpIndexerProxyBase<FlareSolverrSettings>
|
||||
{
|
||||
public FlareSolverr(IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger)
|
||||
: base(cloudRequestBuilder, httpClient, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override string Name => "FlareSolverr";
|
||||
|
||||
public override HttpRequest PreRequest(HttpRequest request)
|
||||
{
|
||||
return GenerateFlareSolverrRequest(request);
|
||||
}
|
||||
|
||||
public override HttpResponse PostResponse(HttpResponse response)
|
||||
{
|
||||
FlareSolverrResponse result = null;
|
||||
|
||||
if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.InternalServerError)
|
||||
{
|
||||
throw new FlareSolverrException("HTTP StatusCode not 200 or 500. Status is :" + response.StatusCode);
|
||||
}
|
||||
|
||||
result = JsonConvert.DeserializeObject<FlareSolverrResponse>(response.Content);
|
||||
|
||||
var cookieCollection = new CookieCollection();
|
||||
var responseHeader = new HttpHeader();
|
||||
|
||||
foreach (var cookie in result.Solution.Cookies)
|
||||
{
|
||||
cookieCollection.Add(cookie.ToCookieObj());
|
||||
}
|
||||
|
||||
//Build new response with FS Cookie and Site Response
|
||||
var newResponse = new HttpResponse(response.Request, responseHeader, cookieCollection, result.Solution.Response);
|
||||
|
||||
return newResponse;
|
||||
}
|
||||
|
||||
private HttpRequest GenerateFlareSolverrRequest(HttpRequest request)
|
||||
{
|
||||
FlareSolverrRequest req;
|
||||
|
||||
var url = request.Url.ToString();
|
||||
var userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36";
|
||||
var maxTimeout = 60000;
|
||||
|
||||
if (request.Method == HttpMethod.GET)
|
||||
{
|
||||
req = new FlareSolverrRequestGet
|
||||
{
|
||||
Cmd = "request.get",
|
||||
Url = url,
|
||||
MaxTimeout = maxTimeout,
|
||||
UserAgent = userAgent
|
||||
};
|
||||
}
|
||||
else if (request.Method == HttpMethod.POST)
|
||||
{
|
||||
var contentTypeType = request.Headers.ContentType;
|
||||
|
||||
if (contentTypeType == "application/x-www-form-urlencoded")
|
||||
{
|
||||
var contentTypeValue = request.Headers.ContentType.ToString();
|
||||
var postData = request.GetContent();
|
||||
|
||||
req = new FlareSolverrRequestPostUrlEncoded
|
||||
{
|
||||
Cmd = "request.post",
|
||||
Url = url,
|
||||
PostData = postData,
|
||||
Headers = new HeadersPost
|
||||
{
|
||||
ContentType = contentTypeValue,
|
||||
ContentLength = null
|
||||
},
|
||||
MaxTimeout = maxTimeout,
|
||||
UserAgent = userAgent
|
||||
};
|
||||
}
|
||||
else if (contentTypeType.Contains("multipart/form-data"))
|
||||
{
|
||||
//TODO Implement - check if we just need to pass the content-type with the relevant headers
|
||||
throw new FlareSolverrException("Unimplemented POST Content-Type: " + request.Headers.ContentType);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FlareSolverrException("Unsupported POST Content-Type: " + request.Headers.ContentType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FlareSolverrException("Unsupported HttpMethod: " + request.Method);
|
||||
}
|
||||
|
||||
var apiUrl = string.Format("{0}/v1", Settings.Host.TrimEnd('/'));
|
||||
var newRequest = new HttpRequest(apiUrl, HttpAccept.Json);
|
||||
|
||||
newRequest.SetContent(req.ToJson());
|
||||
newRequest.Method = HttpMethod.POST;
|
||||
|
||||
_logger.Debug("Applying FlareSolverr Proxy {0} to request {1}", Name, request.Url);
|
||||
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
public override ValidationResult Test()
|
||||
{
|
||||
var failures = new List<ValidationFailure>();
|
||||
|
||||
var request = PreRequest(_cloudRequestBuilder.Create()
|
||||
.Resource("/ping")
|
||||
.Build());
|
||||
|
||||
try
|
||||
{
|
||||
var response = _httpClient.Execute(request);
|
||||
|
||||
// We only care about 400 responses, other error codes can be ignored
|
||||
if (response.StatusCode == HttpStatusCode.BadRequest)
|
||||
{
|
||||
_logger.Error("Proxy Health Check failed: {0}", response.StatusCode);
|
||||
failures.Add(new NzbDroneValidationFailure("Host", "ProxyCheckBadRequestMessage"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Proxy Health Check failed");
|
||||
failures.Add(new NzbDroneValidationFailure("Host", "ProxyCheckFailedToTestMessage"));
|
||||
}
|
||||
|
||||
return new ValidationResult(failures);
|
||||
}
|
||||
|
||||
public class FlareSolverrRequest
|
||||
{
|
||||
public string Cmd { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string UserAgent { get; set; }
|
||||
}
|
||||
|
||||
public class FlareSolverrRequestGet : FlareSolverrRequest
|
||||
{
|
||||
public string Headers { get; set; }
|
||||
public int MaxTimeout { get; set; }
|
||||
}
|
||||
|
||||
public class FlareSolverrRequestPost : FlareSolverrRequest
|
||||
{
|
||||
public string PostData { get; set; }
|
||||
public int MaxTimeout { get; set; }
|
||||
}
|
||||
|
||||
public class FlareSolverrRequestPostUrlEncoded : FlareSolverrRequestPost
|
||||
{
|
||||
public HeadersPost Headers { get; set; }
|
||||
}
|
||||
|
||||
public class HeadersPost
|
||||
{
|
||||
public string ContentType { get; set; }
|
||||
public string ContentLength { get; set; }
|
||||
}
|
||||
|
||||
public class FlareSolverrResponse
|
||||
{
|
||||
public string Status { get; set; }
|
||||
public string Message { get; set; }
|
||||
public long StartTimestamp { get; set; }
|
||||
public long EndTimestamp { get; set; }
|
||||
public string Version { get; set; }
|
||||
public Solution Solution { get; set; }
|
||||
}
|
||||
|
||||
public class Solution
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string Status { get; set; }
|
||||
public Headers Headers { get; set; }
|
||||
public string Response { get; set; }
|
||||
public Cookie[] Cookies { get; set; }
|
||||
public string UserAgent { get; set; }
|
||||
}
|
||||
|
||||
public class Cookie
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
public string Domain { get; set; }
|
||||
public string Path { get; set; }
|
||||
public double Expires { get; set; }
|
||||
public int Size { get; set; }
|
||||
public bool HttpOnly { get; set; }
|
||||
public bool Secure { get; set; }
|
||||
public bool Session { get; set; }
|
||||
public string SameSite { get; set; }
|
||||
|
||||
public string ToHeaderValue() => $"{Name}={Value}";
|
||||
public System.Net.Cookie ToCookieObj() => new System.Net.Cookie(Name, Value);
|
||||
}
|
||||
|
||||
public class Headers
|
||||
{
|
||||
public string Status { get; set; }
|
||||
public string Date { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "content-type")]
|
||||
public string ContentType { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using NzbDrone.Common.Exceptions;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
{
|
||||
public class FlareSolverrException : NzbDroneException
|
||||
{
|
||||
public FlareSolverrException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public FlareSolverrException(string message, params object[] args)
|
||||
: base(message, args)
|
||||
{
|
||||
}
|
||||
|
||||
public FlareSolverrException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
{
|
||||
public class FlareSolverrSettingsValidator : AbstractValidator<FlareSolverrSettings>
|
||||
{
|
||||
public FlareSolverrSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.Host).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class FlareSolverrSettings : IIndexerProxySettings
|
||||
{
|
||||
private static readonly FlareSolverrSettingsValidator Validator = new FlareSolverrSettingsValidator();
|
||||
|
||||
public FlareSolverrSettings()
|
||||
{
|
||||
Host = "http://localhost:8191/";
|
||||
}
|
||||
|
||||
[FieldDefinition(0, Label = "Host")]
|
||||
public string Host { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue