Compare commits

..

5 Commits

Author SHA1 Message Date
bakerboy448
3d6023f2f9 Fixed: Improve RarBG Error Handling
(cherry picked from commit 7cd38bba841659a595fe4a0e14c478fc4e4047b1)
2022-10-03 17:20:16 +00:00
Joe Milazzo
14d74f2eca New: Kavita Connection (#1880)
* Added ability for Readarr to inform Kavita when a change occurs for rescan.

* Use the existing API with a POST rather than a new API.

* Updated some wording

* Fixed PR comments
2022-10-02 18:41:38 -05:00
servarr[bot]
c3cbbb7627 Update Bug Report Template [skip ci] (#1842)
(cherry picked from commit 99e0d42b717b279be0f9005039025cd730f90f6c)

Co-authored-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2022-09-26 21:05:36 -05:00
servarr[bot]
ad3a58c422 New: Parse version with a space before 'v''
* New: Parse anime version with a space before 'v'

(cherry picked from commit e9123982f33ab35ca022f91f345da05fef23d6dc)

* Delete AnimeVersionFixture.cs

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
Co-authored-by: Qstick <qstick@gmail.com>
2022-09-26 16:55:34 -05:00
Robin Dadswell
347b154882 Update Bug Report Template
[skip ci]

(cherry picked from commit b9185574f3761e125151907e3d31511689a4513e)
2022-09-26 16:42:52 -05:00
13 changed files with 317 additions and 10 deletions

View File

@@ -5,9 +5,9 @@ body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
description: Please search to see if an open or closed issue already exists for the bug you encountered. If a bug exists and is closed note that it may only be fixed in an unstable branch.
options:
- label: I have searched the existing issues
- label: I have searched the existing open and closed issues
required: true
- type: textarea
attributes:
@@ -42,12 +42,14 @@ body:
- **Docker Install**: Yes
- **Using Reverse Proxy**: No
- **Browser**: Firefox 90 (If UI related)
- **Database**: Sqlite 3.36.0
value: |
- OS:
- Readarr:
- Docker Install:
- Using Reverse Proxy:
- Browser:
- Database:
render: markdown
validations:
required: true

View File

@@ -7,7 +7,6 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.ParserTests
{
[TestFixture]
public class QualityParserFixture : CoreTest
{
public static object[] SelfQualityParserCases =

View File

@@ -30,9 +30,12 @@ namespace NzbDrone.Core.Indexers.Rarbg
if (jsonResponse.Resource.error_code.HasValue)
{
if (jsonResponse.Resource.error_code == 20 || jsonResponse.Resource.error_code == 8)
if (jsonResponse.Resource.error_code == 5 || jsonResponse.Resource.error_code == 8
|| jsonResponse.Resource.error_code == 9 || jsonResponse.Resource.error_code == 10
|| jsonResponse.Resource.error_code == 13 || jsonResponse.Resource.error_code == 14
|| jsonResponse.Resource.error_code == 20)
{
// No results found
// No results, rate limit, tvdbid not found/invalid, or imdbid not found/invalid
return results;
}

View File

@@ -0,0 +1,74 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Notifications.Kavita;
public class Kavita : NotificationBase<KavitaSettings>
{
private readonly IKavitaService _kavitaService;
private readonly Logger _logger;
public Kavita(IKavitaService kavitaService, Logger logger)
{
_kavitaService = kavitaService;
_logger = logger;
}
public override string Link => "https://www.kavitareader.com/";
public override void OnReleaseImport(BookDownloadMessage message)
{
var allPaths = message.BookFiles.Select(v => v.Path).Distinct();
var path = Directory.GetParent(allPaths.First())?.FullName;
Notify(Settings, BOOK_DOWNLOADED_TITLE_BRANDED, path);
}
public override void OnBookDelete(BookDeleteMessage deleteMessage)
{
var allPaths = deleteMessage.Book.BookFiles.Value.Select(v => v.Path).Distinct();
var path = Directory.GetParent(allPaths.First())?.FullName;
Notify(Settings, BOOK_FILE_DELETED_TITLE_BRANDED, path);
}
public override void OnBookFileDelete(BookFileDeleteMessage message)
{
Notify(Settings, BOOK_FILE_DELETED_TITLE_BRANDED, Directory.GetParent(message.BookFile.Path)?.FullName);
}
public override void OnBookRetag(BookRetagMessage message)
{
Notify(Settings, BOOK_RETAGGED_TITLE_BRANDED, Directory.GetParent(message.BookFile.Path)?.FullName);
}
public override string Name => "Kavita";
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_kavitaService.Test(Settings, "Success! Kavita has been successfully configured!"));
return new ValidationResult(failures);
}
private void Notify(KavitaSettings settings, string header, string message)
{
try
{
if (Settings.Notify)
{
_kavitaService.Notify(Settings, $"{header} - {message}");
}
}
catch (SocketException ex)
{
var logMessage = $"Unable to connect to Subsonic Host: {Settings.Host}:{Settings.Port}";
_logger.Debug(ex, logMessage);
}
}
}

View File

@@ -0,0 +1,14 @@
namespace NzbDrone.Core.Notifications.Kavita;
public class KavitaAuthenticationException : KavitaException
{
public KavitaAuthenticationException(string message)
: base(message)
{
}
public KavitaAuthenticationException(string message, params object[] args)
: base(message, args)
{
}
}

View File

@@ -0,0 +1,11 @@
using System.Text.Json.Serialization;
namespace NzbDrone.Core.Notifications.Kavita;
public class KavitaAuthenticationResult
{
[JsonPropertyName("token")]
public string Token { get; set; }
[JsonPropertyName("apiKey")]
public string ApiKey { get; init; }
}

View File

@@ -0,0 +1,16 @@
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Notifications.Kavita;
public class KavitaException : NzbDroneException
{
public KavitaException(string message)
: base(message)
{
}
public KavitaException(string message, params object[] args)
: base(message, args)
{
}
}

View File

@@ -0,0 +1,56 @@
using System;
using FluentValidation.Results;
using NLog;
namespace NzbDrone.Core.Notifications.Kavita;
public interface IKavitaService
{
void Notify(KavitaSettings settings, string message);
ValidationFailure Test(KavitaSettings settings, string message);
}
public class KavitaService : IKavitaService
{
private readonly IKavitaServiceProxy _proxy;
private readonly Logger _logger;
public KavitaService(IKavitaServiceProxy proxy,
Logger logger)
{
_proxy = proxy;
_logger = logger;
}
public void Notify(KavitaSettings settings, string folderPath)
{
_proxy.Notify(settings, folderPath);
}
private string GetToken(KavitaSettings settings)
{
return _proxy.GetToken(settings);
}
public ValidationFailure Test(KavitaSettings settings, string message)
{
try
{
_logger.Debug("Determining Authentication of Host: {0}", _proxy.GetBaseUrl(settings));
var token = GetToken(settings);
_logger.Debug("Token is: {0}", token);
}
catch (KavitaAuthenticationException ex)
{
_logger.Error(ex, "Unable to connect to Kavita Server");
return new ValidationFailure("ApiKey", "Incorrect ApiKey");
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to connect to Kavita Server");
return new ValidationFailure("Host", "Unable to connect to Kavita Server");
}
return null;
}
}

View File

@@ -0,0 +1,87 @@
using System.Net.Http;
using System.Text.Json;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Notifications.Kavita;
public interface IKavitaServiceProxy
{
string GetBaseUrl(KavitaSettings settings, string relativePath = null);
void Notify(KavitaSettings settings, string message);
string GetToken(KavitaSettings settings);
}
public class KavitaServiceProxy : IKavitaServiceProxy
{
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
public KavitaServiceProxy(IHttpClient httpClient, Logger logger)
{
_httpClient = httpClient;
_logger = logger;
}
public string GetBaseUrl(KavitaSettings settings, string relativePath = null)
{
var baseUrl = HttpRequestBuilder.BuildBaseUrl(settings.UseSsl, settings.Host, settings.Port, string.Empty);
baseUrl = HttpUri.CombinePath(baseUrl, relativePath);
return baseUrl;
}
public void Notify(KavitaSettings settings, string folderPath)
{
var request = GetKavitaServerRequest("library/scan-folder", HttpMethod.Post, settings);
request.Headers.ContentType = "application/json";
var postRequest = request.Build();
postRequest.SetContent(new
{
ApiKey = settings.ApiKey,
FolderPath = folderPath.Replace("/", "//")
}.ToJson());
var response = _httpClient.Post(postRequest);
_logger.Trace("Update response: {0}", string.IsNullOrEmpty(response.Content) ? "Success" : response.Content);
}
public string GetToken(KavitaSettings settings)
{
var request = GetKavitaServerRequest("plugin/authenticate", HttpMethod.Post, settings);
request.AddQueryParam("apiKey", settings.ApiKey)
.AddQueryParam("pluginName", BuildInfo.AppName);
var response = _httpClient.Execute(request.Build());
_logger.Trace("Authenticate response: {0}", response.Content);
var authResult = JsonSerializer.Deserialize<KavitaAuthenticationResult>(response.Content);
if (authResult == null)
{
throw new KavitaException("Could not authenticate with Kavita");
}
return authResult.Token;
}
private HttpRequestBuilder GetKavitaServerRequest(string resource, HttpMethod method, KavitaSettings settings)
{
var client = new HttpRequestBuilder(GetBaseUrl(settings, "api"));
client.Resource(resource);
if (settings.ApiKey.IsNotNullOrWhiteSpace())
{
client.Headers["x-kavita-apikey"] = settings.ApiKey;
client.Headers["x-kavita-plugin"] = BuildInfo.AppName;
}
client.Method = method;
return client;
}
}

View File

@@ -0,0 +1,46 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Notifications.Kavita;
public class KavitaSettingsValidator : AbstractValidator<KavitaSettings>
{
public KavitaSettingsValidator()
{
RuleFor(c => c.Host).ValidHost();
RuleFor(c => c.Port).InclusiveBetween(1, 65535);
RuleFor(c => c.ApiKey).NotEmpty();
}
}
public class KavitaSettings : IProviderConfig
{
private static readonly KavitaSettingsValidator Validator = new KavitaSettingsValidator();
public KavitaSettings()
{
Port = 4040;
}
[FieldDefinition(0, Label = "Host")]
public string Host { get; set; }
[FieldDefinition(1, Label = "Port")]
public int Port { get; set; }
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpLink = "https://wiki.kavitareader.com/en/guides/settings/opds")]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Connect to Kavita over HTTPS instead of HTTP")]
public bool UseSsl { get; set; }
[FieldDefinition(4, Label = "Update Library", Type = FieldType.Checkbox)]
public bool Notify { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}

View File

@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Parser
private static readonly Regex RepackRegex = new Regex(@"\b(?<repack>repack|rerip)\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex VersionRegex = new Regex(@"\dv(?<version>\d)\b|\[v(?<version>\d)\]",
private static readonly Regex VersionRegex = new Regex(@"\d[-._ ]?v(?<version>\d)[-._ ]|\[v(?<version>\d)\]",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex RealRegex = new Regex(@"\b(?<real>REAL)\b",

View File

@@ -33,7 +33,6 @@ namespace Readarr.Http.Authentication
options.AccessDeniedPath = "/login?loginFailed=true";
options.LoginPath = "/login";
options.ExpireTimeSpan = TimeSpan.FromDays(7);
options.SlidingExpiration = true;
})
.AddApiKey("API", options =>
{

View File

@@ -1,8 +1,5 @@
using System;
using System.Net;
using Microsoft.AspNetCore.Http;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
using Readarr.Http.Extensions;
@@ -18,14 +15,17 @@ namespace Readarr.Http.Authentication
public class AuthenticationService : IAuthenticationService
{
private const string AnonymousUser = "Anonymous";
private static readonly Logger _authLogger = LogManager.GetLogger("Auth");
private readonly IUserService _userService;
private static string API_KEY;
private static AuthenticationType AUTH_METHOD;
public AuthenticationService(IConfigFileProvider configFileProvider, IUserService userService)
{
_userService = userService;
API_KEY = configFileProvider.ApiKey;
AUTH_METHOD = configFileProvider.AuthenticationMethod;
}