mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-03-20 17:04:06 -04:00
Compare commits
74 Commits
changelog-
...
v0.4.8.207
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dae21f22b9 | ||
|
|
7ddbe09eca | ||
|
|
90e3c809c3 | ||
|
|
ec8cf5f57a | ||
|
|
f4bbf2f8af | ||
|
|
ea98d41472 | ||
|
|
b8cb0fd291 | ||
|
|
d3dfa620ac | ||
|
|
049668f307 | ||
|
|
c400575aac | ||
|
|
6f122fb2e4 | ||
|
|
a9c210f8e7 | ||
|
|
1068ba8915 | ||
|
|
635335d876 | ||
|
|
2ed51cd933 | ||
|
|
b74c46c554 | ||
|
|
7029e0d6ee | ||
|
|
438ea380f5 | ||
|
|
4eec675d61 | ||
|
|
0a9bd8287f | ||
|
|
b583ac3a97 | ||
|
|
4be41ff3fb | ||
|
|
b911f8cc08 | ||
|
|
22face385f | ||
|
|
3e700b63c2 | ||
|
|
df0b8fc660 | ||
|
|
f96dbbfc21 | ||
|
|
4a75f92cb5 | ||
|
|
dd05a9dbd4 | ||
|
|
e78b8d5346 | ||
|
|
74a1d95ab7 | ||
|
|
f929a7e62f | ||
|
|
e9e4248af4 | ||
|
|
9e3b43ef12 | ||
|
|
738a690aac | ||
|
|
3b7c59e9bb | ||
|
|
b8ca28d955 | ||
|
|
8797bb7d1c | ||
|
|
be430732f5 | ||
|
|
e7b1380b85 | ||
|
|
c29735741c | ||
|
|
f56a13a375 | ||
|
|
148d8ee249 | ||
|
|
3547028b96 | ||
|
|
e4ffa1873e | ||
|
|
2e85a21576 | ||
|
|
0a111e7572 | ||
|
|
25217c0ee8 | ||
|
|
791592927c | ||
|
|
4137193a60 | ||
|
|
99816bfd36 | ||
|
|
59e5b5bd52 | ||
|
|
7fa0a2b33c | ||
|
|
0593ca6b9e | ||
|
|
06a26b5c87 | ||
|
|
dcae6dc151 | ||
|
|
04e3ed0ffe | ||
|
|
1ed5ed9179 | ||
|
|
d292d086ee | ||
|
|
f68915c5dd | ||
|
|
01e970e1a7 | ||
|
|
68df439498 | ||
|
|
33de7ca7ab | ||
|
|
ae2d9b795b | ||
|
|
eadea745f8 | ||
|
|
f958c4aefa | ||
|
|
4cf9fb0e79 | ||
|
|
bfa68347e6 | ||
|
|
f97b35403d | ||
|
|
bf2e057247 | ||
|
|
5a278f4e9d | ||
|
|
232a6efd0d | ||
|
|
7e01c93b2c | ||
|
|
d58f6551e6 |
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -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:
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -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 feature you are requesting.
|
||||
description: Please search to see if an open or closed issue already exists for the feature you are requesting. If a request 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:
|
||||
|
||||
132
CODE_OF_CONDUCT.md
Normal file
132
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
<development@prowlarr.com>.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '0.4.5'
|
||||
majorVersion: '0.4.8'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||
@@ -748,7 +748,7 @@ stages:
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: Packages
|
||||
itemPattern: '/$(pattern)'
|
||||
itemPattern: '**/$(pattern)'
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
- bash: |
|
||||
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
|
||||
@@ -1108,4 +1108,5 @@ stages:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
DISCORDCHANNELID: $(discordChannelId)
|
||||
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
||||
DISCORDTHREADID: $(discordThreadId)
|
||||
|
||||
|
||||
@@ -221,7 +221,7 @@ class IndexerIndex extends Component {
|
||||
|
||||
onKeyUp = (event) => {
|
||||
const jumpBarItems = this.state.jumpBarItems.order;
|
||||
if (event.path.length === 4) {
|
||||
if (event.composedPath && event.composedPath().length === 4) {
|
||||
if (event.keyCode === keyCodes.HOME && event.ctrlKey) {
|
||||
this.setState({ jumpToCharacter: jumpBarItems[0] });
|
||||
}
|
||||
|
||||
@@ -212,6 +212,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Platform(Exclude = "MacOsX", Reason = "Azure agent update prevents brotli on OSX")]
|
||||
public void should_execute_get_using_brotli()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/brotli");
|
||||
|
||||
@@ -98,15 +98,30 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
// Internal
|
||||
[TestCase(@"[Info] MigrationController: *** Migrating Database=prowlarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")]
|
||||
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
|
||||
|
||||
[TestCase(@"[Info] MigrationController: *** Migrating Database=prowlarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
|
||||
public void should_clean_message(string message)
|
||||
{
|
||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||
|
||||
cleansedMessage.Should().NotContain("mySecret");
|
||||
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
|
||||
cleansedMessage.Should().NotContain("01233210");
|
||||
}
|
||||
|
||||
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
|
||||
public void should_keep_message(string message)
|
||||
{
|
||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||
|
||||
cleansedMessage.Should().NotContain("mySecret");
|
||||
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
|
||||
cleansedMessage.Should().NotContain("01233210");
|
||||
|
||||
cleansedMessage.Should().Contain("shouldkeep1");
|
||||
cleansedMessage.Should().Contain("shouldkeep2");
|
||||
cleansedMessage.Should().Contain("shouldkeep3");
|
||||
}
|
||||
|
||||
[TestCase(@"Some message (from 32.2.3.5 user agent)")]
|
||||
[TestCase(@"Auth-Invalidated ip 32.2.3.5")]
|
||||
[TestCase(@"Auth-Success ip 32.2.3.5")]
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
AddRequestHeaders(requestMessage, request.Headers);
|
||||
}
|
||||
|
||||
var httpClient = GetClient(request.Url);
|
||||
var httpClient = GetClient(request.Url, request.ProxySettings);
|
||||
|
||||
var sw = new Stopwatch();
|
||||
|
||||
@@ -154,9 +154,9 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual System.Net.Http.HttpClient GetClient(HttpUri uri)
|
||||
protected virtual System.Net.Http.HttpClient GetClient(HttpUri uri, HttpProxySettings requestProxy)
|
||||
{
|
||||
var proxySettings = _proxySettingsProvider.GetProxySettings(uri);
|
||||
var proxySettings = requestProxy ?? _proxySettingsProvider.GetProxySettings(uri);
|
||||
|
||||
var key = proxySettings?.Key ?? NO_PROXY_KEY;
|
||||
|
||||
@@ -174,6 +174,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
PreAuthenticate = true,
|
||||
MaxConnectionsPerServer = 12,
|
||||
ConnectCallback = onConnect,
|
||||
PooledConnectionLifetime = TimeSpan.FromMinutes(10),
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = _certificateValidationService.ShouldByPassValidationError
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Net.Http;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
@@ -37,7 +38,7 @@ namespace NzbDrone.Common.Http
|
||||
public HttpMethod Method { get; set; }
|
||||
public HttpHeader Headers { get; set; }
|
||||
public Encoding Encoding { get; set; }
|
||||
public IWebProxy Proxy { get; set; }
|
||||
public HttpProxySettings ProxySettings { get; set; }
|
||||
public byte[] ContentData { get; set; }
|
||||
public string ContentSummary { get; set; }
|
||||
public ICredentials Credentials { get; set; }
|
||||
|
||||
@@ -89,13 +89,13 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
return (Request.Url += new HttpUri(match.Groups[2].Value)).FullUri;
|
||||
return (Request.Url + new HttpUri(match.Groups[2].Value)).FullUri;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return (Request.Url += new HttpUri(newUrl)).FullUri;
|
||||
return (Request.Url + new HttpUri(newUrl)).FullUri;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
|
||||
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=authkey = "")(?<secret>[^&=]+?)(?="")", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=beyond-hd\.[a-z]+/api/torrents/)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DryIoc.dll" Version="4.8.8" />
|
||||
<PackageReference Include="DryIoc.dll" Version="5.2.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
|
||||
<PackageReference Include="Sentry" Version="3.19.0" />
|
||||
<PackageReference Include="Sentry" Version="3.21.0" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "success",
|
||||
"response": []
|
||||
}
|
||||
@@ -12,6 +12,7 @@ using NzbDrone.Core.Indexers.Definitions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
|
||||
{
|
||||
@@ -64,5 +65,19 @@ namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
|
||||
torrentInfo.DownloadVolumeFactor.Should().Be(1);
|
||||
torrentInfo.UploadVolumeFactor.Should().Be(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task should_not_error_if_empty_response()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/GazelleGames/recentfeed-empty.json");
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
|
||||
|
||||
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new int[] { 2000 } })).Releases;
|
||||
|
||||
releases.Should().HaveCount(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
|
||||
};
|
||||
|
||||
Mocker.GetMock<IRarbgTokenProvider>()
|
||||
.Setup(v => v.GetToken(It.IsAny<RarbgSettings>(), It.IsAny<string>()))
|
||||
.Setup(v => v.GetToken(It.IsAny<RarbgSettings>()))
|
||||
.Returns("validtoken");
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||
<PackageReference Include="NBuilder" Version="6.1.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="12.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Data.SQLite;
|
||||
using System.Net.Sockets;
|
||||
using NLog;
|
||||
using Npgsql;
|
||||
using NzbDrone.Common.Disk;
|
||||
@@ -125,6 +126,37 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/prowlarr/faq#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName);
|
||||
}
|
||||
catch (NpgsqlException e)
|
||||
{
|
||||
if (e.InnerException is SocketException)
|
||||
{
|
||||
var retryCount = 3;
|
||||
|
||||
while (true)
|
||||
{
|
||||
Logger.Error(e, "Failure to connect to Postgres DB, {0} retries remaining", retryCount);
|
||||
|
||||
try
|
||||
{
|
||||
_migrationController.Migrate(connectionString, migrationContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (--retryCount > 0)
|
||||
{
|
||||
System.Threading.Thread.Sleep(5000);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new ProwlarrStartupException(ex, "Error creating main database");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ProwlarrStartupException(e, "Error creating main database");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ProwlarrStartupException(e, "Error creating main database");
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Applications;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.ThingiProvider.Events;
|
||||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
[CheckOn(typeof(ProviderUpdatedEvent<IApplication>))]
|
||||
[CheckOn(typeof(ProviderDeletedEvent<IApplication>))]
|
||||
[CheckOn(typeof(ProviderStatusChangedEvent<IApplication>))]
|
||||
public class ApplicationLongTermStatusCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IApplicationFactory _providerFactory;
|
||||
private readonly IApplicationStatusService _providerStatusService;
|
||||
|
||||
public ApplicationLongTermStatusCheck(IApplicationFactory providerFactory,
|
||||
IApplicationStatusService providerStatusService,
|
||||
ILocalizationService localizationService)
|
||||
: base(localizationService)
|
||||
{
|
||||
_providerFactory = providerFactory;
|
||||
_providerStatusService = providerStatusService;
|
||||
}
|
||||
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
var enabledProviders = _providerFactory.GetAvailableProviders();
|
||||
var backOffProviders = enabledProviders.Join(_providerStatusService.GetBlockedProviders(),
|
||||
i => i.Definition.Id,
|
||||
s => s.ProviderId,
|
||||
(i, s) => new { Provider = i, Status = s })
|
||||
.Where(p => p.Status.InitialFailure.HasValue &&
|
||||
p.Status.InitialFailure.Value.Before(
|
||||
DateTime.UtcNow.AddHours(-6)))
|
||||
.ToList();
|
||||
|
||||
if (backOffProviders.Empty())
|
||||
{
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
|
||||
if (backOffProviders.Count == enabledProviders.Count)
|
||||
{
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Error,
|
||||
_localizationService.GetLocalizedString("ApplicationLongTermStatusCheckAllClientMessage"),
|
||||
"#applications-are-unavailable-due-to-failures");
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Warning,
|
||||
string.Format(_localizationService.GetLocalizedString("ApplicationLongTermStatusCheckSingleClientMessage"),
|
||||
string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))),
|
||||
"#applications-are-unavailable-due-to-failures");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.PassThePopcorn;
|
||||
using NzbDrone.Core.Localization;
|
||||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
public class PTPOldSettingsCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
|
||||
public PTPOldSettingsCheck(IIndexerFactory indexerFactory, ILocalizationService localizationService)
|
||||
: base(localizationService)
|
||||
{
|
||||
_indexerFactory = indexerFactory;
|
||||
}
|
||||
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
var ptpIndexers = _indexerFactory.All().Where(i => i.Settings.GetType() == typeof(PassThePopcornSettings));
|
||||
|
||||
var ptpIndexerOldSettings = ptpIndexers
|
||||
.Where(i => (i.Settings as PassThePopcornSettings).APIUser.IsNullOrWhiteSpace()).Select(i => i.Name);
|
||||
|
||||
if (ptpIndexerOldSettings.Any())
|
||||
{
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("PtpOldSettingsCheckMessage"), string.Join(", ", ptpIndexerOldSettings)));
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Http.CloudFlare;
|
||||
using NzbDrone.Core.Localization;
|
||||
@@ -20,10 +21,12 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
public class FlareSolverr : HttpIndexerProxyBase<FlareSolverrSettings>
|
||||
{
|
||||
private readonly ICached<string> _cache;
|
||||
private readonly IHttpProxySettingsProvider _proxySettingsProvider;
|
||||
|
||||
public FlareSolverr(IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger, ILocalizationService localizationService, ICacheManager cacheManager)
|
||||
public FlareSolverr(IHttpProxySettingsProvider proxySettingsProvider, IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger, ILocalizationService localizationService, ICacheManager cacheManager)
|
||||
: base(cloudRequestBuilder, httpClient, logger, localizationService)
|
||||
{
|
||||
_proxySettingsProvider = proxySettingsProvider;
|
||||
_cache = cacheManager.GetCache<string>(typeof(string), "UserAgent");
|
||||
}
|
||||
|
||||
@@ -100,6 +103,10 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
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 = Settings.RequestTimeout * 1000;
|
||||
|
||||
// Use Proxy if no credentials are set (creds not supported as of FS 2.2.9)
|
||||
var proxySettings = _proxySettingsProvider.GetProxySettings();
|
||||
var proxyUrl = proxySettings != null && proxySettings.Username.IsNullOrWhiteSpace() && proxySettings.Password.IsNullOrWhiteSpace() ? GetProxyUri(proxySettings) : null;
|
||||
|
||||
if (request.Method == HttpMethod.Get)
|
||||
{
|
||||
req = new FlareSolverrRequestGet
|
||||
@@ -107,7 +114,11 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
Cmd = "request.get",
|
||||
Url = url,
|
||||
MaxTimeout = maxTimeout,
|
||||
UserAgent = userAgent
|
||||
UserAgent = userAgent,
|
||||
Proxy = new FlareSolverrProxy
|
||||
{
|
||||
Url = proxyUrl?.AbsoluteUri
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (request.Method == HttpMethod.Post)
|
||||
@@ -130,7 +141,11 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
ContentLength = null
|
||||
},
|
||||
MaxTimeout = maxTimeout,
|
||||
UserAgent = userAgent
|
||||
UserAgent = userAgent,
|
||||
Proxy = new FlareSolverrProxy
|
||||
{
|
||||
Url = proxyUrl?.AbsoluteUri
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (contentTypeType.Contains("multipart/form-data")
|
||||
@@ -191,38 +206,59 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
return new ValidationResult(failures);
|
||||
}
|
||||
|
||||
public class FlareSolverrRequest
|
||||
private Uri GetProxyUri(HttpProxySettings proxySettings)
|
||||
{
|
||||
switch (proxySettings.Type)
|
||||
{
|
||||
case ProxyType.Http:
|
||||
return new Uri("http://" + proxySettings.Host + ":" + proxySettings.Port);
|
||||
case ProxyType.Socks4:
|
||||
return new Uri("socks4://" + proxySettings.Host + ":" + proxySettings.Port);
|
||||
case ProxyType.Socks5:
|
||||
return new Uri("socks5://" + proxySettings.Host + ":" + proxySettings.Port);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class FlareSolverrRequest
|
||||
{
|
||||
public string Cmd { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string UserAgent { get; set; }
|
||||
public Cookie[] Cookies { get; set; }
|
||||
public FlareSolverrProxy Proxy { get; set; }
|
||||
}
|
||||
|
||||
public class FlareSolverrRequestGet : FlareSolverrRequest
|
||||
private class FlareSolverrRequestGet : FlareSolverrRequest
|
||||
{
|
||||
public string Headers { get; set; }
|
||||
public int MaxTimeout { get; set; }
|
||||
}
|
||||
|
||||
public class FlareSolverrRequestPost : FlareSolverrRequest
|
||||
private class FlareSolverrRequestPost : FlareSolverrRequest
|
||||
{
|
||||
public string PostData { get; set; }
|
||||
public int MaxTimeout { get; set; }
|
||||
}
|
||||
|
||||
public class FlareSolverrRequestPostUrlEncoded : FlareSolverrRequestPost
|
||||
private class FlareSolverrRequestPostUrlEncoded : FlareSolverrRequestPost
|
||||
{
|
||||
public HeadersPost Headers { get; set; }
|
||||
}
|
||||
|
||||
public class HeadersPost
|
||||
private class FlareSolverrProxy
|
||||
{
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
private class HeadersPost
|
||||
{
|
||||
public string ContentType { get; set; }
|
||||
public string ContentLength { get; set; }
|
||||
}
|
||||
|
||||
public class FlareSolverrResponse
|
||||
private class FlareSolverrResponse
|
||||
{
|
||||
public string Status { get; set; }
|
||||
public string Message { get; set; }
|
||||
@@ -232,7 +268,7 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
public Solution Solution { get; set; }
|
||||
}
|
||||
|
||||
public class Solution
|
||||
private class Solution
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string Status { get; set; }
|
||||
@@ -242,7 +278,7 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
public string UserAgent { get; set; }
|
||||
}
|
||||
|
||||
public class Cookie
|
||||
private class Cookie
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
@@ -259,7 +295,7 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
public System.Net.Cookie ToCookieObj() => new System.Net.Cookie(Name, Value);
|
||||
}
|
||||
|
||||
public class Headers
|
||||
private class Headers
|
||||
{
|
||||
public string Status { get; set; }
|
||||
public string Date { get; set; }
|
||||
|
||||
@@ -3,7 +3,9 @@ using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.Notifications.Prowl;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies.Http
|
||||
{
|
||||
@@ -18,14 +20,13 @@ namespace NzbDrone.Core.IndexerProxies.Http
|
||||
|
||||
public override HttpRequest PreRequest(HttpRequest request)
|
||||
{
|
||||
if (Settings.Username.IsNotNullOrWhiteSpace() && Settings.Password.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.Proxy = new WebProxy(Settings.Host + ":" + Settings.Port, false, null, new NetworkCredential(Settings.Username, Settings.Password));
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Proxy = new WebProxy(Settings.Host + ":" + Settings.Port, false, null);
|
||||
}
|
||||
request.ProxySettings = new HttpProxySettings(ProxyType.Http,
|
||||
Settings.Host,
|
||||
Settings.Port,
|
||||
null,
|
||||
false,
|
||||
Settings.Username,
|
||||
Settings.Password);
|
||||
|
||||
_logger.Debug("Applying HTTP(S) Proxy {0} to request {1}", Name, request.Url);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Localization;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies.Socks4
|
||||
@@ -25,14 +26,13 @@ namespace NzbDrone.Core.IndexerProxies.Socks4
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Settings.Username.IsNotNullOrWhiteSpace() && Settings.Password.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.Proxy = new WebProxy(uri, false, null, new NetworkCredential(Settings.Username, Settings.Password));
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Proxy = new WebProxy(uri);
|
||||
}
|
||||
request.ProxySettings = new HttpProxySettings(ProxyType.Socks4,
|
||||
Settings.Host,
|
||||
Settings.Port,
|
||||
null,
|
||||
false,
|
||||
Settings.Username,
|
||||
Settings.Password);
|
||||
|
||||
_logger.Debug("Applying Socks4 Proxy {0} to request {1}", Name, request.Url);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Localization;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies.Socks5
|
||||
@@ -26,14 +27,13 @@ namespace NzbDrone.Core.IndexerProxies.Socks5
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Settings.Username.IsNotNullOrWhiteSpace() && Settings.Password.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.Proxy = new WebProxy(uri, false, null, new NetworkCredential(Settings.Username, Settings.Password));
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Proxy = new WebProxy(uri);
|
||||
}
|
||||
request.ProxySettings = new HttpProxySettings(ProxyType.Socks5,
|
||||
Settings.Host,
|
||||
Settings.Port,
|
||||
null,
|
||||
false,
|
||||
Settings.Username,
|
||||
Settings.Password);
|
||||
|
||||
_logger.Debug("Applying Socks5 Proxy {0} to request {1}", Name, request.Url);
|
||||
|
||||
|
||||
@@ -160,6 +160,18 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (criteriaBase.Categories != null && criteriaBase.Categories.Length > 0)
|
||||
{
|
||||
//Only query supported indexers
|
||||
indexers = indexers.Where(i => ((IndexerDefinition)i.Definition).Capabilities.Categories.SupportedCategories(criteriaBase.Categories).Any()).ToList();
|
||||
|
||||
if (indexers.Count == 0)
|
||||
{
|
||||
_logger.Debug("All provided categories are unsupported by selected indexers: {0}", string.Join(", ", criteriaBase.Categories));
|
||||
return new List<ReleaseInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("Searching indexer(s): [{0}] for {1}", string.Join(", ", indexers.Select(i => i.Definition.Name).ToList()), criteriaBase.ToString());
|
||||
|
||||
var tasks = indexers.Select(x => DispatchIndexer(searchAction, x, criteriaBase));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
@@ -282,44 +283,29 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
{
|
||||
var startupFolder = _appFolderInfo.AppDataFolder;
|
||||
|
||||
var request = new HttpRequest($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}");
|
||||
var response = _httpClient.Get<List<CardigannMetaDefinition>>(request);
|
||||
|
||||
var currentDefs = _versionService.All().ToDictionary(x => x.DefinitionId, x => x.Sha);
|
||||
|
||||
try
|
||||
{
|
||||
EnsureDefinitionsFolder();
|
||||
|
||||
foreach (var def in response.Resource)
|
||||
var definitionsFolder = Path.Combine(startupFolder, "Definitions");
|
||||
var saveFile = Path.Combine(definitionsFolder, $"indexers.zip");
|
||||
|
||||
_httpClient.DownloadFile($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}/package.zip", saveFile);
|
||||
|
||||
using (ZipArchive archive = ZipFile.OpenRead(saveFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
var saveFile = Path.Combine(startupFolder, "Definitions", $"{def.File}.yml");
|
||||
|
||||
if (currentDefs.TryGetValue(def.Id, out var defSha) && defSha == def.Sha)
|
||||
{
|
||||
_logger.Trace("Indexer already up to date: {0}", def.File);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
_httpClient.DownloadFile($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}/{def.File}", saveFile);
|
||||
|
||||
_versionService.Upsert(new IndexerDefinitionVersion { Sha = def.Sha, DefinitionId = def.Id, File = def.File, LastUpdated = DateTime.UtcNow });
|
||||
|
||||
_cache.Remove(def.File);
|
||||
_logger.Debug("Updated definition: {0}", def.File);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error("Definition download failed: {0}, {1}", def.File, ex.Message);
|
||||
}
|
||||
archive.ExtractToDirectory(definitionsFolder, true);
|
||||
}
|
||||
|
||||
_diskProvider.DeleteFile(saveFile);
|
||||
|
||||
_cache.Clear();
|
||||
|
||||
_logger.Debug("Updated indexer definitions");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Definition download failed, error creating definitions folder in {0}", startupFolder);
|
||||
_logger.Error(ex, "Definition update failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,9 +321,10 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
if (episode != null)
|
||||
{
|
||||
releaseInfo = episode is > 0 and < 10
|
||||
var episodeString = episode is > 0 and < 10
|
||||
? "0" + episode
|
||||
: episode.ToString();
|
||||
releaseInfo = $" - {episodeString}";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -449,6 +450,37 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
// Additional 5 hours per GB
|
||||
minimumSeedTime += (int)((size / 1000000000) * 18000);
|
||||
|
||||
if (_settings.UseFilenameForSingleEpisodes && torrent.FileCount == 1)
|
||||
{
|
||||
var fileName = torrent.Files.First().FileName;
|
||||
|
||||
var guid = new Uri(details + "&nh=" + StringUtil.Hash(fileName));
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = minimumSeedTime,
|
||||
Title = fileName,
|
||||
InfoUrl = details.AbsoluteUri,
|
||||
Guid = guid.AbsoluteUri,
|
||||
DownloadUrl = link.AbsoluteUri,
|
||||
PublishDate = publishDate,
|
||||
Categories = category,
|
||||
Description = description,
|
||||
Size = size,
|
||||
Seeders = seeders,
|
||||
Peers = peers,
|
||||
Grabs = snatched,
|
||||
Files = fileCount,
|
||||
DownloadVolumeFactor = rawDownMultiplier,
|
||||
UploadVolumeFactor = rawUpMultiplier,
|
||||
};
|
||||
|
||||
torrentInfos.Add(release);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var title in synonyms)
|
||||
{
|
||||
var releaseTitle = groupName == "Movie" ?
|
||||
@@ -510,6 +542,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
Passkey = "";
|
||||
Username = "";
|
||||
EnableSonarrCompatibility = true;
|
||||
UseFilenameForSingleEpisodes = false;
|
||||
}
|
||||
|
||||
[FieldDefinition(2, Label = "Passkey", HelpText = "Site Passkey", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
@@ -521,6 +554,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
[FieldDefinition(4, Label = "Enable Sonarr Compatibility", Type = FieldType.Checkbox, HelpText = "Makes Prowlarr try to add Season information into Release names, without this Sonarr can't match any Seasons, but it has a lot of false positives as well")]
|
||||
public bool EnableSonarrCompatibility { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Use Filenames for Single Episodes", Type = FieldType.Checkbox, HelpText = "Makes Prowlarr replace AnimeBytes release names with the actual filename, this currently only works for single episode releases")]
|
||||
public bool UseFilenameForSingleEpisodes { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
@@ -678,10 +714,22 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
[JsonProperty("FileCount")]
|
||||
public int FileCount { get; set; }
|
||||
|
||||
[JsonProperty("FileList")]
|
||||
public List<File> Files { get; set; }
|
||||
|
||||
[JsonProperty("UploadTime")]
|
||||
public DateTimeOffset UploadTime { get; set; }
|
||||
}
|
||||
|
||||
public class File
|
||||
{
|
||||
[JsonProperty("filename")]
|
||||
public string FileName { get; set; }
|
||||
|
||||
[JsonProperty("size")]
|
||||
public string FileSize { get; set; }
|
||||
}
|
||||
|
||||
public class EditionData
|
||||
{
|
||||
[JsonProperty("EditionTitle")]
|
||||
|
||||
@@ -19,6 +19,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
[Obsolete("Moved to YML for Cardigann")]
|
||||
public class Anthelion : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
||||
{
|
||||
public override string Name => "Anthelion";
|
||||
|
||||
@@ -36,11 +36,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId, TvSearchParam.Genre
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId, MovieSearchParam.Genre
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -29,6 +29,11 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
return torrentInfos.ToArray();
|
||||
}
|
||||
|
||||
if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
throw new RequestLimitReachedException(indexerResponse, "API Request Limit Reached");
|
||||
}
|
||||
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
// hook to adjust the search category
|
||||
protected virtual List<KeyValuePair<string, string>> GetBasicSearchParameters(int[] categories)
|
||||
protected virtual List<KeyValuePair<string, string>> GetBasicSearchParameters(int[] categories, string genre)
|
||||
{
|
||||
var categoryMapping = Capabilities.Categories.MapTorznabCapsToTrackers(categories).Distinct().ToList();
|
||||
var qc = new List<KeyValuePair<string, string>> // NameValueCollection don't support cat[]=19&cat[]=6
|
||||
@@ -34,6 +34,16 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
{ "type", categoryMapping.Any() ? categoryMapping.First() : "0" }
|
||||
};
|
||||
|
||||
if (Settings.FreeleechOnly)
|
||||
{
|
||||
qc.Add("discount[]", "1");
|
||||
}
|
||||
|
||||
if (genre.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
qc.Add("tags", genre);
|
||||
}
|
||||
|
||||
// resolution filter to improve the search
|
||||
if (!categories.Contains(NewznabStandardCategory.Movies.Id) && !categories.Contains(NewznabStandardCategory.TV.Id) &&
|
||||
!categories.Contains(NewznabStandardCategory.Audio.Id))
|
||||
@@ -71,7 +81,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories);
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories, searchCriteria.Genre);
|
||||
|
||||
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
@@ -93,7 +103,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories);
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories, null);
|
||||
|
||||
parameters.Add("search", GetSearchTerm(searchCriteria.SanitizedSearchTerm).Trim());
|
||||
|
||||
@@ -104,7 +114,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories);
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories, searchCriteria.Genre);
|
||||
|
||||
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
@@ -136,7 +146,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories);
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories, null);
|
||||
|
||||
parameters.Add("search", GetSearchTerm(searchCriteria.SanitizedSearchTerm).Trim());
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
public AvistazSettings()
|
||||
{
|
||||
Token = "";
|
||||
FreeleechOnly = false;
|
||||
}
|
||||
|
||||
public string Token { get; set; }
|
||||
@@ -35,6 +36,9 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
[FieldDefinition(4, Label = "PID", HelpText = "PID from My Account or My Profile page")]
|
||||
public string Pid { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Search freeleech only")]
|
||||
public bool FreeleechOnly { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
.ContainsIgnoreCase("login.php"))
|
||||
{
|
||||
CookiesUpdater(null, null);
|
||||
throw new IndexerException(indexerResponse, "We are being redirected to the login page. Most likely your session expired or was killed. Try testing the indexer in the settings.");
|
||||
throw new IndexerException(indexerResponse, "We are being redirected to the login page. Most likely your session expired or was killed. Recheck your cookie or credentials and try testing the indexer.");
|
||||
}
|
||||
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
||||
|
||||
@@ -152,6 +152,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
variables[".Query.Offset"] = searchCriteria.Offset?.ToString() ?? null;
|
||||
variables[".Query.Extended"] = null;
|
||||
variables[".Query.APIKey"] = null;
|
||||
variables[".Query.Genre"] = null;
|
||||
|
||||
//Movie
|
||||
variables[".Query.Movie"] = null;
|
||||
@@ -168,6 +169,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
variables[".Query.TVRageID"] = null;
|
||||
variables[".Query.TVMazeID"] = null;
|
||||
variables[".Query.TraktID"] = null;
|
||||
variables[".Query.DoubanID"] = null;
|
||||
variables[".Query.Episode"] = null;
|
||||
|
||||
//Music
|
||||
@@ -179,6 +181,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
//Book
|
||||
variables[".Query.Author"] = null;
|
||||
variables[".Query.Title"] = null;
|
||||
variables[".Query.Publisher"] = null;
|
||||
|
||||
return variables;
|
||||
}
|
||||
@@ -942,6 +945,11 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
public bool CheckIfLoginIsNeeded(HttpResponse response)
|
||||
{
|
||||
if (_definition.Login == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.HasHttpRedirect)
|
||||
{
|
||||
var domainHint = GetRedirectDomainHint(response);
|
||||
@@ -949,35 +957,27 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
{
|
||||
var errormessage = "Got redirected to another domain. Try changing the indexer URL to " + domainHint + ".";
|
||||
|
||||
throw new CardigannException(errormessage);
|
||||
_logger.Warn(errormessage);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_definition.Login == null || _definition.Login.Test == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.HasHttpError)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only run html test selector on html responses
|
||||
if (response.Headers.ContentType?.Contains("text/html") ?? true)
|
||||
if (_definition.Login.Test?.Selector != null && (response.Headers.ContentType?.Contains("text/html") ?? true))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var document = parser.ParseDocument(response.Content);
|
||||
|
||||
if (_definition.Login.Test.Selector != null)
|
||||
var selection = document.QuerySelectorAll(_definition.Login.Test.Selector);
|
||||
if (selection.Length == 0)
|
||||
{
|
||||
var selection = document.QuerySelectorAll(_definition.Login.Test.Selector);
|
||||
if (selection.Length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1121,6 +1121,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
var request = new CardigannRequest(requestbuilder.SetEncoding(_encoding).Build(), variables, searchPath);
|
||||
|
||||
request.HttpRequest.AllowAutoRedirect = searchPath.Followredirect;
|
||||
|
||||
yield return request;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,11 +36,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.Genre
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.Genre
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -80,6 +80,30 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
_logger.Debug("Gazelle authentication succeeded.");
|
||||
}
|
||||
|
||||
public override async Task<byte[]> Download(Uri link)
|
||||
{
|
||||
var response = await base.Download(link);
|
||||
|
||||
if (response.Length >= 1
|
||||
&& response[0] != 'd' // simple test for torrent vs HTML content
|
||||
&& link.Query.Contains("usetoken=1"))
|
||||
{
|
||||
var html = Encoding.GetString(response);
|
||||
if (html.Contains("You do not have any freeleech tokens left.")
|
||||
|| html.Contains("You do not have enough freeleech tokens")
|
||||
|| html.Contains("This torrent is too large.")
|
||||
|| html.Contains("You cannot use tokens here"))
|
||||
{
|
||||
// download again with usetoken=0
|
||||
var requestLinkNew = link.ToString().Replace("usetoken=1", "usetoken=0");
|
||||
|
||||
response = await base.Download(new Uri(requestLinkNew));
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse response)
|
||||
{
|
||||
if (response.HasHttpRedirect || (response.Content != null && response.Content.Contains("\"bad credentials\"")))
|
||||
|
||||
@@ -30,22 +30,19 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetRequest(string searchParameters)
|
||||
protected IEnumerable<IndexerRequest> GetRequest(string searchParameters)
|
||||
{
|
||||
var filter = "";
|
||||
if (searchParameters == null)
|
||||
{
|
||||
}
|
||||
|
||||
var request =
|
||||
new IndexerRequest(
|
||||
$"{APIUrl}?{searchParameters}{filter}",
|
||||
$"{APIUrl}?{searchParameters}",
|
||||
HttpAccept.Json);
|
||||
|
||||
request.HttpRequest.AllowAutoRedirect = false;
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
private string GetBasicSearchParameters(string searchTerm, int[] categories)
|
||||
protected string GetBasicSearchParameters(string searchTerm, int[] categories)
|
||||
{
|
||||
var searchString = GetSearchTerm(searchTerm);
|
||||
|
||||
@@ -67,7 +64,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.SearchTerm, searchCriteria.Categories);
|
||||
|
||||
|
||||
@@ -341,7 +341,18 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
return torrentInfos;
|
||||
}
|
||||
|
||||
foreach (var result in jsonResponse.Resource.Response)
|
||||
Dictionary<string, GazelleGamesGroup> response;
|
||||
|
||||
try
|
||||
{
|
||||
response = ((JObject)jsonResponse.Resource.Response).ToObject<Dictionary<string, GazelleGamesGroup>>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return torrentInfos;
|
||||
}
|
||||
|
||||
foreach (var result in response)
|
||||
{
|
||||
Dictionary<string, GazelleGamesTorrent> torrents;
|
||||
|
||||
@@ -455,7 +466,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class GazelleGamesResponse
|
||||
{
|
||||
public string Status { get; set; }
|
||||
public Dictionary<string, GazelleGamesGroup> Response { get; set; }
|
||||
public object Response { get; set; }
|
||||
}
|
||||
|
||||
public class GazelleGamesGroup
|
||||
|
||||
369
src/NzbDrone.Core/Indexers/Definitions/GreatPosterWall.cs
Normal file
369
src/NzbDrone.Core/Indexers/Definitions/GreatPosterWall.cs
Normal file
@@ -0,0 +1,369 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Gazelle;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions;
|
||||
|
||||
public class GreatPosterWall : Gazelle.Gazelle
|
||||
{
|
||||
public override string Name => "GreatPosterWall";
|
||||
public override string[] IndexerUrls => new string[] { "https://greatposterwall.com/" };
|
||||
public override string Description => "GreatPosterWall (GPW) is a CHINESE Private site for MOVIES";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public GreatPosterWall(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new GreatPosterWallRequestGenerator()
|
||||
{
|
||||
Settings = Settings,
|
||||
HttpClient = _httpClient,
|
||||
Logger = _logger,
|
||||
Capabilities = Capabilities
|
||||
};
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new GreatPosterWallParser(Settings, Capabilities);
|
||||
}
|
||||
|
||||
protected override IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies, "Movies 电影");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class GreatPosterWallRequestGenerator : GazelleRequestGenerator
|
||||
{
|
||||
protected override bool ImdbInTags => false;
|
||||
|
||||
public override IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.SearchTerm, searchCriteria.Categories);
|
||||
|
||||
if (searchCriteria.ImdbId != null)
|
||||
{
|
||||
parameters += string.Format("&searchstr={0}", searchCriteria.FullImdbId);
|
||||
}
|
||||
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
pageableRequests.Add(GetRequest(parameters));
|
||||
return pageableRequests;
|
||||
}
|
||||
}
|
||||
|
||||
public class GreatPosterWallParser : GazelleParser
|
||||
{
|
||||
public GreatPosterWallParser(GazelleSettings settings, IndexerCapabilities capabilities)
|
||||
: base(settings, capabilities)
|
||||
{
|
||||
}
|
||||
|
||||
public override IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
// Remove cookie cache
|
||||
CookiesUpdater(null, null);
|
||||
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
||||
}
|
||||
|
||||
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
|
||||
{
|
||||
// Remove cookie cache
|
||||
CookiesUpdater(null, null);
|
||||
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
|
||||
}
|
||||
|
||||
var jsonResponse = new HttpResponse<GreatPosterWallResponse>(indexerResponse.HttpResponse);
|
||||
if (jsonResponse.Resource.Status != "success" ||
|
||||
jsonResponse.Resource.Status.IsNullOrWhiteSpace() ||
|
||||
jsonResponse.Resource.Response == null)
|
||||
{
|
||||
return torrentInfos;
|
||||
}
|
||||
|
||||
foreach (var result in jsonResponse.Resource.Response.Results)
|
||||
{
|
||||
foreach (var torrent in result.Torrents)
|
||||
{
|
||||
var infoUrl = GetInfoUrl(result.GroupId.ToString(), torrent.TorrentId);
|
||||
|
||||
var time = DateTime.SpecifyKind(torrent.Time, DateTimeKind.Unspecified);
|
||||
|
||||
var release = new GazelleInfo
|
||||
{
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 172800,
|
||||
Title = torrent.FileName,
|
||||
InfoUrl = infoUrl,
|
||||
Guid = infoUrl,
|
||||
PosterUrl = GetPosterUrl(result.Cover),
|
||||
DownloadUrl = GetDownloadUrl(torrent.TorrentId, torrent.CanUseToken),
|
||||
PublishDate = new DateTimeOffset(time, TimeSpan.FromHours(8)).LocalDateTime, // Time is Chinese Time, add 8 hours difference from UTC and then convert back to local time
|
||||
Categories = new List<IndexerCategory> { NewznabStandardCategory.Movies },
|
||||
Size = torrent.Size,
|
||||
Seeders = torrent.Seeders,
|
||||
Peers = torrent.Seeders + torrent.Leechers,
|
||||
Grabs = torrent.Snatches,
|
||||
Files = torrent.FileCount,
|
||||
Scene = torrent.Scene,
|
||||
DownloadVolumeFactor = torrent.IsFreeleech || torrent.IsNeutralLeech || torrent.IsPersonalFreeleech ? 0 : 1,
|
||||
UploadVolumeFactor = torrent.IsNeutralLeech ? 0 : 1
|
||||
};
|
||||
|
||||
var imdbId = ParseUtil.GetImdbID(result.ImdbId);
|
||||
|
||||
if (imdbId != null)
|
||||
{
|
||||
release.ImdbId = (int)imdbId;
|
||||
}
|
||||
|
||||
switch (torrent.FreeType)
|
||||
{
|
||||
case "11":
|
||||
release.DownloadVolumeFactor = 0.75;
|
||||
break;
|
||||
case "12":
|
||||
release.DownloadVolumeFactor = 0.5;
|
||||
break;
|
||||
case "13":
|
||||
release.DownloadVolumeFactor = 0.25;
|
||||
break;
|
||||
case "1":
|
||||
release.DownloadVolumeFactor = 0;
|
||||
break;
|
||||
case "2":
|
||||
release.DownloadVolumeFactor = 0;
|
||||
release.UploadVolumeFactor = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
torrentInfos.Add(release);
|
||||
}
|
||||
}
|
||||
|
||||
return torrentInfos;
|
||||
}
|
||||
|
||||
protected string GetDownloadUrl(int torrentId, bool canUseToken)
|
||||
{
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/torrents.php")
|
||||
.AddQueryParam("action", "download")
|
||||
.AddQueryParam("usetoken", _settings.UseFreeleechToken && canUseToken ? "1" : "0")
|
||||
.AddQueryParam("id", torrentId);
|
||||
|
||||
return url.FullUri;
|
||||
}
|
||||
}
|
||||
|
||||
public class GreatPosterWallResponse
|
||||
{
|
||||
[JsonProperty("status")]
|
||||
public string Status { get; set; }
|
||||
|
||||
[JsonProperty("response")]
|
||||
public Response Response { get; set; }
|
||||
}
|
||||
|
||||
public class Response
|
||||
{
|
||||
[JsonProperty("currentPage")]
|
||||
public int CurrentPage { get; set; }
|
||||
|
||||
[JsonProperty("pages")]
|
||||
public int Pages { get; set; }
|
||||
|
||||
[JsonProperty("results")]
|
||||
public List<Result> Results { get; set; }
|
||||
}
|
||||
|
||||
public class Result
|
||||
{
|
||||
[JsonProperty("groupId")]
|
||||
public int GroupId { get; set; }
|
||||
|
||||
[JsonProperty("groupName")]
|
||||
public string GroupName { get; set; }
|
||||
|
||||
[JsonProperty("groupSubName")]
|
||||
public string GroupSubName { get; set; }
|
||||
|
||||
[JsonProperty("cover")]
|
||||
public string Cover { get; set; }
|
||||
|
||||
[JsonProperty("tags")]
|
||||
public List<string> Tags { get; set; }
|
||||
|
||||
[JsonProperty("bookmarked")]
|
||||
public bool Bookmarked { get; set; }
|
||||
|
||||
[JsonProperty("groupYear")]
|
||||
public int GroupYear { get; set; }
|
||||
|
||||
[JsonProperty("releaseType")]
|
||||
public string ReleaseType { get; set; }
|
||||
|
||||
[JsonProperty("groupTime")]
|
||||
public string GroupTime { get; set; }
|
||||
|
||||
[JsonProperty("maxSize")]
|
||||
public object MaxSize { get; set; }
|
||||
|
||||
[JsonProperty("totalSnatched")]
|
||||
public int TotalSnatched { get; set; }
|
||||
|
||||
[JsonProperty("totalSeeders")]
|
||||
public int TotalSeeders { get; set; }
|
||||
|
||||
[JsonProperty("totalLeechers")]
|
||||
public int TotalLeechers { get; set; }
|
||||
|
||||
[JsonProperty("imdbId")]
|
||||
public string ImdbId { get; set; }
|
||||
|
||||
[JsonProperty("imdbRating")]
|
||||
public string ImdbRating { get; set; }
|
||||
|
||||
[JsonProperty("imdbVote")]
|
||||
public string ImdbVote { get; set; }
|
||||
|
||||
[JsonProperty("doubanId")]
|
||||
public string DoubanId { get; set; }
|
||||
|
||||
[JsonProperty("doubanRating")]
|
||||
public string DoubanRating { get; set; }
|
||||
|
||||
[JsonProperty("doubanVote")]
|
||||
public string DoubanVote { get; set; }
|
||||
|
||||
[JsonProperty("rtRating")]
|
||||
public string RtRating { get; set; }
|
||||
|
||||
[JsonProperty("region")]
|
||||
public string Region { get; set; }
|
||||
|
||||
[JsonProperty("torrents")]
|
||||
public List<GreatPosterWallTorrent> Torrents { get; set; }
|
||||
}
|
||||
|
||||
public class GreatPosterWallTorrent
|
||||
{
|
||||
[JsonProperty("torrentId")]
|
||||
public int TorrentId { get; set; }
|
||||
|
||||
[JsonProperty("editionId")]
|
||||
public int EditionId { get; set; }
|
||||
|
||||
[JsonProperty("remasterYear")]
|
||||
public int RemasterYear { get; set; }
|
||||
|
||||
[JsonProperty("remasterTitle")]
|
||||
public string RemasterTitle { get; set; }
|
||||
|
||||
[JsonProperty("remasterCustomTitle")]
|
||||
public string RemasterCustomTitle { get; set; }
|
||||
|
||||
[JsonProperty("scene")]
|
||||
public bool Scene { get; set; }
|
||||
|
||||
[JsonProperty("jinzhuan")]
|
||||
public bool Jinzhuan { get; set; }
|
||||
|
||||
[JsonProperty("fileCount")]
|
||||
public int FileCount { get; set; }
|
||||
|
||||
[JsonProperty("time")]
|
||||
public DateTime Time { get; set; }
|
||||
|
||||
[JsonProperty("size")]
|
||||
public long Size { get; set; }
|
||||
|
||||
[JsonProperty("snatches")]
|
||||
public int Snatches { get; set; }
|
||||
|
||||
[JsonProperty("seeders")]
|
||||
public int Seeders { get; set; }
|
||||
|
||||
[JsonProperty("leechers")]
|
||||
public int Leechers { get; set; }
|
||||
|
||||
[JsonProperty("isFreeleech")]
|
||||
public bool IsFreeleech { get; set; }
|
||||
|
||||
[JsonProperty("isNeutralLeech")]
|
||||
public bool IsNeutralLeech { get; set; }
|
||||
|
||||
[JsonProperty("freeType")]
|
||||
public string FreeType { get; set; }
|
||||
|
||||
[JsonProperty("isPersonalFreeleech")]
|
||||
public bool IsPersonalFreeleech { get; set; }
|
||||
|
||||
[JsonProperty("canUseToken")]
|
||||
public bool CanUseToken { get; set; }
|
||||
|
||||
[JsonProperty("hasSnatched")]
|
||||
public bool HasSnatched { get; set; }
|
||||
|
||||
[JsonProperty("resolution")]
|
||||
public string Resolution { get; set; }
|
||||
|
||||
[JsonProperty("source")]
|
||||
public string Source { get; set; }
|
||||
|
||||
[JsonProperty("codec")]
|
||||
public string Codec { get; set; }
|
||||
|
||||
[JsonProperty("container")]
|
||||
public string Container { get; set; }
|
||||
|
||||
[JsonProperty("processing")]
|
||||
public string Processing { get; set; }
|
||||
|
||||
[JsonProperty("chineseDubbed")]
|
||||
public string ChineseDubbed { get; set; }
|
||||
|
||||
[JsonProperty("specialSub")]
|
||||
public string SpecialSub { get; set; }
|
||||
|
||||
[JsonProperty("subtitles")]
|
||||
public string Subtitles { get; set; }
|
||||
|
||||
[JsonProperty("fileName")]
|
||||
public string FileName { get; set; }
|
||||
|
||||
[JsonProperty("releaseGroup")]
|
||||
public string ReleaseGroup { get; set; }
|
||||
}
|
||||
@@ -124,19 +124,24 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.MoviesBluRay, "Movie / Blu-ray");
|
||||
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.MoviesHD, "Movie / 1080p");
|
||||
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.MoviesHD, "Movie / 720p");
|
||||
caps.Categories.AddCategoryMapping(46, NewznabStandardCategory.MoviesUHD, "Movie / 2160p");
|
||||
caps.Categories.AddCategoryMapping(40, NewznabStandardCategory.MoviesHD, "Movie / Remux");
|
||||
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.MoviesHD, "Movie / HD-DVD");
|
||||
caps.Categories.AddCategoryMapping(41, NewznabStandardCategory.MoviesUHD, "Movie / 4K UHD");
|
||||
caps.Categories.AddCategoryMapping(21, NewznabStandardCategory.TVHD, "TV Show / 720p HDTV");
|
||||
caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.TVHD, "TV Show / 1080p HDTV");
|
||||
caps.Categories.AddCategoryMapping(45, NewznabStandardCategory.TVUHD, "TV Show / 2160p HDTV");
|
||||
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.TVDocumentary, "Documentary / 720p");
|
||||
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.TVDocumentary, "Documentary / 1080p");
|
||||
caps.Categories.AddCategoryMapping(47, NewznabStandardCategory.TVDocumentary, "Documentary / 2160p");
|
||||
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.TVAnime, "Animation / 720p");
|
||||
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.TVAnime, "Animation / 1080p");
|
||||
caps.Categories.AddCategoryMapping(48, NewznabStandardCategory.TVAnime, "Animation / 2160p");
|
||||
caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.AudioLossless, "Music / HQ Audio");
|
||||
caps.Categories.AddCategoryMapping(31, NewznabStandardCategory.AudioVideo, "Music / Videos");
|
||||
caps.Categories.AddCategoryMapping(33, NewznabStandardCategory.XXX, "XXX / 720p");
|
||||
caps.Categories.AddCategoryMapping(34, NewznabStandardCategory.XXX, "XXX / 1080p");
|
||||
caps.Categories.AddCategoryMapping(49, NewznabStandardCategory.XXX, "XXX / 2160p");
|
||||
caps.Categories.AddCategoryMapping(36, NewznabStandardCategory.MoviesOther, "Trailers");
|
||||
caps.Categories.AddCategoryMapping(37, NewznabStandardCategory.PC, "Software");
|
||||
caps.Categories.AddCategoryMapping(38, NewznabStandardCategory.Other, "Others");
|
||||
|
||||
@@ -190,6 +190,12 @@ public class MoreThanTVParser : IParseIndexerResponse
|
||||
{
|
||||
// Parse required data
|
||||
var downloadAnchor = torrent.QuerySelector("span a[href^=\"/torrents.php?action=download\"]");
|
||||
|
||||
if (downloadAnchor == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var title = downloadAnchor.ParentElement.ParentElement.ParentElement.QuerySelector("a[class=\"overlay_torrent\"]").TextContent.Trim();
|
||||
title = CleanUpTitle(title);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using DryIoc;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@@ -268,7 +269,10 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
parameters.Add("offset", searchCriteria.Offset.ToString());
|
||||
}
|
||||
|
||||
yield return new IndexerRequest(string.Format("{0}&{1}", baseUrl, parameters.GetQueryString()), HttpAccept.Rss);
|
||||
var request = new IndexerRequest(string.Format("{0}&{1}", baseUrl, parameters.GetQueryString()), HttpAccept.Rss);
|
||||
request.HttpRequest.AllowAutoRedirect = true;
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
private static string NewsnabifyTitle(string title)
|
||||
|
||||
1190
src/NzbDrone.Core/Indexers/Definitions/NzbIndex.cs
Normal file
1190
src/NzbDrone.Core/Indexers/Definitions/NzbIndex.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
.ContainsIgnoreCase("login.php"))
|
||||
{
|
||||
CookiesUpdater(null, null);
|
||||
throw new IndexerAuthException("We are being redirected to the PTP login page. Most likely your session expired or was killed. Try testing the indexer in the settings.");
|
||||
throw new IndexerAuthException("We are being redirected to the PTP login page. Most likely your session expired or was killed. Recheck your cookie or credentials and try testing the indexer.");
|
||||
}
|
||||
|
||||
if (indexerHttpResponse.StatusCode == HttpStatusCode.Forbidden)
|
||||
|
||||
@@ -255,6 +255,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||
|
||||
request.HttpRequest.AllowAutoRedirect = false;
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,11 +35,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId, TvSearchParam.Genre
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId, MovieSearchParam.Genre
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -8,7 +13,9 @@ using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.Http.CloudFlare;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Rarbg
|
||||
@@ -95,6 +102,57 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
return caps;
|
||||
}
|
||||
|
||||
protected override async Task<IndexerQueryResult> FetchPage(IndexerRequest request, IParseIndexerResponse parser)
|
||||
{
|
||||
var response = await FetchIndexerResponse(request);
|
||||
|
||||
// try and recover from token or rate limit errors
|
||||
var jsonResponse = new HttpResponse<RarbgResponse>(response.HttpResponse);
|
||||
|
||||
if (jsonResponse.Resource.error_code.HasValue)
|
||||
{
|
||||
if (jsonResponse.Resource.error_code == 4 || jsonResponse.Resource.error_code == 2)
|
||||
{
|
||||
_logger.Debug("Invalid or expired token, refreshing token from Rarbg");
|
||||
_tokenProvider.ExpireToken(Settings);
|
||||
var newToken = _tokenProvider.GetToken(Settings);
|
||||
|
||||
var qs = HttpUtility.ParseQueryString(request.HttpRequest.Url.Query);
|
||||
qs.Set("token", newToken);
|
||||
|
||||
request.HttpRequest.Url = request.Url.SetQuery(qs.GetQueryString());
|
||||
response = await FetchIndexerResponse(request);
|
||||
}
|
||||
else if (jsonResponse.Resource.error_code == 5 || jsonResponse.Resource.rate_limit.HasValue)
|
||||
{
|
||||
_logger.Debug("Rarbg rate limit hit, retying request");
|
||||
response = await FetchIndexerResponse(request);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var releases = parser.ParseResponse(response).ToList();
|
||||
|
||||
if (releases.Count == 0)
|
||||
{
|
||||
_logger.Trace(response.Content);
|
||||
}
|
||||
|
||||
return new IndexerQueryResult
|
||||
{
|
||||
Releases = releases,
|
||||
Response = response.HttpResponse
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.WithData(response.HttpResponse, 128 * 1024);
|
||||
_logger.Trace("Unexpected Response content ({0} bytes): {1}", response.HttpResponse.ResponseData.Length, response.HttpResponse.Content);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override object RequestAction(string action, IDictionary<string, string> query)
|
||||
{
|
||||
if (action == "checkCaptcha")
|
||||
|
||||
@@ -40,9 +40,11 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
if (jsonResponse.Resource.error_code.HasValue)
|
||||
{
|
||||
if (jsonResponse.Resource.error_code == 20 || jsonResponse.Resource.error_code == 8
|
||||
|| jsonResponse.Resource.error_code == 9 || jsonResponse.Resource.error_code == 10)
|
||||
|| jsonResponse.Resource.error_code == 9 || jsonResponse.Resource.error_code == 10
|
||||
|| jsonResponse.Resource.error_code == 5 || jsonResponse.Resource.error_code == 13
|
||||
|| jsonResponse.Resource.error_code == 14)
|
||||
{
|
||||
// No results or imdbid not found
|
||||
// No results, rate limit, or imdbid/tvdb not found
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
{
|
||||
requestBuilder.AddQueryParam("search_themoviedb", tmdbId);
|
||||
}
|
||||
else if (tvdbId.HasValue && tmdbId > 0)
|
||||
else if (tvdbId.HasValue && tvdbId > 0)
|
||||
{
|
||||
requestBuilder.AddQueryParam("search_tvdb", tvdbId);
|
||||
}
|
||||
@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
}
|
||||
|
||||
requestBuilder.AddQueryParam("limit", "100");
|
||||
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings, Settings.BaseUrl));
|
||||
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings));
|
||||
requestBuilder.AddQueryParam("format", "json_extended");
|
||||
requestBuilder.AddQueryParam("app_id", BuildInfo.AppName);
|
||||
|
||||
@@ -69,42 +69,36 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var request = GetRequest(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories, searchCriteria.FullImdbId, searchCriteria.TmdbId);
|
||||
return GetRequestChain(request, 2);
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
pageableRequests.Add(GetRequest(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories, searchCriteria.FullImdbId, searchCriteria.TmdbId));
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
var request = GetRequest(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
|
||||
return GetRequestChain(request, 2);
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
pageableRequests.Add(GetRequest(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories));
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var request = GetRequest(searchCriteria.SanitizedTvSearchString, searchCriteria.Categories, searchCriteria.FullImdbId, tvdbId: searchCriteria.TvdbId);
|
||||
return GetRequestChain(request, 2);
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
pageableRequests.Add(GetRequest(searchCriteria.SanitizedTvSearchString, searchCriteria.Categories, searchCriteria.FullImdbId, tvdbId: searchCriteria.TvdbId));
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
var request = GetRequest(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
|
||||
return GetRequestChain(request, 2);
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
pageableRequests.Add(GetRequest(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories));
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var request = GetRequest(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
|
||||
return GetRequestChain(request, 2);
|
||||
}
|
||||
|
||||
private IndexerPageableRequestChain GetRequestChain(IEnumerable<IndexerRequest> requests, int retry)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
for (int i = 0; i < retry; i++)
|
||||
{
|
||||
pageableRequests.AddTier(requests);
|
||||
}
|
||||
pageableRequests.Add(GetRequest(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
{
|
||||
public string error { get; set; }
|
||||
public int? error_code { get; set; }
|
||||
public int? rate_limit { get; set; }
|
||||
public List<RarbgTorrent> torrent_results { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Rarbg
|
||||
{
|
||||
public interface IRarbgTokenProvider
|
||||
{
|
||||
string GetToken(RarbgSettings settings, string baseUrl);
|
||||
string GetToken(RarbgSettings settings);
|
||||
void ExpireToken(RarbgSettings settings);
|
||||
}
|
||||
|
||||
public class RarbgTokenProvider : IRarbgTokenProvider
|
||||
@@ -26,12 +26,17 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string GetToken(RarbgSettings settings, string baseUrl)
|
||||
public void ExpireToken(RarbgSettings settings)
|
||||
{
|
||||
return _tokenCache.Get(baseUrl,
|
||||
_tokenCache.Remove(settings.BaseUrl);
|
||||
}
|
||||
|
||||
public string GetToken(RarbgSettings settings)
|
||||
{
|
||||
return _tokenCache.Get(settings.BaseUrl,
|
||||
() =>
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(baseUrl.Trim('/'))
|
||||
var requestBuilder = new HttpRequestBuilder(settings.BaseUrl.Trim('/'))
|
||||
.WithRateLimit(3.0)
|
||||
.Resource($"/pubapi_v2.php?get_token=get_token&app_id={BuildInfo.AppName}")
|
||||
.Accept(HttpAccept.Json);
|
||||
|
||||
62
src/NzbDrone.Core/Indexers/Definitions/RetroFlix.cs
Normal file
62
src/NzbDrone.Core/Indexers/Definitions/RetroFlix.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class RetroFlix : SpeedAppBase
|
||||
{
|
||||
public override string Name => "RetroFlix";
|
||||
|
||||
public override string[] IndexerUrls => new string[] { "https://retroflix.club/" };
|
||||
public override string[] LegacyUrls => new string[] { "https://retroflix.net/" };
|
||||
|
||||
public override string Description => "Private Torrent Tracker for Classic Movies / TV / General Releases";
|
||||
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override TimeSpan RateLimit => TimeSpan.FromSeconds(2.1);
|
||||
|
||||
public RetroFlix(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger, IIndexerRepository indexerRepository)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger, indexerRepository)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q,
|
||||
TvSearchParam.Season,
|
||||
TvSearchParam.Ep,
|
||||
TvSearchParam.ImdbId
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q,
|
||||
MovieSearchParam.ImdbId
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q,
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q,
|
||||
},
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(401, NewznabStandardCategory.Movies, "Movies");
|
||||
caps.Categories.AddCategoryMapping(402, NewznabStandardCategory.TV, "TV Series");
|
||||
caps.Categories.AddCategoryMapping(406, NewznabStandardCategory.AudioVideo, "Music Videos");
|
||||
caps.Categories.AddCategoryMapping(407, NewznabStandardCategory.TVSport, "Sports");
|
||||
caps.Categories.AddCategoryMapping(409, NewznabStandardCategory.Books, "Books");
|
||||
caps.Categories.AddCategoryMapping(408, NewznabStandardCategory.Audio, "HQ Audio");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,6 +194,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||
|
||||
request.HttpRequest.AllowAutoRedirect = false;
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
|
||||
@@ -1480,6 +1480,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||
|
||||
request.HttpRequest.AllowAutoRedirect = false;
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,187 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class SpeedApp : TorrentIndexerBase<SpeedAppSettings>
|
||||
public class SpeedApp : SpeedAppBase
|
||||
{
|
||||
public override string Name => "SpeedApp.io";
|
||||
|
||||
public override string[] IndexerUrls => new string[] { "https://speedapp.io" };
|
||||
|
||||
private string ApiUrl => $"{Settings.BaseUrl}/api";
|
||||
|
||||
private string LoginUrl => $"{ApiUrl}/login";
|
||||
|
||||
public override string Description => "SpeedApp is a ROMANIAN Private Torrent Tracker for MOVIES / TV / GENERAL";
|
||||
|
||||
public override string Language => "ro-RO";
|
||||
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
private IIndexerRepository _indexerRepository;
|
||||
|
||||
public SpeedApp(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger, IIndexerRepository indexerRepository)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger, indexerRepository)
|
||||
{
|
||||
_indexerRepository = indexerRepository;
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new SpeedAppRequestGenerator(Capabilities, Settings);
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new SpeedAppParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
{
|
||||
return Settings.ApiKey.IsNullOrWhiteSpace() || httpResponse.StatusCode == HttpStatusCode.Unauthorized;
|
||||
}
|
||||
|
||||
protected override async Task DoLogin()
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(LoginUrl)
|
||||
{
|
||||
LogResponseContent = true,
|
||||
AllowAutoRedirect = true,
|
||||
Method = HttpMethod.Post,
|
||||
};
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
|
||||
var data = new SpeedAppAuthenticationRequest
|
||||
{
|
||||
Email = Settings.Email,
|
||||
Password = Settings.Password
|
||||
};
|
||||
|
||||
request.SetContent(JsonConvert.SerializeObject(data));
|
||||
|
||||
request.Headers.ContentType = MediaTypeNames.Application.Json;
|
||||
|
||||
var response = await ExecuteAuth(request);
|
||||
|
||||
var statusCode = (int)response.StatusCode;
|
||||
|
||||
if (statusCode is < 200 or > 299)
|
||||
{
|
||||
throw new HttpException(response);
|
||||
}
|
||||
|
||||
var parsedResponse = JsonConvert.DeserializeObject<SpeedAppAuthenticationResponse>(response.Content);
|
||||
|
||||
Settings.ApiKey = parsedResponse.Token;
|
||||
|
||||
if (Definition.Id > 0)
|
||||
{
|
||||
_indexerRepository.UpdateSettings((IndexerDefinition)Definition);
|
||||
}
|
||||
|
||||
_logger.Debug("SpeedApp authentication succeeded.");
|
||||
}
|
||||
|
||||
protected override void ModifyRequest(IndexerRequest request)
|
||||
{
|
||||
request.HttpRequest.Headers.Set("Authorization", $"Bearer {Settings.ApiKey}");
|
||||
}
|
||||
|
||||
public override async Task<byte[]> Download(Uri link)
|
||||
{
|
||||
Cookies = GetCookies();
|
||||
|
||||
if (link.Scheme == "magnet")
|
||||
{
|
||||
ValidateMagnet(link.OriginalString);
|
||||
return Encoding.UTF8.GetBytes(link.OriginalString);
|
||||
}
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder(link.AbsoluteUri);
|
||||
|
||||
if (Cookies != null)
|
||||
{
|
||||
requestBuilder.SetCookies(Cookies);
|
||||
}
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
request.AllowAutoRedirect = FollowRedirect;
|
||||
request.Headers.Set("Authorization", $"Bearer {Settings.ApiKey}");
|
||||
|
||||
byte[] torrentData;
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
|
||||
torrentData = response.ResponseData;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release failed since it no longer exists ({0})", link.AbsoluteUri);
|
||||
throw new ReleaseUnavailableException("Downloading torrent failed", ex);
|
||||
}
|
||||
|
||||
if (ex.Response.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
_logger.Error("API Grab Limit reached for {0}", link.AbsoluteUri);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release failed ({0})", link.AbsoluteUri);
|
||||
}
|
||||
|
||||
throw new ReleaseDownloadException("Downloading torrent failed", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release failed ({0})", link.AbsoluteUri);
|
||||
|
||||
throw new ReleaseDownloadException("Downloading torrent failed", ex);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_indexerStatusService.RecordFailure(Definition.Id);
|
||||
_logger.Error("Downloading torrent failed");
|
||||
throw;
|
||||
}
|
||||
|
||||
return torrentData;
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
protected override IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
@@ -253,356 +94,4 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
private IndexerCapabilities Capabilities { get; }
|
||||
|
||||
private SpeedAppSettings Settings { get; }
|
||||
|
||||
public SpeedAppRequestGenerator(IndexerCapabilities capabilities, SpeedAppSettings settings)
|
||||
{
|
||||
Capabilities = capabilities;
|
||||
Settings = settings;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria, searchCriteria.FullImdbId);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria, searchCriteria.FullImdbId, searchCriteria.Season, searchCriteria.Episode);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria);
|
||||
}
|
||||
|
||||
private IndexerPageableRequestChain GetSearch(SearchCriteriaBase searchCriteria, string imdbId = null, int? season = null, string episode = null)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories, imdbId, season, episode));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null, int? season = null, string episode = null)
|
||||
{
|
||||
var qc = new NameValueCollection();
|
||||
|
||||
if (imdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
qc.Add("imdbId", imdbId);
|
||||
}
|
||||
else
|
||||
{
|
||||
qc.Add("search", term);
|
||||
}
|
||||
|
||||
if (season != null)
|
||||
{
|
||||
qc.Add("season", season.Value.ToString());
|
||||
}
|
||||
|
||||
if (episode != null)
|
||||
{
|
||||
qc.Add("episode", episode);
|
||||
}
|
||||
|
||||
var cats = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
||||
|
||||
if (cats.Count > 0)
|
||||
{
|
||||
foreach (var cat in cats)
|
||||
{
|
||||
qc.Add("categories[]", cat);
|
||||
}
|
||||
}
|
||||
|
||||
var searchUrl = Settings.BaseUrl + "/api/torrent?" + qc.GetQueryString(duplicateKeysIfMulti: true);
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Json);
|
||||
|
||||
request.HttpRequest.Headers.Set("Authorization", $"Bearer {Settings.ApiKey}");
|
||||
|
||||
yield return request;
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly SpeedAppSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
public SpeedAppParser(SpeedAppSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
||||
}
|
||||
|
||||
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
|
||||
}
|
||||
|
||||
var jsonResponse = new HttpResponse<List<SpeedAppTorrent>>(indexerResponse.HttpResponse);
|
||||
|
||||
return jsonResponse.Resource.Select(torrent => new TorrentInfo
|
||||
{
|
||||
Guid = torrent.Id.ToString(),
|
||||
Title = torrent.Name,
|
||||
Description = torrent.ShortDescription,
|
||||
Size = torrent.Size,
|
||||
ImdbId = ParseUtil.GetImdbID(torrent.ImdbId).GetValueOrDefault(),
|
||||
DownloadUrl = $"{_settings.BaseUrl}/api/torrent/{torrent.Id}/download",
|
||||
PosterUrl = torrent.Poster,
|
||||
InfoUrl = torrent.Url,
|
||||
Grabs = torrent.TimesCompleted,
|
||||
PublishDate = torrent.CreatedAt,
|
||||
Categories = _categories.MapTrackerCatToNewznab(torrent.Category.Id.ToString()),
|
||||
InfoHash = null,
|
||||
Seeders = torrent.Seeders,
|
||||
Peers = torrent.Leechers + torrent.Seeders,
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 172800,
|
||||
DownloadVolumeFactor = torrent.DownloadVolumeFactor,
|
||||
UploadVolumeFactor = torrent.UploadVolumeFactor,
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppSettingsValidator : AbstractValidator<SpeedAppSettings>
|
||||
{
|
||||
public SpeedAppSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.Email).NotEmpty();
|
||||
RuleFor(c => c.Password).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppSettings : NoAuthTorrentBaseSettings
|
||||
{
|
||||
private static readonly SpeedAppSettingsValidator Validator = new ();
|
||||
|
||||
public SpeedAppSettings()
|
||||
{
|
||||
Email = "";
|
||||
Password = "";
|
||||
}
|
||||
|
||||
[FieldDefinition(2, Label = "Email", HelpText = "Site Email", Privacy = PrivacyLevel.UserName)]
|
||||
public string Email { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "API Key", Hidden = HiddenType.Hidden)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppCategory
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppCountry
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("flag_image")]
|
||||
public string FlagImage { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppUploadedBy
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("username")]
|
||||
public string Username { get; set; }
|
||||
|
||||
[JsonProperty("email")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[JsonProperty("created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
[JsonProperty("class")]
|
||||
public int Class { get; set; }
|
||||
|
||||
[JsonProperty("avatar")]
|
||||
public string Avatar { get; set; }
|
||||
|
||||
[JsonProperty("uploaded")]
|
||||
public int Uploaded { get; set; }
|
||||
|
||||
[JsonProperty("downloaded")]
|
||||
public int Downloaded { get; set; }
|
||||
|
||||
[JsonProperty("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonProperty("country")]
|
||||
public SpeedAppCountry Country { get; set; }
|
||||
|
||||
[JsonProperty("passkey")]
|
||||
public string Passkey { get; set; }
|
||||
|
||||
[JsonProperty("invites")]
|
||||
public int Invites { get; set; }
|
||||
|
||||
[JsonProperty("timezone")]
|
||||
public string Timezone { get; set; }
|
||||
|
||||
[JsonProperty("hit_and_run_count")]
|
||||
public int HitAndRunCount { get; set; }
|
||||
|
||||
[JsonProperty("snatch_count")]
|
||||
public int SnatchCount { get; set; }
|
||||
|
||||
[JsonProperty("need_seed")]
|
||||
public int NeedSeed { get; set; }
|
||||
|
||||
[JsonProperty("average_seed_time")]
|
||||
public int AverageSeedTime { get; set; }
|
||||
|
||||
[JsonProperty("free_leech_tokens")]
|
||||
public int FreeLeechTokens { get; set; }
|
||||
|
||||
[JsonProperty("double_upload_tokens")]
|
||||
public int DoubleUploadTokens { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppTag
|
||||
{
|
||||
[JsonProperty("translated_name")]
|
||||
public string TranslatedName { get; set; }
|
||||
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("match_list")]
|
||||
public List<string> MatchList { get; set; }
|
||||
|
||||
[JsonProperty("created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppTorrent
|
||||
{
|
||||
[JsonProperty("download_volume_factor")]
|
||||
public float DownloadVolumeFactor { get; set; }
|
||||
|
||||
[JsonProperty("upload_volume_factor")]
|
||||
public float UploadVolumeFactor { get; set; }
|
||||
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[JsonProperty("category")]
|
||||
public SpeedAppCategory Category { get; set; }
|
||||
|
||||
[JsonProperty("size")]
|
||||
public long Size { get; set; }
|
||||
|
||||
[JsonProperty("created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
[JsonProperty("times_completed")]
|
||||
public int TimesCompleted { get; set; }
|
||||
|
||||
[JsonProperty("leechers")]
|
||||
public int Leechers { get; set; }
|
||||
|
||||
[JsonProperty("seeders")]
|
||||
public int Seeders { get; set; }
|
||||
|
||||
[JsonProperty("uploaded_by")]
|
||||
public SpeedAppUploadedBy UploadedBy { get; set; }
|
||||
|
||||
[JsonProperty("short_description")]
|
||||
public string ShortDescription { get; set; }
|
||||
|
||||
[JsonProperty("poster")]
|
||||
public string Poster { get; set; }
|
||||
|
||||
[JsonProperty("season")]
|
||||
public int Season { get; set; }
|
||||
|
||||
[JsonProperty("episode")]
|
||||
public int Episode { get; set; }
|
||||
|
||||
[JsonProperty("tags")]
|
||||
public List<SpeedAppTag> Tags { get; set; }
|
||||
|
||||
[JsonProperty("imdb_id")]
|
||||
public string ImdbId { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppAuthenticationRequest
|
||||
{
|
||||
[JsonProperty("username")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[JsonProperty("password")]
|
||||
public string Password { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppAuthenticationResponse
|
||||
{
|
||||
[JsonProperty("token")]
|
||||
public string Token { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
533
src/NzbDrone.Core/Indexers/Definitions/SpeedApp/SpeedAppBase.cs
Normal file
533
src/NzbDrone.Core/Indexers/Definitions/SpeedApp/SpeedAppBase.cs
Normal file
@@ -0,0 +1,533 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public abstract class SpeedAppBase : TorrentIndexerBase<SpeedAppSettings>
|
||||
{
|
||||
private string ApiUrl => $"{Settings.BaseUrl}/api";
|
||||
|
||||
private string LoginUrl => $"{ApiUrl}/login";
|
||||
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
private IIndexerRepository _indexerRepository;
|
||||
|
||||
public SpeedAppBase(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger, IIndexerRepository indexerRepository)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
_indexerRepository = indexerRepository;
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new SpeedAppRequestGenerator(Capabilities, Settings);
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new SpeedAppParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
{
|
||||
return Settings.ApiKey.IsNullOrWhiteSpace() || httpResponse.StatusCode == HttpStatusCode.Unauthorized;
|
||||
}
|
||||
|
||||
protected override async Task DoLogin()
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(LoginUrl)
|
||||
{
|
||||
LogResponseContent = true,
|
||||
AllowAutoRedirect = true,
|
||||
Method = HttpMethod.Post,
|
||||
};
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
|
||||
var data = new SpeedAppAuthenticationRequest
|
||||
{
|
||||
Email = Settings.Email,
|
||||
Password = Settings.Password
|
||||
};
|
||||
|
||||
request.SetContent(JsonConvert.SerializeObject(data));
|
||||
|
||||
request.Headers.ContentType = MediaTypeNames.Application.Json;
|
||||
|
||||
var response = await ExecuteAuth(request);
|
||||
|
||||
var statusCode = (int)response.StatusCode;
|
||||
|
||||
if (statusCode is < 200 or > 299)
|
||||
{
|
||||
throw new HttpException(response);
|
||||
}
|
||||
|
||||
var parsedResponse = JsonConvert.DeserializeObject<SpeedAppAuthenticationResponse>(response.Content);
|
||||
|
||||
Settings.ApiKey = parsedResponse.Token;
|
||||
|
||||
if (Definition.Id > 0)
|
||||
{
|
||||
_indexerRepository.UpdateSettings((IndexerDefinition)Definition);
|
||||
}
|
||||
|
||||
_logger.Debug("SpeedApp authentication succeeded.");
|
||||
}
|
||||
|
||||
protected override void ModifyRequest(IndexerRequest request)
|
||||
{
|
||||
request.HttpRequest.Headers.Set("Authorization", $"Bearer {Settings.ApiKey}");
|
||||
}
|
||||
|
||||
public override async Task<byte[]> Download(Uri link)
|
||||
{
|
||||
Cookies = GetCookies();
|
||||
|
||||
if (link.Scheme == "magnet")
|
||||
{
|
||||
ValidateMagnet(link.OriginalString);
|
||||
return Encoding.UTF8.GetBytes(link.OriginalString);
|
||||
}
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder(link.AbsoluteUri);
|
||||
|
||||
if (Cookies != null)
|
||||
{
|
||||
requestBuilder.SetCookies(Cookies);
|
||||
}
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
request.AllowAutoRedirect = FollowRedirect;
|
||||
request.Headers.Set("Authorization", $"Bearer {Settings.ApiKey}");
|
||||
|
||||
byte[] torrentData;
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
|
||||
torrentData = response.ResponseData;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release failed since it no longer exists ({0})", link.AbsoluteUri);
|
||||
throw new ReleaseUnavailableException("Downloading torrent failed", ex);
|
||||
}
|
||||
|
||||
if (ex.Response.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
_logger.Error("API Grab Limit reached for {0}", link.AbsoluteUri);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release failed ({0})", link.AbsoluteUri);
|
||||
}
|
||||
|
||||
throw new ReleaseDownloadException("Downloading torrent failed", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release failed ({0})", link.AbsoluteUri);
|
||||
|
||||
throw new ReleaseDownloadException("Downloading torrent failed", ex);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_indexerStatusService.RecordFailure(Definition.Id);
|
||||
_logger.Error("Downloading torrent failed");
|
||||
throw;
|
||||
}
|
||||
|
||||
return torrentData;
|
||||
}
|
||||
|
||||
protected virtual IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities();
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
private IndexerCapabilities Capabilities { get; }
|
||||
|
||||
private SpeedAppSettings Settings { get; }
|
||||
|
||||
public SpeedAppRequestGenerator(IndexerCapabilities capabilities, SpeedAppSettings settings)
|
||||
{
|
||||
Capabilities = capabilities;
|
||||
Settings = settings;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria, searchCriteria.FullImdbId);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria, searchCriteria.FullImdbId, searchCriteria.Season, searchCriteria.Episode);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria);
|
||||
}
|
||||
|
||||
private IndexerPageableRequestChain GetSearch(SearchCriteriaBase searchCriteria, string imdbId = null, int? season = null, string episode = null)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories, imdbId, season, episode));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null, int? season = null, string episode = null)
|
||||
{
|
||||
var qc = new NameValueCollection();
|
||||
|
||||
if (imdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
qc.Add("imdbId", imdbId);
|
||||
}
|
||||
else
|
||||
{
|
||||
qc.Add("search", term);
|
||||
}
|
||||
|
||||
if (season != null)
|
||||
{
|
||||
qc.Add("season", season.Value.ToString());
|
||||
}
|
||||
|
||||
if (episode != null)
|
||||
{
|
||||
qc.Add("episode", episode);
|
||||
}
|
||||
|
||||
var cats = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
||||
|
||||
if (cats.Count > 0)
|
||||
{
|
||||
foreach (var cat in cats)
|
||||
{
|
||||
qc.Add("categories[]", cat);
|
||||
}
|
||||
}
|
||||
|
||||
var searchUrl = Settings.BaseUrl + "/api/torrent?" + qc.GetQueryString(duplicateKeysIfMulti: true);
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Json);
|
||||
|
||||
request.HttpRequest.Headers.Set("Authorization", $"Bearer {Settings.ApiKey}");
|
||||
|
||||
yield return request;
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly SpeedAppSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
public SpeedAppParser(SpeedAppSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
||||
}
|
||||
|
||||
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
|
||||
}
|
||||
|
||||
var jsonResponse = new HttpResponse<List<SpeedAppTorrent>>(indexerResponse.HttpResponse);
|
||||
|
||||
return jsonResponse.Resource.Select(torrent => new TorrentInfo
|
||||
{
|
||||
Guid = torrent.Id.ToString(),
|
||||
Title = torrent.Name,
|
||||
Description = torrent.ShortDescription,
|
||||
Size = torrent.Size,
|
||||
ImdbId = ParseUtil.GetImdbID(torrent.ImdbId).GetValueOrDefault(),
|
||||
DownloadUrl = $"{_settings.BaseUrl}/api/torrent/{torrent.Id}/download",
|
||||
PosterUrl = torrent.Poster,
|
||||
InfoUrl = torrent.Url,
|
||||
Grabs = torrent.TimesCompleted,
|
||||
PublishDate = torrent.CreatedAt,
|
||||
Categories = _categories.MapTrackerCatToNewznab(torrent.Category.Id.ToString()),
|
||||
InfoHash = null,
|
||||
Seeders = torrent.Seeders,
|
||||
Peers = torrent.Leechers + torrent.Seeders,
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 172800,
|
||||
DownloadVolumeFactor = torrent.DownloadVolumeFactor,
|
||||
UploadVolumeFactor = torrent.UploadVolumeFactor,
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppSettingsValidator : AbstractValidator<SpeedAppSettings>
|
||||
{
|
||||
public SpeedAppSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.Email).NotEmpty();
|
||||
RuleFor(c => c.Password).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppSettings : NoAuthTorrentBaseSettings
|
||||
{
|
||||
private static readonly SpeedAppSettingsValidator Validator = new ();
|
||||
|
||||
public SpeedAppSettings()
|
||||
{
|
||||
Email = "";
|
||||
Password = "";
|
||||
}
|
||||
|
||||
[FieldDefinition(2, Label = "Email", HelpText = "Site Email", Privacy = PrivacyLevel.UserName)]
|
||||
public string Email { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "API Key", Hidden = HiddenType.Hidden)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppCategory
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppCountry
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("flag_image")]
|
||||
public string FlagImage { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppUploadedBy
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("username")]
|
||||
public string Username { get; set; }
|
||||
|
||||
[JsonProperty("email")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[JsonProperty("created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
[JsonProperty("class")]
|
||||
public int Class { get; set; }
|
||||
|
||||
[JsonProperty("avatar")]
|
||||
public string Avatar { get; set; }
|
||||
|
||||
[JsonProperty("uploaded")]
|
||||
public int Uploaded { get; set; }
|
||||
|
||||
[JsonProperty("downloaded")]
|
||||
public int Downloaded { get; set; }
|
||||
|
||||
[JsonProperty("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonProperty("country")]
|
||||
public SpeedAppCountry Country { get; set; }
|
||||
|
||||
[JsonProperty("passkey")]
|
||||
public string Passkey { get; set; }
|
||||
|
||||
[JsonProperty("invites")]
|
||||
public int Invites { get; set; }
|
||||
|
||||
[JsonProperty("timezone")]
|
||||
public string Timezone { get; set; }
|
||||
|
||||
[JsonProperty("hit_and_run_count")]
|
||||
public int HitAndRunCount { get; set; }
|
||||
|
||||
[JsonProperty("snatch_count")]
|
||||
public int SnatchCount { get; set; }
|
||||
|
||||
[JsonProperty("need_seed")]
|
||||
public int NeedSeed { get; set; }
|
||||
|
||||
[JsonProperty("average_seed_time")]
|
||||
public int AverageSeedTime { get; set; }
|
||||
|
||||
[JsonProperty("free_leech_tokens")]
|
||||
public int FreeLeechTokens { get; set; }
|
||||
|
||||
[JsonProperty("double_upload_tokens")]
|
||||
public int DoubleUploadTokens { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppTag
|
||||
{
|
||||
[JsonProperty("translated_name")]
|
||||
public string TranslatedName { get; set; }
|
||||
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("match_list")]
|
||||
public List<string> MatchList { get; set; }
|
||||
|
||||
[JsonProperty("created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppTorrent
|
||||
{
|
||||
[JsonProperty("download_volume_factor")]
|
||||
public float DownloadVolumeFactor { get; set; }
|
||||
|
||||
[JsonProperty("upload_volume_factor")]
|
||||
public float UploadVolumeFactor { get; set; }
|
||||
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[JsonProperty("category")]
|
||||
public SpeedAppCategory Category { get; set; }
|
||||
|
||||
[JsonProperty("size")]
|
||||
public long Size { get; set; }
|
||||
|
||||
[JsonProperty("created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
[JsonProperty("times_completed")]
|
||||
public int TimesCompleted { get; set; }
|
||||
|
||||
[JsonProperty("leechers")]
|
||||
public int Leechers { get; set; }
|
||||
|
||||
[JsonProperty("seeders")]
|
||||
public int Seeders { get; set; }
|
||||
|
||||
[JsonProperty("uploaded_by")]
|
||||
public SpeedAppUploadedBy UploadedBy { get; set; }
|
||||
|
||||
[JsonProperty("short_description")]
|
||||
public string ShortDescription { get; set; }
|
||||
|
||||
[JsonProperty("poster")]
|
||||
public string Poster { get; set; }
|
||||
|
||||
[JsonProperty("season")]
|
||||
public int Season { get; set; }
|
||||
|
||||
[JsonProperty("episode")]
|
||||
public int Episode { get; set; }
|
||||
|
||||
[JsonProperty("tags")]
|
||||
public List<SpeedAppTag> Tags { get; set; }
|
||||
|
||||
[JsonProperty("imdb_id")]
|
||||
public string ImdbId { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppAuthenticationRequest
|
||||
{
|
||||
[JsonProperty("username")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[JsonProperty("password")]
|
||||
public string Password { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppAuthenticationResponse
|
||||
{
|
||||
[JsonProperty("token")]
|
||||
public string Token { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -355,8 +355,6 @@ namespace NzbDrone.Core.Indexers
|
||||
request.HttpRequest.LogResponseContent = true;
|
||||
}
|
||||
|
||||
request.HttpRequest.AllowAutoRedirect = FollowRedirect;
|
||||
|
||||
var originalUrl = request.Url;
|
||||
|
||||
Cookies = GetCookies();
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Indexers
|
||||
[FieldDefinition(1, Type = FieldType.Textbox, Label = "Seed Ratio", HelpText = "The ratio a torrent should reach before stopping, empty is app's default", Advanced = true)]
|
||||
public double? SeedRatio { get; set; }
|
||||
|
||||
[FieldDefinition(2, Type = FieldType.Number, Label = "Seed Time", HelpText = "The time a torrent should be seeded before stopping, empty is app's default", Advanced = true)]
|
||||
[FieldDefinition(2, Type = FieldType.Number, Label = "Seed Time", HelpText = "The time a torrent should be seeded before stopping, empty is app's default", Unit = "minutes", Advanced = true)]
|
||||
public int? SeedTime { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,7 +397,7 @@
|
||||
"BookSearch": "Buch Suche",
|
||||
"Id": "Id",
|
||||
"IndexerProxies": "Indexer-Proxies",
|
||||
"IndexerTagsHelpText": "Benutze Tags, um Indexer-Proxies zu spezifizieren oder um Indexer zu organisieren.",
|
||||
"IndexerTagsHelpText": "Benutze Tags, um Indexer-Proxies zu spezifizieren, mit welchen Apps der Indexer synchronisiert oder um Indexer zu organisieren.",
|
||||
"MovieSearch": "Film Suche",
|
||||
"QueryOptions": "Abfrage-Optionen",
|
||||
"Categories": "Kategorien",
|
||||
@@ -433,5 +433,32 @@
|
||||
"UnableToLoadIndexers": "Indexer konnten nicht geladen werden",
|
||||
"Yes": "Ja",
|
||||
"InstanceName": "Instanzname",
|
||||
"InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname"
|
||||
"InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname",
|
||||
"SyncProfiles": "Sync-Profile",
|
||||
"ThemeHelpText": "Prowlarr UI Theme ändern, inspiriert von {0}",
|
||||
"Duration": "Dauer",
|
||||
"EditSyncProfile": "Synchronisationsprofil bearbeiten",
|
||||
"ElapsedTime": "Vergangene Zeit",
|
||||
"EnabledRedirected": "Aktiviert, Weitergeleitet",
|
||||
"Ended": "Beendet",
|
||||
"GrabTitle": "Titel holen",
|
||||
"LastDuration": "Letzte Dauer",
|
||||
"LastExecution": "Letzte Ausführung",
|
||||
"MinimumSeeders": "Mindest-Seeder",
|
||||
"MinimumSeedersHelpText": "Mindest-Seeder sind benötigt von der App für den Indexer um zu holen",
|
||||
"NextExecution": "Nächste Ausführung",
|
||||
"Parameters": "Parameter",
|
||||
"Queued": "In der Warteschlange",
|
||||
"Started": "gestartet",
|
||||
"SyncProfile": "Sync-Profile",
|
||||
"IndexerSite": "Indexer-Seite",
|
||||
"MovieSearchTypes": "Film-Suchtypen",
|
||||
"MusicSearchTypes": "Musik-Suchtypen",
|
||||
"NotSupported": "Nicht unterstützt",
|
||||
"RawSearchSupported": "Raw-Suche unterstützt",
|
||||
"SearchCapabilities": "Suchfähigkeiten",
|
||||
"AddSyncProfile": "Synchronisationsprofil hinzufügen",
|
||||
"BookSearchTypes": "Buch-Suchtypen",
|
||||
"IndexerDetails": "Indexer-Details",
|
||||
"IndexerName": "Indexer-Name"
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
"AppDataDirectory": "AppData directory",
|
||||
"AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update",
|
||||
"Application": "Application",
|
||||
"ApplicationLongTermStatusCheckAllClientMessage": "All applications are unavailable due to failures for more than 6 hours",
|
||||
"ApplicationLongTermStatusCheckSingleClientMessage": "Applications unavailable due to failures for more than 6 hours: {0}",
|
||||
"Applications": "Applications",
|
||||
"ApplicationStatusCheckAllClientMessage": "All applications are unavailable due to failures",
|
||||
"ApplicationStatusCheckSingleClientMessage": "Applications unavailable due to failures: {0}",
|
||||
|
||||
@@ -336,8 +336,8 @@
|
||||
"OnHealthIssue": "En Problema de Salud",
|
||||
"TestAllIndexers": "Comprobar Todos los Indexers",
|
||||
"NotificationTriggersHelpText": "Seleccione qué eventos deben activar esta notificación",
|
||||
"OnApplicationUpdate": "Al actualizar la aplicación",
|
||||
"OnApplicationUpdateHelpText": "Al actualizar la aplicación",
|
||||
"OnApplicationUpdate": "Al Actualizar La Aplicación",
|
||||
"OnApplicationUpdateHelpText": "Al Actualizar La Aplicación",
|
||||
"AddRemoveOnly": "Sólo añadir y eliminar",
|
||||
"AddedToDownloadClient": "Descarga añadida al cliente",
|
||||
"AddNewIndexer": "Añadir nuevo indexador",
|
||||
@@ -361,5 +361,8 @@
|
||||
"Notifications": "Notificaciones",
|
||||
"UnableToLoadIndexers": "No se pueden cargar los indexers",
|
||||
"Yes": "si",
|
||||
"UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent proporcionado por la aplicación llamó a la API"
|
||||
"UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent proporcionado por la aplicación llamó a la API",
|
||||
"InstanceName": "Nombre de Instancia",
|
||||
"InstanceNameHelpText": "Nombre de instancia en pestaña y para nombre de aplicación en Syslog",
|
||||
"Database": "Base de Datos"
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"UpdateCheckStartupTranslocationMessage": "Päivitystä ei voi asentaa, koska käynnistyskansio '{0}' sijaitsee 'App Translocation' -kansiossa.",
|
||||
"UpdateCheckUINotWritableMessage": "Päivitystä ei voi asentaa, koska käyttäjällä '{1}' ei ole kirjoitusoikeutta käyttöliittymäkansioon '{0}'.",
|
||||
"UpdateMechanismHelpText": "Käytä Prowlarrin sisäänrakennettua päivitystoimintoa tai omaa komentosarjaasi.",
|
||||
"ApplyTagsHelpTexts3": "– 'Poista' ainoastaan syötetyt tunnisteet",
|
||||
"ApplyTagsHelpTexts3": "- \"Poista\" tyhjentää syötetyt tunnisteet.",
|
||||
"Enable": "Käytä",
|
||||
"UI": "Käyttöliittymä",
|
||||
"UrlBaseHelpText": "Käänteisen välityspalvelimen tuki (esim. 'http://[host]:[port]/[urlBase]'). Käytä oletusta jättämällä tyhjäksi.",
|
||||
@@ -71,10 +71,10 @@
|
||||
"Username": "Käyttäjätunnus",
|
||||
"YesCancel": "Kyllä, peruuta",
|
||||
"NoTagsHaveBeenAddedYet": "Tunnisteita ei ole vielä lisätty.",
|
||||
"ApplyTags": "Toimenpide tunnisteille",
|
||||
"ApplyTags": "Tunnistetoimenpide",
|
||||
"Authentication": "Todennus",
|
||||
"AuthenticationMethodHelpText": "Vaadi käyttäjätunnus ja salasana.",
|
||||
"BindAddressHelpText": "Toimiva IPv4-osoite tai jokerimerkkinä '*' (tähti) kaikille yhteyksille.",
|
||||
"BindAddressHelpText": "Toimiva IPv4-osoite tai '*' (tähti) kaikille yhteyksille.",
|
||||
"Close": "Sulje",
|
||||
"DeleteNotification": "Poista kytkentä",
|
||||
"Docker": "Docker",
|
||||
@@ -186,7 +186,7 @@
|
||||
"Discord": "Discord",
|
||||
"Donations": "Lahjoitukset",
|
||||
"Edit": "Muokkaa",
|
||||
"EnableAutomaticSearchHelpText": "Profiilia käytetään automaattihaun yhteydessä, kun sellainen suoritetaan käyttöliittymästä tai Prowlarin toimesta.",
|
||||
"EnableAutomaticSearchHelpText": "Profiilia käytetään automaattihaun yhteydessä, kun haku suoritetaan käyttöliittymästä tai Prowlarrin toimesta.",
|
||||
"Enabled": "Käytössä",
|
||||
"EventType": "Tapahtumatyyppi",
|
||||
"Exception": "Poikkeus",
|
||||
@@ -224,7 +224,7 @@
|
||||
"Wiki": "Wiki",
|
||||
"ApplyTagsHelpTexts1": "Tunnisteisiin kohdistettavat toimenpiteet:",
|
||||
"ApplyTagsHelpTexts2": "– 'Lisää' syötetyt tunnisteet aiempiin tunnisteisiin",
|
||||
"ApplyTagsHelpTexts4": "– 'Korvaa' kaikki aiemmat tunnisteet tai poista kaikki tunnisteet jättämällä tyhjäksi",
|
||||
"ApplyTagsHelpTexts4": "- \"Korvaa\" nykyiset tunnisteet syötetyillä tai tyhjennä kaikki tunnisteet jättämällä tyhjäksi.",
|
||||
"Port": "Portti",
|
||||
"AreYouSureYouWantToResetYourAPIKey": "Haluatko varmasti uudistaa API-avaimesi?",
|
||||
"Automatic": "Automaattinen",
|
||||
@@ -243,7 +243,7 @@
|
||||
"Cancel": "Peruuta",
|
||||
"CancelPendingTask": "Haluatko varmasti perua tämän odottavan tehtävän?",
|
||||
"CertificateValidation": "Varmenteen vahvistus",
|
||||
"CertificateValidationHelpText": "Valitse HTTPS-varmenteen vahvistuksen tarkkuus. Älä muuta, jollet ymmärrä tähän liittyviä riskejä.",
|
||||
"CertificateValidationHelpText": "Muuta HTTPS-varmennevahvistuksen tarkkuutta. Älä muuta, jollet ymmärrä tähän liittyviä riskejä.",
|
||||
"ChangeHasNotBeenSavedYet": "Muutosta ei ole vielä tallennettu",
|
||||
"Clear": "Tyhjennä",
|
||||
"CloneProfile": "Kloonaa profiili",
|
||||
@@ -402,7 +402,7 @@
|
||||
"Filters": "Suodattimet",
|
||||
"OnGrab": "Kun elokuva siepataan",
|
||||
"OnHealthIssue": "Kun havaitaan kuntoon liittyvä ongelma",
|
||||
"HistoryCleanupDaysHelpText": "Älä tyhjennä automaattisesti asettamalla arvoksi '0'.",
|
||||
"HistoryCleanupDaysHelpText": "Poista automaattinen tyhjennys käytöstä asettamalla arvoksi '0'.",
|
||||
"HistoryCleanupDaysHelpTextWarning": "Tässä määritettyä aikaa vanhemmat tiedostot poistetaan automaattisesti roskakorista pysyvästi.",
|
||||
"TestAllIndexers": "Testaa tietolähteet",
|
||||
"UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent-tiedon ilmoitti sovellus, joka kommunikoi API:n kanssa",
|
||||
|
||||
@@ -358,7 +358,7 @@
|
||||
"Description": "Description",
|
||||
"Donations": "Dons",
|
||||
"Enabled": "Activé",
|
||||
"Grabs": "Saisie",
|
||||
"Grabs": "Complétés",
|
||||
"Id": "Id",
|
||||
"Presets": "Préconfigurations",
|
||||
"Privacy": "Vie privée",
|
||||
@@ -440,5 +440,9 @@
|
||||
"MovieSearchTypes": "Types de recherches de films",
|
||||
"MusicSearchTypes": "Type de recherche de musiques",
|
||||
"NotSupported": "Non supporté",
|
||||
"SearchCapabilities": "Capacités de recherche"
|
||||
"SearchCapabilities": "Capacités de recherche",
|
||||
"Duration": "Durée",
|
||||
"LastDuration": "Dernière durée",
|
||||
"InstanceName": "Nom de l'instance",
|
||||
"InstanceNameHelpText": "Nom de l'instance dans l'onglet du navigateur et pour le nom d'application dans Syslog"
|
||||
}
|
||||
|
||||
@@ -460,5 +460,7 @@
|
||||
"Parameters": "Paraméterek",
|
||||
"Queued": "Sorba helyezve",
|
||||
"Started": "Elkezdődött",
|
||||
"NextExecution": "Következő végrehajtás"
|
||||
"NextExecution": "Következő végrehajtás",
|
||||
"ApplicationLongTermStatusCheckSingleClientMessage": "Az alkamazások elérhetetlenek több mint 6 órája az alábbi hiba miatt: {0}",
|
||||
"ApplicationLongTermStatusCheckAllClientMessage": "Az összes alkalmazás elérhetetlen több mint 6 órája meghibásodás miatt"
|
||||
}
|
||||
|
||||
1
src/NzbDrone.Core/Localization/Core/lv.json
Normal file
1
src/NzbDrone.Core/Localization/Core/lv.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -301,8 +301,8 @@
|
||||
"IndexerLongTermStatusCheckAllClientMessage": "Alle indexeerders zijn niet beschikbaar vanwege storingen gedurende meer dan 6 uur",
|
||||
"ClearHistoryMessageText": "Weet je zeker dat je alle geschiedenis van Prowlarr wilt verwijderen",
|
||||
"ClearHistory": "Geschiedenis verwijderen",
|
||||
"ApplicationStatusCheckSingleClientMessage": "Applicaties niet toegankelijk door fouten",
|
||||
"ApplicationStatusCheckAllClientMessage": "Alle applicaties niet toegankelijk door fouten",
|
||||
"ApplicationStatusCheckSingleClientMessage": "Applicaties onbeschikbaar door fouten",
|
||||
"ApplicationStatusCheckAllClientMessage": "Alle applicaties onbeschikbaar door fouten",
|
||||
"AllIndexersHiddenDueToFilter": "Alle indexeerders zijn verborgen door actieve filter",
|
||||
"AddToDownloadClient": "Release toevoegen aan download client",
|
||||
"AddNewIndexer": "Voeg nieuwe Indexeerder Toe",
|
||||
|
||||
@@ -153,7 +153,7 @@
|
||||
"NetCore": ".NET",
|
||||
"Mode": "Modo",
|
||||
"Mechanism": "Mecanismo",
|
||||
"Logs": "Logs",
|
||||
"Logs": "Registos",
|
||||
"LogLevel": "Nível de log",
|
||||
"Interval": "Intervalo",
|
||||
"IndexerFlags": "Sinalizadores do indexador",
|
||||
@@ -392,7 +392,7 @@
|
||||
"UserAgentProvidedByTheAppThatCalledTheAPI": "Par Utilizador-Agente fornecido pela aplicação que chamou a API",
|
||||
"OnApplicationUpdate": "Quando a aplicação atualizar",
|
||||
"OnApplicationUpdateHelpText": "Quando a aplicação atualizar",
|
||||
"Database": "base de dados",
|
||||
"Database": "Base de dados",
|
||||
"HistoryCleanupDaysHelpTextWarning": "Ficheiros na reciclagem serão eliminados automaticamente após o número de dias selecionado",
|
||||
"Application": "Aplicações",
|
||||
"Link": "Ligações",
|
||||
@@ -401,5 +401,8 @@
|
||||
"UnableToLoadIndexers": "Não foi possível carregar os indexadores",
|
||||
"Yes": "Sim",
|
||||
"GrabReleases": "Capturar versão",
|
||||
"InstanceName": "Nome da Instancia"
|
||||
"InstanceName": "Nome da Instancia",
|
||||
"InstanceNameHelpText": "Nome da instância na aba e nome da aplicação para Syslog",
|
||||
"UnableToLoadIndexerProxies": "Incapaz de ler o indexador de proxies",
|
||||
"UnableToLoadApplicationList": "Não foi possível carregar a lista de aplicações"
|
||||
}
|
||||
|
||||
@@ -404,7 +404,7 @@
|
||||
"HistoryCleanupDaysHelpText": "Defina como 0 para desabilitar a limpeza automática",
|
||||
"OnApplicationUpdate": "Ao Atualizar o Aplicativo",
|
||||
"OnApplicationUpdateHelpText": "Ao Atualizar o Aplicativo",
|
||||
"OnGrab": "Ao Baixar",
|
||||
"OnGrab": "Em Espera",
|
||||
"OnHealthIssue": "Ao ter problema de integridade",
|
||||
"TestAllIndexers": "Testar todos os indexadores",
|
||||
"UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent fornecido pelo aplicativo que chamou a API",
|
||||
@@ -460,5 +460,7 @@
|
||||
"GrabTitle": "Obter Título",
|
||||
"LastDuration": "Última Duração",
|
||||
"NextExecution": "Próxima Execução",
|
||||
"Started": "Iniciado"
|
||||
"Started": "Iniciado",
|
||||
"ApplicationLongTermStatusCheckAllClientMessage": "Todos os aplicativos estão indisponíveis devido a falhas por mais de 6 horas",
|
||||
"ApplicationLongTermStatusCheckSingleClientMessage": "Aplicativos indisponíveis devido a falhas por mais de 6 horas: {0}"
|
||||
}
|
||||
|
||||
@@ -19,25 +19,25 @@
|
||||
"Added": "Adăugat",
|
||||
"Actions": "Acțiuni",
|
||||
"About": "Despre",
|
||||
"IndexerStatusCheckAllClientMessage": "Niciun indexator nu este disponibil datorită eșuărilor",
|
||||
"IndexerStatusCheckAllClientMessage": "Niciun indexator nu este disponibil datorită erorilor",
|
||||
"Indexers": "Indexatori",
|
||||
"Indexer": "Indexator",
|
||||
"Host": "Gazdă",
|
||||
"History": "Istorie",
|
||||
"History": "Istoric",
|
||||
"HideAdvanced": "Ascunde Avansat",
|
||||
"Health": "Sănătate",
|
||||
"Grabbed": "Prins",
|
||||
"Grabbed": "În curs de descărcare",
|
||||
"GeneralSettingsSummary": "Port, SSL, utilizator/parolă, proxy, statistici și actualizări",
|
||||
"General": "General",
|
||||
"Folder": "Dosar",
|
||||
"Filter": "Filtru",
|
||||
"Files": "Fișiere",
|
||||
"Filename": "Numele Fișierului",
|
||||
"Filename": "Numele fișierului",
|
||||
"Failed": "Eșuat",
|
||||
"EventType": "Tip de eveniment",
|
||||
"Events": "Evenimente",
|
||||
"Edit": "Editează",
|
||||
"DownloadClientStatusCheckSingleClientMessage": "Clienții de descărcare sunt indisponibili datorită erorii: {0}",
|
||||
"DownloadClientStatusCheckSingleClientMessage": "Clienții de descărcare sunt indisponibili datorită erorilor: {0}",
|
||||
"DownloadClientStatusCheckAllClientMessage": "Toți clienții de descărcare sunt indisponibili datorită erorilor",
|
||||
"Peers": "Parteneri",
|
||||
"PageSize": "Mărimea Paginii",
|
||||
@@ -56,11 +56,11 @@
|
||||
"Language": "Limbă",
|
||||
"KeyboardShortcuts": "Scurtături din tastatură",
|
||||
"Info": "Info",
|
||||
"IndexerStatusCheckSingleClientMessage": "Indexator indisponibil datorită erorilor: {0}",
|
||||
"HealthNoIssues": "Nicio problemă în configurare",
|
||||
"IndexerStatusCheckSingleClientMessage": "Indexatoare indisponibile datorită erorilor: {0}",
|
||||
"HealthNoIssues": "Nicio problemă de configurare",
|
||||
"Error": "Eroare",
|
||||
"ConnectionLostMessage": "Prowlarr a pierdut conexiunea cu backend-ul și trebuie reîncărcat pentru a restabili funcționalitatea.",
|
||||
"ConnectionLostAutomaticMessage": "Prowlarr va încerca să se reconecteze automat sau poți apăsa reîncarcă mai jos.",
|
||||
"ConnectionLostAutomaticMessage": "Prowlarr va încerca să se conecteze automat, sau poți apăsa reîncarcă mai jos.",
|
||||
"ConnectionLost": "Conexiune Pierdută",
|
||||
"Component": "Componentă",
|
||||
"Columns": "Coloane",
|
||||
@@ -68,7 +68,7 @@
|
||||
"Cancel": "Anulează",
|
||||
"Apply": "Aplică",
|
||||
"Age": "Vechime",
|
||||
"PtpOldSettingsCheckMessage": "Următorul indexer PassThePopcorn are setări depreciate și ar trebui actualizate: {0}",
|
||||
"PtpOldSettingsCheckMessage": "Următoarele indexatoare PassThePopcorn au setări depreciate și ar trebui actualizate: {0}",
|
||||
"ProxyCheckResolveIpMessage": "Nu am putut găsi adresa IP pentru Hostul Proxy Configurat {0}",
|
||||
"ProxyCheckFailedToTestMessage": "Nu am putut testa proxy: {0}",
|
||||
"ProxyCheckBadRequestMessage": "Testul proxy a eșuat. StatusCode: {0}",
|
||||
@@ -125,7 +125,7 @@
|
||||
"SettingsLongDateFormat": "Format de dată lungă",
|
||||
"SettingsShortDateFormat": "Format scurt de dată",
|
||||
"SettingsTimeFormat": "Format de timp",
|
||||
"RSSIsNotSupportedWithThisIndexer": "RSS nu este acceptat cu acest indexer",
|
||||
"RSSIsNotSupportedWithThisIndexer": "RSS nu este suportat de acest indexator",
|
||||
"ShowSearchHelpText": "Afișați butonul de căutare pe hover",
|
||||
"UILanguageHelpText": "Limba pe care Radarr o va folosi pentru interfața de utilizare",
|
||||
"UILanguageHelpTextWarning": "Reîncărcare browser necesară",
|
||||
@@ -137,35 +137,35 @@
|
||||
"AddingTag": "Se adaugă etichetă",
|
||||
"LaunchBrowserHelpText": " Deschideți un browser web și navigați la pagina de pornire Radarr la pornirea aplicației.",
|
||||
"AppDataDirectory": "Directorul AppData",
|
||||
"ApplicationStatusCheckAllClientMessage": "Toate listele sunt indisponibile datorită erorilor",
|
||||
"ApplicationStatusCheckAllClientMessage": "Toate aplicațiile sunt indisponibile datorită erorilor",
|
||||
"AreYouSureYouWantToResetYourAPIKey": "Sigur doriți să vă resetați cheia API?",
|
||||
"Authentication": "Autentificare",
|
||||
"AuthenticationMethodHelpText": "Solicitați numele de utilizator și parola pentru a accesa Radarr",
|
||||
"AuthenticationMethodHelpText": "Solicitați numele de utilizator și parola pentru a accesa Prowlarr",
|
||||
"AutomaticSearch": "Căutare automată",
|
||||
"BackupFolderHelpText": "Căile relative vor fi în directorul AppData al lui Radarr",
|
||||
"BackupIntervalHelpText": "Interval între copiile de rezervă automate",
|
||||
"Backups": "Copii de rezervă",
|
||||
"BackupFolderHelpText": "Căile relative vor fi în directorul AppData al lui Prowlarr",
|
||||
"BackupIntervalHelpText": "Interval între crearea copiile de siguranță automate",
|
||||
"Backups": "Copii de siguranță",
|
||||
"BeforeUpdate": "Înainte de actualizare",
|
||||
"BindAddressHelpText": "Adresă IP4 validă sau „*” pentru toate interfețele",
|
||||
"BindAddressHelpText": "Adresă IPv4 validă sau '*' pentru toate interfațele",
|
||||
"Branch": "Ramură",
|
||||
"BranchUpdate": "Sucursală de utilizat pentru actualizarea Radarr",
|
||||
"BranchUpdate": "Ramură utilizată pentru a actualiza Prowlarr",
|
||||
"BranchUpdateMechanism": "Ramură utilizată de mecanismul extern de actualizare",
|
||||
"BypassProxyForLocalAddresses": "Bypass Proxy pentru adrese locale",
|
||||
"BypassProxyForLocalAddresses": "Nu folosiți Proxy pentru adrese locale",
|
||||
"CancelPendingTask": "Sigur doriți să anulați această sarcină în așteptare?",
|
||||
"CertificateValidation": "Validarea certificatului",
|
||||
"CertificateValidationHelpText": "Modificați cât de strictă este validarea certificării HTTPS",
|
||||
"ClientPriority": "Prioritatea clientului",
|
||||
"CloseCurrentModal": "Închideți modul curent",
|
||||
"EnableAutomaticSearch": "Activați Căutarea automată",
|
||||
"EnableAutomaticSearchHelpText": "Va fi utilizat atunci când căutările automate sunt efectuate prin interfața de utilizare sau de către Radarr",
|
||||
"EnableAutomaticSearch": "Activați căutarea automată",
|
||||
"EnableAutomaticSearchHelpText": "Va fi utilizat atunci când căutările automate sunt efectuate prin interfața de utilizare sau de către Prowlarr",
|
||||
"EnableInteractiveSearchHelpText": "Va fi utilizat atunci când este utilizată căutarea interactivă",
|
||||
"ForMoreInformationOnTheIndividualDownloadClients": "Pentru mai multe informații despre clienții individuali de descărcare, faceți clic pe butoanele de informații.",
|
||||
"IllRestartLater": "Voi reporni mai târziu",
|
||||
"IndexerProxyStatusCheckAllClientMessage": "Niciun indexator nu este disponibil datorită eșuărilor",
|
||||
"IndexerProxyStatusCheckSingleClientMessage": "Liste indisponibile datorită erorilor: {0}",
|
||||
"Add": "Adăuga",
|
||||
"IndexerProxyStatusCheckAllClientMessage": "Niciun indexator nu este disponibil datorită erorilor",
|
||||
"IndexerProxyStatusCheckSingleClientMessage": "Proxiuri indisponibile datorită erorilor: {0}",
|
||||
"Add": "Adaugă",
|
||||
"Custom": "Personalizat",
|
||||
"DeleteBackup": "Ștergeți Backup",
|
||||
"DeleteBackup": "Ștergeți copie de siguranță",
|
||||
"MovieIndexScrollBottom": "Index film: Derulați partea de jos",
|
||||
"NoLinks": "Fără legături",
|
||||
"NoUpdatesAreAvailable": "Nu sunt disponibile actualizări",
|
||||
@@ -189,8 +189,8 @@
|
||||
"URLBase": "Baza URL",
|
||||
"UrlBaseHelpText": "Pentru suport proxy invers, implicit este gol",
|
||||
"Usenet": "Usenet",
|
||||
"EditIndexer": "Editați Indexer",
|
||||
"Enable": "Permite",
|
||||
"EditIndexer": "Editați indexator",
|
||||
"Enable": "Activați",
|
||||
"EnableRss": "Activați RSS",
|
||||
"ErrorLoadingContents": "Eroare la încărcarea conținutului",
|
||||
"FocusSearchBox": "Caseta de căutare Focus",
|
||||
@@ -203,10 +203,10 @@
|
||||
"Torrents": "Torente",
|
||||
"UnableToAddANewApplicationPleaseTryAgain": "Imposibil de adăugat o nouă notificare, încercați din nou.",
|
||||
"UnableToAddANewDownloadClientPleaseTryAgain": "Imposibil de adăugat un nou client de descărcare, încercați din nou.",
|
||||
"DownloadClientSettings": "Descărcați setările clientului",
|
||||
"DownloadClientSettings": "Setări client de descărcare",
|
||||
"Enabled": "Activat",
|
||||
"IncludeHealthWarningsHelpText": "Includeți avertismente de sănătate",
|
||||
"IndexerPriorityHelpText": "Prioritatea indexerului de la 1 (cea mai mare) la 50 (cea mai mică). Implicit: 25.",
|
||||
"IndexerPriorityHelpText": "Prioritatea indexatorului de la 1 (cea mai mare) la 50 (cea mai mică). Implicit: 25.",
|
||||
"InteractiveSearch": "Căutare interactivă",
|
||||
"LogLevel": "Nivel jurnal",
|
||||
"LogLevelTraceHelpTextWarning": "Înregistrarea urmăririi trebuie activată doar temporar",
|
||||
@@ -227,15 +227,15 @@
|
||||
"UnableToLoadTags": "Nu se pot încărca etichete",
|
||||
"UnableToLoadUISettings": "Nu se pot încărca setările UI",
|
||||
"UpdateMechanismHelpText": "Utilizați actualizatorul încorporat al lui Radarr sau un script",
|
||||
"AnalyticsEnabledHelpText": "Trimiteți informații anonime privind utilizarea și erorile către serverele Radarr. Aceasta include informații despre browserul dvs., ce pagini WebUI Radarr utilizați, raportarea erorilor, precum și sistemul de operare și versiunea de execuție. Vom folosi aceste informații pentru a acorda prioritate caracteristicilor și remedierilor de erori.",
|
||||
"AnalyticsEnabledHelpText": "Trimiteți informații anonime privind utilizarea și erorile către serverele Prowlarr. Aceasta include informații despre browserul folosit, ce pagini WebUI Prowlarr utilizați, raportarea erorilor, precum și sistemul de operare și versiunea de execuție. Vom folosi aceste informații pentru a acorda prioritate caracteristicilor și remedierilor de erori.",
|
||||
"ApiKey": "Cheie API",
|
||||
"BackupRetentionHelpText": "Copiile de rezervă automate mai vechi de perioada de păstrare vor fi curățate automat",
|
||||
"BackupRetentionHelpText": "Copiile de siguranță automate mai vechi decât perioada de păstrare vor fi curățate automat",
|
||||
"BindAddress": "Adresa de legare",
|
||||
"ChangeHasNotBeenSavedYet": "Modificarea nu a fost încă salvată",
|
||||
"CloneProfile": "Profil de clonare",
|
||||
"CloneProfile": "Clonați profil",
|
||||
"NoLeaveIt": "Nu, lasă-l",
|
||||
"DBMigration": "Migrarea DB",
|
||||
"DeleteBackupMessageText": "Sigur doriți să ștergeți copia de rezervă „{0}”?",
|
||||
"DBMigration": "Migrarea BD",
|
||||
"DeleteBackupMessageText": "Sigur doriți să ștergeți copia de siguranță „{0}”?",
|
||||
"DeleteTagMessageText": "Sigur doriți să ștergeți eticheta „{0}”?",
|
||||
"EnableInteractiveSearch": "Activați căutarea interactivă",
|
||||
"EnableSSL": "Activați SSL",
|
||||
@@ -243,12 +243,12 @@
|
||||
"ExistingTag": "Etichetă existentă",
|
||||
"FeatureRequests": "Cereri de caracteristici",
|
||||
"HiddenClickToShow": "Ascuns, faceți clic pentru a afișa",
|
||||
"HomePage": "Pagina principala",
|
||||
"HomePage": "Pagina principală",
|
||||
"Hostname": "Numele gazdei",
|
||||
"IgnoredAddresses": "Adrese ignorate",
|
||||
"IndexerLongTermStatusCheckAllClientMessage": "Toți indexatorii nu sunt disponibili din cauza unor eșecuri de mai mult de 6 ore",
|
||||
"IndexerLongTermStatusCheckSingleClientMessage": "Indexatori indisponibili din cauza unor eșecuri de mai mult de 6 ore: {0}",
|
||||
"IndexerPriority": "Prioritatea indexerului",
|
||||
"IndexerLongTermStatusCheckAllClientMessage": "Toți indexatorii sunt indisponibili datorită erorilor de mai mult de 6 ore",
|
||||
"IndexerLongTermStatusCheckSingleClientMessage": "Indexatori indisponibili datorită erorilor de mai mult de 6 ore: {0}",
|
||||
"IndexerPriority": "Prioritatea indexatorului",
|
||||
"Mode": "Mod",
|
||||
"MovieIndexScrollTop": "Index film: Derulați sus",
|
||||
"NoBackupsAreAvailable": "Nu sunt disponibile copii de rezervă",
|
||||
@@ -266,14 +266,14 @@
|
||||
"ShowSearch": "Afișați Căutare",
|
||||
"TagCannotBeDeletedWhileInUse": "Nu poate fi șters în timpul utilizării",
|
||||
"TagIsNotUsedAndCanBeDeleted": "Eticheta nu este utilizată și poate fi ștearsă",
|
||||
"TagsHelpText": "Se aplică filmelor cu cel puțin o etichetă potrivită",
|
||||
"TagsHelpText": "Se aplică indexatoarelor cu cel puțin o etichetă potrivită",
|
||||
"Tomorrow": "Mâine",
|
||||
"Torrent": "Torente",
|
||||
"UILanguage": "Limbajul UI",
|
||||
"UISettings": "Setări UI",
|
||||
"UnableToAddANewAppProfilePleaseTryAgain": "Imposibil de adăugat un nou profil de calitate, încercați din nou.",
|
||||
"UnableToAddANewIndexerPleaseTryAgain": "Imposibil de adăugat un nou indexer, încercați din nou.",
|
||||
"UnableToAddANewIndexerProxyPleaseTryAgain": "Imposibil de adăugat un nou indexer, încercați din nou.",
|
||||
"UnableToAddANewIndexerPleaseTryAgain": "Nu se poate adăuga un nou indexator, vă rugăm să încercați din nou.",
|
||||
"UnableToAddANewIndexerProxyPleaseTryAgain": "Nu se poate adăuga un nou proxy indexator, vă rugăm să încercați din nou.",
|
||||
"UpdateScriptPathHelpText": "Calea către un script personalizat care preia un pachet de actualizare extras și se ocupă de restul procesului de actualizare",
|
||||
"Uptime": "Timp de funcționare",
|
||||
"UseProxy": "Utilizarea proxy",
|
||||
@@ -281,10 +281,10 @@
|
||||
"Version": "Versiune",
|
||||
"Yesterday": "Ieri",
|
||||
"AcceptConfirmationModal": "Acceptați Modul de confirmare",
|
||||
"DeleteIndexerProxyMessageText": "Sigur doriți să ștergeți eticheta „{0}”?",
|
||||
"DeleteIndexerProxyMessageText": "Sigur doriți să ștergeți proxyul „{0}”?",
|
||||
"Disabled": "Dezactivat",
|
||||
"Discord": "Discordie",
|
||||
"Docker": "Docher",
|
||||
"Discord": "Discord",
|
||||
"Docker": "Docker",
|
||||
"Donations": "Donații",
|
||||
"Interval": "Interval",
|
||||
"Manual": "Manual",
|
||||
@@ -293,14 +293,14 @@
|
||||
"UnsavedChanges": "Modificări nesalvate",
|
||||
"UpdateAutomaticallyHelpText": "Descărcați și instalați automat actualizări. Veți putea în continuare să instalați din System: Updates",
|
||||
"AddDownloadClient": "Adăugați client de descărcare",
|
||||
"AddIndexer": "Adăugați Indexer",
|
||||
"AllIndexersHiddenDueToFilter": "Toate filmele sunt ascunse datorită filtrelor aplicate.",
|
||||
"AddIndexer": "Adăugați Indexator",
|
||||
"AllIndexersHiddenDueToFilter": "Toate indexatoarele sunt ascunse datorită filtrelor aplicate.",
|
||||
"DeleteDownloadClient": "Ștergeți clientul de descărcare",
|
||||
"DeleteDownloadClientMessageText": "Sigur doriți să ștergeți clientul de descărcare „{0}”?",
|
||||
"ConnectSettings": "Setări conectare",
|
||||
"CouldNotConnectSignalR": "Nu s-a putut conecta la SignalR, UI nu se va actualiza",
|
||||
"GeneralSettings": "setari generale",
|
||||
"Grabs": "Apuca",
|
||||
"GeneralSettings": "Setări generale",
|
||||
"Grabs": "Descărcări",
|
||||
"Port": "Port",
|
||||
"PortNumber": "Numarul portului",
|
||||
"Reset": "Resetați",
|
||||
@@ -313,27 +313,68 @@
|
||||
"StartTypingOrSelectAPathBelow": "Începeți să tastați sau selectați o cale de mai jos",
|
||||
"StartupDirectory": "Director de pornire",
|
||||
"SuggestTranslationChange": "Sugerează modificarea traducerii",
|
||||
"ApplicationStatusCheckSingleClientMessage": "Liste indisponibile datorită erorilor: {0}",
|
||||
"ApplicationStatusCheckSingleClientMessage": "Aplicații indisponibile datorită erorilor: {0}",
|
||||
"ApplyTags": "Aplicați etichete",
|
||||
"ApplyTagsHelpTexts1": "Cum se aplică etichete filmelor selectate",
|
||||
"ApplyTagsHelpTexts1": "Cum se aplică etichete indexatoarelor selectate",
|
||||
"ApplyTagsHelpTexts2": "Adăugare: adăugați etichetele la lista de etichete existentă",
|
||||
"ApplyTagsHelpTexts3": "Eliminați: eliminați etichetele introduse",
|
||||
"ApplyTagsHelpTexts4": "Înlocuire: înlocuiți etichetele cu etichetele introduse (nu introduceți etichete pentru a șterge toate etichetele)",
|
||||
"Automatic": "Automat",
|
||||
"DeleteApplicationMessageText": "Sigur doriți să ștergeți notificarea „{0}”?",
|
||||
"DeleteApplicationMessageText": "Sigur doriți să ștergeți aplicația „{0}”?",
|
||||
"Exception": "Excepție",
|
||||
"MaintenanceRelease": "Versiune de întreținere: remedieri de erori și alte îmbunătățiri. Consultați Istoricul comiterilor Github pentru mai multe detalii",
|
||||
"Filters": "Filtru",
|
||||
"Filters": "Filtre",
|
||||
"HistoryCleanupDaysHelpText": "Setați la 0 pentru a dezactiva curățarea automată",
|
||||
"HistoryCleanupDaysHelpTextWarning": "Fișierele din coșul de reciclare mai vechi de numărul de zile selectat vor fi curățate automat",
|
||||
"HistoryCleanupDaysHelpTextWarning": "Obiectele din istoric în coșul de reciclare mai vechi de numărul de zile selectat vor fi curățate automat",
|
||||
"OnGrab": "Pe Grab",
|
||||
"OnHealthIssue": "Cu privire la problema sănătății",
|
||||
"TestAllIndexers": "Testați toți indexatorii",
|
||||
"TestAllIndexers": "Testați toate indexatoarele",
|
||||
"Link": "Link-uri",
|
||||
"NetCore": ".NET Core",
|
||||
"UnableToLoadIndexers": "Imposibil de încărcat indexatori",
|
||||
"GrabReleases": "Grab Release",
|
||||
"UnableToLoadIndexers": "Nu se pot încărca indexatoarele",
|
||||
"GrabReleases": "Descarcă fișier(e)",
|
||||
"MappedDrivesRunningAsService": "Unitățile de rețea mapate nu sunt disponibile atunci când rulează ca serviciu Windows. Vă rugăm să consultați FAQ pentru mai multe informații",
|
||||
"No": "Nu",
|
||||
"Yes": "da"
|
||||
"Yes": "da",
|
||||
"IndexerSettingsSummary": "Configurați diverse setări globale indexatoare inclusiv proxiuri.",
|
||||
"EnableRssHelpText": "Activați flux RSS pentru indexator",
|
||||
"IndexerHealthCheckNoIndexers": "Niciun indexator nu este activat, Prowlarr nu va returna rezultate la căutare.",
|
||||
"IndexerProxy": "Proxy indexator",
|
||||
"IndexerVipCheckExpiredClientMessage": "Beneficiile VIP pentru indexator au expirat: {0}",
|
||||
"IndexerNoDefCheckMessage": "Indexatorii nu au definiție și nu vor funcționa: {0}. Vă rugăm să-i ștergeți și (sau) să-i adăugați din nou în Prowlarr",
|
||||
"IndexerRss": "RSS indexator",
|
||||
"EnabledRedirected": "Activat, Redirecționat",
|
||||
"Ended": "Finalizat",
|
||||
"EnableIndexer": "Activați indexator",
|
||||
"GrabTitle": "Descarcă titlu",
|
||||
"HistoryCleanup": "Istoric curățare",
|
||||
"Id": "Id",
|
||||
"FilterPlaceHolder": "Căutați folosind indexatoare",
|
||||
"IndexerAuth": "Autentificare indexator",
|
||||
"IndexerDetails": "Detalii indexator",
|
||||
"IndexerName": "Nume indexator",
|
||||
"IndexerInfo": "Informații indexator",
|
||||
"IndexerQuery": "Căutare indexator",
|
||||
"IndexerSite": "Site indexator",
|
||||
"IndexersSelectedInterp": "{0} Indexatoare selectate",
|
||||
"IndexerTagsHelpText": "Folosiți etichete pentru a specifica proxiurile indexatoarelor, cu ce aplicații sunt sincronizate indexatoarele, sau doar pentru a le organiza.",
|
||||
"SyncAppIndexers": "Sincronizați indexatoare aplicații",
|
||||
"SyncLevelAddRemove": "Doar Adaugă sau Șterge: Când indexatoarele sunt adăugate sau șterse din Prowlarr, va actualiza această aplicație.",
|
||||
"DeleteIndexerProxy": "Ștergeți proxy indexator",
|
||||
"UnableToLoadIndexerProxies": "Nu se pot încărca proxiurile indexatoarelor",
|
||||
"SettingsIndexerLoggingHelpText": "Logați informații adiționale despre indexatoare, inclusiv răspunsul",
|
||||
"AddIndexerProxy": "Adăugați proxy indexator",
|
||||
"AddNewIndexer": "Adăugați indexator nou",
|
||||
"IndexerAlreadySetup": "Cel puțin o instanță de indexator e deja configurată",
|
||||
"MinimumSeedersHelpText": "Seederi necesari pentru ca aplicația să descarce folosind indexatorul",
|
||||
"ProwlarrSupportsAnyIndexer": "Prowlarr suportă multe indexatoare pe lângă orice indexator ce folosește standardul Newznab/Torznab folosind 'Generic Newznab' (pentru usenet) sau 'Generic Torznab' (pentru torrent). Căutați și selectați-vă indexatorul mai jos.",
|
||||
"RedirectHelpText": "Redirecționați cererile de descărcare pentru indexator și predați descărcarea direct, in loc de a-o trece prin Prowlarr",
|
||||
"SearchIndexers": "Căutare folosind indexatoare",
|
||||
"SettingsIndexerLogging": "Logare îmbunătățită indexator",
|
||||
"SyncLevelFull": "Sincronizare Completă: Va păstra indexatoarele acestei aplicații sincronizate complet. Schimbările făcute indexatoarelor în Prowlarr sunt sincronizate cu această aplicație. Orice schimbare făcută indexatoarelor în această aplicație vor fi șterse la următoarea sincronizare cu Prowlarr.",
|
||||
"Encoding": "Encodare",
|
||||
"FullSync": "Sincronizare completă",
|
||||
"IndexerObsoleteCheckMessage": "Indexatorii sunt învechiți sau nu au fost actualizați: {0}. Vă rugăm să-i ștergeți și (sau) să-i adăugați din nou în Prowlarr",
|
||||
"IndexerProxies": "Proxiuri indexatoare",
|
||||
"IndexerVipCheckExpiringClientMessage": "Beneficiile VIP pentru indexator expiră în curând: {0}"
|
||||
}
|
||||
|
||||
@@ -347,5 +347,9 @@
|
||||
"Encoding": "Кодирование",
|
||||
"Applications": "Приложения",
|
||||
"Application": "Приложения",
|
||||
"Notifications": "Оповещения"
|
||||
"Notifications": "Оповещения",
|
||||
"InstanceName": "Имя экземпляра",
|
||||
"InstanceNameHelpText": "Имя экземпляра на вкладке и для имени приложения системного журнала",
|
||||
"Started": "Запущено",
|
||||
"Database": "База данных"
|
||||
}
|
||||
|
||||
@@ -44,5 +44,17 @@
|
||||
"AuthenticationMethodHelpText": "Vyžadovať používateľské meno a heslo pre prístup k Radarru",
|
||||
"BackupFolderHelpText": "Relatívne cesty budú v priečinku AppData Radarru",
|
||||
"BranchUpdate": "Vetva, ktorá sa má použiť k aktualizácií Radarru",
|
||||
"DeleteDownloadClientMessageText": "Naozaj chcete zmazať značku formátu {0} ?"
|
||||
"DeleteDownloadClientMessageText": "Naozaj chcete zmazať značku formátu {0} ?",
|
||||
"ChangeHasNotBeenSavedYet": "Zmena ešte nebola uložená",
|
||||
"Clear": "Vymazať",
|
||||
"Close": "Zatvoriť",
|
||||
"CertificateValidation": "Overenie certifikátu",
|
||||
"CloneProfile": "Klonovať profil",
|
||||
"BindAddress": "Viazať adresu",
|
||||
"CancelPendingTask": "Naozaj chcete zrušiť túto prebiehajúcu úlohu?",
|
||||
"ClientPriority": "Priorita klienta",
|
||||
"CloseCurrentModal": "Zatvoriť aktuálne okno",
|
||||
"Columns": "Stĺpce",
|
||||
"Component": "Komponent",
|
||||
"ConnectionLost": "Spojenie prerušené"
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@
|
||||
"RemovedFromTaskQueue": "从任务队列中移除",
|
||||
"RemoveFilter": "移除过滤条件",
|
||||
"RemovingTag": "移除标签",
|
||||
"RestartRequiredHelpTextWarning": "重启生效",
|
||||
"RestartRequiredHelpTextWarning": "需要重新启动才能生效",
|
||||
"RestoreBackup": "恢复备份",
|
||||
"Result": "结果",
|
||||
"Retention": "保留",
|
||||
@@ -460,5 +460,7 @@
|
||||
"Parameters": "参数",
|
||||
"Queued": "队列中",
|
||||
"Started": "已开始",
|
||||
"LastDuration": "上一次用时"
|
||||
"LastDuration": "上一次用时",
|
||||
"ApplicationLongTermStatusCheckAllClientMessage": "由于故障超过6小时,所有程序都不可用",
|
||||
"ApplicationLongTermStatusCheckSingleClientMessage": "由于故障超过6小时而无法使用的程序:{0}"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"About": "關於",
|
||||
"Add": "新增",
|
||||
"Added": "以新增",
|
||||
"Added": "已新增",
|
||||
"Actions": "執行",
|
||||
"Age": "年齡",
|
||||
"AddIndexer": "新增索引",
|
||||
@@ -11,5 +11,6 @@
|
||||
"AddIndexerProxy": "新增索引器代理",
|
||||
"AddingTag": "新增標籤",
|
||||
"All": "全部",
|
||||
"AddRemoveOnly": "僅限新增或移除"
|
||||
"AddRemoveOnly": "僅限新增或移除",
|
||||
"AcceptConfirmationModal": "接受確認模式"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
{
|
||||
@@ -15,27 +16,21 @@ namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
|
||||
public class NotifiarrProxy : INotifiarrProxy
|
||||
{
|
||||
private const string URL = "https://notifiarr.com/notifier.php";
|
||||
private const string URL = "https://notifiarr.com";
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public NotifiarrProxy(IHttpClient httpClient, Logger logger)
|
||||
public NotifiarrProxy(IHttpClient httpClient, IConfigFileProvider configFileProvider, Logger logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_configFileProvider = configFileProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void SendNotification(StringDictionary message, NotifiarrSettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessNotification(message, settings);
|
||||
}
|
||||
catch (NotifiarrException ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send notification");
|
||||
throw new NotifiarrException("Unable to send notification");
|
||||
}
|
||||
}
|
||||
|
||||
public ValidationFailure Test(NotifiarrSettings settings)
|
||||
@@ -48,21 +43,14 @@ namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
SendNotification(variables, settings);
|
||||
return null;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
catch (NotifiarrException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
_logger.Error(ex, "API key is invalid: " + ex.Message);
|
||||
return new ValidationFailure("APIKey", "API key is invalid");
|
||||
}
|
||||
|
||||
_logger.Error(ex, "Unable to send test message: " + ex.Message);
|
||||
return new ValidationFailure("APIKey", "Unable to send test notification");
|
||||
return new ValidationFailure("APIKey", ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test notification: " + ex.Message);
|
||||
return new ValidationFailure("", "Unable to send test notification");
|
||||
_logger.Error(ex, ex.Message);
|
||||
return new ValidationFailure("", "Unable to send test notification. Check the log for more details.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +58,10 @@ namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
{
|
||||
try
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(URL).Post();
|
||||
requestBuilder.AddFormParameter("api", settings.APIKey).Build();
|
||||
var instanceName = _configFileProvider.InstanceName;
|
||||
var requestBuilder = new HttpRequestBuilder(URL + "/api/v1/notification/prowlarr").Post();
|
||||
requestBuilder.AddFormParameter("instanceName", instanceName).Build();
|
||||
requestBuilder.SetHeader("X-API-Key", settings.APIKey);
|
||||
|
||||
foreach (string key in message.Keys)
|
||||
{
|
||||
@@ -84,13 +74,31 @@ namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
|
||||
var responseCode = ex.Response.StatusCode;
|
||||
switch ((int)responseCode)
|
||||
{
|
||||
_logger.Error(ex, "API key is invalid");
|
||||
throw;
|
||||
case 401:
|
||||
_logger.Error("Unauthorized", "HTTP 401 - API key is invalid");
|
||||
throw new NotifiarrException("API key is invalid");
|
||||
case 400:
|
||||
_logger.Error("Invalid Request", "HTTP 400 - Unable to send notification. Ensure Prowlarr Integration is enabled & assigned a channel on Notifiarr");
|
||||
throw new NotifiarrException("Unable to send notification. Ensure Prowlarr Integration is enabled & assigned a channel on Notifiarr");
|
||||
case 502:
|
||||
case 503:
|
||||
case 504:
|
||||
_logger.Error("Service Unavailable", "Unable to send notification. Service Unavailable");
|
||||
throw new NotifiarrException("Unable to send notification. Service Unavailable", ex);
|
||||
case 520:
|
||||
case 521:
|
||||
case 522:
|
||||
case 523:
|
||||
case 524:
|
||||
_logger.Error(ex, "Cloudflare Related HTTP Error - Unable to send notification");
|
||||
throw new NotifiarrException("Cloudflare Related HTTP Error - Unable to send notification", ex);
|
||||
default:
|
||||
_logger.Error(ex, "Unknown HTTP Error - Unable to send notification");
|
||||
throw new NotifiarrException("Unknown HTTP Error - Unable to send notification", ex);
|
||||
}
|
||||
|
||||
throw new NotifiarrException("Unable to send notification", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<PackageReference Include="AngleSharp.Xml" Version="0.17.0" />
|
||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||
<PackageReference Include="FluentMigrator.Runner" Version="3.3.2" />
|
||||
<PackageReference Include="MailKit" Version="3.3.0" />
|
||||
<PackageReference Include="MailKit" Version="3.4.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="5.0.11" />
|
||||
@@ -21,7 +21,7 @@
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.5" />
|
||||
<PackageReference Include="MonoTorrent" Version="2.0.5" />
|
||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="12.0.1" />
|
||||
<PackageReference Include="AngleSharp" Version="0.17.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.3.1" />
|
||||
<PackageReference Include="DryIoc.dll" Version="4.8.8" />
|
||||
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="5.1.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.4.0" />
|
||||
<PackageReference Include="DryIoc.dll" Version="5.2.2" />
|
||||
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="6.1.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Common\Prowlarr.Common.csproj" />
|
||||
|
||||
@@ -96,11 +96,16 @@ namespace NzbDrone.Test.Common.AutoMoq
|
||||
return null;
|
||||
}
|
||||
|
||||
if (serviceType == typeof(System.Text.Json.Serialization.JsonConverter))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// get the Mock object for the abstract class or interface
|
||||
if (serviceType.IsInterface || serviceType.IsAbstract)
|
||||
{
|
||||
var mockType = typeof(Mock<>).MakeGenericType(serviceType);
|
||||
var mockFactory = new DelegateFactory(r =>
|
||||
var mockFactory = DelegateFactory.Of(r =>
|
||||
{
|
||||
var mock = (Mock)r.Resolve(mockType);
|
||||
SetMock(serviceType, mock);
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DryIoc.dll" Version="4.8.8" />
|
||||
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="5.1.0" />
|
||||
<PackageReference Include="DryIoc.dll" Version="5.2.2" />
|
||||
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="6.1.0" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace NzbDrone.Api.V1.Indexers
|
||||
//TODO Optimize this so it's not called here and in NzbSearchService (for manual search)
|
||||
if (_indexerLimitService.AtQueryLimit(indexerDef))
|
||||
{
|
||||
return Content(CreateErrorXML(500, $"Request limit reached ({((IIndexerSettings)indexer.Definition.Settings).BaseSettings.QueryLimit})"), "application/rss+xml");
|
||||
return Content(CreateErrorXML(429, $"Request limit reached ({((IIndexerSettings)indexer.Definition.Settings).BaseSettings.QueryLimit})"), "application/rss+xml");
|
||||
}
|
||||
|
||||
switch (requestType)
|
||||
@@ -171,7 +171,7 @@ namespace NzbDrone.Api.V1.Indexers
|
||||
|
||||
if (_indexerLimitService.AtDownloadLimit(indexerDef))
|
||||
{
|
||||
throw new BadRequestException("Grab limit reached");
|
||||
return Content(CreateErrorXML(429, $"Grab limit reached ({((IIndexerSettings)indexer.Definition.Settings).BaseSettings.GrabLimit})"), "application/rss+xml");
|
||||
}
|
||||
|
||||
if (link.IsNullOrWhiteSpace() || file.IsNullOrWhiteSpace())
|
||||
|
||||
23
src/Prowlarr.Http/ApiInfoController.cs
Normal file
23
src/Prowlarr.Http/ApiInfoController.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Prowlarr.Http
|
||||
{
|
||||
public class ApiInfoController : Controller
|
||||
{
|
||||
public ApiInfoController()
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("/api")]
|
||||
[Produces("application/json")]
|
||||
public ApiInfoResource GetApiInfo()
|
||||
{
|
||||
return new ApiInfoResource
|
||||
{
|
||||
Current = "v1",
|
||||
Deprecated = new List<string>()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/Prowlarr.Http/ApiInfoResource.cs
Normal file
14
src/Prowlarr.Http/ApiInfoResource.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Prowlarr.Http
|
||||
{
|
||||
public class ApiInfoResource
|
||||
{
|
||||
public string Current { get; set; }
|
||||
public List<string> Deprecated { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user