mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-04-17 21:44:48 -04:00
Compare commits
44 Commits
v1.1.3.252
...
v1.2.1.266
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0984976378 | ||
|
|
fcb3c96455 | ||
|
|
acf7a425b5 | ||
|
|
da898fe958 | ||
|
|
5bb3ea0806 | ||
|
|
b41cb80e33 | ||
|
|
a39341be4b | ||
|
|
27b3d8618a | ||
|
|
550b9b58df | ||
|
|
035ad33b72 | ||
|
|
85f8e0c451 | ||
|
|
ea2061a7d3 | ||
|
|
ea6d01a49b | ||
|
|
252cd97e35 | ||
|
|
a8ea05af07 | ||
|
|
24d6a0cb06 | ||
|
|
8e1771b5a9 | ||
|
|
d767a82e84 | ||
|
|
76bfd29f23 | ||
|
|
c923982711 | ||
|
|
f03a64f9ac | ||
|
|
e713e58e83 | ||
|
|
4fb5d3432b | ||
|
|
a31b107a90 | ||
|
|
f91ffb8328 | ||
|
|
a3ba070296 | ||
|
|
bccb0bd5c8 | ||
|
|
4517f271c4 | ||
|
|
2ae2a0b184 | ||
|
|
b5e43e7a1a | ||
|
|
3a52048dc2 | ||
|
|
8b898733ab | ||
|
|
f99a2e1164 | ||
|
|
306209fcc2 | ||
|
|
5d09c2b5fa | ||
|
|
41a9d2d732 | ||
|
|
49b120ba55 | ||
|
|
a88fc34a78 | ||
|
|
c46b7c5e4b | ||
|
|
94c45541ae | ||
|
|
f8082047a5 | ||
|
|
011fd57f7d | ||
|
|
6c35c3fc6f | ||
|
|
5da02c49eb |
@@ -117,7 +117,6 @@ dotnet_diagnostic.CA1003.severity = suggestion
|
||||
dotnet_diagnostic.CA1008.severity = suggestion
|
||||
dotnet_diagnostic.CA1010.severity = suggestion
|
||||
dotnet_diagnostic.CA1012.severity = suggestion
|
||||
dotnet_diagnostic.CA1014.severity = suggestion
|
||||
dotnet_diagnostic.CA1016.severity = suggestion
|
||||
dotnet_diagnostic.CA1017.severity = suggestion
|
||||
dotnet_diagnostic.CA1018.severity = suggestion
|
||||
@@ -163,6 +162,7 @@ dotnet_diagnostic.CA1309.severity = suggestion
|
||||
dotnet_diagnostic.CA1310.severity = suggestion
|
||||
dotnet_diagnostic.CA1401.severity = suggestion
|
||||
dotnet_diagnostic.CA1416.severity = suggestion
|
||||
dotnet_diagnostic.CA1419.severity = suggestion
|
||||
dotnet_diagnostic.CA1507.severity = suggestion
|
||||
dotnet_diagnostic.CA1508.severity = suggestion
|
||||
dotnet_diagnostic.CA1707.severity = suggestion
|
||||
@@ -178,9 +178,6 @@ dotnet_diagnostic.CA1720.severity = suggestion
|
||||
dotnet_diagnostic.CA1721.severity = suggestion
|
||||
dotnet_diagnostic.CA1724.severity = suggestion
|
||||
dotnet_diagnostic.CA1725.severity = suggestion
|
||||
dotnet_diagnostic.CA1801.severity = suggestion
|
||||
dotnet_diagnostic.CA1802.severity = suggestion
|
||||
dotnet_diagnostic.CA1805.severity = suggestion
|
||||
dotnet_diagnostic.CA1806.severity = suggestion
|
||||
dotnet_diagnostic.CA1810.severity = suggestion
|
||||
dotnet_diagnostic.CA1812.severity = suggestion
|
||||
@@ -192,13 +189,14 @@ dotnet_diagnostic.CA1819.severity = suggestion
|
||||
dotnet_diagnostic.CA1822.severity = suggestion
|
||||
dotnet_diagnostic.CA1823.severity = suggestion
|
||||
dotnet_diagnostic.CA1824.severity = suggestion
|
||||
dotnet_diagnostic.CA1835.severity = suggestion
|
||||
dotnet_diagnostic.CA1845.severity = suggestion
|
||||
dotnet_diagnostic.CA1848.severity = suggestion
|
||||
dotnet_diagnostic.CA1849.severity = suggestion
|
||||
dotnet_diagnostic.CA2000.severity = suggestion
|
||||
dotnet_diagnostic.CA2002.severity = suggestion
|
||||
dotnet_diagnostic.CA2007.severity = suggestion
|
||||
dotnet_diagnostic.CA2008.severity = suggestion
|
||||
dotnet_diagnostic.CA2009.severity = suggestion
|
||||
dotnet_diagnostic.CA2010.severity = suggestion
|
||||
dotnet_diagnostic.CA2011.severity = suggestion
|
||||
dotnet_diagnostic.CA2012.severity = suggestion
|
||||
dotnet_diagnostic.CA2013.severity = suggestion
|
||||
dotnet_diagnostic.CA2100.severity = suggestion
|
||||
@@ -229,6 +227,7 @@ dotnet_diagnostic.CA2243.severity = suggestion
|
||||
dotnet_diagnostic.CA2244.severity = suggestion
|
||||
dotnet_diagnostic.CA2245.severity = suggestion
|
||||
dotnet_diagnostic.CA2246.severity = suggestion
|
||||
dotnet_diagnostic.CA2254.severity = suggestion
|
||||
dotnet_diagnostic.CA3061.severity = suggestion
|
||||
dotnet_diagnostic.CA3075.severity = suggestion
|
||||
dotnet_diagnostic.CA3076.severity = suggestion
|
||||
@@ -255,6 +254,7 @@ dotnet_diagnostic.CA5385.severity = suggestion
|
||||
dotnet_diagnostic.CA5392.severity = suggestion
|
||||
dotnet_diagnostic.CA5394.severity = suggestion
|
||||
dotnet_diagnostic.CA5397.severity = suggestion
|
||||
dotnet_diagnostic.CA5401.severity = suggestion
|
||||
|
||||
dotnet_diagnostic.SYSLIB0014.severity = none
|
||||
|
||||
|
||||
41
.github/workflows/azuresync.yml
vendored
41
.github/workflows/azuresync.yml
vendored
@@ -1,41 +0,0 @@
|
||||
name: Sync issue to Azure DevOps work item
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
[opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
|
||||
|
||||
concurrency: azuresync-${{ github.event.issue.number }}
|
||||
|
||||
jobs:
|
||||
alert:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: danhellem/github-actions-issue-to-work-item@master
|
||||
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == true }}"
|
||||
env:
|
||||
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
|
||||
github_token: "${{ github.token }}"
|
||||
ado_organization: "Servarr"
|
||||
ado_project: "Servarr"
|
||||
ado_area_path: "Servarr\\Prowlarr"
|
||||
ado_wit: "Bug"
|
||||
ado_new_state: "New"
|
||||
ado_active_state: "Active"
|
||||
ado_close_state: "Closed"
|
||||
ado_bypassrules: true
|
||||
log_level: 100
|
||||
- uses: danhellem/github-actions-issue-to-work-item@master
|
||||
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == false }}"
|
||||
env:
|
||||
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
|
||||
github_token: "${{ github.token }}"
|
||||
ado_organization: "Servarr"
|
||||
ado_project: "Servarr"
|
||||
ado_area_path: "Servarr\\Prowlarr"
|
||||
ado_wit: "User Story"
|
||||
ado_new_state: "New"
|
||||
ado_active_state: "Active"
|
||||
ado_close_state: "Closed"
|
||||
ado_bypassrules: true
|
||||
log_level: 100
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '1.1.3'
|
||||
majorVersion: '1.2.1'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||
|
||||
@@ -50,7 +50,7 @@ function CustomFiltersModalContent(props) {
|
||||
|
||||
<div className={styles.addButtonContainer}>
|
||||
<Button onPress={onAddCustomFilter}>
|
||||
Add Custom Filter
|
||||
{translate('AddCustomFilter')}
|
||||
</Button>
|
||||
</div>
|
||||
</ModalBody>
|
||||
|
||||
@@ -61,15 +61,15 @@ class TagsModalContent extends Component {
|
||||
} = this.state;
|
||||
|
||||
const applyTagsOptions = [
|
||||
{ key: 'add', value: 'Add' },
|
||||
{ key: 'remove', value: 'Remove' },
|
||||
{ key: 'replace', value: 'Replace' }
|
||||
{ key: 'add', value: translate('Add') },
|
||||
{ key: 'remove', value: translate('Remove') },
|
||||
{ key: 'replace', value: translate('Replace') }
|
||||
];
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Tags
|
||||
{translate('Tags')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
|
||||
@@ -26,7 +26,7 @@ function IndexerIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Status
|
||||
{translate('Status')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -62,7 +62,7 @@ function IndexerIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{'Priority'}
|
||||
{translate('Priority')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -71,7 +71,7 @@ function IndexerIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{'Protocol'}
|
||||
{translate('Protocol')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -80,7 +80,7 @@ function IndexerIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{'Privacy'}
|
||||
{translate('Privacy')}
|
||||
</SortMenuItem>
|
||||
</MenuContent>
|
||||
</SortMenu>
|
||||
|
||||
@@ -97,7 +97,7 @@ class IndexerIndexRow extends Component {
|
||||
isIndexerInfoModalOpen
|
||||
} = this.state;
|
||||
|
||||
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? indexerUrls[0];
|
||||
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? (Array.isArray(indexerUrls) ? indexerUrls[0] : undefined);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -248,7 +248,7 @@ class IndexerIndexRow extends Component {
|
||||
/>
|
||||
|
||||
{
|
||||
indexerUrls ?
|
||||
baseUrl ?
|
||||
<IconButton
|
||||
className={styles.externalLink}
|
||||
name={icons.EXTERNAL_LINK}
|
||||
|
||||
@@ -28,7 +28,7 @@ class AddApplicationModalContent extends Component {
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Add Application
|
||||
{translate('AddApplication')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
|
||||
@@ -71,14 +71,14 @@ class Application extends Component {
|
||||
{
|
||||
syncLevel === 'addOnly' &&
|
||||
<Label kind={kinds.WARNING}>
|
||||
Add and Remove Only
|
||||
{translate('AddRemoveOnly')}
|
||||
</Label>
|
||||
}
|
||||
|
||||
{
|
||||
syncLevel === 'fullSync' &&
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
Full Sync
|
||||
{translate('FullSync')}
|
||||
</Label>
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ class Application extends Component {
|
||||
kind={kinds.DISABLED}
|
||||
outline={true}
|
||||
>
|
||||
Disabled
|
||||
{translate('Disabled')}
|
||||
</Label>
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ class IndexerProxy extends Component {
|
||||
kind={kinds.DISABLED}
|
||||
outline={true}
|
||||
>
|
||||
Disabled
|
||||
{translate('Disabled')}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export const defaultState = {
|
||||
},
|
||||
{
|
||||
name: 'grabTitle',
|
||||
label: translate('Grab Title'),
|
||||
label: translate('GrabTitle'),
|
||||
isSortable: false,
|
||||
isVisible: false
|
||||
},
|
||||
@@ -78,7 +78,7 @@ export const defaultState = {
|
||||
},
|
||||
{
|
||||
name: 'elapsedTime',
|
||||
label: translate('Elapsed Time'),
|
||||
label: translate('ElapsedTime'),
|
||||
isSortable: false,
|
||||
isVisible: true
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@ const columns = [
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
label: 'Size',
|
||||
label: translate('Size'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
|
||||
@@ -113,7 +113,7 @@ class Updates extends Component {
|
||||
/>
|
||||
|
||||
<div className={styles.message}>
|
||||
The latest version of Prowlarr is already installed
|
||||
{translate('TheLatestVersionIsAlreadyInstalled', ['Prowlarr'])}
|
||||
</div>
|
||||
|
||||
{
|
||||
|
||||
3
src/.globalconfig
Normal file
3
src/.globalconfig
Normal file
@@ -0,0 +1,3 @@
|
||||
is_global = true
|
||||
|
||||
dotnet_diagnostic.CA1014.severity = none
|
||||
@@ -1,6 +1,7 @@
|
||||
<Project>
|
||||
<!-- Common to all Prowlarr Projects -->
|
||||
<PropertyGroup>
|
||||
<AnalysisLevel>6.0-all</AnalysisLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
public static string WrapInQuotes(this string text)
|
||||
{
|
||||
if (!text.Contains(" "))
|
||||
if (!text.Contains(' '))
|
||||
{
|
||||
return text;
|
||||
}
|
||||
@@ -255,7 +255,7 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
public static string ToUrlHost(this string input)
|
||||
{
|
||||
return input.Contains(":") ? $"[{input}]" : input;
|
||||
return input.Contains(':') ? $"[{input}]" : input;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
{
|
||||
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
responseMessage.Content.CopyTo(request.ResponseStream, null, cts.Token);
|
||||
await responseMessage.Content.CopyToAsync(request.ResponseStream, null, cts.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -226,7 +226,7 @@ namespace NzbDrone.Common.OAuth
|
||||
#if WINRT
|
||||
return CultureInfo.InvariantCulture.CompareInfo.Compare(left, right, CompareOptions.IgnoreCase) == 0;
|
||||
#else
|
||||
return string.Compare(left, right, StringComparison.InvariantCultureIgnoreCase) == 0;
|
||||
return string.Equals(left, right, StringComparison.InvariantCultureIgnoreCase);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DryIoc.dll" Version="5.3.1" />
|
||||
<PackageReference Include="DryIoc.dll" Version="5.3.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace NzbDrone.Common
|
||||
|
||||
var args = $"create {serviceName} " +
|
||||
$"DisplayName= \"{serviceName}\" " +
|
||||
$"binpath= \"{Process.GetCurrentProcess().MainModule.FileName}\" " +
|
||||
$"binpath= \"{Environment.ProcessPath}\" " +
|
||||
"start= auto " +
|
||||
"depend= EventLog/Tcpip/http " +
|
||||
"obj= \"NT AUTHORITY\\LocalService\"";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -19,7 +19,7 @@ namespace NzbDrone.Common.TPL
|
||||
private readonly int _maxDegreeOfParallelism;
|
||||
|
||||
/// <summary>Whether the scheduler is currently processing work items.</summary>
|
||||
private int _delegatesQueuedOrRunning = 0;
|
||||
private int _delegatesQueuedOrRunning;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the
|
||||
|
||||
@@ -162,7 +162,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
|
||||
releaseInfo.InfoHash.Should().Be("(removed)");
|
||||
releaseInfo.Seeders.Should().Be(3);
|
||||
releaseInfo.Peers.Should().Be(3);
|
||||
releaseInfo.Categories.Count().Should().Be(4);
|
||||
releaseInfo.Categories.Count.Should().Be(4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -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="12.3.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="13.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />
|
||||
|
||||
@@ -19,14 +19,14 @@ namespace NzbDrone.Core.Authentication
|
||||
|
||||
public class UserService : IUserService
|
||||
{
|
||||
private const int ITERATIONS = 10000;
|
||||
private const int SALT_SIZE = 128 / 8;
|
||||
private const int NUMBER_OF_BYTES = 256 / 8;
|
||||
|
||||
private readonly IUserRepository _repo;
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
|
||||
private static readonly int ITERATIONS = 10000;
|
||||
private static readonly int SALT_SIZE = 128 / 8;
|
||||
private static readonly int NUMBER_OF_BYTES = 256 / 8;
|
||||
|
||||
public UserService(IUserRepository repo, IAppFolderInfo appFolderInfo, IDiskProvider diskProvider)
|
||||
{
|
||||
_repo = repo;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Text;
|
||||
@@ -250,7 +250,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
}
|
||||
|
||||
Index = end + 1;
|
||||
identifier.Append(Buffer.Substring(start, end - start));
|
||||
identifier.Append(Buffer.AsSpan(start, end - start));
|
||||
|
||||
if (Buffer[Index] != escape)
|
||||
{
|
||||
|
||||
@@ -15,9 +15,9 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
private const DbType EnumerableMultiParameter = (DbType)(-1);
|
||||
private readonly string _paramNamePrefix;
|
||||
private readonly bool _requireConcreteValue = false;
|
||||
private int _paramCount = 0;
|
||||
private bool _gotConcreteValue = false;
|
||||
private readonly bool _requireConcreteValue;
|
||||
private int _paramCount;
|
||||
private bool _gotConcreteValue;
|
||||
|
||||
public WhereBuilderPostgres(Expression filter, bool requireConcreteValue, int seq)
|
||||
{
|
||||
|
||||
@@ -15,9 +15,9 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
private const DbType EnumerableMultiParameter = (DbType)(-1);
|
||||
private readonly string _paramNamePrefix;
|
||||
private readonly bool _requireConcreteValue = false;
|
||||
private int _paramCount = 0;
|
||||
private bool _gotConcreteValue = false;
|
||||
private readonly bool _requireConcreteValue;
|
||||
private int _paramCount;
|
||||
private bool _gotConcreteValue;
|
||||
|
||||
public WhereBuilderSqlite(Expression filter, bool requireConcreteValue, int seq)
|
||||
{
|
||||
|
||||
@@ -155,7 +155,9 @@ namespace NzbDrone.Core.Download
|
||||
throw;
|
||||
}
|
||||
|
||||
_logger.Trace("Downloaded {0} bytes from {1}", downloadedBytes.Length, link);
|
||||
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, host, title, url.AbsoluteUri));
|
||||
|
||||
return downloadedBytes;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ namespace NzbDrone.Core.HealthCheck
|
||||
|
||||
private readonly ICached<HealthCheck> _healthCheckResults;
|
||||
|
||||
private bool _hasRunHealthChecksAfterGracePeriod = false;
|
||||
private bool _isRunningHealthChecksAfterGracePeriod = false;
|
||||
private bool _hasRunHealthChecksAfterGracePeriod;
|
||||
private bool _isRunningHealthChecksAfterGracePeriod;
|
||||
|
||||
public HealthCheckService(IEnumerable<IProvideHealthCheck> healthChecks,
|
||||
IServerSideNotificationService serverSideNotificationService,
|
||||
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.HealthCheck
|
||||
_startupHealthChecks = _healthChecks.Where(v => v.CheckOnStartup).ToArray();
|
||||
_scheduledHealthChecks = _healthChecks.Where(v => v.CheckOnSchedule).ToArray();
|
||||
_eventDrivenHealthChecks = GetEventDrivenHealthChecks();
|
||||
_startupGracePeriodEndTime = runtimeInfo.StartTime + TimeSpan.FromMinutes(15);
|
||||
_startupGracePeriodEndTime = runtimeInfo.StartTime.AddMinutes(15);
|
||||
}
|
||||
|
||||
public List<HealthCheck> Results()
|
||||
|
||||
@@ -173,7 +173,7 @@ namespace NzbDrone.Core.History
|
||||
history.Data.Add("Categories", string.Join(",", message.Query.Categories) ?? string.Empty);
|
||||
history.Data.Add("Source", message.Query.Source ?? string.Empty);
|
||||
history.Data.Add("Host", message.Query.Host ?? string.Empty);
|
||||
history.Data.Add("QueryResults", message.QueryResult.Releases?.Count().ToString() ?? string.Empty);
|
||||
history.Data.Add("QueryResults", message.QueryResult.Releases?.Count.ToString() ?? string.Empty);
|
||||
history.Data.Add("Url", message.QueryResult.Response?.Request.Url.FullUri ?? string.Empty);
|
||||
|
||||
_historyRepository.Insert(history);
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
|
||||
namespace NzbDrone.Core.Http.CloudFlare
|
||||
@@ -41,7 +42,7 @@ namespace NzbDrone.Core.Http.CloudFlare
|
||||
|
||||
// detect Custom CloudFlare for EbookParadijs, Film-Paleis, MuziekFabriek and Puur-Hollands
|
||||
if (response.Headers.Vary == "Accept-Encoding,User-Agent" &&
|
||||
response.Headers.ContentEncoding == "" &&
|
||||
response.Headers.ContentEncoding.IsNullOrWhiteSpace() &&
|
||||
response.Content.ToLower().Contains("ddos"))
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -10,17 +10,6 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
public int? Year { get; set; }
|
||||
public string Genre { get; set; }
|
||||
|
||||
public override bool RssSearch
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SearchTerm.IsNullOrWhiteSpace() && Author.IsNullOrWhiteSpace() && Title.IsNullOrWhiteSpace())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public override bool RssSearch => SearchTerm.IsNullOrWhiteSpace() && Author.IsNullOrWhiteSpace() && Title.IsNullOrWhiteSpace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
public int? Year { get; set; }
|
||||
public string Genre { get; set; }
|
||||
|
||||
public override bool RssSearch
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SearchTerm.IsNullOrWhiteSpace() && ImdbId.IsNullOrWhiteSpace() && !TmdbId.HasValue && !TraktId.HasValue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public override bool RssSearch => SearchTerm.IsNullOrWhiteSpace() && ImdbId.IsNullOrWhiteSpace() && !TmdbId.HasValue && !TraktId.HasValue;
|
||||
|
||||
public string FullImdbId => ParseUtil.GetFullImdbId(ImdbId);
|
||||
|
||||
|
||||
@@ -11,17 +11,6 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
public string Track { get; set; }
|
||||
public int? Year { get; set; }
|
||||
|
||||
public override bool RssSearch
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SearchTerm.IsNullOrWhiteSpace() && Album.IsNullOrWhiteSpace() && Artist.IsNullOrWhiteSpace() && Label.IsNullOrWhiteSpace())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public override bool RssSearch => SearchTerm.IsNullOrWhiteSpace() && Album.IsNullOrWhiteSpace() && Artist.IsNullOrWhiteSpace() && Label.IsNullOrWhiteSpace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,8 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
{
|
||||
public abstract class SearchCriteriaBase
|
||||
{
|
||||
private static readonly Regex SpecialCharacter = new Regex(@"[`'.]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
private static readonly Regex NonWord = new Regex(@"[\W]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
private static readonly Regex BeginningThe = new Regex(@"^the\s", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
private static readonly Regex StandardizeDashesRegex = new (@"\p{Pd}+", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
private static readonly Regex StandardizeSingleQuotesRegex = new (@"[\u0060\u00B4\u2018\u2019]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
public virtual bool InteractiveSearch { get; set; }
|
||||
public List<int> IndexerIds { get; set; }
|
||||
@@ -21,58 +20,24 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
public string Source { get; set; }
|
||||
public string Host { get; set; }
|
||||
|
||||
public virtual string SearchQuery
|
||||
public override string ToString() => $"{SearchQuery}, Offset: {Offset ?? 0}, Limit: {Limit ?? 0}, Categories: [{string.Join(", ", Categories)}]";
|
||||
|
||||
public virtual string SearchQuery => $"Term: [{SearchTerm}]";
|
||||
|
||||
public virtual bool RssSearch => SearchTerm.IsNullOrWhiteSpace();
|
||||
|
||||
public string SanitizedSearchTerm => GetSanitizedTerm(SearchTerm);
|
||||
|
||||
private static string GetSanitizedTerm(string term)
|
||||
{
|
||||
get
|
||||
{
|
||||
return $"Term: [{SearchTerm}]";
|
||||
}
|
||||
}
|
||||
term ??= "";
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{SearchQuery}, Offset: {Offset ?? 0}, Limit: {Limit ?? 0}, Categories: [{string.Join(", ", Categories)}]";
|
||||
}
|
||||
term = StandardizeDashesRegex.Replace(term, "-");
|
||||
term = StandardizeSingleQuotesRegex.Replace(term, "'");
|
||||
|
||||
public virtual bool RssSearch
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SearchTerm.IsNullOrWhiteSpace())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var safeTitle = term.Where(c => char.IsLetterOrDigit(c) || char.IsWhiteSpace(c) || c is '-' or '.' or '_' or '(' or ')' or '@' or '/' or '\'' or '[' or ']' or '+' or '%');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string SanitizedSearchTerm
|
||||
{
|
||||
get
|
||||
{
|
||||
var term = SearchTerm;
|
||||
if (SearchTerm == null)
|
||||
{
|
||||
term = "";
|
||||
}
|
||||
|
||||
var safeTitle = term.Where(c => (char.IsLetterOrDigit(c)
|
||||
|| char.IsWhiteSpace(c)
|
||||
|| c == '-'
|
||||
|| c == '.'
|
||||
|| c == '_'
|
||||
|| c == '('
|
||||
|| c == ')'
|
||||
|| c == '@'
|
||||
|| c == '/'
|
||||
|| c == '\''
|
||||
|| c == '['
|
||||
|| c == ']'
|
||||
|| c == '+'
|
||||
|| c == '%'));
|
||||
return string.Concat(safeTitle);
|
||||
}
|
||||
return string.Concat(safeTitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,23 +21,12 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
public int? Year { get; set; }
|
||||
public string Genre { get; set; }
|
||||
|
||||
public string SanitizedTvSearchString => (SanitizedSearchTerm + " " + EpisodeSearchString).Trim();
|
||||
public string SanitizedTvSearchString => $"{SanitizedSearchTerm} {EpisodeSearchString}".Trim();
|
||||
public string EpisodeSearchString => GetEpisodeSearchString();
|
||||
|
||||
public string FullImdbId => ParseUtil.GetFullImdbId(ImdbId);
|
||||
|
||||
public override bool RssSearch
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SearchTerm.IsNullOrWhiteSpace() && ImdbId.IsNullOrWhiteSpace() && !TvdbId.HasValue && !RId.HasValue && !TraktId.HasValue && !TvMazeId.HasValue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public override bool RssSearch => SearchTerm.IsNullOrWhiteSpace() && ImdbId.IsNullOrWhiteSpace() && !TvdbId.HasValue && !RId.HasValue && !TraktId.HasValue && !TvMazeId.HasValue;
|
||||
|
||||
public override string SearchQuery
|
||||
{
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace NzbDrone.Core.IndexerStats
|
||||
var elapsedTimeEvents = sortedEvents.Where(h => int.TryParse(h.Data.GetValueOrDefault("elapsedTime"), out temp))
|
||||
.Select(h => temp);
|
||||
|
||||
indexerStats.AverageResponseTime = elapsedTimeEvents.Count() > 0 ? (int)elapsedTimeEvents.Average() : 0;
|
||||
indexerStats.AverageResponseTime = elapsedTimeEvents.Any() ? (int)elapsedTimeEvents.Average() : 0;
|
||||
|
||||
foreach (var historyEvent in sortedEvents)
|
||||
{
|
||||
|
||||
@@ -31,11 +31,12 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
private const string DEFINITION_BRANCH = "master";
|
||||
private const int DEFINITION_VERSION = 8;
|
||||
|
||||
//Used when moving yml to C#
|
||||
private readonly List<string> _defintionBlocklist = new List<string>()
|
||||
// Used when moving yml to C#
|
||||
private readonly List<string> _definitionBlocklist = new ()
|
||||
{
|
||||
"aither",
|
||||
"animeworld",
|
||||
"audiobookbay",
|
||||
"beyond-hd-oneurl",
|
||||
"beyond-hd",
|
||||
"blutopia",
|
||||
@@ -89,7 +90,7 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
{
|
||||
var request = new HttpRequest($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}");
|
||||
var response = _httpClient.Get<List<CardigannMetaDefinition>>(request);
|
||||
indexerList = response.Resource.Where(i => !_defintionBlocklist.Contains(i.File)).ToList();
|
||||
indexerList = response.Resource.Where(i => !_definitionBlocklist.Contains(i.File)).ToList();
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -125,7 +126,7 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
|
||||
public List<string> GetBlocklist()
|
||||
{
|
||||
return _defintionBlocklist;
|
||||
return _definitionBlocklist;
|
||||
}
|
||||
|
||||
private List<CardigannMetaDefinition> ReadDefinitionsFromDisk(List<CardigannMetaDefinition> defs, string path, SearchOption options = SearchOption.TopDirectoryOnly)
|
||||
@@ -227,10 +228,10 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
if (definition.Settings == null)
|
||||
{
|
||||
definition.Settings = new List<SettingsField>
|
||||
{
|
||||
new SettingsField { Name = "username", Label = "Username", Type = "text" },
|
||||
new SettingsField { Name = "password", Label = "Password", Type = "password" }
|
||||
};
|
||||
{
|
||||
new () { Name = "username", Label = "Username", Type = "text" },
|
||||
new () { Name = "password", Label = "Password", Type = "password" }
|
||||
};
|
||||
}
|
||||
|
||||
if (definition.Encoding == null)
|
||||
|
||||
@@ -39,12 +39,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new AnidubRequestGenerator { Settings = Settings, Capabilities = Capabilities };
|
||||
return new AnidubRequestGenerator(Settings);
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new AnidubParser(Settings, Capabilities.Categories) { HttpClient = _httpClient, Logger = _logger };
|
||||
return new AnidubParser(Settings, Capabilities.Categories, RateLimit, _httpClient, _logger);
|
||||
}
|
||||
|
||||
protected override async Task DoLogin()
|
||||
@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
if (response.Content != null && !CheckIfLoginNeeded(response))
|
||||
{
|
||||
UpdateCookies(response.GetCookies(), DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(response.GetCookies(), DateTime.Now.AddDays(30));
|
||||
_logger.Debug("Anidub authentication succeeded");
|
||||
}
|
||||
else
|
||||
@@ -135,21 +135,25 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public class AnidubRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public UserPassTorrentBaseSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
private readonly UserPassTorrentBaseSettings _settings;
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
|
||||
public AnidubRequestGenerator(UserPassTorrentBaseSettings settings)
|
||||
{
|
||||
var requestUrl = string.Empty;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term)
|
||||
{
|
||||
string requestUrl;
|
||||
var isSearch = !string.IsNullOrWhiteSpace(term);
|
||||
|
||||
if (isSearch)
|
||||
{
|
||||
requestUrl = string.Format("{0}/index.php?do=search", Settings.BaseUrl.TrimEnd('/'));
|
||||
requestUrl = $"{_settings.BaseUrl.TrimEnd('/')}/index.php?do=search";
|
||||
}
|
||||
else
|
||||
{
|
||||
requestUrl = Settings.BaseUrl;
|
||||
requestUrl = _settings.BaseUrl;
|
||||
}
|
||||
|
||||
var request = new IndexerRequest(requestUrl, HttpAccept.Html);
|
||||
@@ -194,7 +198,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -203,7 +207,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedTvSearchString}"));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -212,7 +216,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -221,7 +225,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -230,7 +234,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -243,33 +247,37 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
private readonly UserPassTorrentBaseSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
public IIndexerHttpClient HttpClient { get; set; }
|
||||
public Logger Logger { get; set; }
|
||||
private readonly TimeSpan _rateLimit;
|
||||
private readonly IIndexerHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private static Dictionary<string, string> CategoriesMap => new Dictionary<string, string>
|
||||
{
|
||||
{ "/anime_tv/full", "14" },
|
||||
{ "/anime_tv/anime_ongoing", "10" },
|
||||
{ "/anime_tv/shonen", "11" },
|
||||
{ "/anime_tv", "2" },
|
||||
{ "/xxx", "13" },
|
||||
{ "/manga", "15" },
|
||||
{ "/ost", "16" },
|
||||
{ "/podcast", "17" },
|
||||
{ "/anime_movie", "3" },
|
||||
{ "/anime_ova/anime_ona", "5" },
|
||||
{ "/anime_ova", "4" },
|
||||
{ "/dorama/japan_dorama", "6" },
|
||||
{ "/dorama/korea_dorama", "7" },
|
||||
{ "/dorama/china_dorama", "8" },
|
||||
{ "/dorama", "9" },
|
||||
{ "/anons_ongoing", "12" }
|
||||
};
|
||||
private static Dictionary<string, string> CategoriesMap => new ()
|
||||
{
|
||||
{ "/anime_tv/full", "14" },
|
||||
{ "/anime_tv/anime_ongoing", "10" },
|
||||
{ "/anime_tv/shonen", "11" },
|
||||
{ "/anime_tv", "2" },
|
||||
{ "/xxx", "13" },
|
||||
{ "/manga", "15" },
|
||||
{ "/ost", "16" },
|
||||
{ "/podcast", "17" },
|
||||
{ "/anime_movie", "3" },
|
||||
{ "/anime_ova/anime_ona", "5" },
|
||||
{ "/anime_ova", "4" },
|
||||
{ "/dorama/japan_dorama", "6" },
|
||||
{ "/dorama/korea_dorama", "7" },
|
||||
{ "/dorama/china_dorama", "8" },
|
||||
{ "/dorama", "9" },
|
||||
{ "/anons_ongoing", "12" }
|
||||
};
|
||||
|
||||
public AnidubParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
|
||||
public AnidubParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories, TimeSpan rateLimit, IIndexerHttpClient httpClient, Logger logger)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
_rateLimit = rateLimit;
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private static string GetTitle(AngleSharp.Html.Dom.IHtmlDocument content, AngleSharp.Dom.IElement tabNode)
|
||||
@@ -314,9 +322,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
private static int GetReleaseLeechers(AngleSharp.Dom.IElement tabNode)
|
||||
{
|
||||
const string LeechersSelector = ".list.down > .li_swing_m";
|
||||
const string leechersSelector = ".list.down > .li_swing_m";
|
||||
|
||||
var leechersStr = tabNode.QuerySelector(LeechersSelector).TextContent;
|
||||
var leechersStr = tabNode.QuerySelector(leechersSelector).TextContent;
|
||||
int.TryParse(leechersStr, out var leechers);
|
||||
return leechers;
|
||||
}
|
||||
@@ -332,18 +340,18 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
private static int GetReleaseGrabs(AngleSharp.Dom.IElement tabNode)
|
||||
{
|
||||
const string GrabsSelector = ".list.down > .li_download_m";
|
||||
const string grabsSelector = ".list.down > .li_download_m";
|
||||
|
||||
var grabsStr = tabNode.QuerySelector(GrabsSelector).TextContent;
|
||||
var grabsStr = tabNode.QuerySelector(grabsSelector).TextContent;
|
||||
int.TryParse(grabsStr, out var grabs);
|
||||
return grabs;
|
||||
}
|
||||
|
||||
private static string GetDateFromDocument(AngleSharp.Html.Dom.IHtmlDocument content)
|
||||
{
|
||||
const string DateSelector = ".story_inf > li:nth-child(2)";
|
||||
const string dateSelector = ".story_inf > li:nth-child(2)";
|
||||
|
||||
var domDate = content.QuerySelector(DateSelector).LastChild;
|
||||
var domDate = content.QuerySelector(dateSelector).LastChild;
|
||||
|
||||
if (domDate?.NodeName != "#text")
|
||||
{
|
||||
@@ -384,16 +392,16 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
return utcDate.AddHours(-russianStandardTimeDiff);
|
||||
}
|
||||
|
||||
Logger.Warn($"[AniDub] Date time couldn't be parsed on. Date text: {dateText}");
|
||||
_logger.Warn($"[AniDub] Date time couldn't be parsed on. Date text: {dateText}");
|
||||
|
||||
return DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private static long GetReleaseSize(AngleSharp.Dom.IElement tabNode)
|
||||
{
|
||||
const string SizeSelector = ".list.down > .red";
|
||||
const string sizeSelector = ".list.down > .red";
|
||||
|
||||
var sizeStr = tabNode.QuerySelector(SizeSelector).TextContent;
|
||||
var sizeStr = tabNode.QuerySelector(sizeSelector).TextContent;
|
||||
return ParseUtil.GetBytes(sizeStr);
|
||||
}
|
||||
|
||||
@@ -433,11 +441,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Title = GetTitle(dom, t),
|
||||
InfoUrl = indexerResponse.Request.Url.ToString(),
|
||||
InfoUrl = indexerResponse.Request.Url.FullUri,
|
||||
DownloadVolumeFactor = 0,
|
||||
UploadVolumeFactor = 1,
|
||||
|
||||
Guid = indexerResponse.Request.Url.ToString() + t.Id,
|
||||
Guid = indexerResponse.Request.Url.FullUri + t.Id,
|
||||
Seeders = GetReleaseSeeders(t),
|
||||
Peers = GetReleaseSeeders(t) + GetReleaseLeechers(t),
|
||||
Grabs = GetReleaseGrabs(t),
|
||||
@@ -459,36 +467,30 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
var domQuery = string.Empty;
|
||||
|
||||
if (indexerResponse.Request.Url.Query.Contains("do=search"))
|
||||
{
|
||||
domQuery = ".searchitem > h3 > a";
|
||||
}
|
||||
else
|
||||
{
|
||||
domQuery = "#dle-content > .story > .story_h > .lcol > h2 > a";
|
||||
}
|
||||
|
||||
var links = dom.QuerySelectorAll(domQuery);
|
||||
var links = dom.QuerySelectorAll(".searchitem > h3 > a[href], #dle-content > .story > .story_h > .lcol > h2 > a[href]");
|
||||
foreach (var link in links)
|
||||
{
|
||||
var url = link.GetAttribute("href");
|
||||
|
||||
var releaseRequest = new IndexerRequest(url, HttpAccept.Html);
|
||||
var releaseResponse = new IndexerResponse(releaseRequest, HttpClient.Execute(releaseRequest.HttpRequest));
|
||||
var releaseRequest = new HttpRequestBuilder(url)
|
||||
.WithRateLimit(_rateLimit.TotalSeconds)
|
||||
.SetHeader("Referer", _settings.BaseUrl)
|
||||
.Accept(HttpAccept.Html)
|
||||
.Build();
|
||||
|
||||
var releaseIndexerRequest = new IndexerRequest(releaseRequest);
|
||||
var releaseResponse = new IndexerResponse(releaseIndexerRequest, _httpClient.Execute(releaseIndexerRequest.HttpRequest));
|
||||
|
||||
// Throw common http errors here before we try to parse
|
||||
if (releaseResponse.HttpResponse.HasHttpError)
|
||||
{
|
||||
if (releaseResponse.HttpResponse.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
throw new TooManyRequestsException(releaseRequest.HttpRequest, releaseResponse.HttpResponse);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IndexerException(releaseResponse, "Http error code: " + releaseResponse.HttpResponse.StatusCode);
|
||||
throw new TooManyRequestsException(releaseResponse.HttpRequest, releaseResponse.HttpResponse);
|
||||
}
|
||||
|
||||
throw new IndexerException(releaseResponse, $"HTTP Error - {releaseResponse.HttpResponse.StatusCode}. {url}");
|
||||
}
|
||||
|
||||
torrentInfos.AddRange(ParseRelease(releaseResponse));
|
||||
|
||||
@@ -425,7 +425,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
|
||||
var releaseGroup = releaseTags.LastOrDefault();
|
||||
if (releaseGroup != null && releaseGroup.Contains("(") && releaseGroup.Contains(")"))
|
||||
if (releaseGroup != null && releaseGroup.Contains('(') && releaseGroup.Contains(')'))
|
||||
{
|
||||
//// Skip raws if set
|
||||
//if (releaseGroup.ToLowerInvariant().StartsWith("raw") && !AllowRaws)
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
if (response.Content != null && response.Content.Contains("logout.php"))
|
||||
{
|
||||
UpdateCookies(response.GetCookies(), DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(response.GetCookies(), DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("AnimeTorrents authentication succeeded");
|
||||
}
|
||||
|
||||
@@ -5,10 +5,8 @@ using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using AngleSharp.Html.Parser;
|
||||
using FluentValidation;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
@@ -16,14 +14,13 @@ using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class Animedia : TorrentIndexerBase<NoAuthTorrentBaseSettings>
|
||||
{
|
||||
public override string Name => "Animedia";
|
||||
public override string[] IndexerUrls => new string[] { "https://tt.animedia.tv/" };
|
||||
public override string[] IndexerUrls => new[] { "https://tt.animedia.tv/" };
|
||||
public override string Description => "Animedia is russian anime voiceover group and eponymous anime tracker.";
|
||||
public override string Language => "ru-RU";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
@@ -38,12 +35,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new AnimediaRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
||||
return new AnimediaRequestGenerator(Settings);
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new AnimediaParser(Settings, Capabilities.Categories) { HttpClient = _httpClient, Logger = _logger };
|
||||
return new AnimediaParser(Settings, Capabilities.Categories, RateLimit, _httpClient);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
@@ -51,38 +48,40 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
}
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "TV Anime");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "OVA/ONA/Special");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TV, "Dorama");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.Movies, "Movies");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class AnimediaRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public NoAuthTorrentBaseSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
private readonly NoAuthTorrentBaseSettings _settings;
|
||||
|
||||
public AnimediaRequestGenerator()
|
||||
public AnimediaRequestGenerator(NoAuthTorrentBaseSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term)
|
||||
{
|
||||
var requestUrl = string.Empty;
|
||||
string requestUrl;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(term))
|
||||
{
|
||||
requestUrl = Settings.BaseUrl;
|
||||
requestUrl = _settings.BaseUrl;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -94,18 +93,17 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{ "orderby_sort", "entry_date|desc" }
|
||||
};
|
||||
|
||||
requestUrl = string.Format("{0}/ajax/search_result/P0?{1}", Settings.BaseUrl.TrimEnd('/'), queryCollection.GetQueryString());
|
||||
requestUrl = $"{_settings.BaseUrl.TrimEnd('/')}/ajax/search_result/P0?{queryCollection.GetQueryString()}";
|
||||
}
|
||||
|
||||
var request = new IndexerRequest(requestUrl, HttpAccept.Html);
|
||||
yield return request;
|
||||
yield return new IndexerRequest(requestUrl, HttpAccept.Html);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -114,7 +112,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedTvSearchString}"));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -123,7 +121,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -148,6 +146,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
private readonly NoAuthTorrentBaseSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
private readonly TimeSpan _rateLimit;
|
||||
private readonly IIndexerHttpClient _httpClient;
|
||||
|
||||
private static readonly Regex EpisodesInfoQueryRegex = new Regex(@"сери[ия] (\d+)(?:-(\d+))? из.*", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex ResolutionInfoQueryRegex = new Regex(@"качество (\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex SizeInfoQueryRegex = new Regex(@"размер:(.*)\n", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
@@ -155,25 +156,25 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
private static readonly Regex CategorieMovieRegex = new Regex(@"Фильм", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex CategorieOVARegex = new Regex(@"ОВА|OVA|ОНА|ONA|Special", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex CategorieDoramaRegex = new Regex(@"Дорама", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
public IIndexerHttpClient HttpClient { get; set; }
|
||||
public Logger Logger { get; set; }
|
||||
|
||||
public AnimediaParser(NoAuthTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
|
||||
public AnimediaParser(NoAuthTorrentBaseSettings settings, IndexerCapabilitiesCategories categories, TimeSpan rateLimit, IIndexerHttpClient httpClient)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
_rateLimit = rateLimit;
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
private string composeTitle(AngleSharp.Html.Dom.IHtmlDocument dom, AngleSharp.Dom.IElement t, AngleSharp.Dom.IElement tr)
|
||||
private string ComposeTitle(AngleSharp.Html.Dom.IHtmlDocument dom, AngleSharp.Dom.IElement t, AngleSharp.Dom.IElement tr)
|
||||
{
|
||||
var name_ru = dom.QuerySelector("div.media__post__header > h1").TextContent.Trim();
|
||||
var name_en = dom.QuerySelector("div.media__panel > div:nth-of-type(1) > div.col-l:nth-of-type(1) > div > span").TextContent.Trim();
|
||||
var name_orig = dom.QuerySelector("div.media__panel > div:nth-of-type(1) > div.col-l:nth-of-type(2) > div > span").TextContent.Trim();
|
||||
var nameRu = dom.QuerySelector("div.media__post__header > h1")?.TextContent.Trim() ?? string.Empty;
|
||||
var nameEn = dom.QuerySelector("div.media__panel > div:nth-of-type(1) > div.col-l:nth-of-type(1) > div > span")?.TextContent.Trim() ?? string.Empty;
|
||||
var nameOrig = dom.QuerySelector("div.media__panel > div:nth-of-type(1) > div.col-l:nth-of-type(2) > div > span")?.TextContent.Trim() ?? string.Empty;
|
||||
|
||||
var title = name_ru + " / " + name_en;
|
||||
if (name_en != name_orig)
|
||||
var title = nameRu + " / " + nameEn;
|
||||
if (nameEn != nameOrig)
|
||||
{
|
||||
title += " / " + name_orig;
|
||||
title += " / " + nameOrig;
|
||||
}
|
||||
|
||||
var tabName = t.TextContent;
|
||||
@@ -183,7 +184,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
tabName = "";
|
||||
}
|
||||
|
||||
var heading = tr.QuerySelector("h3.tracker_info_bold").TextContent;
|
||||
var heading = tr.QuerySelector("h3.tracker_info_bold")?.TextContent.Trim() ?? string.Empty;
|
||||
|
||||
// Parse episodes info from heading if episods info present
|
||||
var match = EpisodesInfoQueryRegex.Match(heading);
|
||||
@@ -192,40 +193,40 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
if (string.IsNullOrEmpty(match.Groups[2].Value))
|
||||
{
|
||||
heading += " E" + match.Groups[1].Value;
|
||||
heading += $" E{match.Groups[1].Value}";
|
||||
}
|
||||
else
|
||||
{
|
||||
heading += string.Format(" E{0}-{1}", match.Groups[1].Value, match.Groups[2].Value);
|
||||
heading += $" E{match.Groups[1].Value}-{match.Groups[2].Value}";
|
||||
}
|
||||
}
|
||||
|
||||
return title + " - " + heading + " [" + getResolution(tr) + "p]";
|
||||
return title + " - " + heading + " [" + GetResolution(tr) + "p]";
|
||||
}
|
||||
|
||||
private string getResolution(AngleSharp.Dom.IElement tr)
|
||||
private string GetResolution(AngleSharp.Dom.IElement tr)
|
||||
{
|
||||
var resolution = tr.QuerySelector("div.tracker_info_left").TextContent;
|
||||
var resolution = tr.QuerySelector("div.tracker_info_left")?.TextContent.Trim() ?? string.Empty;
|
||||
return ResolutionInfoQueryRegex.Match(resolution).Groups[1].Value;
|
||||
}
|
||||
|
||||
private long getReleaseSize(AngleSharp.Dom.IElement tr)
|
||||
private long GetReleaseSize(AngleSharp.Dom.IElement tr)
|
||||
{
|
||||
var sizeStr = tr.QuerySelector("div.tracker_info_left").TextContent;
|
||||
var sizeStr = tr.QuerySelector("div.tracker_info_left")?.TextContent.Trim() ?? string.Empty;
|
||||
return ParseUtil.GetBytes(SizeInfoQueryRegex.Match(sizeStr).Groups[1].Value.Trim());
|
||||
}
|
||||
|
||||
private DateTime getReleaseDate(AngleSharp.Dom.IElement tr)
|
||||
private DateTime GetReleaseDate(AngleSharp.Dom.IElement tr)
|
||||
{
|
||||
var sizeStr = tr.QuerySelector("div.tracker_info_left").TextContent;
|
||||
var sizeStr = tr.QuerySelector("div.tracker_info_left")?.TextContent.Trim() ?? string.Empty;
|
||||
return DateTime.Parse(ReleaseDateInfoQueryRegex.Match(sizeStr).Groups[1].Value.Trim());
|
||||
}
|
||||
|
||||
private ICollection<IndexerCategory> MapCategories(AngleSharp.Html.Dom.IHtmlDocument dom, AngleSharp.Dom.IElement t, AngleSharp.Dom.IElement tr)
|
||||
{
|
||||
var rName = t.TextContent;
|
||||
var rDesc = tr.QuerySelector("h3.tracker_info_bold").TextContent;
|
||||
var type = dom.QuerySelector("div.releases-date:contains('Тип:')").TextContent;
|
||||
var rDesc = tr.QuerySelector("h3.tracker_info_bold")?.TextContent.Trim() ?? string.Empty;
|
||||
var type = dom.QuerySelector("div.releases-date:contains('Тип:')")?.TextContent.Trim() ?? string.Empty;
|
||||
|
||||
// Check OVA first cause OVA looks like anime with OVA in release name or description
|
||||
if (CategorieOVARegex.IsMatch(rName) || CategorieOVARegex.IsMatch(rDesc))
|
||||
@@ -256,28 +257,28 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
foreach (var t in dom.QuerySelectorAll("ul.media__tabs__nav > li > a"))
|
||||
{
|
||||
var tr_id = t.Attributes["href"].Value;
|
||||
var tr = dom.QuerySelector("div" + tr_id);
|
||||
var trId = t.GetAttribute("href");
|
||||
var tr = dom.QuerySelector("div" + trId);
|
||||
var seeders = int.Parse(tr.QuerySelector("div.circle_green_text_top").TextContent);
|
||||
var url = indexerResponse.HttpRequest.Url.ToString();
|
||||
var url = indexerResponse.HttpRequest.Url.FullUri;
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Title = composeTitle(dom, t, tr),
|
||||
Title = ComposeTitle(dom, t, tr),
|
||||
InfoUrl = url,
|
||||
DownloadVolumeFactor = 0,
|
||||
UploadVolumeFactor = 1,
|
||||
|
||||
Guid = url + tr_id,
|
||||
Guid = url + trId,
|
||||
Seeders = seeders,
|
||||
Peers = seeders + int.Parse(tr.QuerySelector("div.circle_red_text_top").TextContent),
|
||||
Grabs = int.Parse(tr.QuerySelector("div.circle_grey_text_top").TextContent),
|
||||
Categories = MapCategories(dom, t, tr),
|
||||
PublishDate = getReleaseDate(tr),
|
||||
DownloadUrl = tr.QuerySelector("div.download_tracker > a.btn__green").Attributes["href"].Value,
|
||||
MagnetUrl = tr.QuerySelector("div.download_tracker > a.btn__d-gray").Attributes["href"].Value,
|
||||
Size = getReleaseSize(tr),
|
||||
Resolution = getResolution(tr)
|
||||
PublishDate = GetReleaseDate(tr),
|
||||
DownloadUrl = tr.QuerySelector("div.download_tracker > a.btn__green").GetAttribute("href"),
|
||||
MagnetUrl = tr.QuerySelector("div.download_tracker > a.btn__d-gray").GetAttribute("href"),
|
||||
Size = GetReleaseSize(tr),
|
||||
Resolution = GetResolution(tr)
|
||||
};
|
||||
torrentInfos.Add(release);
|
||||
}
|
||||
@@ -291,6 +292,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var links = dom.QuerySelectorAll("a.ads-list__item__title");
|
||||
foreach (var link in links)
|
||||
{
|
||||
@@ -302,20 +304,24 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
url = "https:" + url;
|
||||
}
|
||||
|
||||
var releaseRequest = new IndexerRequest(url, HttpAccept.Html);
|
||||
var releaseResponse = new IndexerResponse(releaseRequest, HttpClient.Execute(releaseRequest.HttpRequest));
|
||||
var releaseRequest = new HttpRequestBuilder(url)
|
||||
.WithRateLimit(_rateLimit.TotalSeconds)
|
||||
.SetHeader("Referer", _settings.BaseUrl)
|
||||
.Accept(HttpAccept.Html)
|
||||
.Build();
|
||||
|
||||
var releaseIndexerRequest = new IndexerRequest(releaseRequest);
|
||||
var releaseResponse = new IndexerResponse(releaseIndexerRequest, _httpClient.Execute(releaseIndexerRequest.HttpRequest));
|
||||
|
||||
// Throw common http errors here before we try to parse
|
||||
if (releaseResponse.HttpResponse.HasHttpError)
|
||||
{
|
||||
if (releaseResponse.HttpResponse.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
throw new TooManyRequestsException(releaseRequest.HttpRequest, releaseResponse.HttpResponse);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IndexerException(releaseResponse, "Http error code: " + releaseResponse.HttpResponse.StatusCode);
|
||||
throw new TooManyRequestsException(releaseResponse.HttpRequest, releaseResponse.HttpResponse);
|
||||
}
|
||||
|
||||
throw new IndexerException(releaseResponse, $"HTTP Error - {releaseResponse.HttpResponse.StatusCode}. {url}");
|
||||
}
|
||||
|
||||
torrentInfos.AddRange(ParseRelease(releaseResponse));
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
|
||||
cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("Anthelion authentication succeeded.");
|
||||
}
|
||||
|
||||
358
src/NzbDrone.Core/Indexers/Definitions/AudioBookBay.cs
Normal file
358
src/NzbDrone.Core/Indexers/Definitions/AudioBookBay.cs
Normal file
@@ -0,0 +1,358 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Html.Dom;
|
||||
using AngleSharp.Html.Parser;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
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 AudioBookBay : TorrentIndexerBase<NoAuthTorrentBaseSettings>
|
||||
{
|
||||
public override string Name => "AudioBook Bay";
|
||||
public override string[] IndexerUrls => new[]
|
||||
{
|
||||
"https://audiobookbay.li/",
|
||||
"https://audiobookbay.se/"
|
||||
};
|
||||
public override string[] LegacyUrls => new[]
|
||||
{
|
||||
"https://audiobookbay.la/",
|
||||
"http://audiobookbay.net/",
|
||||
"https://audiobookbay.unblockit.tv/",
|
||||
"http://audiobookbay.nl/",
|
||||
"http://audiobookbay.ws/",
|
||||
"https://audiobookbay.unblockit.how/",
|
||||
"https://audiobookbay.unblockit.cam/",
|
||||
"https://audiobookbay.unblockit.biz/",
|
||||
"https://audiobookbay.unblockit.day/",
|
||||
"https://audiobookbay.unblockit.llc/",
|
||||
"https://audiobookbay.unblockit.blue/",
|
||||
"https://audiobookbay.unblockit.name/",
|
||||
"http://audiobookbay.fi/",
|
||||
"http://audiobookbay.se/",
|
||||
"http://audiobookbayabb.com/",
|
||||
"https://audiobookbay.unblockit.ist/",
|
||||
"https://audiobookbay.unblockit.bet/",
|
||||
"https://audiobookbay.unblockit.cat/",
|
||||
"https://audiobookbay.unblockit.nz/",
|
||||
"https://audiobookbay.fi/",
|
||||
"https://audiobookbay.unblockit.page/",
|
||||
"https://audiobookbay.unblockit.pet/",
|
||||
"https://audiobookbay.unblockit.ink/",
|
||||
"https://audiobookbay.unblockit.bio/" // error 502
|
||||
};
|
||||
public override string Description => "AudioBook Bay (ABB) is a public Torrent Tracker for AUDIOBOOKS";
|
||||
public override string Language => "en-US";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
public override int PageSize => 15;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public AudioBookBay(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new AudioBookBayRequestGenerator(Settings, Capabilities);
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new AudioBookBayParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
public override async Task<byte[]> Download(Uri link)
|
||||
{
|
||||
var request = new HttpRequestBuilder(link.ToString())
|
||||
.SetCookies(GetCookies() ?? new Dictionary<string, string>())
|
||||
.Accept(HttpAccept.Html)
|
||||
.Build();
|
||||
|
||||
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
|
||||
var hash = dom.QuerySelector("td:contains(\"Info Hash:\") ~ td")?.TextContent.Trim();
|
||||
if (hash == null)
|
||||
{
|
||||
throw new Exception($"Failed to fetch hash from {link}");
|
||||
}
|
||||
|
||||
var title = dom.QuerySelector("div.postTitle h1")?.TextContent.Trim();
|
||||
if (title == null)
|
||||
{
|
||||
throw new Exception($"Failed to fetch title from {link}");
|
||||
}
|
||||
|
||||
title = StringUtil.MakeValidFileName(title, '_', false);
|
||||
|
||||
var magnet = MagnetLinkBuilder.BuildPublicMagnetLink(hash, title);
|
||||
|
||||
return await base.Download(new Uri(magnet));
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
// Age
|
||||
caps.Categories.AddCategoryMapping("children", NewznabStandardCategory.AudioAudiobook, "Children");
|
||||
caps.Categories.AddCategoryMapping("teen-young-adult", NewznabStandardCategory.AudioAudiobook, "Teen & Young Adult");
|
||||
caps.Categories.AddCategoryMapping("adults", NewznabStandardCategory.AudioAudiobook, "Adults");
|
||||
|
||||
// Category
|
||||
caps.Categories.AddCategoryMapping("postapocalyptic", NewznabStandardCategory.AudioAudiobook, "(Post)apocalyptic");
|
||||
caps.Categories.AddCategoryMapping("action", NewznabStandardCategory.AudioAudiobook, "Action");
|
||||
caps.Categories.AddCategoryMapping("adventure", NewznabStandardCategory.AudioAudiobook, "Adventure");
|
||||
caps.Categories.AddCategoryMapping("art", NewznabStandardCategory.AudioAudiobook, "Art");
|
||||
caps.Categories.AddCategoryMapping("autobiography-biographies", NewznabStandardCategory.AudioAudiobook, "Autobiography & Biographies");
|
||||
caps.Categories.AddCategoryMapping("business", NewznabStandardCategory.AudioAudiobook, "Business");
|
||||
caps.Categories.AddCategoryMapping("computer", NewznabStandardCategory.AudioAudiobook, "Computer");
|
||||
caps.Categories.AddCategoryMapping("contemporary", NewznabStandardCategory.AudioAudiobook, "Contemporary");
|
||||
caps.Categories.AddCategoryMapping("crime", NewznabStandardCategory.AudioAudiobook, "Crime");
|
||||
caps.Categories.AddCategoryMapping("detective", NewznabStandardCategory.AudioAudiobook, "Detective");
|
||||
caps.Categories.AddCategoryMapping("doctor-who-sci-fi", NewznabStandardCategory.AudioAudiobook, "Doctor Who");
|
||||
caps.Categories.AddCategoryMapping("education", NewznabStandardCategory.AudioAudiobook, "Education");
|
||||
caps.Categories.AddCategoryMapping("fantasy", NewznabStandardCategory.AudioAudiobook, "Fantasy");
|
||||
caps.Categories.AddCategoryMapping("general-fiction", NewznabStandardCategory.AudioAudiobook, "General Fiction");
|
||||
caps.Categories.AddCategoryMapping("historical-fiction", NewznabStandardCategory.AudioAudiobook, "Historical Fiction");
|
||||
caps.Categories.AddCategoryMapping("history", NewznabStandardCategory.AudioAudiobook, "History");
|
||||
caps.Categories.AddCategoryMapping("horror", NewznabStandardCategory.AudioAudiobook, "Horror");
|
||||
caps.Categories.AddCategoryMapping("humor", NewznabStandardCategory.AudioAudiobook, "Humor");
|
||||
caps.Categories.AddCategoryMapping("lecture", NewznabStandardCategory.AudioAudiobook, "Lecture");
|
||||
caps.Categories.AddCategoryMapping("lgbt", NewznabStandardCategory.AudioAudiobook, "LGBT");
|
||||
caps.Categories.AddCategoryMapping("literature", NewznabStandardCategory.AudioAudiobook, "Literature");
|
||||
caps.Categories.AddCategoryMapping("litrpg", NewznabStandardCategory.AudioAudiobook, "LitRPG");
|
||||
caps.Categories.AddCategoryMapping("general-non-fiction", NewznabStandardCategory.AudioAudiobook, "Misc. Non-fiction");
|
||||
caps.Categories.AddCategoryMapping("mystery", NewznabStandardCategory.AudioAudiobook, "Mystery");
|
||||
caps.Categories.AddCategoryMapping("paranormal", NewznabStandardCategory.AudioAudiobook, "Paranormal");
|
||||
caps.Categories.AddCategoryMapping("plays-theater", NewznabStandardCategory.AudioAudiobook, "Plays & Theater");
|
||||
caps.Categories.AddCategoryMapping("poetry", NewznabStandardCategory.AudioAudiobook, "Poetry");
|
||||
caps.Categories.AddCategoryMapping("political", NewznabStandardCategory.AudioAudiobook, "Political");
|
||||
caps.Categories.AddCategoryMapping("radio-productions", NewznabStandardCategory.AudioAudiobook, "Radio Productions");
|
||||
caps.Categories.AddCategoryMapping("romance", NewznabStandardCategory.AudioAudiobook, "Romance");
|
||||
caps.Categories.AddCategoryMapping("sci-fi", NewznabStandardCategory.AudioAudiobook, "Sci-Fi");
|
||||
caps.Categories.AddCategoryMapping("science", NewznabStandardCategory.AudioAudiobook, "Science");
|
||||
caps.Categories.AddCategoryMapping("self-help", NewznabStandardCategory.AudioAudiobook, "Self-help");
|
||||
caps.Categories.AddCategoryMapping("spiritual", NewznabStandardCategory.AudioAudiobook, "Spiritual & Religious");
|
||||
caps.Categories.AddCategoryMapping("sports", NewznabStandardCategory.AudioAudiobook, "Sport & Recreation");
|
||||
caps.Categories.AddCategoryMapping("suspense", NewznabStandardCategory.AudioAudiobook, "Suspense");
|
||||
caps.Categories.AddCategoryMapping("thriller", NewznabStandardCategory.AudioAudiobook, "Thriller");
|
||||
caps.Categories.AddCategoryMapping("true-crime", NewznabStandardCategory.AudioAudiobook, "True Crime");
|
||||
caps.Categories.AddCategoryMapping("tutorial", NewznabStandardCategory.AudioAudiobook, "Tutorial");
|
||||
caps.Categories.AddCategoryMapping("westerns", NewznabStandardCategory.AudioAudiobook, "Westerns");
|
||||
caps.Categories.AddCategoryMapping("zombies", NewznabStandardCategory.AudioAudiobook, "Zombies");
|
||||
|
||||
// Category Modifiers
|
||||
caps.Categories.AddCategoryMapping("anthology", NewznabStandardCategory.AudioAudiobook, "Anthology");
|
||||
caps.Categories.AddCategoryMapping("bestsellers", NewznabStandardCategory.AudioAudiobook, "Bestsellers");
|
||||
caps.Categories.AddCategoryMapping("classic", NewznabStandardCategory.AudioAudiobook, "Classic");
|
||||
caps.Categories.AddCategoryMapping("documentary", NewznabStandardCategory.AudioAudiobook, "Documentary");
|
||||
caps.Categories.AddCategoryMapping("full-cast", NewznabStandardCategory.AudioAudiobook, "Full Cast");
|
||||
caps.Categories.AddCategoryMapping("libertarian", NewznabStandardCategory.AudioAudiobook, "Libertarian");
|
||||
caps.Categories.AddCategoryMapping("military", NewznabStandardCategory.AudioAudiobook, "Military");
|
||||
caps.Categories.AddCategoryMapping("novel", NewznabStandardCategory.AudioAudiobook, "Novel");
|
||||
caps.Categories.AddCategoryMapping("short-story", NewznabStandardCategory.AudioAudiobook, "Short Story");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class AudioBookBayRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
private readonly NoAuthTorrentBaseSettings _settings;
|
||||
private readonly IndexerCapabilities _capabilities;
|
||||
|
||||
public AudioBookBayRequestGenerator(NoAuthTorrentBaseSettings settings, IndexerCapabilities capabilities)
|
||||
{
|
||||
_settings = settings;
|
||||
_capabilities = capabilities;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term)
|
||||
{
|
||||
var searchUrl = _settings.BaseUrl;
|
||||
|
||||
var parameters = new NameValueCollection();
|
||||
|
||||
term = Regex.Replace(term, @"[\W]+", " ").Trim();
|
||||
|
||||
if (term.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Set("s", term);
|
||||
parameters.Set("tt", "1");
|
||||
}
|
||||
|
||||
if (parameters.Count > 0)
|
||||
{
|
||||
searchUrl += $"?{parameters.GetQueryString()}";
|
||||
}
|
||||
|
||||
yield return new IndexerRequest(new UriBuilder(searchUrl) { Path = "/" }.Uri.AbsoluteUri, HttpAccept.Html);
|
||||
yield return new IndexerRequest(new UriBuilder(searchUrl) { Path = "/page/2/" }.Uri.AbsoluteUri, HttpAccept.Html);
|
||||
yield return new IndexerRequest(new UriBuilder(searchUrl) { Path = "/page/3/" }.Uri.AbsoluteUri, HttpAccept.Html);
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class AudioBookBayParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly NoAuthTorrentBaseSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
public AudioBookBayParser(NoAuthTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var doc = ParseHtmlDocument(indexerResponse.Content);
|
||||
|
||||
var rows = doc.QuerySelectorAll("div.post:has(div[class=\"postTitle\"])");
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var infoUrl = _settings.BaseUrl + row.QuerySelector("div.postTitle h2 a")?.GetAttribute("href")?.Trim().TrimStart('/');
|
||||
|
||||
var title = row.QuerySelector("div.postTitle")?.TextContent.Trim();
|
||||
|
||||
var infoString = row.QuerySelector("div.postContent")?.TextContent.Trim() ?? string.Empty;
|
||||
|
||||
var matchFormat = Regex.Match(infoString, @"Format: (.+) \/", RegexOptions.IgnoreCase);
|
||||
if (matchFormat.Groups[1].Success && matchFormat.Groups[1].Value.Length > 0 && matchFormat.Groups[1].Value != "?")
|
||||
{
|
||||
title += $" [{matchFormat.Groups[1].Value.Trim()}]";
|
||||
}
|
||||
|
||||
var matchBitrate = Regex.Match(infoString, @"Bitrate: (.+)File", RegexOptions.IgnoreCase);
|
||||
if (matchBitrate.Groups[1].Success && matchBitrate.Groups[1].Value.Length > 0 && matchBitrate.Groups[1].Value != "?")
|
||||
{
|
||||
title += $" [{matchBitrate.Groups[1].Value.Trim()}]";
|
||||
}
|
||||
|
||||
var matchSize = Regex.Match(infoString, @"File Size: (.+?)s?$", RegexOptions.IgnoreCase);
|
||||
var size = matchSize.Groups[1].Success ? ParseUtil.GetBytes(matchSize.Groups[1].Value) : 0;
|
||||
|
||||
var matchDateAdded = Regex.Match(infoString, @"Posted: (\d{1,2} \D{3} \d{4})", RegexOptions.IgnoreCase);
|
||||
var publishDate = matchDateAdded.Groups[1].Success && DateTime.TryParseExact(matchDateAdded.Groups[1].Value, "d MMM yyyy", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsedDate) ? parsedDate : DateTime.Now;
|
||||
|
||||
var postInfo = row.QuerySelector("div.postInfo")?.FirstChild?.TextContent.Trim().Replace("\xA0", ";") ?? string.Empty;
|
||||
var matchCategory = Regex.Match(postInfo, @"Category: (.+)$", RegexOptions.IgnoreCase);
|
||||
var category = matchCategory.Groups[1].Success ? matchCategory.Groups[1].Value.Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList() : new List<string>();
|
||||
var categories = category.SelectMany(_categories.MapTrackerCatDescToNewznab).Distinct().ToList();
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Guid = infoUrl,
|
||||
InfoUrl = infoUrl,
|
||||
DownloadUrl = infoUrl,
|
||||
Title = CleanTitle(title),
|
||||
Categories = categories,
|
||||
Size = size,
|
||||
Seeders = 1,
|
||||
Peers = 1,
|
||||
PublishDate = publishDate,
|
||||
DownloadVolumeFactor = 0,
|
||||
UploadVolumeFactor = 1
|
||||
};
|
||||
|
||||
var cover = row.QuerySelector("img[src]")?.GetAttribute("src")?.Trim();
|
||||
if (!string.IsNullOrEmpty(cover))
|
||||
{
|
||||
release.PosterUrl = cover.StartsWith("http") ? cover : _settings.BaseUrl + cover;
|
||||
}
|
||||
|
||||
releaseInfos.Add(release);
|
||||
}
|
||||
|
||||
return releaseInfos;
|
||||
}
|
||||
|
||||
private static IHtmlDocument ParseHtmlDocument(string response)
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var doc = parser.ParseDocument(response);
|
||||
|
||||
var hidden = doc.QuerySelectorAll("div.post.re-ab");
|
||||
foreach (var element in hidden)
|
||||
{
|
||||
var body = doc.CreateElement("div");
|
||||
body.ClassList.Add("post");
|
||||
body.InnerHtml = Encoding.UTF8.GetString(Convert.FromBase64String(element.TextContent));
|
||||
element.Parent.ReplaceChild(body, element);
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
private static string CleanTitle(string title)
|
||||
{
|
||||
title = Regex.Replace(title, @"[\u0000-\u0008\u000A-\u001F\u0100-\uFFFF]", string.Empty, RegexOptions.Compiled);
|
||||
title = Regex.Replace(title, @"\s+", " ", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
return title.Trim();
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
@@ -94,14 +94,14 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
{
|
||||
_logger.Warn(ex, "Unable to connect to indexer");
|
||||
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details. " + ex.Message);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "Unable to connect to indexer");
|
||||
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details");
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
|
||||
cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("BB authentication succeeded.");
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
if (response.Content != null && response.Content.Contains("<a href=\"logout.php\">Logout</a>"))
|
||||
{
|
||||
UpdateCookies(response.GetCookies(), DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(response.GetCookies(), DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("BakaBT authentication succeeded");
|
||||
}
|
||||
@@ -281,7 +281,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var stringSeparator = new[] { " | " };
|
||||
var titles = titleSeries.Split(stringSeparator, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (titles.Count() > 1 && !_settings.AddRomajiTitle)
|
||||
if (titles.Length > 1 && !_settings.AddRomajiTitle)
|
||||
{
|
||||
titles = titles.Skip(1).ToArray();
|
||||
}
|
||||
@@ -293,7 +293,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
release.Title = (name + releaseInfo).Trim();
|
||||
|
||||
// Ensure the season is defined as this tracker only deals with full seasons
|
||||
if (release.Title.IndexOf("Season") == -1 && _settings.AppendSeason)
|
||||
if (!release.Title.Contains("Season", StringComparison.CurrentCulture) && _settings.AppendSeason)
|
||||
{
|
||||
// Insert before the release info
|
||||
var aidx = release.Title.IndexOf('(');
|
||||
|
||||
@@ -189,6 +189,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
if (request.Url.Scheme == "magnet")
|
||||
{
|
||||
ValidateMagnet(request.Url.FullUri);
|
||||
|
||||
return Encoding.UTF8.GetBytes(request.Url.FullUri);
|
||||
}
|
||||
|
||||
@@ -233,6 +234,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
throw;
|
||||
}
|
||||
|
||||
ValidateTorrent(downloadBytes);
|
||||
|
||||
return downloadBytes;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
public double? RequestDelay { get; set; }
|
||||
public List<string> Links { get; set; }
|
||||
public List<string> Legacylinks { get; set; }
|
||||
public bool Followredirect { get; set; } = false;
|
||||
public bool Followredirect { get; set; }
|
||||
public bool TestLinkTorrent { get; set; } = true;
|
||||
public List<string> Certificates { get; set; }
|
||||
public CapabilitiesBlock Caps { get; set; }
|
||||
@@ -95,7 +95,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
public List<string> Cookies { get; set; }
|
||||
public string Method { get; set; }
|
||||
public string Form { get; set; }
|
||||
public bool Selectors { get; set; } = false;
|
||||
public bool Selectors { get; set; }
|
||||
public Dictionary<string, string> Inputs { get; set; }
|
||||
public Dictionary<string, SelectorBlock> Selectorinputs { get; set; }
|
||||
public Dictionary<string, SelectorBlock> Getselectorinputs { get; set; }
|
||||
@@ -114,7 +114,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
public class SelectorBlock
|
||||
{
|
||||
public string Selector { get; set; }
|
||||
public bool Optional { get; set; } = false;
|
||||
public bool Optional { get; set; }
|
||||
public string Text { get; set; }
|
||||
public string Attribute { get; set; }
|
||||
public string Remove { get; set; }
|
||||
@@ -157,7 +157,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
public int After { get; set; }
|
||||
public SelectorBlock Dateheaders { get; set; }
|
||||
public SelectorBlock Count { get; set; }
|
||||
public bool Multiple { get; set; } = false;
|
||||
public bool Multiple { get; set; }
|
||||
}
|
||||
|
||||
public class SearchPathBlock : RequestBlock
|
||||
|
||||
@@ -40,12 +40,16 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
// Remove cookie cache
|
||||
if (indexerResponse.HttpResponse.HasHttpRedirect && indexerResponse.HttpResponse.RedirectUrl
|
||||
.ContainsIgnoreCase("login.php"))
|
||||
if (indexerResponse.HttpResponse.HasHttpRedirect)
|
||||
{
|
||||
CookiesUpdater(null, null);
|
||||
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.");
|
||||
if (indexerResponse.HttpResponse.RedirectUrl.ContainsIgnoreCase("login.php"))
|
||||
{
|
||||
// Remove cookie cache
|
||||
CookiesUpdater(null, null);
|
||||
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, $"Redirected to {indexerResponse.HttpResponse.RedirectUrl} from API request");
|
||||
}
|
||||
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
||||
@@ -62,7 +66,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
{
|
||||
if (request.SearchPath.Response != null &&
|
||||
request.SearchPath.Response.NoResultsMessage != null &&
|
||||
((request.SearchPath.Response.NoResultsMessage != string.Empty && results.Contains(request.SearchPath.Response.NoResultsMessage)) || (request.SearchPath.Response.NoResultsMessage == string.Empty && results == string.Empty)))
|
||||
((request.SearchPath.Response.NoResultsMessage.IsNotNullOrWhiteSpace() && results.Contains(request.SearchPath.Response.NoResultsMessage)) || (request.SearchPath.Response.NoResultsMessage.IsNullOrWhiteSpace() && results.IsNullOrWhiteSpace())))
|
||||
{
|
||||
return releases;
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
CheckForError(response, login.Error);
|
||||
|
||||
CookiesUpdater(Cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
CookiesUpdater(Cookies, DateTime.Now.AddDays(30));
|
||||
}
|
||||
else if (login.Method == "form")
|
||||
{
|
||||
@@ -467,13 +467,13 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
Cookies = loginResult.GetCookies();
|
||||
CheckForError(loginResult, login.Error);
|
||||
CookiesUpdater(Cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
CookiesUpdater(Cookies, DateTime.Now.AddDays(30));
|
||||
}
|
||||
else if (login.Method == "cookie")
|
||||
{
|
||||
CookiesUpdater(null, null);
|
||||
Settings.ExtraFieldData.TryGetValue("cookie", out var cookies);
|
||||
CookiesUpdater(CookieUtil.CookieHeaderToDictionary((string)cookies), DateTime.Now + TimeSpan.FromDays(30));
|
||||
CookiesUpdater(CookieUtil.CookieHeaderToDictionary((string)cookies), DateTime.Now.AddDays(30));
|
||||
}
|
||||
else if (login.Method == "get")
|
||||
{
|
||||
@@ -504,7 +504,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
CheckForError(response, login.Error);
|
||||
|
||||
CookiesUpdater(Cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
CookiesUpdater(Cookies, DateTime.Now.AddDays(30));
|
||||
}
|
||||
else if (login.Method == "oneurl")
|
||||
{
|
||||
@@ -529,7 +529,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
CheckForError(response, login.Error);
|
||||
|
||||
CookiesUpdater(Cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
CookiesUpdater(Cookies, DateTime.Now.AddDays(30));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@@ -18,54 +19,58 @@ public class FileListRequestGenerator : IIndexerRequestGenerator
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
var parameters = GetDefaultParameters();
|
||||
var parameters = new NameValueCollection();
|
||||
|
||||
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace() || searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Add("action", "search-torrents");
|
||||
parameters.Set("action", "search-torrents");
|
||||
|
||||
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Add("type", "imdb");
|
||||
parameters.Add("query", searchCriteria.FullImdbId);
|
||||
parameters.Set("type", "imdb");
|
||||
parameters.Set("query", searchCriteria.FullImdbId);
|
||||
}
|
||||
else if (searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Add("type", "name");
|
||||
parameters.Add("query", searchCriteria.SanitizedSearchTerm.Trim());
|
||||
parameters.Set("type", "name");
|
||||
parameters.Set("query", searchCriteria.SanitizedSearchTerm.Trim());
|
||||
}
|
||||
|
||||
if (searchCriteria.Season.HasValue)
|
||||
{
|
||||
parameters.Add("season", searchCriteria.Season.ToString());
|
||||
parameters.Add("episode", searchCriteria.Episode);
|
||||
parameters.Set("season", searchCriteria.Season.ToString());
|
||||
}
|
||||
|
||||
if (searchCriteria.Episode.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Set("episode", searchCriteria.Episode);
|
||||
}
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetRequest(searchCriteria, parameters));
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria, parameters));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
var parameters = GetDefaultParameters();
|
||||
var parameters = new NameValueCollection();
|
||||
|
||||
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Add("action", "search-torrents");
|
||||
parameters.Add("type", "imdb");
|
||||
parameters.Add("query", searchCriteria.FullImdbId);
|
||||
parameters.Set("action", "search-torrents");
|
||||
parameters.Set("type", "imdb");
|
||||
parameters.Set("query", searchCriteria.FullImdbId);
|
||||
}
|
||||
else if (searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Add("action", "search-torrents");
|
||||
parameters.Add("type", "name");
|
||||
parameters.Add("query", searchCriteria.SanitizedSearchTerm.Trim());
|
||||
parameters.Set("action", "search-torrents");
|
||||
parameters.Set("type", "name");
|
||||
parameters.Set("query", searchCriteria.SanitizedSearchTerm.Trim());
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetRequest(searchCriteria, parameters));
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria, parameters));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -73,16 +78,16 @@ public class FileListRequestGenerator : IIndexerRequestGenerator
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
var parameters = GetDefaultParameters();
|
||||
var parameters = new NameValueCollection();
|
||||
|
||||
if (searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Add("action", "search-torrents");
|
||||
parameters.Add("type", "name");
|
||||
parameters.Add("query", searchCriteria.SanitizedSearchTerm.Trim());
|
||||
parameters.Set("action", "search-torrents");
|
||||
parameters.Set("type", "name");
|
||||
parameters.Set("query", searchCriteria.SanitizedSearchTerm.Trim());
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetRequest(searchCriteria, parameters));
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria, parameters));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -90,16 +95,16 @@ public class FileListRequestGenerator : IIndexerRequestGenerator
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
var parameters = GetDefaultParameters();
|
||||
var parameters = new NameValueCollection();
|
||||
|
||||
if (searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Add("action", "search-torrents");
|
||||
parameters.Add("type", "name");
|
||||
parameters.Add("query", searchCriteria.SanitizedSearchTerm.Trim());
|
||||
parameters.Set("action", "search-torrents");
|
||||
parameters.Set("type", "name");
|
||||
parameters.Set("query", searchCriteria.SanitizedSearchTerm.Trim());
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetRequest(searchCriteria, parameters));
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria, parameters));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -107,47 +112,47 @@ public class FileListRequestGenerator : IIndexerRequestGenerator
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
var parameters = GetDefaultParameters();
|
||||
var parameters = new NameValueCollection();
|
||||
|
||||
if (searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Add("action", "search-torrents");
|
||||
parameters.Add("type", "name");
|
||||
parameters.Add("query", searchCriteria.SanitizedSearchTerm.Trim());
|
||||
parameters.Set("action", "search-torrents");
|
||||
parameters.Set("type", "name");
|
||||
parameters.Set("query", searchCriteria.SanitizedSearchTerm.Trim());
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetRequest(searchCriteria, parameters));
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria, parameters));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetRequest(SearchCriteriaBase searchCriteria, NameValueCollection parameters)
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(SearchCriteriaBase searchCriteria, NameValueCollection parameters)
|
||||
{
|
||||
if (parameters.Get("action") is null)
|
||||
{
|
||||
parameters.Add("action", "latest-torrents");
|
||||
parameters.Set("action", "latest-torrents");
|
||||
}
|
||||
|
||||
parameters.Add("category", string.Join(",", Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories)));
|
||||
|
||||
var searchUrl = $"{Settings.BaseUrl.TrimEnd('/')}/api.php?{parameters.GetQueryString()}";
|
||||
|
||||
yield return new IndexerRequest(searchUrl, HttpAccept.Json);
|
||||
}
|
||||
|
||||
private NameValueCollection GetDefaultParameters()
|
||||
{
|
||||
var parameters = new NameValueCollection
|
||||
if (searchCriteria.Categories != null && searchCriteria.Categories.Any())
|
||||
{
|
||||
{ "username", Settings.Username.Trim() },
|
||||
{ "passkey", Settings.Passkey.Trim() }
|
||||
};
|
||||
parameters.Set("category", string.Join(",", Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories)));
|
||||
}
|
||||
|
||||
if (Settings.FreeleechOnly)
|
||||
{
|
||||
parameters.Add("freeleech", "1");
|
||||
parameters.Set("freeleech", "1");
|
||||
}
|
||||
|
||||
return parameters;
|
||||
var searchUrl = $"{Settings.BaseUrl.TrimEnd('/')}/api.php?{parameters.GetQueryString()}";
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Json)
|
||||
{
|
||||
HttpRequest =
|
||||
{
|
||||
Credentials = new BasicNetworkCredential(Settings.Username.Trim(), Settings.Passkey.Trim())
|
||||
}
|
||||
};
|
||||
|
||||
yield return request;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public class FunFile : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
||||
}
|
||||
|
||||
var cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("Authentication succeeded.");
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public abstract class GazelleBase<TSettings> : TorrentIndexerBase<TSettings>
|
||||
CheckForLoginError(response);
|
||||
|
||||
cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("Gazelle authentication succeeded.");
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public class GazelleRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.SearchTerm, searchCriteria.Categories);
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
|
||||
|
||||
if (searchCriteria.ImdbId != null)
|
||||
{
|
||||
@@ -62,9 +62,9 @@ public class GazelleRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.SearchTerm, searchCriteria.Categories);
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
|
||||
|
||||
if (searchCriteria.Artist.IsNotNullOrWhiteSpace())
|
||||
if (searchCriteria.Artist.IsNotNullOrWhiteSpace() && searchCriteria.Artist != "VA")
|
||||
{
|
||||
parameters.Set("artistname", searchCriteria.Artist);
|
||||
}
|
||||
@@ -104,7 +104,7 @@ public class GazelleRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.SearchTerm, searchCriteria.Categories);
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
|
||||
pageableRequests.Add(GetRequest(parameters));
|
||||
|
||||
return pageableRequests;
|
||||
@@ -114,7 +114,7 @@ public class GazelleRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.SearchTerm, searchCriteria.Categories);
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
|
||||
pageableRequests.Add(GetRequest(parameters));
|
||||
|
||||
return pageableRequests;
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
|
||||
cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("HDSpace authentication succeeded.");
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var response = await ExecuteAuth(authLoginRequest);
|
||||
|
||||
cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("HDTorrents authentication succeeded.");
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
public override TimeSpan RateLimit => TimeSpan.FromSeconds(5);
|
||||
private string LoginUrl => Settings.BaseUrl + "takelogin.php";
|
||||
|
||||
public ImmortalSeed(IIndexerHttpClient httpClient,
|
||||
@@ -72,7 +73,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
|
||||
var cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("ImmortalSeed authentication succeeded.");
|
||||
}
|
||||
@@ -311,7 +312,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
release.PublishDate = DateTime.ParseExact(dateAddedMatch.Value, "yyyy-MM-dd hh:mm tt", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (row.QuerySelector("img[title^=\"Free Torrent\"]") != null)
|
||||
if (row.QuerySelector("img[title^=\"Free Torrent\"], img[title^=\"Sitewide Free Torrent\"]") != null)
|
||||
{
|
||||
release.DownloadVolumeFactor = 0;
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ public class Libble : TorrentIndexerBase<LibbleSettings>
|
||||
}
|
||||
|
||||
cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("Authentication succeeded.");
|
||||
}
|
||||
@@ -128,14 +128,17 @@ public class LibbleRequestGenerator : IIndexerRequestGenerator
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
var parameters = new NameValueCollection();
|
||||
|
||||
if (searchCriteria.Artist.IsNotNullOrWhiteSpace())
|
||||
if (searchCriteria.Artist.IsNotNullOrWhiteSpace() && searchCriteria.Artist != "VA")
|
||||
{
|
||||
parameters.Set("artistname", searchCriteria.Artist);
|
||||
}
|
||||
|
||||
if (searchCriteria.Album.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Set("groupname", searchCriteria.Album);
|
||||
// Remove year
|
||||
var album = Regex.Replace(searchCriteria.Album, @"(.+)\b\d{4}$", "$1");
|
||||
|
||||
parameters.Set("groupname", album.Trim());
|
||||
}
|
||||
|
||||
if (searchCriteria.Label.IsNotNullOrWhiteSpace())
|
||||
@@ -188,9 +191,14 @@ public class LibbleRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
var term = searchCriteria.SanitizedSearchTerm.Trim();
|
||||
|
||||
parameters.Set("action", "advanced");
|
||||
parameters.Set("order_by", "time");
|
||||
parameters.Set("order_way", "desc");
|
||||
parameters.Set("searchstr", term);
|
||||
|
||||
if (term.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Set("searchstr", term);
|
||||
}
|
||||
|
||||
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
|
||||
if (queryCats.Any())
|
||||
@@ -276,7 +284,9 @@ public class LibbleParser : IParseIndexerResponse
|
||||
Guid = infoUrl,
|
||||
InfoUrl = infoUrl,
|
||||
DownloadUrl = downloadLink,
|
||||
Title = $"{releaseArtist} - {releaseAlbumName} {releaseAlbumYear} {releaseTags}".Trim(' ', '-'),
|
||||
Title = $"{releaseArtist} - {releaseAlbumName} {releaseAlbumYear.Value} {releaseTags}".Trim(' ', '-'),
|
||||
Artist = releaseArtist,
|
||||
Album = releaseAlbumName,
|
||||
Categories = ParseCategories(group),
|
||||
Description = releaseDescription,
|
||||
Size = ParseUtil.GetBytes(row.QuerySelector("td:nth-child(4)").TextContent.Trim()),
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -27,6 +28,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override bool SupportsRedirect => true;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public Nebulance(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
@@ -44,6 +46,14 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
return new NebulanceParser(Settings);
|
||||
}
|
||||
|
||||
public override async Task<byte[]> Download(Uri link)
|
||||
{
|
||||
// Invalidate cookies before downloading to prevent redirect to login page.
|
||||
UpdateCookies(null, null);
|
||||
|
||||
return await base.Download(link);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
|
||||
@@ -211,7 +211,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
_logger.Warn(ex, "Unable to connect to indexer: " + ex.Message);
|
||||
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ public class NorBits : TorrentIndexerBase<NorBitsSettings>
|
||||
}
|
||||
|
||||
var cookies = loginResponse.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("Authentication succeeded.");
|
||||
}
|
||||
|
||||
@@ -112,6 +112,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
_logger.Error("Download failed");
|
||||
}
|
||||
|
||||
ValidateTorrent(downloadBytes);
|
||||
|
||||
return downloadBytes;
|
||||
}
|
||||
}
|
||||
@@ -135,22 +137,22 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
var parameters = new NameValueCollection();
|
||||
|
||||
if (searchCriteria.Artist.IsNotNullOrWhiteSpace())
|
||||
if (searchCriteria.Artist.IsNotNullOrWhiteSpace() && searchCriteria.Artist != "VA")
|
||||
{
|
||||
parameters.Add("artistname", searchCriteria.Artist);
|
||||
parameters.Set("artistname", searchCriteria.Artist);
|
||||
}
|
||||
|
||||
if (searchCriteria.Album.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Add("groupname", searchCriteria.Album);
|
||||
parameters.Set("groupname", searchCriteria.Album);
|
||||
}
|
||||
|
||||
if (searchCriteria.Year.HasValue)
|
||||
{
|
||||
parameters.Add("year", searchCriteria.Year.ToString());
|
||||
parameters.Set("year", searchCriteria.Year.ToString());
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetRequest(searchCriteria, parameters));
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria, parameters));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -160,7 +162,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
var parameters = new NameValueCollection();
|
||||
|
||||
pageableRequests.Add(GetRequest(searchCriteria, parameters));
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria, parameters));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -180,28 +182,28 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
var parameters = new NameValueCollection();
|
||||
|
||||
pageableRequests.Add(GetRequest(searchCriteria, parameters));
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria, parameters));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetRequest(SearchCriteriaBase searchCriteria, NameValueCollection parameters)
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(SearchCriteriaBase searchCriteria, NameValueCollection parameters)
|
||||
{
|
||||
var term = searchCriteria.SanitizedSearchTerm.Trim();
|
||||
|
||||
parameters.Add("action", "browse");
|
||||
parameters.Add("order_by", "time");
|
||||
parameters.Add("order_way", "desc");
|
||||
parameters.Add("searchstr", term);
|
||||
parameters.Set("action", "browse");
|
||||
parameters.Set("order_by", "time");
|
||||
parameters.Set("order_way", "desc");
|
||||
|
||||
if (term.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Set("searchstr", term);
|
||||
}
|
||||
|
||||
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
|
||||
|
||||
if (queryCats.Count > 0)
|
||||
if (queryCats.Any())
|
||||
{
|
||||
foreach (var cat in queryCats)
|
||||
{
|
||||
parameters.Add($"filter_cat[{cat}]", "1");
|
||||
}
|
||||
queryCats.ForEach(cat => parameters.Set($"filter_cat[{cat}]", "1"));
|
||||
}
|
||||
|
||||
var request = RequestBuilder()
|
||||
@@ -267,12 +269,14 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var release = new GazelleInfo
|
||||
{
|
||||
Guid = infoUrl,
|
||||
InfoUrl = infoUrl,
|
||||
DownloadUrl = GetDownloadUrl(id, torrent.CanUseToken),
|
||||
Title = WebUtility.HtmlDecode(title),
|
||||
Artist = WebUtility.HtmlDecode(result.Artist),
|
||||
Album = WebUtility.HtmlDecode(result.GroupName),
|
||||
Container = torrent.Encoding,
|
||||
Codec = torrent.Format,
|
||||
Size = long.Parse(torrent.Size),
|
||||
DownloadUrl = GetDownloadUrl(id, torrent.CanUseToken),
|
||||
InfoUrl = infoUrl,
|
||||
Seeders = int.Parse(torrent.Seeders),
|
||||
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
|
||||
PublishDate = torrent.Time.ToUniversalTime(),
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
request.HttpRequest.Cookies[cookie.Key] = cookie.Value;
|
||||
}
|
||||
|
||||
CookiesUpdater(Cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
CookiesUpdater(Cookies, DateTime.Now.AddDays(30));
|
||||
}
|
||||
|
||||
yield return request;
|
||||
|
||||
@@ -83,7 +83,7 @@ public class PirateTheNet : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
||||
}
|
||||
|
||||
var cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("Authentication succeeded.");
|
||||
}
|
||||
@@ -275,7 +275,7 @@ public class PirateTheNetParser : IParseIndexerResponse
|
||||
}
|
||||
else if (added.StartsWith("Yesterday "))
|
||||
{
|
||||
release.PublishDate = DateTime.Now.Date + DateTime.ParseExact(added.Split(" ", 2).Last(), "hh:mm tt", CultureInfo.InvariantCulture).TimeOfDay - TimeSpan.FromDays(1);
|
||||
release.PublishDate = DateTime.Now.AddDays(-1).Date + DateTime.ParseExact(added.Split(" ", 2).Last(), "hh:mm tt", CultureInfo.InvariantCulture).TimeOfDay;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report");
|
||||
}
|
||||
|
||||
UpdateCookies(response.GetCookies(), DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(response.GetCookies(), DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("Authentication succeeded");
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ public class PreToMe : TorrentIndexerBase<PreToMeSettings>
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||
}
|
||||
|
||||
UpdateCookies(response.GetCookies(), DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(response.GetCookies(), DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("Authentication succeeded");
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
{
|
||||
public class RarbgParser : IParseIndexerResponse
|
||||
{
|
||||
private static readonly Regex RegexGuid = new Regex(@"^magnet:\?xt=urn:btih:([a-f0-9]+)", RegexOptions.Compiled);
|
||||
private static readonly Regex RegexGuid = new (@"^magnet:\?xt=urn:btih:([a-f0-9]+)", RegexOptions.Compiled);
|
||||
|
||||
private readonly IndexerCapabilities _capabilities;
|
||||
private readonly Logger _logger;
|
||||
@@ -23,6 +23,8 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var results = new List<ReleaseInfo>();
|
||||
@@ -48,9 +50,9 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
{
|
||||
var reason = $"{jsonResponse.Resource.error} ({jsonResponse.Resource.error_code})";
|
||||
|
||||
if (jsonResponse.Resource.rate_limit is 1)
|
||||
if (jsonResponse.Resource.error_code is 5 || (jsonResponse.Resource.rate_limit is 1 && jsonResponse.Resource.torrent_results == null))
|
||||
{
|
||||
_logger.Debug("No results due to rate limiting. Reason: {0}", reason);
|
||||
throw new TooManyRequestsException(indexerResponse.HttpRequest, indexerResponse.HttpResponse, TimeSpan.FromMinutes(5));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -65,24 +67,30 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
|
||||
if (jsonResponse.Resource.torrent_results == null)
|
||||
{
|
||||
if (jsonResponse.Resource.rate_limit == 1)
|
||||
{
|
||||
throw new TooManyRequestsException(indexerResponse.HttpRequest, indexerResponse.HttpResponse, TimeSpan.FromMinutes(5));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
foreach (var torrent in jsonResponse.Resource.torrent_results)
|
||||
{
|
||||
var torrentInfo = new TorrentInfo();
|
||||
|
||||
torrentInfo.Guid = GetGuid(torrent);
|
||||
torrentInfo.Categories = _capabilities.Categories.MapTrackerCatDescToNewznab(torrent.category);
|
||||
torrentInfo.Title = torrent.title;
|
||||
torrentInfo.Size = torrent.size;
|
||||
torrentInfo.DownloadUrl = torrent.download;
|
||||
torrentInfo.InfoUrl = $"{torrent.info_page}&app_id={BuildInfo.AppName}";
|
||||
torrentInfo.PublishDate = torrent.pubdate.ToUniversalTime();
|
||||
torrentInfo.Seeders = torrent.seeders;
|
||||
torrentInfo.Peers = torrent.leechers + torrent.seeders;
|
||||
torrentInfo.DownloadVolumeFactor = 0;
|
||||
torrentInfo.UploadVolumeFactor = 1;
|
||||
var torrentInfo = new TorrentInfo
|
||||
{
|
||||
Guid = GetGuid(torrent),
|
||||
Categories = _capabilities.Categories.MapTrackerCatDescToNewznab(torrent.category),
|
||||
Title = torrent.title,
|
||||
Size = torrent.size,
|
||||
DownloadUrl = torrent.download,
|
||||
InfoUrl = $"{torrent.info_page}&app_id={BuildInfo.AppName}",
|
||||
PublishDate = torrent.pubdate.ToUniversalTime(),
|
||||
Seeders = torrent.seeders,
|
||||
Peers = torrent.leechers + torrent.seeders,
|
||||
DownloadVolumeFactor = 0,
|
||||
UploadVolumeFactor = 1
|
||||
};
|
||||
|
||||
if (torrent.movie_info != null)
|
||||
{
|
||||
@@ -103,8 +111,6 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
return results;
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
private string GetGuid(RarbgTorrent torrent)
|
||||
{
|
||||
var match = RegexGuid.Match(torrent.download);
|
||||
|
||||
@@ -94,6 +94,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
_logger.Error("Download failed");
|
||||
}
|
||||
|
||||
ValidateTorrent(downloadBytes);
|
||||
|
||||
return downloadBytes;
|
||||
}
|
||||
}
|
||||
@@ -117,22 +119,22 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
var parameters = new NameValueCollection();
|
||||
|
||||
if (searchCriteria.Artist.IsNotNullOrWhiteSpace())
|
||||
if (searchCriteria.Artist.IsNotNullOrWhiteSpace() && searchCriteria.Artist != "VA")
|
||||
{
|
||||
parameters.Add("artistname", searchCriteria.Artist);
|
||||
parameters.Set("artistname", searchCriteria.Artist);
|
||||
}
|
||||
|
||||
if (searchCriteria.Album.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Add("groupname", searchCriteria.Album);
|
||||
parameters.Set("groupname", searchCriteria.Album);
|
||||
}
|
||||
|
||||
if (searchCriteria.Year.HasValue)
|
||||
{
|
||||
parameters.Add("year", searchCriteria.Year.ToString());
|
||||
parameters.Set("year", searchCriteria.Year.ToString());
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetRequest(searchCriteria, parameters));
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria, parameters));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -142,7 +144,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
var parameters = new NameValueCollection();
|
||||
|
||||
pageableRequests.Add(GetRequest(searchCriteria, parameters));
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria, parameters));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -162,27 +164,28 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
var parameters = new NameValueCollection();
|
||||
|
||||
pageableRequests.Add(GetRequest(searchCriteria, parameters));
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria, parameters));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetRequest(SearchCriteriaBase searchCriteria, NameValueCollection parameters)
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(SearchCriteriaBase searchCriteria, NameValueCollection parameters)
|
||||
{
|
||||
var term = searchCriteria.SanitizedSearchTerm.Trim();
|
||||
|
||||
parameters.Add("action", "browse");
|
||||
parameters.Add("order_by", "time");
|
||||
parameters.Add("order_way", "desc");
|
||||
parameters.Add("searchstr", term);
|
||||
parameters.Set("action", "browse");
|
||||
parameters.Set("order_by", "time");
|
||||
parameters.Set("order_way", "desc");
|
||||
|
||||
if (term.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Set("searchstr", term);
|
||||
}
|
||||
|
||||
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
|
||||
if (queryCats.Any())
|
||||
{
|
||||
foreach (var cat in queryCats)
|
||||
{
|
||||
parameters.Add($"filter_cat[{cat}]", "1");
|
||||
}
|
||||
queryCats.ForEach(cat => parameters.Set($"filter_cat[{cat}]", "1"));
|
||||
}
|
||||
|
||||
var request = RequestBuilder()
|
||||
@@ -248,12 +251,14 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var release = new GazelleInfo
|
||||
{
|
||||
Guid = infoUrl,
|
||||
InfoUrl = infoUrl,
|
||||
DownloadUrl = GetDownloadUrl(id, torrent.CanUseToken),
|
||||
Title = WebUtility.HtmlDecode(title),
|
||||
Artist = WebUtility.HtmlDecode(result.Artist),
|
||||
Album = WebUtility.HtmlDecode(result.GroupName),
|
||||
Container = torrent.Encoding,
|
||||
Codec = torrent.Format,
|
||||
Size = long.Parse(torrent.Size),
|
||||
DownloadUrl = GetDownloadUrl(id, torrent.CanUseToken),
|
||||
InfoUrl = infoUrl,
|
||||
Seeders = int.Parse(torrent.Seeders),
|
||||
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
|
||||
PublishDate = torrent.Time.ToUniversalTime(),
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(401, NewznabStandardCategory.Movies, "Movies");
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
if (response.Content != null && response.Content.Contains("/logout.php"))
|
||||
{
|
||||
UpdateCookies(response.GetCookies(), DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(response.GetCookies(), DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("RevolutionTT authentication succeeded");
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
@@ -25,10 +24,13 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class RuTracker : TorrentIndexerBase<RuTrackerSettings>
|
||||
{
|
||||
public override string Name => "RuTracker";
|
||||
public override string[] IndexerUrls => new[] { "https://rutracker.org/", "https://rutracker.net/" };
|
||||
private string LoginUrl => Settings.BaseUrl + "forum/login.php";
|
||||
public override string[] IndexerUrls => new[]
|
||||
{
|
||||
"https://rutracker.org/",
|
||||
"https://rutracker.net/"
|
||||
};
|
||||
public override string Description => "RuTracker is a Semi-Private Russian torrent site with a thriving file-sharing community";
|
||||
public override string Language => "ru-org";
|
||||
public override string Language => "ru-RU";
|
||||
public override Encoding Encoding => Encoding.GetEncoding("windows-1251");
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.SemiPrivate;
|
||||
@@ -49,23 +51,52 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
return new RuTrackerParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
public override async Task<byte[]> Download(Uri link)
|
||||
{
|
||||
if (Settings.UseMagnetLinks && link.PathAndQuery.Contains("viewtopic.php?t="))
|
||||
{
|
||||
var request = new HttpRequestBuilder(link.ToString())
|
||||
.SetCookies(GetCookies() ?? new Dictionary<string, string>())
|
||||
.Accept(HttpAccept.Html)
|
||||
.Build();
|
||||
|
||||
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
var magnetLink = dom.QuerySelector("table.attach a.magnet-link[href^=\"magnet:?\"]")?.GetAttribute("href");
|
||||
|
||||
if (magnetLink == null)
|
||||
{
|
||||
throw new Exception($"Failed to fetch magnet link from {link}");
|
||||
}
|
||||
|
||||
link = new Uri(magnetLink);
|
||||
}
|
||||
|
||||
return await base.Download(link);
|
||||
}
|
||||
|
||||
protected override async Task DoLogin()
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(LoginUrl)
|
||||
var loginUrl = $"{Settings.BaseUrl}forum/login.php";
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder(loginUrl)
|
||||
{
|
||||
LogResponseContent = true,
|
||||
AllowAutoRedirect = true,
|
||||
Method = HttpMethod.Post
|
||||
AllowAutoRedirect = true
|
||||
};
|
||||
|
||||
var cookies = Cookies;
|
||||
Cookies = null;
|
||||
|
||||
var authLoginRequest = requestBuilder
|
||||
var authLoginRequest = requestBuilder.Post()
|
||||
.AddFormParameter("login_username", Settings.Username)
|
||||
.AddFormParameter("login_password", Settings.Password)
|
||||
.AddFormParameter("login", "Login")
|
||||
.AddFormParameter("redirect", "index.php")
|
||||
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
.SetHeader("Referer", loginUrl)
|
||||
.Build();
|
||||
|
||||
var response = await ExecuteAuth(authLoginRequest);
|
||||
@@ -76,7 +107,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
|
||||
cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("Authentication succeeded");
|
||||
}
|
||||
@@ -1416,21 +1447,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
public override object RequestAction(string action, IDictionary<string, string> query)
|
||||
{
|
||||
if (action == "getUrls")
|
||||
{
|
||||
var links = IndexerUrls;
|
||||
|
||||
return new
|
||||
{
|
||||
options = links.Select(d => new { Value = d, Name = d })
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class RuTrackerRequestGenerator : IIndexerRequestGenerator
|
||||
@@ -1446,16 +1462,14 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, int season = 0)
|
||||
{
|
||||
var searchUrl = $"{_settings.BaseUrl.TrimEnd('/')}/forum/tracker.php";
|
||||
|
||||
var queryCollection = new NameValueCollection();
|
||||
var parameters = new NameValueCollection();
|
||||
|
||||
var searchString = term;
|
||||
|
||||
// if the search string is empty use the getnew view
|
||||
if (searchString.IsNullOrWhiteSpace())
|
||||
{
|
||||
queryCollection.Add("nm", searchString);
|
||||
parameters.Set("nm", searchString);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1466,19 +1480,28 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
searchString += " Сезон: " + season;
|
||||
}
|
||||
|
||||
queryCollection.Add("nm", searchString);
|
||||
parameters.Set("nm", searchString);
|
||||
}
|
||||
|
||||
if (categories != null && categories.Length > 0)
|
||||
{
|
||||
queryCollection.Add("f", string.Join(",", _capabilities.Categories.MapTorznabCapsToTrackers(categories)));
|
||||
parameters.Set("f", string.Join(",", _capabilities.Categories.MapTorznabCapsToTrackers(categories)));
|
||||
}
|
||||
|
||||
searchUrl = searchUrl + "?" + queryCollection.GetQueryString();
|
||||
var searchUrl = $"{_settings.BaseUrl}forum/tracker.php";
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||
if (parameters.Count > 0)
|
||||
{
|
||||
searchUrl += $"?{parameters.GetQueryString()}";
|
||||
}
|
||||
|
||||
request.HttpRequest.AllowAutoRedirect = false;
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html)
|
||||
{
|
||||
HttpRequest =
|
||||
{
|
||||
AllowAutoRedirect = false
|
||||
}
|
||||
};
|
||||
|
||||
yield return request;
|
||||
}
|
||||
@@ -1542,6 +1565,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
private readonly RuTrackerSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
private readonly RuTrackerTitleParser _titleParser = new ();
|
||||
|
||||
public RuTrackerParser(RuTrackerSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
@@ -1578,12 +1603,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
return null;
|
||||
}
|
||||
|
||||
var link = _settings.BaseUrl + "forum/" + qDownloadLink.GetAttribute("href");
|
||||
|
||||
var qDetailsLink = row.QuerySelector("td.t-title-col > div.t-title > a.tLink");
|
||||
var details = _settings.BaseUrl + "forum/" + qDetailsLink.GetAttribute("href");
|
||||
var infoUrl = _settings.BaseUrl + "forum/" + qDetailsLink.GetAttribute("href");
|
||||
var downloadUrl = _settings.BaseUrl + "forum/" + qDownloadLink.GetAttribute("href");
|
||||
|
||||
var category = GetCategoryOfRelease(row);
|
||||
var title = qDetailsLink.TextContent.Trim();
|
||||
var categories = GetCategoryOfRelease(row);
|
||||
|
||||
var size = GetSizeOfRelease(row);
|
||||
|
||||
@@ -1596,86 +1621,23 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 0,
|
||||
Title = qDetailsLink.TextContent,
|
||||
InfoUrl = details,
|
||||
DownloadUrl = link,
|
||||
Guid = details,
|
||||
Guid = infoUrl,
|
||||
InfoUrl = infoUrl,
|
||||
DownloadUrl = _settings.UseMagnetLinks ? infoUrl : downloadUrl,
|
||||
Title = _titleParser.Parse(title, categories, _settings.RussianLetters, _settings.MoveFirstTagsToEndOfReleaseTitle, _settings.MoveAllTagsToEndOfReleaseTitle, _settings.AddRussianToTitle),
|
||||
Description = title,
|
||||
Categories = categories,
|
||||
Size = size,
|
||||
Seeders = seeders,
|
||||
Peers = leechers + seeders,
|
||||
Grabs = grabs,
|
||||
PublishDate = publishDate,
|
||||
Categories = category,
|
||||
DownloadVolumeFactor = 1,
|
||||
UploadVolumeFactor = 1
|
||||
UploadVolumeFactor = 1,
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 0
|
||||
};
|
||||
|
||||
// TODO finish extracting release variables to simplify release initialization
|
||||
if (IsAnyTvCategory(release.Categories))
|
||||
{
|
||||
// extract season and episodes
|
||||
// should also handle multi-season releases listed as Сезон: 1-8 and Сезоны: 1-8
|
||||
var regex = new Regex(@".+\/\s([^а-яА-я\/]+)\s\/.+Сезон.\s*[:]*\s+(\d*\-?\d*).+(?:Серии|Эпизод)+\s*[:]*\s+(\d+-?\d*).+(\[.*\])[\s]?(.*)");
|
||||
|
||||
var title = regex.Replace(release.Title, "$1 - S$2E$3 - rus $4 $5");
|
||||
title = Regex.Replace(title, "-Rip", "Rip", RegexOptions.IgnoreCase);
|
||||
title = Regex.Replace(title, "WEB-DLRip", "WEBDL", RegexOptions.IgnoreCase);
|
||||
title = Regex.Replace(title, "WEB-DL", "WEBDL", RegexOptions.IgnoreCase);
|
||||
title = Regex.Replace(title, "HDTVRip", "HDTV", RegexOptions.IgnoreCase);
|
||||
title = Regex.Replace(title, "Кураж-Бамбей", "kurazh", RegexOptions.IgnoreCase);
|
||||
|
||||
release.Title = title;
|
||||
}
|
||||
else if (IsAnyMovieCategory(release.Categories))
|
||||
{
|
||||
// Bluray quality fix: radarr parse Blu-ray Disc as Bluray-1080p but should be BR-DISK
|
||||
release.Title = Regex.Replace(release.Title, "Blu-ray Disc", "BR-DISK", RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
if (IsAnyTvCategory(release.Categories) | IsAnyMovieCategory(release.Categories))
|
||||
{
|
||||
// remove director's name from title
|
||||
// rutracker movies titles look like: russian name / english name (russian director / english director) other stuff
|
||||
// Ирландец / The Irishman (Мартин Скорсезе / Martin Scorsese) [2019, США, криминал, драма, биография, WEB-DL 1080p] Dub (Пифагор) + MVO (Jaskier) + AVO (Юрий Сербин) + Sub Rus, Eng + Original Eng
|
||||
// this part should be removed: (Мартин Скорсезе / Martin Scorsese)
|
||||
//var director = new Regex(@"(\([А-Яа-яЁё\W]+)\s/\s(.+?)\)");
|
||||
var director = new Regex(@"(\([А-Яа-яЁё\W].+?\))");
|
||||
release.Title = director.Replace(release.Title, "");
|
||||
|
||||
// Remove VO, MVO and DVO from titles
|
||||
var vo = new Regex(@".VO\s\(.+?\)");
|
||||
release.Title = vo.Replace(release.Title, "");
|
||||
|
||||
// Remove R5 and (R5) from release names
|
||||
var r5 = new Regex(@"(.*)(.R5.)(.*)");
|
||||
release.Title = r5.Replace(release.Title, "$1");
|
||||
|
||||
// Remove Sub languages from release names
|
||||
var sub = new Regex(@"(Sub.*\+)|(Sub.*$)");
|
||||
release.Title = sub.Replace(release.Title, "");
|
||||
|
||||
// language fix: all rutracker releases contains russian track
|
||||
if (release.Title.IndexOf("rus", StringComparison.OrdinalIgnoreCase) < 0)
|
||||
{
|
||||
release.Title += " rus";
|
||||
}
|
||||
|
||||
// remove russian letters
|
||||
if (_settings.RussianLetters == true)
|
||||
{
|
||||
//Strip russian letters
|
||||
var rusRegex = new Regex(@"(\([А-Яа-яЁё\W]+\))|(^[А-Яа-яЁё\W\d]+\/ )|([а-яА-ЯЁё \-]+,+)|([а-яА-ЯЁё]+)");
|
||||
|
||||
release.Title = rusRegex.Replace(release.Title, "");
|
||||
|
||||
// Replace everything after first forward slash with a year (to avoid filtering away releases with an fwdslash after title+year, like: Title Year [stuff / stuff])
|
||||
var fwdslashRegex = new Regex(@"(\/\s.+?\[)");
|
||||
release.Title = fwdslashRegex.Replace(release.Title, "[");
|
||||
}
|
||||
}
|
||||
|
||||
return release;
|
||||
}
|
||||
|
||||
@@ -1697,48 +1659,217 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
private ICollection<IndexerCategory> GetCategoryOfRelease(in IElement row)
|
||||
{
|
||||
var forum = row.QuerySelector("td.f-name-col > div.f-name > a");
|
||||
var forumid = forum.GetAttribute("href").Split('=')[1];
|
||||
return _categories.MapTrackerCatToNewznab(forumid);
|
||||
var forum = row.QuerySelector("td.f-name-col > div.f-name > a")?.GetAttribute("href");
|
||||
var cat = ParseUtil.GetArgumentFromQueryString(forum, "f");
|
||||
|
||||
return _categories.MapTrackerCatToNewznab(cat);
|
||||
}
|
||||
|
||||
private long GetSizeOfRelease(in IElement row)
|
||||
{
|
||||
var qSize = row.QuerySelector("td.tor-size");
|
||||
var size = ParseUtil.GetBytes(qSize.GetAttribute("data-ts_text"));
|
||||
return size;
|
||||
return ParseUtil.GetBytes(row.QuerySelector("td.tor-size").GetAttribute("data-ts_text"));
|
||||
}
|
||||
|
||||
private DateTime GetPublishDateOfRelease(in IElement row)
|
||||
{
|
||||
var timestr = row.QuerySelector("td:nth-child(10)").GetAttribute("data-ts_text");
|
||||
var publishDate = DateTimeUtil.UnixTimestampToDateTime(long.Parse(timestr));
|
||||
return publishDate;
|
||||
}
|
||||
|
||||
private bool IsAnyTvCategory(ICollection<IndexerCategory> category)
|
||||
{
|
||||
return category.Contains(NewznabStandardCategory.TV)
|
||||
|| NewznabStandardCategory.TV.SubCategories.Any(subCat => category.Contains(subCat));
|
||||
}
|
||||
|
||||
private bool IsAnyMovieCategory(ICollection<IndexerCategory> category)
|
||||
{
|
||||
return category.Contains(NewznabStandardCategory.Movies)
|
||||
|| NewznabStandardCategory.Movies.SubCategories.Any(subCat => category.Contains(subCat));
|
||||
return DateTimeUtil.UnixTimestampToDateTime(long.Parse(row.QuerySelector("td:nth-child(10)").GetAttribute("data-ts_text")));
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class RuTrackerTitleParser
|
||||
{
|
||||
private static readonly List<Regex> FindTagsInTitlesRegexList = new ()
|
||||
{
|
||||
new Regex(@"\((?>\((?<c>)|[^()]+|\)(?<-c>))*(?(c)(?!))\)"),
|
||||
new Regex(@"\[(?>\[(?<c>)|[^\[\]]+|\](?<-c>))*(?(c)(?!))\]")
|
||||
};
|
||||
|
||||
private readonly Regex _stripCyrillicRegex = new (@"(\([\p{IsCyrillic}\W]+\))|(^[\p{IsCyrillic}\W\d]+\/ )|([\p{IsCyrillic} \-]+,+)|([\p{IsCyrillic}]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private readonly Regex _tvTitleCommaRegex = new (@"\s(\d+),(\d+)", RegexOptions.Compiled);
|
||||
private readonly Regex _tvTitleCyrillicXRegex = new (@"([\s-])Х+([\s\)\]])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private readonly Regex _tvTitleRusSeasonEpisodeOfRegex = new (@"Сезон\s*[:]*\s+(\d+).+(?:Серии|Эпизод|Выпуски)+\s*[:]*\s+(\d+(?:-\d+)?)\s*из\s*([\w?])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private readonly Regex _tvTitleRusSeasonEpisodeRegex = new (@"Сезон\s*[:]*\s+(\d+).+(?:Серии|Эпизод|Выпуски)+\s*[:]*\s+(\d+(?:-\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private readonly Regex _tvTitleRusSeasonRegex = new (@"Сезон\s*[:]*\s+(\d+(?:-\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private readonly Regex _tvTitleRusEpisodeOfRegex = new (@"(?:Серии|Эпизод|Выпуски)+\s*[:]*\s+(\d+(?:-\d+)?)\s*из\s*([\w?])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private readonly Regex _tvTitleRusEpisodeRegex = new (@"(?:Серии|Эпизод|Выпуски)+\s*[:]*\s+(\d+(?:-\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public string Parse(string title,
|
||||
ICollection<IndexerCategory> categories,
|
||||
bool stripCyrillicLetters = true,
|
||||
bool moveFirstTagsToEndOfReleaseTitle = false,
|
||||
bool moveAllTagsToEndOfReleaseTitle = false,
|
||||
bool addRussianToTitle = false)
|
||||
{
|
||||
// https://www.fileformat.info/info/unicode/category/Pd/list.htm
|
||||
title = Regex.Replace(title, @"\p{Pd}", "-", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
// replace double 4K quality in title
|
||||
title = Regex.Replace(title, @"\b(2160p), 4K\b", "$1", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
if (IsAnyTvCategory(categories))
|
||||
{
|
||||
title = _tvTitleCommaRegex.Replace(title, " $1-$2");
|
||||
title = _tvTitleCyrillicXRegex.Replace(title, "$1XX$2");
|
||||
|
||||
title = _tvTitleRusSeasonEpisodeOfRegex.Replace(title, "S$1E$2 of $3");
|
||||
title = _tvTitleRusSeasonEpisodeRegex.Replace(title, "S$1E$2");
|
||||
title = _tvTitleRusSeasonRegex.Replace(title, "S$1");
|
||||
title = _tvTitleRusEpisodeOfRegex.Replace(title, "E$1 of $2");
|
||||
title = _tvTitleRusEpisodeRegex.Replace(title, "E$1");
|
||||
}
|
||||
else if (IsAnyMovieCategory(categories))
|
||||
{
|
||||
// Bluray quality fix: radarr parse Blu-ray Disc as Bluray-1080p but should be BR-DISK
|
||||
title = Regex.Replace(title, @"\bBlu-ray Disc\b", "BR-DISK", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
if (IsAnyTvCategory(categories) | IsAnyMovieCategory(categories))
|
||||
{
|
||||
// remove director's name from title
|
||||
// rutracker movies titles look like: russian name / english name (russian director / english director) other stuff
|
||||
// Ирландец / The Irishman (Мартин Скорсезе / Martin Scorsese) [2019, США, криминал, драма, биография, WEB-DL 1080p] Dub (Пифагор) + MVO (Jaskier) + AVO (Юрий Сербин) + Sub Rus, Eng + Original Eng
|
||||
// this part should be removed: (Мартин Скорсезе / Martin Scorsese)
|
||||
title = Regex.Replace(title, @"(\([\p{IsCyrillic}\W]+)\s/\s(.+?)\)", string.Empty, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
// Remove VO, MVO and DVO from titles
|
||||
var vo = new Regex(@".VO\s\(.+?\)");
|
||||
title = vo.Replace(title, string.Empty);
|
||||
|
||||
// Remove R5 and (R5) from release names
|
||||
var r5 = new Regex(@"(.*)(.R5.)(.*)");
|
||||
title = r5.Replace(title, "$1");
|
||||
|
||||
// Remove Sub languages from release names
|
||||
title = Regex.Replace(title, @"(\bSub\b.*$|\b[\+]*Sub[\+]*\b)", string.Empty);
|
||||
}
|
||||
|
||||
// language fix: all rutracker releases contains russian track
|
||||
if (addRussianToTitle && (IsAnyTvCategory(categories) || IsAnyMovieCategory(categories)) && !Regex.Match(title, "\bRUS\b", RegexOptions.IgnoreCase).Success)
|
||||
{
|
||||
title += " RUS";
|
||||
}
|
||||
|
||||
if (stripCyrillicLetters)
|
||||
{
|
||||
title = _stripCyrillicRegex.Replace(title, string.Empty).Trim(' ', '-');
|
||||
}
|
||||
|
||||
if (moveAllTagsToEndOfReleaseTitle)
|
||||
{
|
||||
title = MoveAllTagsToEndOfReleaseTitle(title);
|
||||
}
|
||||
else if (moveFirstTagsToEndOfReleaseTitle)
|
||||
{
|
||||
title = MoveFirstTagsToEndOfReleaseTitle(title);
|
||||
}
|
||||
|
||||
title = Regex.Replace(title, @"\b-Rip\b", "Rip", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
title = Regex.Replace(title, @"\bHDTVRip\b", "HDTV", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
title = Regex.Replace(title, @"\bWEB-DLRip\b", "WEB-DL", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
title = Regex.Replace(title, @"\bWEBDLRip\b", "WEB-DL", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
title = Regex.Replace(title, @"\bWEBDL\b", "WEB-DL", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
title = Regex.Replace(title, @"\bКураж-Бамбей\b", "kurazh", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
title = Regex.Replace(title, @"\(\s*\/\s*", "(", RegexOptions.Compiled);
|
||||
title = Regex.Replace(title, @"\s*\/\s*\)", ")", RegexOptions.Compiled);
|
||||
|
||||
title = Regex.Replace(title, @"[\[\(]\s*[\)\]]", "", RegexOptions.Compiled);
|
||||
|
||||
title = Regex.Replace(title, @"\s+\+(?:\s+\+)+\s+", " + ", RegexOptions.Compiled);
|
||||
|
||||
title = title.Trim(' ', '&', ',', '.', '!', '?', '+', '-', '_', '|', '/', '\\', ':');
|
||||
|
||||
// replace multiple spaces with a single space
|
||||
title = Regex.Replace(title, @"\s+", " ");
|
||||
|
||||
return title.Trim();
|
||||
}
|
||||
|
||||
private static bool IsAnyTvCategory(ICollection<IndexerCategory> category)
|
||||
{
|
||||
return category.Contains(NewznabStandardCategory.TV) || NewznabStandardCategory.TV.SubCategories.Any(subCat => category.Contains(subCat));
|
||||
}
|
||||
|
||||
private static bool IsAnyMovieCategory(ICollection<IndexerCategory> category)
|
||||
{
|
||||
return category.Contains(NewznabStandardCategory.Movies) || NewznabStandardCategory.Movies.SubCategories.Any(subCat => category.Contains(subCat));
|
||||
}
|
||||
|
||||
private static string MoveAllTagsToEndOfReleaseTitle(string input)
|
||||
{
|
||||
var output = input;
|
||||
foreach (var findTagsRegex in FindTagsInTitlesRegexList)
|
||||
{
|
||||
foreach (Match match in findTagsRegex.Matches(input))
|
||||
{
|
||||
var tag = match.ToString();
|
||||
output = $"{output.Replace(tag, "")} {tag}".Trim();
|
||||
}
|
||||
}
|
||||
|
||||
return output.Trim();
|
||||
}
|
||||
|
||||
private static string MoveFirstTagsToEndOfReleaseTitle(string input)
|
||||
{
|
||||
var output = input;
|
||||
foreach (var findTagsRegex in FindTagsInTitlesRegexList)
|
||||
{
|
||||
var expectedIndex = 0;
|
||||
foreach (Match match in findTagsRegex.Matches(output))
|
||||
{
|
||||
if (match.Index > expectedIndex)
|
||||
{
|
||||
var substring = output.Substring(expectedIndex, match.Index - expectedIndex);
|
||||
if (string.IsNullOrWhiteSpace(substring))
|
||||
{
|
||||
expectedIndex = match.Index;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var tag = match.ToString();
|
||||
var regex = new Regex(Regex.Escape(tag));
|
||||
output = $"{regex.Replace(output, string.Empty, 1)} {tag}".Trim();
|
||||
expectedIndex += tag.Length;
|
||||
}
|
||||
}
|
||||
|
||||
return output.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
public class RuTrackerSettings : UserPassTorrentBaseSettings
|
||||
{
|
||||
public RuTrackerSettings()
|
||||
{
|
||||
RussianLetters = false;
|
||||
UseMagnetLinks = false;
|
||||
AddRussianToTitle = false;
|
||||
MoveFirstTagsToEndOfReleaseTitle = false;
|
||||
MoveAllTagsToEndOfReleaseTitle = false;
|
||||
}
|
||||
|
||||
[FieldDefinition(4, Label = "Strip Russian letters", Type = FieldType.Checkbox, SelectOptionsProviderAction = "stripRussian", HelpText = "Removes russian letters")]
|
||||
[FieldDefinition(4, Label = "Strip Russian letters", Type = FieldType.Checkbox, HelpText = "Removes russian letters")]
|
||||
public bool RussianLetters { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Use Magnet Links", Type = FieldType.Checkbox, HelpText = "When enabled this option will disable torrent links")]
|
||||
public bool UseMagnetLinks { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Add RUS to titles", Type = FieldType.Checkbox, HelpText = "Add RUS to end of all titles to improve language detection by Sonarr and Radarr. Will cause English-only results to be misidentified.")]
|
||||
public bool AddRussianToTitle { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "Move first tags to end of release title", Type = FieldType.Checkbox)]
|
||||
public bool MoveFirstTagsToEndOfReleaseTitle { get; set; }
|
||||
|
||||
[FieldDefinition(8, Label = "Move all tags to end of release title", Type = FieldType.Checkbox)]
|
||||
public bool MoveAllTagsToEndOfReleaseTitle { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,13 +146,15 @@ public class SecretCinemaParser : IParseIndexerResponse
|
||||
release.Title = $"{title} ({result.GroupYear}) {torrent.Media}";
|
||||
|
||||
// Replace media formats with standards
|
||||
release.Title = Regex.Replace(release.Title, "BDMV", "COMPLETE BLURAY", RegexOptions.IgnoreCase);
|
||||
release.Title = Regex.Replace(release.Title, "SD", "DVDRip", RegexOptions.IgnoreCase);
|
||||
release.Title = Regex.Replace(release.Title, @"\bBDMV\b", "COMPLETE BLURAY", RegexOptions.IgnoreCase);
|
||||
release.Title = Regex.Replace(release.Title, @"\bSD\b", "DVDRip", RegexOptions.IgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
// SC API currently doesn't return anything but title.
|
||||
release.Title = $"{artist} - {title} ({result.GroupYear}) [{torrent.Format} {torrent.Encoding}] [{torrent.Media}]";
|
||||
release.Artist = artist;
|
||||
release.Album = title;
|
||||
}
|
||||
|
||||
if (torrent.HasCue)
|
||||
|
||||
398
src/NzbDrone.Core/Indexers/Definitions/Shazbat.cs
Normal file
398
src/NzbDrone.Core/Indexers/Definitions/Shazbat.cs
Normal file
@@ -0,0 +1,398 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp.Html.Parser;
|
||||
using FluentValidation;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
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.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions;
|
||||
|
||||
public class Shazbat : TorrentIndexerBase<ShazbatSettings>
|
||||
{
|
||||
public override string Name => "Shazbat";
|
||||
public override string[] IndexerUrls => new[] { "https://www.shazbat.tv/" };
|
||||
public override string Description => "Shazbat is a PRIVATE Torrent Tracker with highly curated TV content";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
public override TimeSpan RateLimit => TimeSpan.FromSeconds(5.1);
|
||||
|
||||
public Shazbat(IIndexerHttpClient httpClient,
|
||||
IEventAggregator eventAggregator,
|
||||
IIndexerStatusService indexerStatusService,
|
||||
IConfigService configService,
|
||||
Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new ShazbatRequestGenerator(Settings);
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new ShazbatParser(Settings, RateLimit, _httpClient, _logger);
|
||||
}
|
||||
|
||||
protected override async Task DoLogin()
|
||||
{
|
||||
var loginUrl = Settings.BaseUrl + "login";
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder(loginUrl)
|
||||
{
|
||||
LogResponseContent = true,
|
||||
AllowAutoRedirect = true
|
||||
};
|
||||
|
||||
var authLoginRequest = requestBuilder.Post()
|
||||
.AddFormParameter("referer", "")
|
||||
.AddFormParameter("query", "")
|
||||
.AddFormParameter("tv_timezone", "0")
|
||||
.AddFormParameter("tv_login", Settings.Username)
|
||||
.AddFormParameter("tv_password", Settings.Password)
|
||||
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
.SetHeader("Referer", loginUrl)
|
||||
.Build();
|
||||
|
||||
var response = await ExecuteAuth(authLoginRequest);
|
||||
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
var errorMessage = dom.QuerySelector("div#fail .modal-body")?.TextContent.Trim();
|
||||
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||
}
|
||||
|
||||
var cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("Authentication succeeded.");
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse response)
|
||||
{
|
||||
return response.Content.ContainsIgnoreCase("sign in now");
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping("1", NewznabStandardCategory.TV);
|
||||
caps.Categories.AddCategoryMapping("2", NewznabStandardCategory.TVSD);
|
||||
caps.Categories.AddCategoryMapping("3", NewznabStandardCategory.TVHD);
|
||||
caps.Categories.AddCategoryMapping("4", NewznabStandardCategory.TVUHD);
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class ShazbatRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
private readonly ShazbatSettings _settings;
|
||||
|
||||
public ShazbatRequestGenerator(ShazbatSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term)
|
||||
{
|
||||
term = FixSearchTerm(term);
|
||||
|
||||
if (term.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var request = new HttpRequestBuilder(_settings.BaseUrl + "search").Post()
|
||||
.AddFormParameter("search", term)
|
||||
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
.SetHeader("X-Requested-With", "XMLHttpRequest")
|
||||
.SetHeader("Referer", _settings.BaseUrl)
|
||||
.Accept(HttpAccept.Html)
|
||||
.Build();
|
||||
|
||||
yield return new IndexerRequest(request);
|
||||
}
|
||||
else
|
||||
{
|
||||
var request = new HttpRequestBuilder(_settings.BaseUrl + "torrents")
|
||||
.SetHeader("Referer", _settings.BaseUrl)
|
||||
.Accept(HttpAccept.Html)
|
||||
.Build();
|
||||
|
||||
yield return new IndexerRequest(request);
|
||||
}
|
||||
}
|
||||
|
||||
private static string FixSearchTerm(string term)
|
||||
{
|
||||
term = Regex.Replace(term, @"\b[S|E]\d+\b", string.Empty, RegexOptions.IgnoreCase);
|
||||
term = Regex.Replace(term, @"(.+)\b\d{4}(\.\d{2}\.\d{2})?\b", "$1");
|
||||
term = Regex.Replace(term, @"[\.\s\(\)\[\]]+", " ");
|
||||
|
||||
return term.ToLower().Trim();
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class ShazbatParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly ShazbatSettings _settings;
|
||||
private readonly TimeSpan _rateLimit;
|
||||
private readonly IIndexerHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private readonly Regex _torrentInfoRegex = new (@"\((?<size>\d+)\):(?<seeders>\d+) \/ :(?<leechers>\d+)$", RegexOptions.Compiled);
|
||||
private readonly HashSet<string> _hdResolutions = new () { "1080p", "1080i", "720p" };
|
||||
|
||||
public ShazbatParser(ShazbatSettings settings, TimeSpan rateLimit, IIndexerHttpClient httpClient, Logger logger)
|
||||
{
|
||||
_settings = settings;
|
||||
_rateLimit = rateLimit;
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var hasGlobalFreeleech = dom.QuerySelector("span:contains(\"Freeleech until:\"):has(span.datetime)") != null;
|
||||
|
||||
releaseInfos.AddRange(ParseResults(indexerResponse, hasGlobalFreeleech));
|
||||
|
||||
var shows = dom.QuerySelectorAll("div.show[data-id]");
|
||||
if (shows.Any())
|
||||
{
|
||||
var showPagesFetchLimit = _settings.ShowPagesFetchLimit ?? 2;
|
||||
|
||||
if (showPagesFetchLimit is < 1 or > 5)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, "Value for Show Pages Fetch Limit should be between 1 and 5. Current value: {0}.", showPagesFetchLimit);
|
||||
}
|
||||
|
||||
if (shows.Length > showPagesFetchLimit)
|
||||
{
|
||||
_logger.Debug($"Your search returned {shows.Length} shows. Use a more specific search term for more relevant results.");
|
||||
}
|
||||
|
||||
if (indexerResponse.HttpResponse.GetCookies() == null || !indexerResponse.HttpResponse.GetCookies().Any())
|
||||
{
|
||||
throw new IndexerException(indexerResponse, "Invalid cookies. Most likely your session expired or was killed.");
|
||||
}
|
||||
|
||||
foreach (var show in shows.Take(showPagesFetchLimit))
|
||||
{
|
||||
var showPageUrl = new HttpRequestBuilder(_settings.BaseUrl + "show")
|
||||
.AddQueryParam("id", show.GetAttribute("data-id"))
|
||||
.Build()
|
||||
.Url.FullUri;
|
||||
|
||||
var showRequest = new HttpRequestBuilder(_settings.BaseUrl + "show").Post()
|
||||
.SetCookies(indexerResponse.HttpResponse.GetCookies() ?? new Dictionary<string, string>())
|
||||
.AddQueryParam("id", show.GetAttribute("data-id"))
|
||||
.AddQueryParam("show_mode", "torrents")
|
||||
.AddFormParameter("portlet", "true")
|
||||
.AddFormParameter("tab", "true")
|
||||
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
.SetHeader("X-Requested-With", "XMLHttpRequest")
|
||||
.SetHeader("Referer", showPageUrl)
|
||||
.Accept(HttpAccept.Html)
|
||||
.WithRateLimit(_rateLimit.TotalSeconds)
|
||||
.Build();
|
||||
|
||||
_logger.Debug("Downloading Feed " + showRequest.ToString());
|
||||
|
||||
var releaseRequest = new IndexerRequest(showRequest);
|
||||
var releaseResponse = new IndexerResponse(releaseRequest, _httpClient.Execute(releaseRequest.HttpRequest));
|
||||
|
||||
if (releaseResponse.HttpResponse.Content.ContainsIgnoreCase("sign in now"))
|
||||
{
|
||||
// Remove cookie cache
|
||||
CookiesUpdater(null, null);
|
||||
throw new IndexerAuthException("We are being redirected to the Shazbat login page. Most likely your session expired or was killed.");
|
||||
}
|
||||
|
||||
if (releaseResponse.HttpResponse.HasHttpError)
|
||||
{
|
||||
if (releaseResponse.HttpResponse.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
throw new TooManyRequestsException(releaseRequest.HttpRequest, releaseResponse.HttpResponse);
|
||||
}
|
||||
|
||||
throw new IndexerException(releaseResponse, $"HTTP Error - {releaseResponse.HttpResponse.StatusCode}. {showRequest.Url.FullUri}");
|
||||
}
|
||||
|
||||
releaseInfos.AddRange(ParseResults(releaseResponse, hasGlobalFreeleech));
|
||||
}
|
||||
}
|
||||
|
||||
return releaseInfos.ToArray();
|
||||
}
|
||||
|
||||
private IList<ReleaseInfo> ParseResults(IndexerResponse indexerResponse, bool hasGlobalFreeleech = false)
|
||||
{
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
if (!hasGlobalFreeleech)
|
||||
{
|
||||
hasGlobalFreeleech = dom.QuerySelector("span:contains(\"Freeleech until:\"):has(span.datetime)") != null;
|
||||
}
|
||||
|
||||
var publishDate = DateTime.Now;
|
||||
|
||||
var rows = dom.QuerySelectorAll("#torrent-table tr.eprow, table tr.eprow");
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var downloadUrl = _settings.BaseUrl + row.QuerySelector("td:nth-of-type(5) a[href^=\"load_torrent?\"]")?.GetAttribute("href");
|
||||
var infoUrl = _settings.BaseUrl + row.QuerySelector("td:nth-of-type(5) [href^=\"torrent_info?\"]")?.GetAttribute("href");
|
||||
var title = ParseTitle(row.QuerySelector("td:nth-of-type(3)"));
|
||||
|
||||
var infoString = row.QuerySelector("td:nth-of-type(4)")?.TextContent.Trim() ?? string.Empty;
|
||||
var matchInfo = _torrentInfoRegex.Match(infoString);
|
||||
var size = matchInfo.Groups["size"].Success && long.TryParse(matchInfo.Groups["size"].Value, out var outSize) ? outSize : 0;
|
||||
var seeders = matchInfo.Groups["seeders"].Success && int.TryParse(matchInfo.Groups["seeders"].Value, out var outSeeders) ? outSeeders : 0;
|
||||
var leechers = matchInfo.Groups["leechers"].Success && int.TryParse(matchInfo.Groups["leechers"].Value, out var outLeechers) ? outLeechers : 0;
|
||||
|
||||
var dateTimestamp = row.QuerySelector(".datetime[data-timestamp]")?.GetAttribute("data-timestamp");
|
||||
publishDate = dateTimestamp != null && ParseUtil.TryCoerceDouble(dateTimestamp, out var timestamp) ? DateTimeUtil.UnixTimestampToDateTime(timestamp) : publishDate.AddMinutes(-1);
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Guid = infoUrl,
|
||||
InfoUrl = infoUrl,
|
||||
DownloadUrl = downloadUrl,
|
||||
Title = title,
|
||||
Categories = ParseCategories(title),
|
||||
Size = size,
|
||||
Seeders = seeders,
|
||||
Peers = seeders + leechers,
|
||||
PublishDate = publishDate,
|
||||
Genres = row.QuerySelectorAll("label.label-tag").Select(t => t.TextContent.Trim()).ToList(),
|
||||
DownloadVolumeFactor = hasGlobalFreeleech ? 0 : 1,
|
||||
UploadVolumeFactor = 1,
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 172800, // 48 hours
|
||||
};
|
||||
|
||||
releaseInfos.Add(release);
|
||||
}
|
||||
|
||||
return releaseInfos;
|
||||
}
|
||||
|
||||
private static string ParseTitle(IElement titleRow)
|
||||
{
|
||||
var title = titleRow?.ChildNodes.First(n => n.NodeType == NodeType.Text && n.TextContent.Trim().IsNotNullOrWhiteSpace());
|
||||
|
||||
return title?.TextContent.Trim();
|
||||
}
|
||||
|
||||
protected virtual List<IndexerCategory> ParseCategories(string title)
|
||||
{
|
||||
var categories = new List<IndexerCategory>
|
||||
{
|
||||
NewznabStandardCategory.TV,
|
||||
title switch
|
||||
{
|
||||
_ when _hdResolutions.Any(title.Contains) => NewznabStandardCategory.TVHD,
|
||||
_ when title.Contains("2160p") => NewznabStandardCategory.TVUHD,
|
||||
_ => NewznabStandardCategory.TVSD
|
||||
}
|
||||
};
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class ShazbatSettingsValidator : UserPassBaseSettingsValidator<ShazbatSettings>
|
||||
{
|
||||
public ShazbatSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.ShowPagesFetchLimit).GreaterThan(0).When(c => c.ShowPagesFetchLimit.HasValue).WithMessage("Should be greater than zero");
|
||||
RuleFor(c => c.ShowPagesFetchLimit).LessThanOrEqualTo(5).When(c => c.ShowPagesFetchLimit.HasValue).WithMessage("Should be less than or equal to 5");
|
||||
}
|
||||
}
|
||||
|
||||
public class ShazbatSettings : UserPassTorrentBaseSettings
|
||||
{
|
||||
private static readonly ShazbatSettingsValidator Validator = new ();
|
||||
|
||||
[FieldDefinition(4, Type = FieldType.Number, Label = "Show Pages Fetch Limit", HelpText = "The number of show pages should Prowlarr fetch when searching. Default: 2.")]
|
||||
public int? ShowPagesFetchLimit { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class SpeedApp : SpeedAppBase
|
||||
{
|
||||
public override string Name => "SpeedApp.io";
|
||||
public override string[] IndexerUrls => new string[] { "https://speedapp.io/" };
|
||||
public override string[] LegacyUrls => new string[] { "https://speedapp.io" };
|
||||
public override string[] IndexerUrls => new[] { "https://speedapp.io/" };
|
||||
public override string[] LegacyUrls => new[] { "https://speedapp.io" };
|
||||
public override string Description => "SpeedApp is a ROMANIAN Private Torrent Tracker for MOVIES / TV / GENERAL";
|
||||
public override string Language => "ro-RO";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(38, NewznabStandardCategory.Movies, "Movie Packs");
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
private string LoginUrl => Settings.BaseUrl + "api/login";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override int PageSize => 100;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
protected virtual int MinimumSeedTime => 172800; // 48 hours
|
||||
private IIndexerRepository _indexerRepository;
|
||||
@@ -43,7 +44,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new SpeedAppRequestGenerator(Capabilities, Settings);
|
||||
return new SpeedAppRequestGenerator(Capabilities, Settings, PageSize);
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
@@ -110,6 +111,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
if (link.Scheme == "magnet")
|
||||
{
|
||||
ValidateMagnet(link.OriginalString);
|
||||
|
||||
return Encoding.UTF8.GetBytes(link.OriginalString);
|
||||
}
|
||||
|
||||
@@ -163,31 +165,32 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
throw;
|
||||
}
|
||||
|
||||
ValidateTorrent(torrentData);
|
||||
|
||||
return torrentData;
|
||||
}
|
||||
|
||||
protected virtual IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities();
|
||||
|
||||
return caps;
|
||||
return new IndexerCapabilities();
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
private readonly IndexerCapabilities _capabilities;
|
||||
private readonly SpeedAppSettings _settings;
|
||||
private readonly int _pageSize;
|
||||
|
||||
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)
|
||||
public SpeedAppRequestGenerator(IndexerCapabilities capabilities, SpeedAppSettings settings, int pageSize)
|
||||
{
|
||||
Capabilities = capabilities;
|
||||
Settings = settings;
|
||||
_capabilities = capabilities;
|
||||
_settings = settings;
|
||||
_pageSize = pageSize;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
@@ -219,54 +222,62 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories, imdbId, season, episode));
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories, searchCriteria.Limit ?? _pageSize, searchCriteria.Offset ?? 0, imdbId, season, episode));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null, int? season = null, string episode = null)
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, int limit, int offset, string imdbId = null, int? season = null, string episode = null)
|
||||
{
|
||||
var qc = new NameValueCollection()
|
||||
limit = Math.Min(_pageSize, limit);
|
||||
offset = Math.Max(0, offset);
|
||||
|
||||
var parameters = new NameValueCollection
|
||||
{
|
||||
{ "itemsPerPage", "100" },
|
||||
{ "itemsPerPage", limit.ToString() },
|
||||
{ "sort", "torrent.createdAt" },
|
||||
{ "direction", "desc" }
|
||||
};
|
||||
|
||||
if (limit > 0 && offset > 0)
|
||||
{
|
||||
var page = (offset / limit) + 1;
|
||||
parameters.Set("page", page.ToString());
|
||||
}
|
||||
|
||||
if (imdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
qc.Add("imdbId", imdbId);
|
||||
parameters.Set("imdbId", imdbId);
|
||||
}
|
||||
else
|
||||
{
|
||||
qc.Add("search", term);
|
||||
parameters.Set("search", term);
|
||||
}
|
||||
|
||||
if (season != null)
|
||||
{
|
||||
qc.Add("season", season.Value.ToString());
|
||||
parameters.Set("season", season.Value.ToString());
|
||||
}
|
||||
|
||||
if (episode != null)
|
||||
{
|
||||
qc.Add("episode", episode);
|
||||
parameters.Set("episode", episode);
|
||||
}
|
||||
|
||||
var cats = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
||||
|
||||
var cats = _capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
||||
if (cats.Count > 0)
|
||||
{
|
||||
foreach (var cat in cats)
|
||||
{
|
||||
qc.Add("categories[]", cat);
|
||||
parameters.Add("categories[]", cat);
|
||||
}
|
||||
}
|
||||
|
||||
var searchUrl = Settings.BaseUrl + "api/torrent?" + qc.GetQueryString(duplicateKeysIfMulti: true);
|
||||
var searchUrl = _settings.BaseUrl + "api/torrent?" + parameters.GetQueryString(duplicateKeysIfMulti: true);
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Json);
|
||||
|
||||
request.HttpRequest.Headers.Set("Authorization", $"Bearer {Settings.ApiKey}");
|
||||
request.HttpRequest.Headers.Set("Authorization", $"Bearer {_settings.ApiKey}");
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ public class SpeedCD : TorrentIndexerBase<SpeedCDSettings>
|
||||
}
|
||||
|
||||
var cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("Authentication succeeded.");
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
|
||||
cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("TVVault authentication succeeded.");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
@@ -52,16 +51,15 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
protected override async Task DoLogin()
|
||||
{
|
||||
var loginUrl = Settings.BaseUrl + "login.php";
|
||||
var loginUrl = $"{Settings.BaseUrl}login.php";
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder(loginUrl)
|
||||
{
|
||||
LogResponseContent = true,
|
||||
AllowAutoRedirect = true,
|
||||
Method = HttpMethod.Post
|
||||
AllowAutoRedirect = true
|
||||
};
|
||||
|
||||
var authLoginRequest = requestBuilder
|
||||
var authLoginRequest = requestBuilder.Post()
|
||||
.AddFormParameter("username", Settings.Username)
|
||||
.AddFormParameter("password", Settings.Password)
|
||||
.AddFormParameter("autologin", "on")
|
||||
@@ -76,8 +74,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
_logger.Debug(response.Content);
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
var errorMessage = dom.QuerySelector("table.forumline table span.gen")?.FirstChild?.TextContent;
|
||||
@@ -86,7 +82,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
|
||||
var cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("Toloka.to authentication succeeded.");
|
||||
}
|
||||
@@ -328,16 +324,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
};
|
||||
|
||||
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
||||
|
||||
if (queryCats.Any())
|
||||
{
|
||||
foreach (var cat in queryCats)
|
||||
{
|
||||
parameters.Add("f[]", $"{cat}");
|
||||
}
|
||||
queryCats.ForEach(cat => parameters.Add("f[]", $"{cat}"));
|
||||
}
|
||||
|
||||
var searchUrl = _settings.BaseUrl + "tracker.php";
|
||||
var searchUrl = $"{_settings.BaseUrl}tracker.php";
|
||||
|
||||
if (parameters.Count > 0)
|
||||
{
|
||||
@@ -358,6 +350,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
private readonly TolokaSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
private readonly TolokaTitleParser _titleParser = new ();
|
||||
|
||||
public TolokaParser(TolokaSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
@@ -383,10 +377,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
|
||||
var infoUrl = _settings.BaseUrl + row.QuerySelector("td:nth-child(3) > a")?.GetAttribute("href");
|
||||
var title = row.QuerySelector("td:nth-child(3) > a")?.TextContent.Trim() ?? string.Empty;
|
||||
|
||||
var title = row.QuerySelector("td:nth-child(3) > a").TextContent.Trim();
|
||||
|
||||
var categoryLink = row.QuerySelector("td:nth-child(2) > a").GetAttribute("href");
|
||||
var categoryLink = row.QuerySelector("td:nth-child(2) > a")?.GetAttribute("href") ?? string.Empty;
|
||||
var cat = ParseUtil.GetArgumentFromQueryString(categoryLink, "f");
|
||||
var categories = _categories.MapTrackerCatToNewznab(cat);
|
||||
|
||||
@@ -394,14 +387,15 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var peers = seeders + ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(11) > b")?.TextContent.Trim());
|
||||
|
||||
// 2023-01-21
|
||||
var added = row.QuerySelector("td:nth-child(13)").TextContent.Trim();
|
||||
var added = row.QuerySelector("td:nth-child(13)")?.TextContent.Trim() ?? string.Empty;
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Guid = infoUrl,
|
||||
InfoUrl = infoUrl,
|
||||
DownloadUrl = _settings.BaseUrl + downloadUrl,
|
||||
Title = CleanTitle(title, categories, _settings.StripCyrillicLetters),
|
||||
Title = _titleParser.Parse(title, categories, _settings.StripCyrillicLetters),
|
||||
Description = title,
|
||||
Categories = categories,
|
||||
Seeders = seeders,
|
||||
Peers = peers,
|
||||
@@ -420,27 +414,65 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
return releaseInfos.ToArray();
|
||||
}
|
||||
|
||||
private static bool IsAnyTvCategory(ICollection<IndexerCategory> category)
|
||||
{
|
||||
return category.Contains(NewznabStandardCategory.TV) || NewznabStandardCategory.TV.SubCategories.Any(subCategory => category.Contains(subCategory));
|
||||
}
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
private static string CleanTitle(string title, ICollection<IndexerCategory> categories, bool stripCyrillicLetters = true)
|
||||
public class TolokaTitleParser
|
||||
{
|
||||
private static readonly List<Regex> FindTagsInTitlesRegexList = new ()
|
||||
{
|
||||
var tvShowTitleRegex = new Regex(".+\\/\\s([^а-яА-я\\/]+)\\s\\/.+Сезон\\s*[:]*\\s+(\\d+).+(?:Серії|Епізод)+\\s*[:]*\\s+(\\d+-*\\d*).+,\\s+(.+)\\]\\s(.+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
var stripCyrillicRegex = new Regex(@"(\([\p{IsCyrillic}\W]+\))|(^[\p{IsCyrillic}\W\d]+\/ )|([\p{IsCyrillic} \-]+,+)|([\p{IsCyrillic}]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
new Regex(@"\((?>\((?<c>)|[^()]+|\)(?<-c>))*(?(c)(?!))\)"),
|
||||
new Regex(@"\[(?>\[(?<c>)|[^\[\]]+|\](?<-c>))*(?(c)(?!))\]")
|
||||
};
|
||||
|
||||
private readonly Regex _tvTitleCommaRegex = new (@"\s(\d+),(\d+)", RegexOptions.Compiled);
|
||||
private readonly Regex _tvTitleCyrillicXRegex = new (@"([\s-])Х+([\)\]])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private readonly Regex _tvTitleMultipleSeasonsRegex = new (@"(?:Сезон|Seasons?)\s*[:]*\s+(\d+-\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private readonly Regex _tvTitleUkrSeasonEpisodeOfRegex = new (@"Сезон\s*[:]*\s+(\d+).+(?:Серії|Серія|Серій|Епізод)+\s*[:]*\s+(\d+(?:-\d+)?)\s*з\s*([\w?])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private readonly Regex _tvTitleUkrSeasonEpisodeRegex = new (@"Сезон\s*[:]*\s+(\d+).+(?:Серії|Серія|Серій|Епізод)+\s*[:]*\s+(\d+(?:-\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private readonly Regex _tvTitleUkrSeasonRegex = new (@"Сезон\s*[:]*\s+(\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private readonly Regex _tvTitleUkrEpisodeOfRegex = new (@"(?:Серії|Серія|Серій|Епізод)+\s*[:]*\s+(\d+(?:-\d+)?)\s*з\s*([\w?])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private readonly Regex _tvTitleUkrEpisodeRegex = new (@"(?:Серії|Серія|Серій|Епізод)+\s*[:]*\s+(\d+(?:-\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private readonly Regex _tvTitleEngSeasonEpisodeOfRegex = new (@"Season\s*[:]*\s+(\d+).+(?:Episodes?)+\s*[:]*\s+(\d+(?:-\d+)?)\s*of\s*([\w?])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private readonly Regex _tvTitleEngSeasonEpisodeRegex = new (@"Season\s*[:]*\s+(\d+).+(?:Episodes?)+\s*[:]*\s+(\d+(?:-\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private readonly Regex _tvTitleEngSeasonRegex = new (@"Season\s*[:]*\s+(\d+(?:-\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private readonly Regex _tvTitleEngEpisodeOfRegex = new (@"(?:Episodes?)+\s*[:]*\s+(\d+(?:-\d+)?)\s*of\s*([\w?])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private readonly Regex _tvTitleEngEpisodeRegex = new (@"(?:Episodes?)+\s*[:]+\s*[:]*\s+(\d+(?:-\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private readonly Regex _stripCyrillicRegex = new (@"(\([\p{IsCyrillic}\W]+\))|(^[\p{IsCyrillic}\W\d]+\/ )|([\p{IsCyrillic} \-]+,+)|([\p{IsCyrillic}]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public string Parse(string title, ICollection<IndexerCategory> categories, bool stripCyrillicLetters = true)
|
||||
{
|
||||
// https://www.fileformat.info/info/unicode/category/Pd/list.htm
|
||||
title = Regex.Replace(title, "\\p{Pd}", "-", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
title = Regex.Replace(title, @"\p{Pd}", "-", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
if (IsAnyTvCategory(categories))
|
||||
{
|
||||
// extract season and episodes
|
||||
title = tvShowTitleRegex.Replace(title, "$1 - S$2E$3 - rus $4 $5");
|
||||
title = _tvTitleCommaRegex.Replace(title, " $1-$2");
|
||||
title = _tvTitleCyrillicXRegex.Replace(title, "$1XX$2");
|
||||
|
||||
// special case for multiple seasons
|
||||
title = _tvTitleMultipleSeasonsRegex.Replace(title, "S$1");
|
||||
|
||||
title = _tvTitleUkrSeasonEpisodeOfRegex.Replace(title, "S$1E$2 of $3");
|
||||
title = _tvTitleUkrSeasonEpisodeRegex.Replace(title, "S$1E$2");
|
||||
title = _tvTitleUkrSeasonRegex.Replace(title, "S$1");
|
||||
title = _tvTitleUkrEpisodeOfRegex.Replace(title, "E$1 of $2");
|
||||
title = _tvTitleUkrEpisodeRegex.Replace(title, "E$1");
|
||||
|
||||
title = _tvTitleEngSeasonEpisodeOfRegex.Replace(title, "S$1E$2 of $3");
|
||||
title = _tvTitleEngSeasonEpisodeRegex.Replace(title, "S$1E$2");
|
||||
title = _tvTitleEngSeasonRegex.Replace(title, "S$1");
|
||||
title = _tvTitleEngEpisodeOfRegex.Replace(title, "E$1 of $2");
|
||||
title = _tvTitleEngEpisodeRegex.Replace(title, "E$1");
|
||||
}
|
||||
else if (stripCyrillicLetters)
|
||||
|
||||
if (stripCyrillicLetters)
|
||||
{
|
||||
title = stripCyrillicRegex.Replace(title, string.Empty);
|
||||
title = _stripCyrillicRegex.Replace(title, string.Empty).Trim(' ', '-');
|
||||
}
|
||||
|
||||
title = Regex.Replace(title, @"\b-Rip\b", "Rip", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
@@ -449,10 +481,56 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
title = Regex.Replace(title, @"\bWEBDLRip\b", "WEB-DL", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
title = Regex.Replace(title, @"\bWEBDL\b", "WEB-DL", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
return title.Trim(' ', '.', '-', '_', '|', '/', '\'');
|
||||
title = MoveFirstTagsToEndOfReleaseTitle(title);
|
||||
|
||||
title = Regex.Replace(title, @"\(\s*\/\s*", "(", RegexOptions.Compiled);
|
||||
title = Regex.Replace(title, @"\s*\/\s*\)", ")", RegexOptions.Compiled);
|
||||
|
||||
title = Regex.Replace(title, @"[\[\(]\s*[\)\]]", "", RegexOptions.Compiled);
|
||||
|
||||
title = title.Trim(' ', '&', ',', '.', '!', '?', '+', '-', '_', '|', '/', '\\', ':');
|
||||
|
||||
// replace multiple spaces with a single space
|
||||
title = Regex.Replace(title, @"\s+", " ");
|
||||
|
||||
return title.Trim();
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
private static bool IsAnyTvCategory(ICollection<IndexerCategory> category)
|
||||
{
|
||||
return category.Contains(NewznabStandardCategory.TV) || NewznabStandardCategory.TV.SubCategories.Any(subCategory => category.Contains(subCategory));
|
||||
}
|
||||
|
||||
private static string MoveFirstTagsToEndOfReleaseTitle(string input)
|
||||
{
|
||||
var output = input;
|
||||
foreach (var findTagsRegex in FindTagsInTitlesRegexList)
|
||||
{
|
||||
var expectedIndex = 0;
|
||||
foreach (Match match in findTagsRegex.Matches(output))
|
||||
{
|
||||
if (match.Index > expectedIndex)
|
||||
{
|
||||
var substring = output.Substring(expectedIndex, match.Index - expectedIndex);
|
||||
if (string.IsNullOrWhiteSpace(substring))
|
||||
{
|
||||
expectedIndex = match.Index;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var tag = match.ToString();
|
||||
var regex = new Regex(Regex.Escape(tag));
|
||||
output = $"{regex.Replace(output, string.Empty, 1)} {tag}".Trim();
|
||||
expectedIndex += tag.Length;
|
||||
}
|
||||
}
|
||||
|
||||
return output.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
public class TolokaSettings : UserPassTorrentBaseSettings
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
|
||||
cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("TorrentBytes authentication succeeded.");
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||
{
|
||||
_logger.Warn(ex, "Unable to connect to indexer: " + ex.Message);
|
||||
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
312
src/NzbDrone.Core/Indexers/Definitions/Uniotaku.cs
Normal file
312
src/NzbDrone.Core/Indexers/Definitions/Uniotaku.cs
Normal file
@@ -0,0 +1,312 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Html.Parser;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
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;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions;
|
||||
|
||||
public class Uniotaku : TorrentIndexerBase<UniotakuSettings>
|
||||
{
|
||||
public override string Name => "UniOtaku";
|
||||
public override string[] IndexerUrls => new[] { "https://tracker.uniotaku.com/" };
|
||||
public override string Description => "UniOtaku is a BRAZILIAN Semi-Private Torrent Tracker for ANIME";
|
||||
public override string Language => "pt-BR";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.SemiPrivate;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public Uniotaku(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new UniotakuRequestGenerator(Settings, Capabilities);
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new UniotakuParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override async Task DoLogin()
|
||||
{
|
||||
var loginUrl = Settings.BaseUrl + "account-login.php";
|
||||
var requestBuilder = new HttpRequestBuilder(loginUrl);
|
||||
|
||||
var cookies = Cookies;
|
||||
Cookies = null;
|
||||
|
||||
var authLoginRequest = requestBuilder
|
||||
.Post()
|
||||
.AddFormParameter("username", Settings.Username)
|
||||
.AddFormParameter("password", Settings.Password)
|
||||
.AddFormParameter("manter", "1")
|
||||
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
.SetHeader("Referer", Settings.BaseUrl)
|
||||
.Build();
|
||||
|
||||
var response = await ExecuteAuth(authLoginRequest);
|
||||
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
var errorMessage = dom.QuerySelector(".login-content span.text-red")?.TextContent.Trim();
|
||||
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||
}
|
||||
|
||||
cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("Authentication succeeded");
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
{
|
||||
return !httpResponse.GetCookies().ContainsKey("uid") || !httpResponse.GetCookies().ContainsKey("pass");
|
||||
}
|
||||
|
||||
public override async Task<byte[]> Download(Uri link)
|
||||
{
|
||||
var request = new HttpRequestBuilder(link.ToString())
|
||||
.SetCookies(GetCookies() ?? new Dictionary<string, string>())
|
||||
.Accept(HttpAccept.Html)
|
||||
.Build();
|
||||
|
||||
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
var downloadLink = dom.QuerySelector("a[href^=\"download.php?id=\"]")?.GetAttribute("href")?.Trim();
|
||||
|
||||
if (downloadLink == null)
|
||||
{
|
||||
throw new Exception($"Failed to fetch download link from {link}");
|
||||
}
|
||||
|
||||
return await base.Download(new Uri(Settings.BaseUrl + downloadLink));
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.TVAnime, "Anime");
|
||||
caps.Categories.AddCategoryMapping(47, NewznabStandardCategory.MoviesOther, "Filme");
|
||||
caps.Categories.AddCategoryMapping(48, NewznabStandardCategory.TVAnime, "OVA");
|
||||
caps.Categories.AddCategoryMapping(49, NewznabStandardCategory.BooksComics, "Mangá");
|
||||
caps.Categories.AddCategoryMapping(50, NewznabStandardCategory.TVOther, "Dorama");
|
||||
caps.Categories.AddCategoryMapping(51, NewznabStandardCategory.Audio, "OST");
|
||||
caps.Categories.AddCategoryMapping(52, NewznabStandardCategory.TVAnime, "Anime Completo");
|
||||
caps.Categories.AddCategoryMapping(53, NewznabStandardCategory.BooksComics, "Mangá Completo");
|
||||
caps.Categories.AddCategoryMapping(54, NewznabStandardCategory.TVOther, "Dorama Completo");
|
||||
caps.Categories.AddCategoryMapping(55, NewznabStandardCategory.XXX, "Hentai");
|
||||
caps.Categories.AddCategoryMapping(56, NewznabStandardCategory.XXXOther, "H Doujinshi");
|
||||
caps.Categories.AddCategoryMapping(57, NewznabStandardCategory.TVOther, "Tokusatsu");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class UniotakuRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
private readonly UniotakuSettings _settings;
|
||||
private readonly IndexerCapabilities _capabilities;
|
||||
|
||||
public UniotakuRequestGenerator(UniotakuSettings settings, IndexerCapabilities capabilities)
|
||||
{
|
||||
_settings = settings;
|
||||
_capabilities = capabilities;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(term))
|
||||
{
|
||||
term = "%" + Regex.Replace(term, @"[ -._]+", "%").Trim() + "%";
|
||||
}
|
||||
|
||||
var categoryMapping = _capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
||||
|
||||
var parameters = new NameValueCollection
|
||||
{
|
||||
{ "categoria", categoryMapping.FirstIfSingleOrDefault("0") },
|
||||
{ "grupo", "0" },
|
||||
{ "status", _settings.FreeleechOnly ? "1" : "0" },
|
||||
{ "ordenar", "0" },
|
||||
{ "start", "0" },
|
||||
{ "length", "100" },
|
||||
{ "search[value]", term ?? string.Empty },
|
||||
{ "search[regex]", "false" },
|
||||
};
|
||||
|
||||
var searchUrl = $"{_settings.BaseUrl}torrents_.php?{parameters.GetQueryString()}";
|
||||
|
||||
yield return new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class UniotakuParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly UniotakuSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
public UniotakuParser(UniotakuSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
|
||||
var jsonContent = JObject.Parse(indexerResponse.Content);
|
||||
|
||||
var publishDate = DateTime.Now;
|
||||
foreach (var item in jsonContent.Value<JArray>("data"))
|
||||
{
|
||||
var detailsDom = parser.ParseDocument(item.SelectToken("[0]").Value<string>());
|
||||
var categoryDom = parser.ParseDocument(item.SelectToken("[1]").Value<string>());
|
||||
var groupDom = parser.ParseDocument(item.SelectToken("[7]").Value<string>());
|
||||
|
||||
var qTitleLink = detailsDom.QuerySelector("a[href^=\"torrents-details.php?id=\"]");
|
||||
var title = qTitleLink?.TextContent.Trim();
|
||||
var infoUrl = _settings.BaseUrl + qTitleLink?.GetAttribute("href");
|
||||
|
||||
var category = categoryDom.QuerySelector("img[alt]")?.GetAttribute("alt")?.Trim() ?? "Anime";
|
||||
|
||||
var releaseGroup = groupDom.QuerySelector("a[href*=\"teams-view.php?id=\"]")?.TextContent.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(releaseGroup))
|
||||
{
|
||||
title += $" [{releaseGroup}]";
|
||||
}
|
||||
|
||||
var seeders = item.SelectToken("[3]")?.Value<int>();
|
||||
var leechers = item.SelectToken("[4]")?.Value<int>();
|
||||
|
||||
publishDate = publishDate.AddMinutes(-1);
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Guid = infoUrl,
|
||||
InfoUrl = infoUrl,
|
||||
DownloadUrl = infoUrl,
|
||||
Title = title,
|
||||
Categories = _categories.MapTrackerCatDescToNewznab(category),
|
||||
Size = ParseUtil.GetBytes(item.SelectToken("[6]")?.Value<string>()),
|
||||
Grabs = item.SelectToken("[5]")?.Value<int>(),
|
||||
Seeders = seeders,
|
||||
Peers = seeders + leechers,
|
||||
PublishDate = publishDate,
|
||||
DownloadVolumeFactor =
|
||||
detailsDom.QuerySelector("img[src*=\"images/free.gif\"]") != null ? 0 :
|
||||
detailsDom.QuerySelector("img[src*=\"images/silverdownload.gif\"]") != null ? 0.5 : 1,
|
||||
UploadVolumeFactor = 1,
|
||||
MinimumRatio = 0.7
|
||||
};
|
||||
|
||||
releaseInfos.Add(release);
|
||||
}
|
||||
|
||||
return releaseInfos;
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class UniotakuSettings : UserPassTorrentBaseSettings
|
||||
{
|
||||
public UniotakuSettings()
|
||||
{
|
||||
FreeleechOnly = false;
|
||||
}
|
||||
|
||||
[FieldDefinition(4, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Search Freeleech torrents only")]
|
||||
public bool FreeleechOnly { get; set; }
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public class XSpeeds : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
||||
}
|
||||
|
||||
var cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("Authentication succeeded.");
|
||||
}
|
||||
@@ -311,7 +311,7 @@ public class XSpeedsParser : IParseIndexerResponse
|
||||
release.PublishDate = DateTime.ParseExact(dateAddedMatch.Value, "dd-MM-yyyy HH:mm", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (row.QuerySelector("img[title^=\"Free Torrent\"]") != null)
|
||||
if (row.QuerySelector("img[title^=\"Free Torrent\"], img[title^=\"Sitewide Free Torrent\"]") != null)
|
||||
{
|
||||
release.DownloadVolumeFactor = 0;
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
response = await ExecuteAuth(authLoginRequest3);
|
||||
|
||||
UpdateCookies(response.GetCookies(), DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(response.GetCookies(), DateTime.Now.AddDays(30));
|
||||
}
|
||||
|
||||
private static string Sha1Hash(string input)
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation.Results;
|
||||
@@ -33,7 +34,7 @@ namespace NzbDrone.Core.Indexers
|
||||
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override string Language => "en-US";
|
||||
public override string[] LegacyUrls => new string[] { };
|
||||
public override string[] LegacyUrls => Array.Empty<string>();
|
||||
|
||||
public override bool FollowRedirect => false;
|
||||
public override IndexerCapabilities Capabilities { get; protected set; }
|
||||
@@ -258,6 +259,16 @@ namespace NzbDrone.Core.Indexers
|
||||
_indexerStatusService.RecordFailure(Definition.Id);
|
||||
_logger.Warn(ex, "{0}", url);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_indexerStatusService.RecordFailure(Definition.Id);
|
||||
_logger.Warn(ex, "Unable to connect to indexer, please check your DNS settings and ensure IPv6 is working or disabled. {0}", url);
|
||||
}
|
||||
catch (TaskCanceledException ex)
|
||||
{
|
||||
_indexerStatusService.RecordFailure(Definition.Id);
|
||||
_logger.Warn(ex, "Unable to connect to indexer, possibly due to a timeout. {0}", url);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_indexerStatusService.RecordFailure(Definition.Id);
|
||||
@@ -362,7 +373,7 @@ namespace NzbDrone.Core.Indexers
|
||||
}
|
||||
|
||||
request.HttpRequest.SuppressHttpError = true;
|
||||
request.HttpRequest.Encoding = request.HttpRequest.Encoding ?? Encoding;
|
||||
request.HttpRequest.Encoding ??= Encoding;
|
||||
|
||||
var response = await _httpClient.ExecuteProxiedAsync(request.HttpRequest, Definition);
|
||||
|
||||
@@ -395,7 +406,7 @@ namespace NzbDrone.Core.Indexers
|
||||
throw new CloudFlareProtectionException(response);
|
||||
}
|
||||
|
||||
UpdateCookies(request.HttpRequest.Cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
UpdateCookies(request.HttpRequest.Cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
return new IndexerResponse(request, response);
|
||||
}
|
||||
@@ -490,18 +501,28 @@ namespace NzbDrone.Core.Indexers
|
||||
_logger.Warn(ex, "Indexer does not support the query");
|
||||
return new ValidationFailure(string.Empty, "Indexer does not support the current query. Check if the categories and or searching for movies are supported. Check the log for more details.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn(ex, "Unable to connect to indexer");
|
||||
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
|
||||
}
|
||||
_logger.Warn(ex, "Unable to connect to indexer");
|
||||
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details. " + ex.Message);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.Warn(ex, "Unable to connect to indexer");
|
||||
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, please check your DNS settings and ensure IPv6 is working or disabled. " + ex.Message);
|
||||
}
|
||||
catch (TaskCanceledException ex)
|
||||
{
|
||||
_logger.Warn(ex, "Unable to connect to indexer");
|
||||
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, possibly due to a timeout. Try again or check your network settings. " + ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "Unable to connect to indexer");
|
||||
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details");
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Indexers
|
||||
|
||||
public DateTime GetIndexerCookiesExpirationDate(int indexerId)
|
||||
{
|
||||
return GetProviderStatus(indexerId)?.CookiesExpirationDate ?? DateTime.Now + TimeSpan.FromDays(12);
|
||||
return GetProviderStatus(indexerId)?.CookiesExpirationDate ?? DateTime.Now.AddDays(12);
|
||||
}
|
||||
|
||||
public void UpdateRssSyncStatus(int indexerId, ReleaseInfo releaseInfo)
|
||||
|
||||
@@ -11,25 +11,25 @@ namespace NzbDrone.Core.Indexers
|
||||
private static readonly List<string> _trackers = new List<string>
|
||||
{
|
||||
"udp://tracker.opentrackr.org:1337/announce",
|
||||
"http://p4p.arenabg.com:1337/announce",
|
||||
"udp://9.rarbg.com:2810/announce",
|
||||
"udp://tracker.openbittorrent.com:6969/announce",
|
||||
"udp://www.torrent.eu.org:451/announce",
|
||||
"udp://tracker.torrent.eu.org:451/announce",
|
||||
"udp://retracker.lanta-net.ru:2710/announce",
|
||||
"udp://open.stealth.si:80/announce",
|
||||
"udp://exodus.desync.com:6969/announce",
|
||||
"http://openbittorrent.com:80/announce",
|
||||
"http://tracker.openbittorrent.com:80/announce",
|
||||
"udp://opentracker.i2p.rocks:6969/announce",
|
||||
"udp://opentor.org:2710/announce",
|
||||
"udp://ipv4.tracker.harry.lu:80/announce",
|
||||
"udp://tracker.uw0.xyz:6969/announce",
|
||||
"https://opentracker.i2p.rocks:443/announce",
|
||||
"udp://www.peckservers.com:9000/announce",
|
||||
"udp://tracker.torrent.eu.org:451/announce",
|
||||
"udp://tracker.tiny-vps.com:6969/announce",
|
||||
"udp://tracker.moeking.me:6969/announce",
|
||||
"udp://tracker.dler.org:6969/announce",
|
||||
"udp://tracker.altrosky.nl:6969/announce",
|
||||
"udp://p4p.arenabg.com:1337/announce",
|
||||
"udp://open.stealth.si:80/announce",
|
||||
"udp://open.demonii.com:1337/announce",
|
||||
"udp://ipv4.tracker.harry.lu:80/announce",
|
||||
"udp://explodie.org:6969/announce",
|
||||
"udp://bt2.archive.org:6969/announce",
|
||||
"udp://exodus.desync.com:6969/announce",
|
||||
"udp://bt1.archive.org:6969/announce",
|
||||
"https://trakx.herokuapp.com:443/announce",
|
||||
"https://tracker.lilithraws.org:443/announce"
|
||||
};
|
||||
|
||||
public static string BuildPublicMagnetLink(string infoHash, string releaseTitle)
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace NzbDrone.Core.Indexers
|
||||
if (link.Scheme == "magnet")
|
||||
{
|
||||
ValidateMagnet(link.OriginalString);
|
||||
|
||||
return Encoding.UTF8.GetBytes(link.OriginalString);
|
||||
}
|
||||
|
||||
@@ -78,6 +79,8 @@ namespace NzbDrone.Core.Indexers
|
||||
throw;
|
||||
}
|
||||
|
||||
ValidateTorrent(torrentData);
|
||||
|
||||
return torrentData;
|
||||
}
|
||||
|
||||
@@ -85,5 +88,18 @@ namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
MagnetLink.Parse(link);
|
||||
}
|
||||
|
||||
protected void ValidateTorrent(byte[] torrentData)
|
||||
{
|
||||
try
|
||||
{
|
||||
Torrent.Load(torrentData);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_logger.Trace("Invalid torrent file contents: {0}", Encoding.ASCII.GetString(torrentData));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +81,9 @@
|
||||
"Source": "مصدر",
|
||||
"SSLCertPassword": "كلمة مرور شهادة SSL",
|
||||
"UpdateCheckStartupNotWritableMessage": "لا يمكن تثبيت التحديث لأن مجلد بدء التشغيل \"{0}\" غير قابل للكتابة بواسطة المستخدم \"{1}\".",
|
||||
"UpdateMechanismHelpText": "استخدم المحدث أو البرنامج النصي المدمج في Radarr",
|
||||
"UpdateMechanismHelpText": "استخدم المحدث أو البرنامج النصي المدمج في Prowlarr",
|
||||
"AppDataDirectory": "دليل AppData",
|
||||
"ConnectionLostAutomaticMessage": "سيحاول Radarr الاتصال تلقائيًا ، أو يمكنك النقر فوق إعادة التحميل أدناه.",
|
||||
"ConnectionLostAutomaticMessage": "سيحاول Prowlarr الاتصال تلقائيًا ، أو يمكنك النقر فوق إعادة التحميل أدناه.",
|
||||
"ConnectSettings": "ربط الإعدادات",
|
||||
"CouldNotConnectSignalR": "تعذر الاتصال بـ SignalR ، لن يتم تحديث واجهة المستخدم",
|
||||
"Dates": "تواريخ",
|
||||
@@ -106,7 +106,7 @@
|
||||
"ApplyTagsHelpTexts3": "إزالة: قم بإزالة العلامات التي تم إدخالها",
|
||||
"AutomaticSearch": "البحث التلقائي",
|
||||
"Backup": "دعم",
|
||||
"BackupFolderHelpText": "ستكون المسارات النسبية ضمن دليل AppData الخاص بـ Radarr",
|
||||
"BackupFolderHelpText": "ستكون المسارات النسبية ضمن دليل AppData الخاص بـ Prowlarr",
|
||||
"BeforeUpdate": "قبل التحديث",
|
||||
"BindAddress": "عنوان ملزم",
|
||||
"CertificateValidationHelpText": "تغيير مدى صرامة التحقق من صحة شهادة HTTPS",
|
||||
@@ -153,7 +153,7 @@
|
||||
"OpenBrowserOnStart": "افتح المتصفح عند البدء",
|
||||
"OpenThisModal": "افتح هذا النموذج",
|
||||
"SSLCertPathHelpText": "مسار ملف pfx",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "الفرع {0} ليس فرع إصدار Radarr صالح ، لن تتلقى تحديثات",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "الفرع {0} ليس فرع إصدار Prowlarr صالح ، لن تتلقى تحديثات",
|
||||
"RemovedFromTaskQueue": "تمت إزالته من قائمة انتظار المهام",
|
||||
"RemoveFilter": "قم بإزالة الفلتر",
|
||||
"RemovingTag": "إزالة العلامة",
|
||||
@@ -185,7 +185,7 @@
|
||||
"Hostname": "اسم المضيف",
|
||||
"IgnoredAddresses": "العناوين التي تم تجاهلها",
|
||||
"UILanguage": "لغة واجهة المستخدم",
|
||||
"UILanguageHelpText": "اللغة التي سيستخدمها Radarr لواجهة المستخدم",
|
||||
"UILanguageHelpText": "اللغة التي سيستخدمها Prowlarr لواجهة المستخدم",
|
||||
"UILanguageHelpTextWarning": "يلزم إعادة تحميل المتصفح",
|
||||
"KeyboardShortcuts": "اختصارات لوحة المفاتيح",
|
||||
"LogFiles": "ملفات الدخول",
|
||||
@@ -243,10 +243,10 @@
|
||||
"SSLPort": "منفذ SSL",
|
||||
"UpdateAutomaticallyHelpText": "تنزيل التحديثات وتثبيتها تلقائيًا. ستظل قادرًا على التثبيت من النظام: التحديثات",
|
||||
"All": "الكل",
|
||||
"AnalyticsEnabledHelpText": "إرسال معلومات الاستخدام والخطأ المجهولة إلى خوادم Radarr. يتضمن ذلك معلومات حول متصفحك ، وصفحات Radarr WebUI التي تستخدمها ، والإبلاغ عن الأخطاء بالإضافة إلى إصدار نظام التشغيل ووقت التشغيل. سنستخدم هذه المعلومات لتحديد أولويات الميزات وإصلاحات الأخطاء.",
|
||||
"AnalyticsEnabledHelpText": "إرسال معلومات الاستخدام والخطأ المجهولة إلى خوادم Prowlarr. يتضمن ذلك معلومات حول متصفحك ، وصفحات Prowlarr WebUI التي تستخدمها ، والإبلاغ عن الأخطاء بالإضافة إلى إصدار نظام التشغيل ووقت التشغيل. سنستخدم هذه المعلومات لتحديد أولويات الميزات وإصلاحات الأخطاء.",
|
||||
"ApplyTags": "تطبيق العلامات",
|
||||
"Branch": "فرع شجرة",
|
||||
"BranchUpdate": "فرع لاستخدامه لتحديث Radarr",
|
||||
"BranchUpdate": "فرع لاستخدامه لتحديث Prowlarr",
|
||||
"CancelPendingTask": "هل أنت متأكد أنك تريد إلغاء هذه المهمة المعلقة؟",
|
||||
"DeleteNotificationMessageText": "هل تريد بالتأكيد حذف الإشعار \"{0}\"؟",
|
||||
"DeleteTag": "حذف العلامة",
|
||||
@@ -284,18 +284,18 @@
|
||||
"Indexers": "مفهرسات",
|
||||
"InteractiveSearch": "بحث تفاعلي",
|
||||
"LastWriteTime": "وقت الكتابة الأخير",
|
||||
"LaunchBrowserHelpText": " افتح مستعرض ويب وانتقل إلى صفحة Radarr الرئيسية عند بدء التطبيق.",
|
||||
"LaunchBrowserHelpText": " افتح مستعرض ويب وانتقل إلى صفحة Prowlarr الرئيسية عند بدء التطبيق.",
|
||||
"Level": "مستوى",
|
||||
"Logs": "السجلات",
|
||||
"Mechanism": "آلية",
|
||||
"Message": "رسالة",
|
||||
"MIA": "MIA",
|
||||
"RefreshMovie": "تحديث الفيلم",
|
||||
"EnableAutomaticSearchHelpText": "سيتم استخدامه عند إجراء عمليات البحث التلقائي عبر واجهة المستخدم أو بواسطة Radarr",
|
||||
"EnableAutomaticSearchHelpText": "سيتم استخدامه عند إجراء عمليات البحث التلقائي عبر واجهة المستخدم أو بواسطة Prowlarr",
|
||||
"Status": "الحالة",
|
||||
"Uptime": "مدة التشغيل",
|
||||
"ApplyTagsHelpTexts4": "استبدال: استبدل العلامات بالعلامات التي تم إدخالها (لا تدخل أي علامات لمسح جميع العلامات)",
|
||||
"AuthenticationMethodHelpText": "طلب اسم المستخدم وكلمة المرور للوصول إلى Radarr",
|
||||
"AuthenticationMethodHelpText": "طلب اسم المستخدم وكلمة المرور للوصول إلى Prowlarr",
|
||||
"Automatic": "تلقائي",
|
||||
"Mode": "الوضع",
|
||||
"Options": "خيارات",
|
||||
@@ -338,5 +338,8 @@
|
||||
"ApplicationLongTermStatusCheckSingleClientMessage": "المفهرسات غير متاحة بسبب الإخفاقات لأكثر من 6 ساعات: {0}",
|
||||
"LastExecution": "آخر تنفيذ",
|
||||
"NextExecution": "التنفيذ القادم",
|
||||
"Queued": "في قائمة الانتظار"
|
||||
"Queued": "في قائمة الانتظار",
|
||||
"Remove": "إزالة",
|
||||
"Replace": "يحل محل",
|
||||
"TheLatestVersionIsAlreadyInstalled": "تم بالفعل تثبيت أحدث إصدار من {0}"
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"Tomorrow": "Утре",
|
||||
"Torrents": "Торенти",
|
||||
"Type": "Тип",
|
||||
"UILanguageHelpText": "Език, който Radarr ще използва за потребителски интерфейс",
|
||||
"UILanguageHelpText": "Език, който Prowlarr ще използва за потребителски интерфейс",
|
||||
"UnableToAddANewIndexerPleaseTryAgain": "Не може да се добави нов индексатор, моля, опитайте отново.",
|
||||
"UnableToAddANewIndexerProxyPleaseTryAgain": "Не може да се добави нов индексатор, моля, опитайте отново.",
|
||||
"UnableToAddANewNotificationPleaseTryAgain": "Не може да се добави ново известие, моля, опитайте отново.",
|
||||
@@ -214,7 +214,7 @@
|
||||
"ProxyType": "Тип прокси",
|
||||
"ExistingTag": "Съществуващ маркер",
|
||||
"ProxyUsernameHelpText": "Трябва само да въведете потребителско име и парола, ако е необходимо. В противен случай ги оставете празни.",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "Клон {0} не е валиден клон за издаване на Radarr, няма да получавате актуализации",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "Клон {0} не е валиден клон за издаване на Prowlarr, няма да получавате актуализации",
|
||||
"IncludeHealthWarningsHelpText": "Включете здравни предупреждения",
|
||||
"SaveSettings": "Запазване на настройките",
|
||||
"Scheduled": "Планиран",
|
||||
@@ -267,7 +267,7 @@
|
||||
"UrlBaseHelpText": "За обратна поддръжка на прокси по подразбиране е празно",
|
||||
"View": "Изглед",
|
||||
"ApplyTagsHelpTexts1": "Как да приложите тагове към избраните филми",
|
||||
"BranchUpdate": "Клон, който да се използва за актуализиране на Radarr",
|
||||
"BranchUpdate": "Клон, който да се използва за актуализиране на Prowlarr",
|
||||
"Indexers": "Индексатори",
|
||||
"IndexerStatusCheckAllClientMessage": "Всички индексатори са недостъпни поради грешки",
|
||||
"Mode": "Режим",
|
||||
@@ -278,7 +278,7 @@
|
||||
"UnableToLoadUISettings": "Настройките на потребителския интерфейс не могат да се заредят",
|
||||
"UnsavedChanges": "Незапазени промени",
|
||||
"UnselectAll": "Деселектирайте всички",
|
||||
"UpdateMechanismHelpText": "Използвайте вградения в Radarr актуализатор или скрипт",
|
||||
"UpdateMechanismHelpText": "Използвайте вградения в Prowlarr актуализатор или скрипт",
|
||||
"Updates": "Актуализации",
|
||||
"Uptime": "Време за работа",
|
||||
"DownloadClientSettings": "Изтеглете настройките на клиента",
|
||||
@@ -286,7 +286,7 @@
|
||||
"DownloadClientStatusCheckSingleClientMessage": "Клиентите за изтегляне са недостъпни поради грешки: {0}",
|
||||
"Edit": "редактиране",
|
||||
"EnableAutomaticSearch": "Активирайте автоматичното търсене",
|
||||
"EnableAutomaticSearchHelpText": "Ще се използва, когато се извършват автоматични търсения чрез потребителския интерфейс или от Radarr",
|
||||
"EnableAutomaticSearchHelpText": "Ще се използва, когато се извършват автоматични търсения чрез потребителския интерфейс или от Prowlarr",
|
||||
"EnableSSL": "Активирайте SSL",
|
||||
"EnableSslHelpText": " Изисква рестартиране, изпълнено като администратор, за да влезе в сила",
|
||||
"Error": "Грешка",
|
||||
@@ -301,7 +301,7 @@
|
||||
"IndexerLongTermStatusCheckSingleClientMessage": "Индексатори не са налични поради неуспехи за повече от 6 часа: {0}",
|
||||
"IndexerPriorityHelpText": "Приоритет на индексатора от 1 (най-висок) до 50 (най-нисък). По подразбиране: 25.",
|
||||
"IndexerStatusCheckSingleClientMessage": "Индексатори не са налични поради грешки: {0}",
|
||||
"LaunchBrowserHelpText": " Отворете уеб браузър и отворете началната страница на Radarr при стартиране на приложението.",
|
||||
"LaunchBrowserHelpText": " Отворете уеб браузър и отворете началната страница на Prowlarr при стартиране на приложението.",
|
||||
"ResetAPIKey": "Нулиране на API ключ",
|
||||
"Restart": "Рестартирам",
|
||||
"RestartNow": "Рестартирай сега",
|
||||
@@ -338,5 +338,8 @@
|
||||
"ApplicationLongTermStatusCheckAllClientMessage": "Всички индексатори са недостъпни поради грешки за повече от 6 часа",
|
||||
"LastDuration": "lastDuration",
|
||||
"NextExecution": "Следващо изпълнение",
|
||||
"Queued": "На опашка"
|
||||
"Queued": "На опашка",
|
||||
"Remove": "Премахване",
|
||||
"Replace": "Сменете",
|
||||
"TheLatestVersionIsAlreadyInstalled": "Вече е инсталирана най-новата версия на {0}"
|
||||
}
|
||||
|
||||
1
src/NzbDrone.Core/Localization/Core/bs.json
Normal file
1
src/NzbDrone.Core/Localization/Core/bs.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -307,18 +307,18 @@
|
||||
"View": "Visualitza",
|
||||
"Yesterday": "Ahir",
|
||||
"ApplicationStatusCheckSingleClientMessage": "Llistes no disponibles a causa d'errors: {0}",
|
||||
"AnalyticsEnabledHelpText": "Envieu informació anònima d'ús i errors als servidors de Radarr. Això inclou informació sobre el vostre navegador, quines pàgines Radarr WebUI feu servir, informes d'errors, així com el sistema operatiu i la versió del temps d'execució. Utilitzarem aquesta informació per prioritzar les funcions i les correccions d'errors.",
|
||||
"AnalyticsEnabledHelpText": "Envieu informació anònima d'ús i errors als servidors de Prowlarr. Això inclou informació sobre el vostre navegador, quines pàgines Prowlarr WebUI feu servir, informes d'errors, així com el sistema operatiu i la versió del temps d'execució. Utilitzarem aquesta informació per prioritzar les funcions i les correccions d'errors.",
|
||||
"ApplyTagsHelpTexts1": "Com aplicar etiquetes a les pel·lícules seleccionades",
|
||||
"ApplyTagsHelpTexts2": "Afegeix: afegeix les etiquetes a la llista d'etiquetes existent",
|
||||
"ConnectionLostAutomaticMessage": "Radarr intentarà connectar-se automàticament, o podeu fer clic a recarregar.",
|
||||
"ConnectionLostMessage": "Radarr ha perdut la connexió amb el backend i s'haurà de tornar a carregar per restaurar la funcionalitat.",
|
||||
"ConnectionLostAutomaticMessage": "Prowlarr intentarà connectar-se automàticament, o podeu fer clic a recarregar.",
|
||||
"ConnectionLostMessage": "Prowlarr ha perdut la connexió amb el backend i s'haurà de tornar a carregar per restaurar la funcionalitat.",
|
||||
"HistoryCleanupDaysHelpTextWarning": "Els fitxers de la paperera de reciclatge més antics que el nombre de dies seleccionat es netejaran automàticament",
|
||||
"UnableToAddANewAppProfilePleaseTryAgain": "No es pot afegir un perfil de qualitat nou, torneu-ho a provar.",
|
||||
"BackupFolderHelpText": "Els camins relatius estaran sota el directori AppData del Lidarr",
|
||||
"BackupFolderHelpText": "Els camins relatius estaran sota el directori AppData del Prowlarr",
|
||||
"AllIndexersHiddenDueToFilter": "Totes les pel·lícules estan ocultes a causa del filtre aplicat.",
|
||||
"EnableRss": "Activa RSS",
|
||||
"Grabs": "Captura",
|
||||
"EnableAutomaticSearchHelpText": "S'utilitzarà quan es realitzin cerques automàtiques mitjançant la interfície d'usuari o per Radarr",
|
||||
"EnableAutomaticSearchHelpText": "S'utilitzarà quan es realitzin cerques automàtiques mitjançant la interfície d'usuari o per Prowlarr",
|
||||
"UnableToAddANewApplicationPleaseTryAgain": "No es pot afegir una notificació nova, torneu-ho a provar.",
|
||||
"Application": "Aplicacions",
|
||||
"Applications": "Aplicacions",
|
||||
@@ -326,8 +326,8 @@
|
||||
"AuthenticationMethodHelpText": "Requereix nom d'usuari i contrasenya per accedir al radar",
|
||||
"ApplicationLongTermStatusCheckAllClientMessage": "Tots els indexadors no estan disponibles a causa d'errors durant més de 6 hores",
|
||||
"ApplicationLongTermStatusCheckSingleClientMessage": "Els indexadors no estan disponibles a causa d'errors durant més de 6 hores: {0}",
|
||||
"BindAddressHelpText": "Adreça IPv4 vàlida o '*' per a totes les interfícies",
|
||||
"BranchUpdate": "Branca que s'utilitza per actualitzar Radarr",
|
||||
"BindAddressHelpText": "Adreça IP vàlida, localhost o '*' per a totes les interfícies",
|
||||
"BranchUpdate": "Branca que s'utilitza per actualitzar Prowlarr",
|
||||
"Connect": "Notificacions",
|
||||
"DeleteApplicationMessageText": "Esteu segur que voleu suprimir la notificació '{0}'?",
|
||||
"DeleteIndexerProxyMessageText": "Esteu segur que voleu suprimir la llista '{0}'?",
|
||||
@@ -339,7 +339,7 @@
|
||||
"Notification": "Notificacions",
|
||||
"Notifications": "Notificacions",
|
||||
"PrioritySettings": "Prioritat",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "La branca {0} no és una branca de llançament de Radarr vàlida, no rebreu actualitzacions",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "La branca {0} no és una branca de llançament de Prowlarr vàlida, no rebreu actualitzacions",
|
||||
"TagsHelpText": "S'aplica a pel·lícules amb almenys una etiqueta coincident",
|
||||
"Torrent": "Torrent",
|
||||
"UnableToAddANewIndexerProxyPleaseTryAgain": "No es pot afegir un indexador nou, torneu-ho a provar.",
|
||||
@@ -347,7 +347,11 @@
|
||||
"UserAgentProvidedByTheAppThatCalledTheAPI": "Agent d'usuari proporcionat per l'aplicació per fer peticions a l'API",
|
||||
"IndexerProxyStatusCheckAllClientMessage": "Tots els indexadors no estan disponibles a causa d'errors",
|
||||
"IndexerProxyStatusCheckSingleClientMessage": "Els indexadors no estan disponibles a causa d'errors: {0}",
|
||||
"LaunchBrowserHelpText": " Obriu un navegador web i navegueu a la pàgina d'inici de Radarr a l'inici de l'aplicació.",
|
||||
"LaunchBrowserHelpText": " Obriu un navegador web i navegueu a la pàgina d'inici de Prowlarr a l'inici de l'aplicació.",
|
||||
"Link": "Enllaços",
|
||||
"UILanguageHelpText": "Idioma que utilitzarà Radarr per a la interfície d'usuari"
|
||||
"UILanguageHelpText": "Idioma que utilitzarà Prowlarr per a la interfície d'usuari",
|
||||
"Remove": "Elimina",
|
||||
"Replace": "Substitueix",
|
||||
"TheLatestVersionIsAlreadyInstalled": "La darrera versió de {0} ja està instal·lada",
|
||||
"ThemeHelpText": "Canvieu el tema de la interfície d'usuari de l'aplicació, el tema \"Automàtic\" utilitzarà el tema del vostre sistema operatiu per configurar el mode clar o fosc. Inspirat en Theme.Park"
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"UnselectAll": "Odznačit vše",
|
||||
"UpdateCheckStartupNotWritableMessage": "Aktualizaci nelze nainstalovat, protože spouštěcí složku „{0}“ nelze zapisovat uživatelem „{1}“.",
|
||||
"Version": "Verze",
|
||||
"AnalyticsEnabledHelpText": "Odesílejte anonymní informace o použití a chybách na servery Radarru. To zahrnuje informace o vašem prohlížeči, které stránky Radarr WebUI používáte, hlášení chyb a také verzi operačního systému a běhového prostředí. Tyto informace použijeme k upřednostnění funkcí a oprav chyb.",
|
||||
"AnalyticsEnabledHelpText": "Odesílejte anonymní informace o použití a chybách na servery Prowlarru. To zahrnuje informace o vašem prohlížeči, které stránky Prowlarr WebUI používáte, hlášení chyb a také verzi operačního systému a běhového prostředí. Tyto informace použijeme k upřednostnění funkcí a oprav chyb.",
|
||||
"ApiKey": "Klíč API",
|
||||
"AppDataDirectory": "Adresář AppData",
|
||||
"AppDataLocationHealthCheckMessage": "Aktualizace nebude možné zabránit smazání AppData při aktualizaci",
|
||||
@@ -81,7 +81,7 @@
|
||||
"ApplyTagsHelpTexts2": "Přidat: Přidejte značky do existujícího seznamu značek",
|
||||
"ApplyTagsHelpTexts1": "Jak použít značky na vybrané filmy",
|
||||
"Branch": "Větev",
|
||||
"BranchUpdate": "Pobočka, která se má použít k aktualizaci Radarr",
|
||||
"BranchUpdate": "Pobočka, která se má použít k aktualizaci Prowlarr",
|
||||
"EditIndexer": "Upravit indexátor",
|
||||
"ForMoreInformationOnTheIndividualDownloadClients": "Další informace o jednotlivých klientech pro stahování získáte kliknutím na informační tlačítka.",
|
||||
"General": "Všeobecné",
|
||||
@@ -120,7 +120,7 @@
|
||||
"Actions": "Akce",
|
||||
"Added": "Přidané",
|
||||
"AddIndexer": "Přidat indexátor",
|
||||
"LaunchBrowserHelpText": " Otevřete webový prohlížeč a při spuštění aplikace přejděte na domovskou stránku Radarr.",
|
||||
"LaunchBrowserHelpText": " Otevřete webový prohlížeč a při spuštění aplikace přejděte na domovskou stránku Prowlarr.",
|
||||
"Logging": "Protokolování",
|
||||
"Mechanism": "Mechanismus",
|
||||
"NoLinks": "Žádné odkazy",
|
||||
@@ -141,7 +141,7 @@
|
||||
"Refresh": "Obnovit",
|
||||
"RefreshMovie": "Obnovit film",
|
||||
"MovieIndexScrollTop": "Rejstřík filmů: Posun nahoru",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "Pobočka {0} není platná větev vydání Radarr, nebudete dostávat aktualizace",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "Pobočka {0} není platná větev vydání Prowlarr, nebudete dostávat aktualizace",
|
||||
"ReleaseStatus": "Stav vydání",
|
||||
"Proxy": "Proxy",
|
||||
"Reload": "Znovu načíst",
|
||||
@@ -170,7 +170,7 @@
|
||||
"UnableToLoadNotifications": "Nelze načíst oznámení",
|
||||
"UpdateCheckStartupTranslocationMessage": "Aktualizaci nelze nainstalovat, protože spouštěcí složka „{0}“ je ve složce Translocation aplikace.",
|
||||
"UpdateCheckUINotWritableMessage": "Aktualizaci nelze nainstalovat, protože uživatelská složka „{0}“ není zapisovatelná uživatelem „{1}“.",
|
||||
"UpdateMechanismHelpText": "Použijte vestavěný aktualizátor Radarr nebo skript",
|
||||
"UpdateMechanismHelpText": "Použijte vestavěný aktualizátor Prowlarr nebo skript",
|
||||
"UpdateScriptPathHelpText": "Cesta k vlastnímu skriptu, který přebírá extrahovaný balíček aktualizace a zpracovává zbytek procesu aktualizace",
|
||||
"Uptime": "Provozuschopnost",
|
||||
"URLBase": "URL Base",
|
||||
@@ -179,7 +179,7 @@
|
||||
"Username": "Uživatelské jméno",
|
||||
"Yesterday": "Včera",
|
||||
"AutomaticSearch": "Automatické vyhledávání",
|
||||
"BackupFolderHelpText": "Relativní cesty budou v adresáři AppData společnosti Radarr",
|
||||
"BackupFolderHelpText": "Relativní cesty budou v adresáři AppData společnosti Prowlarr",
|
||||
"BackupIntervalHelpText": "Interval mezi automatickými zálohami",
|
||||
"BackupNow": "Zálohovat hned",
|
||||
"BackupRetentionHelpText": "Automatické zálohy starší než doba uchování budou automaticky vyčištěny",
|
||||
@@ -239,7 +239,7 @@
|
||||
"ApplyTagsHelpTexts4": "Nahradit: Nahradit tagy zadanými tagy (pro vymazání všech tagů zadejte žádné tagy)",
|
||||
"AreYouSureYouWantToResetYourAPIKey": "Opravdu chcete resetovat klíč API?",
|
||||
"Authentication": "Ověření",
|
||||
"AuthenticationMethodHelpText": "Vyžadovat uživatelské jméno a heslo pro přístup k Radarr",
|
||||
"AuthenticationMethodHelpText": "Vyžadovat uživatelské jméno a heslo pro přístup k Prowlarr",
|
||||
"Automatic": "Automatický",
|
||||
"Backup": "Záloha",
|
||||
"Cancel": "zrušení",
|
||||
@@ -249,7 +249,7 @@
|
||||
"ClientPriority": "Priorita klienta",
|
||||
"CloneProfile": "Klonovat profil",
|
||||
"Close": "Zavřít",
|
||||
"ConnectionLostAutomaticMessage": "Radarr se pokusí připojit automaticky, nebo můžete kliknout na znovu načíst níže.",
|
||||
"ConnectionLostAutomaticMessage": "Prowlarr se pokusí připojit automaticky, nebo můžete kliknout na znovu načíst níže.",
|
||||
"CouldNotConnectSignalR": "Nelze se připojit k SignalR, uživatelské rozhraní se neaktualizuje",
|
||||
"CustomFilters": "Vlastní filtry",
|
||||
"Date": "datum",
|
||||
@@ -266,7 +266,7 @@
|
||||
"DownloadClients": "Stáhnout klienty",
|
||||
"Edit": "Upravit",
|
||||
"Enable": "Umožnit",
|
||||
"EnableAutomaticSearchHelpText": "Použije se, když se automatické vyhledávání provádí pomocí uživatelského rozhraní nebo Radarr",
|
||||
"EnableAutomaticSearchHelpText": "Použije se, když se automatické vyhledávání provádí pomocí uživatelského rozhraní nebo Prowlarr",
|
||||
"EnableInteractiveSearch": "Povolit interaktivní vyhledávání",
|
||||
"EnableSSL": "Povolit SSL",
|
||||
"EnableSslHelpText": " Vyžaduje restartování spuštěné jako správce, aby se projevilo",
|
||||
@@ -312,14 +312,14 @@
|
||||
"Type": "Typ",
|
||||
"UI": "UI",
|
||||
"UILanguage": "Jazyk uživatelského rozhraní",
|
||||
"UILanguageHelpText": "Jazyk, který Radarr použije pro uživatelské rozhraní",
|
||||
"UILanguageHelpText": "Jazyk, který Prowlarr použije pro uživatelské rozhraní",
|
||||
"UISettings": "Nastavení uživatelského rozhraní",
|
||||
"UnableToLoadUISettings": "Nelze načíst nastavení uživatelského rozhraní",
|
||||
"UnsavedChanges": "Neuložené změny",
|
||||
"UpdateAutomaticallyHelpText": "Automaticky stahovat a instalovat aktualizace. Stále budete moci instalovat ze systému: Aktualizace",
|
||||
"NetCore": ".NET Core",
|
||||
"Filters": "Filtr",
|
||||
"ConnectionLostMessage": "Radarr ztratil spojení s back-endem a pro obnovení funkčnosti bude nutné jej znovu načíst.",
|
||||
"ConnectionLostMessage": "Prowlarr ztratil spojení s back-endem a pro obnovení funkčnosti bude nutné jej znovu načíst.",
|
||||
"HistoryCleanupDaysHelpText": "Nastavením na 0 zakážete automatické čištění",
|
||||
"HistoryCleanupDaysHelpTextWarning": "Soubory v koši starší než vybraný počet dní budou automaticky vyčištěny",
|
||||
"MaintenanceRelease": "Údržbové vydání: opravy chyb a další vylepšení. Další podrobnosti najdete v GitHub Commit History",
|
||||
@@ -338,5 +338,8 @@
|
||||
"LastDuration": "lastDuration",
|
||||
"LastExecution": "Poslední poprava",
|
||||
"NextExecution": "Další spuštění",
|
||||
"Queued": "Ve frontě"
|
||||
"Queued": "Ve frontě",
|
||||
"Remove": "Odstranit",
|
||||
"Replace": "Nahradit",
|
||||
"TheLatestVersionIsAlreadyInstalled": "Nejnovější verze aplikace Prowlarr je již nainstalována"
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
"EditIndexer": "Rediger indekser",
|
||||
"Enable": "Aktiver",
|
||||
"EnableAutomaticSearch": "Aktivér automatisk søgning",
|
||||
"EnableAutomaticSearchHelpText": "Bruges, når der foretages automatiske søgninger via brugergrænsefladen eller af Radarr",
|
||||
"EnableAutomaticSearchHelpText": "Bruges, når der foretages automatiske søgninger via brugergrænsefladen eller af Prowlarr",
|
||||
"Enabled": "Aktiveret",
|
||||
"EnableInteractiveSearch": "Aktivér interaktiv søgning",
|
||||
"EnableRss": "Aktivér RSS",
|
||||
@@ -146,7 +146,7 @@
|
||||
"IndexerPriorityHelpText": "Indekseringsprioritet fra 1 (højest) til 50 (lavest). Standard: 25.",
|
||||
"IndexerProxyStatusCheckAllClientMessage": "Alle indexere er utilgængelige på grund af fejl",
|
||||
"IndexerProxyStatusCheckSingleClientMessage": "Indexere utilgængelige på grund af fejl: {0}",
|
||||
"LaunchBrowserHelpText": " Åbn en webbrowser, og naviger til Radarr-hjemmesiden ved start af appen.",
|
||||
"LaunchBrowserHelpText": " Åbn en webbrowser, og naviger til Prowlarr-hjemmesiden ved start af appen.",
|
||||
"Logging": "Logning",
|
||||
"LogLevel": "Logniveau",
|
||||
"LogLevelTraceHelpTextWarning": "Sporlogning bør kun aktiveres midlertidigt",
|
||||
@@ -188,7 +188,7 @@
|
||||
"Reddit": "Reddit",
|
||||
"Refresh": "Opdater",
|
||||
"RefreshMovie": "Opdater film",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "Filial {0} er ikke en gyldig Radarr-frigivelsesfilial, du modtager ikke opdateringer",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "Filial {0} er ikke en gyldig Prowlarr-frigivelsesfilial, du modtager ikke opdateringer",
|
||||
"Reload": "Genindlæs",
|
||||
"RemovedFromTaskQueue": "Fjernet fra opgavekøen",
|
||||
"RemoveFilter": "Fjern filteret",
|
||||
@@ -233,7 +233,7 @@
|
||||
"Torrent": "Torrenter",
|
||||
"Torrents": "Torrenter",
|
||||
"UI": "UI",
|
||||
"UILanguageHelpText": "Sprog, som Radarr vil bruge til UI",
|
||||
"UILanguageHelpText": "Sprog, som Prowlarr vil bruge til UI",
|
||||
"UnableToAddANewApplicationPleaseTryAgain": "Kan ikke tilføje en ny underretning, prøv igen.",
|
||||
"UnableToAddANewAppProfilePleaseTryAgain": "Kan ikke tilføje en ny kvalitetsprofil, prøv igen.",
|
||||
"UnableToAddANewDownloadClientPleaseTryAgain": "Kunne ikke tilføje en ny downloadklient. Prøv igen.",
|
||||
@@ -286,17 +286,17 @@
|
||||
"ApplyTagsHelpTexts3": "Fjern: Fjern de indtastede tags",
|
||||
"AreYouSureYouWantToResetYourAPIKey": "Er du sikker på, at du vil nulstille din API-nøgle?",
|
||||
"Authentication": "Godkendelse",
|
||||
"AuthenticationMethodHelpText": "Kræv brugernavn og adgangskode for at få adgang til Radarr",
|
||||
"AuthenticationMethodHelpText": "Kræv brugernavn og adgangskode for at få adgang til Prowlarr",
|
||||
"Automatic": "Automatisk",
|
||||
"AutomaticSearch": "Automatisk søgning",
|
||||
"BackupFolderHelpText": "Relative stier vil være under Radarrs AppData-bibliotek",
|
||||
"BackupFolderHelpText": "Relative stier vil være under Prowlarrs AppData-bibliotek",
|
||||
"BackupIntervalHelpText": "Interval mellem automatiske sikkerhedskopier",
|
||||
"BackupRetentionHelpText": "Automatiske sikkerhedskopier, der er ældre end opbevaringsperioden, renses automatisk",
|
||||
"BeforeUpdate": "Før opdatering",
|
||||
"BindAddress": "Bind adresse",
|
||||
"BindAddressHelpText": "Gyldig IP4-adresse, 'localhost' eller '*' for alle grænseflader",
|
||||
"Branch": "Afdeling",
|
||||
"BranchUpdate": "Filial, der skal bruges til at opdatere Radarr",
|
||||
"BranchUpdate": "Filial, der skal bruges til at opdatere Prowlarr",
|
||||
"BranchUpdateMechanism": "Gren brugt af ekstern opdateringsmekanisme",
|
||||
"ClientPriority": "Kundens prioritet",
|
||||
"CloneProfile": "Klonprofil",
|
||||
@@ -347,5 +347,11 @@
|
||||
"AddToDownloadClient": "Føj udgivelse til downloadklient",
|
||||
"AppProfileDeleteConfirm": "Er du sikker på at du vil fjerne {0}?",
|
||||
"AddIndexerProxy": "Tilføj en indeksørproxy",
|
||||
"AddSyncProfile": "Tilføj synkroniseringsprofil"
|
||||
"AddSyncProfile": "Tilføj synkroniseringsprofil",
|
||||
"EditSyncProfile": "Tilføj synkroniseringsprofil",
|
||||
"Notifications": "Notifikationer",
|
||||
"Notification": "Notifikationer",
|
||||
"Remove": "Fjerne",
|
||||
"Replace": "erstat",
|
||||
"TheLatestVersionIsAlreadyInstalled": "Den seneste version af Prowlarr er allerede installeret"
|
||||
}
|
||||
|
||||
@@ -426,7 +426,7 @@
|
||||
"Application": "Anwendungen",
|
||||
"GrabReleases": "Release erfassen",
|
||||
"Link": "Links",
|
||||
"MappedDrivesRunningAsService": "Zugeordnete Netzlaufwerke sind nicht verfügbar, wenn Radarr als Windows-Dienst ausgeführt wird. Bitte lesen Sie die FAQ für weitere Informationen",
|
||||
"MappedDrivesRunningAsService": "Zugeordnete Netzlaufwerke sind nicht verfügbar, wenn Prowlarr als Windows-Dienst ausgeführt wird. Bitte lesen Sie die FAQ für weitere Informationen",
|
||||
"No": "Nein",
|
||||
"SearchTypes": "Suchtyp",
|
||||
"TVSearchTypes": "Suchtyp",
|
||||
@@ -462,5 +462,8 @@
|
||||
"IndexerDetails": "Indexer-Details",
|
||||
"IndexerName": "Indexer-Name",
|
||||
"ApplicationLongTermStatusCheckAllClientMessage": "Alle Anwendungen sind nicht verfügbar, da es zu Störungen für mehr als 6 Stunden kam",
|
||||
"ApplicationLongTermStatusCheckSingleClientMessage": "Anwendungen nicht verfügbar, da es zu Störungen für mehr als 6 Stunden kam: {0}"
|
||||
"ApplicationLongTermStatusCheckSingleClientMessage": "Anwendungen nicht verfügbar, da es zu Störungen für mehr als 6 Stunden kam: {0}",
|
||||
"Remove": "Entfernen",
|
||||
"Replace": "Ersetzen",
|
||||
"TheLatestVersionIsAlreadyInstalled": "Die aktuellste Version ist bereits installiert"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Dates": "Ημερομηνίες",
|
||||
"Date": "Ημερομηνία",
|
||||
"Connections": "Συνδέσεις",
|
||||
"Connect": "Σύνδεση",
|
||||
"Connect": "Ειδοποιήσεις",
|
||||
"Clear": "Καθαρισμός",
|
||||
"BackupNow": "Δημιουργία Αντιγράφου Ασφαλείας",
|
||||
"Backup": "Αντίγραφο Ασφαλείας",
|
||||
@@ -18,7 +18,7 @@
|
||||
"History": "Ιστορία",
|
||||
"HideAdvanced": "Απόκρυψη Προχωρημένων",
|
||||
"Health": "Υγεία",
|
||||
"GeneralSettingsSummary": "Θύρα, SSL, όνομα χρήστη/κωδικός, proxy, analytics και αναβαθμίσεις",
|
||||
"GeneralSettingsSummary": "Θύρα, SSL, όνομα χρήστη/κωδικός πρόσβασης, διακομιστής μεσολάβησης, αναλυτικά στοιχεία και ενημερώσεις",
|
||||
"General": "Γενικά",
|
||||
"Folder": "Φάκελος",
|
||||
"Filter": "Φίλτρο",
|
||||
@@ -30,9 +30,9 @@
|
||||
"Edit": "Επεξεργασία",
|
||||
"DownloadClientStatusCheckSingleClientMessage": "Προγράμματα λήψης που είναι μη διαθέσιμα λόγων αποτυχιών: {0}",
|
||||
"DownloadClientStatusCheckAllClientMessage": "Όλα τα προγράμματα λήψης είναι μη διαθέσιμα λόγων αποτυχιών",
|
||||
"DownloadClientsSettingsSummary": "Προγράμματα λήψης, διαχείριση λήψεων και αντιστοίχηση remote path",
|
||||
"DownloadClientsSettingsSummary": "Κάντε λήψη της διαμόρφωσης πελατών για ενσωμάτωση στην αναζήτηση διεπαφής χρήστη Prowlarr",
|
||||
"CustomFilters": "Custom Φιλτρα",
|
||||
"ConnectSettingsSummary": "Ειδοποιήσεις, συνδέσεις σε media servers/players και custom scripts",
|
||||
"ConnectSettingsSummary": "Ειδοποιήσεις και προσαρμοσμένα σενάρια",
|
||||
"AppDataLocationHealthCheckMessage": "Η αναβάθμιση δεν είναι πιθανό να αποτρέψει την διαγραφή των AppData κατά την αναβάθμιση",
|
||||
"ConnectionLostAutomaticMessage": "Το Prowlarr θα προσπαθήσει να συνδεθεί αυτόματα, αλλιώς μπορείτε να κάνετε reload απο κάτω.",
|
||||
"Component": "Στοιχείο",
|
||||
@@ -103,7 +103,7 @@
|
||||
"Tags": "Ετικέτες",
|
||||
"UI": "Διεπαφή χρήστη",
|
||||
"UILanguage": "Γλώσσα διεπαφής χρήστη",
|
||||
"UILanguageHelpText": "Γλώσσα που θα χρησιμοποιήσει ο Radarr για τη διεπαφή χρήστη",
|
||||
"UILanguageHelpText": "Γλώσσα που θα χρησιμοποιήσει ο Prowlarr για τη διεπαφή χρήστη",
|
||||
"UISettings": "Ρυθμίσεις διεπαφής χρήστη",
|
||||
"Yesterday": "Εχθές",
|
||||
"FeatureRequests": "Αιτήματα χαρακτηριστικών",
|
||||
@@ -122,7 +122,7 @@
|
||||
"UnableToLoadHistory": "Δεν είναι δυνατή η φόρτωση του ιστορικού",
|
||||
"UpdateCheckUINotWritableMessage": "Δεν είναι δυνατή η εγκατάσταση της ενημέρωσης επειδή ο φάκελος διεπαφής χρήστη \"{0}\" δεν είναι εγγράψιμος από τον χρήστη \"{1}\".",
|
||||
"ApplyTagsHelpTexts4": "Αντικατάσταση: Αντικαταστήστε τις ετικέτες με τις εισαγόμενες ετικέτες (μην εισάγετε ετικέτες για να διαγράψετε όλες τις ετικέτες)",
|
||||
"AuthenticationMethodHelpText": "Απαιτήστε όνομα χρήστη και κωδικό πρόσβασης για πρόσβαση στο Radarr",
|
||||
"AuthenticationMethodHelpText": "Απαιτήστε όνομα χρήστη και κωδικό πρόσβασης για πρόσβαση στο Prowlarr",
|
||||
"Automatic": "Αυτόματο",
|
||||
"BeforeUpdate": "Πριν από την ενημέρωση",
|
||||
"BindAddressHelpText": "Έγκυρη διεύθυνση IP, localhost ή '*' για όλες τις διεπαφές",
|
||||
@@ -137,7 +137,7 @@
|
||||
"DeleteNotificationMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε την ειδοποίηση \"{0}\";",
|
||||
"DeleteTagMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε την ετικέτα \"{0}\";",
|
||||
"EnableAutomaticSearch": "Ενεργοποίηση αυτόματης αναζήτησης",
|
||||
"EnableAutomaticSearchHelpText": "Θα χρησιμοποιηθεί όταν πραγματοποιούνται αυτόματες αναζητήσεις μέσω του περιβάλλοντος χρήστη ή του Radarr",
|
||||
"EnableAutomaticSearchHelpText": "Θα χρησιμοποιηθεί όταν πραγματοποιούνται αυτόματες αναζητήσεις μέσω του περιβάλλοντος χρήστη ή του Prowlarr",
|
||||
"EnableSslHelpText": " Απαιτείται επανεκκίνηση ως διαχειριστής για να τεθεί σε ισχύ",
|
||||
"Error": "Λάθος",
|
||||
"ErrorLoadingContents": "Σφάλμα κατά τη φόρτωση περιεχομένων",
|
||||
@@ -151,7 +151,7 @@
|
||||
"IndexerPriorityHelpText": "Προτεραιότητα ευρετηρίου από 1 (Υψηλότερη) έως 50 (Χαμηλότερη). Προεπιλογή: 25.",
|
||||
"IndexerProxyStatusCheckAllClientMessage": "Όλες οι λίστες δεν είναι διαθέσιμες λόγω αστοχιών",
|
||||
"IndexerProxyStatusCheckSingleClientMessage": "Τα ευρετήρια δεν είναι διαθέσιμα λόγω αστοχιών: {0}",
|
||||
"LaunchBrowserHelpText": " Ανοίξτε ένα πρόγραμμα περιήγησης ιστού και μεταβείτε στην αρχική σελίδα του Radarr κατά την έναρξη της εφαρμογής.",
|
||||
"LaunchBrowserHelpText": " Ανοίξτε ένα πρόγραμμα περιήγησης ιστού και μεταβείτε στην αρχική σελίδα του Prowlarr κατά την έναρξη της εφαρμογής.",
|
||||
"LogFiles": "Αρχεία καταγραφής",
|
||||
"Logging": "Ξύλευση",
|
||||
"LogLevelTraceHelpTextWarning": "Η καταγραφή ιχνών πρέπει να ενεργοποιηθεί προσωρινά",
|
||||
@@ -181,7 +181,7 @@
|
||||
"ReadTheWikiForMoreInformation": "Διαβάστε το Wiki για περισσότερες πληροφορίες",
|
||||
"Refresh": "Φρεσκάρω",
|
||||
"RefreshMovie": "Ανανέωση ταινίας",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "Το υποκατάστημα {0} δεν είναι έγκυρο υποκατάστημα κυκλοφορίας Radarr, δεν θα λαμβάνετε ενημερώσεις",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "Το υποκατάστημα {0} δεν είναι έγκυρο υποκατάστημα κυκλοφορίας Prowlarr, δεν θα λαμβάνετε ενημερώσεις",
|
||||
"Reload": "Φορτώνω πάλι",
|
||||
"RemovedFromTaskQueue": "Καταργήθηκε από την ουρά εργασιών",
|
||||
"RemoveFilter": "Αφαιρέστε το φίλτρο",
|
||||
@@ -246,8 +246,8 @@
|
||||
"Tasks": "Καθήκοντα",
|
||||
"UnableToLoadBackups": "Δεν είναι δυνατή η φόρτωση αντιγράφων ασφαλείας",
|
||||
"UnableToLoadDownloadClients": "Δεν είναι δυνατή η φόρτωση πελατών λήψης",
|
||||
"UpdateMechanismHelpText": "Χρησιμοποιήστε το ενσωματωμένο πρόγραμμα ενημέρωσης του Radarr ή ένα σενάριο",
|
||||
"AnalyticsEnabledHelpText": "Στείλτε ανώνυμες πληροφορίες χρήσης και σφάλματος στους διακομιστές του Radarr. Αυτό περιλαμβάνει πληροφορίες στο πρόγραμμα περιήγησής σας, ποιες σελίδες Radarr WebUI χρησιμοποιείτε, αναφορά σφαλμάτων καθώς και έκδοση λειτουργικού συστήματος και χρόνου εκτέλεσης. Θα χρησιμοποιήσουμε αυτές τις πληροφορίες για να δώσουμε προτεραιότητα σε λειτουργίες και διορθώσεις σφαλμάτων.",
|
||||
"UpdateMechanismHelpText": "Χρησιμοποιήστε το ενσωματωμένο πρόγραμμα ενημέρωσης του Prowlarr ή ένα σενάριο",
|
||||
"AnalyticsEnabledHelpText": "Στείλτε ανώνυμες πληροφορίες χρήσης και σφάλματος στους διακομιστές του Prowlarr. Αυτό περιλαμβάνει πληροφορίες στο πρόγραμμα περιήγησής σας, ποιες σελίδες Prowlarr WebUI χρησιμοποιείτε, αναφορά σφαλμάτων καθώς και έκδοση λειτουργικού συστήματος και χρόνου εκτέλεσης. Θα χρησιμοποιήσουμε αυτές τις πληροφορίες για να δώσουμε προτεραιότητα σε λειτουργίες και διορθώσεις σφαλμάτων.",
|
||||
"AppDataDirectory": "Κατάλογος AppData",
|
||||
"BindAddress": "Δεσμευμένη διεύθυνση",
|
||||
"EnableRss": "Ενεργοποίηση RSS",
|
||||
@@ -270,12 +270,12 @@
|
||||
"ApplicationStatusCheckSingleClientMessage": "Μη διαθέσιμες λίστες λόγω αποτυχιών: {0}",
|
||||
"ApplyTags": "Εφαρμογή ετικετών",
|
||||
"ApplyTagsHelpTexts1": "Πώς να εφαρμόσετε ετικέτες στις επιλεγμένες ταινίες",
|
||||
"BackupFolderHelpText": "Οι σχετικές διαδρομές θα βρίσκονται στον κατάλογο AppData του Radarr",
|
||||
"BackupFolderHelpText": "Οι σχετικές διαδρομές θα βρίσκονται στον κατάλογο AppData του Prowlarr",
|
||||
"AutomaticSearch": "Αυτόματη αναζήτηση",
|
||||
"BackupIntervalHelpText": "Διάστημα μεταξύ των αυτόματων αντιγράφων ασφαλείας",
|
||||
"BackupRetentionHelpText": "Τα αυτόματα αντίγραφα ασφαλείας που είναι παλαιότερα από την περίοδο διατήρησης θα καθαρίζονται αυτόματα",
|
||||
"Backups": "Δημιουργία αντιγράφων ασφαλείας",
|
||||
"BranchUpdate": "Υποκατάστημα για χρήση για την ενημέρωση του Radarr",
|
||||
"BranchUpdate": "Υποκατάστημα για χρήση για την ενημέρωση του Prowlarr",
|
||||
"CancelPendingTask": "Είστε βέβαιοι ότι θέλετε να ακυρώσετε αυτήν την εργασία σε εκκρεμότητα;",
|
||||
"CertificateValidation": "Επικύρωση πιστοποιητικού",
|
||||
"CertificateValidationHelpText": "Αλλάξτε πόσο αυστηρή είναι η επικύρωση πιστοποίησης HTTPS",
|
||||
@@ -327,7 +327,7 @@
|
||||
"OnHealthIssue": "Σχετικά με το θέμα της υγείας",
|
||||
"TestAllIndexers": "Δοκιμάστε όλους τους δείκτες",
|
||||
"MaintenanceRelease": "Έκδοση συντήρησης: επιδιορθώσεις σφαλμάτων και άλλες βελτιώσεις. Δείτε το Github Commit History για περισσότερες λεπτομέρειες",
|
||||
"ConnectionLostMessage": "Το Radarr έχασε τη σύνδεσή του με το backend και θα χρειαστεί να επαναφορτωθεί για να αποκαταστήσει τη λειτουργικότητά του.",
|
||||
"ConnectionLostMessage": "Το Prowlarr έχει χάσει τη σύνδεσή του με το backend και θα χρειαστεί να φορτωθεί ξανά για να επαναφέρετε τη λειτουργικότητα.",
|
||||
"NetCore": ".NET",
|
||||
"GrabReleases": "Πιάσε την απελευθέρωση",
|
||||
"Link": "Συνδέσεις",
|
||||
@@ -351,5 +351,126 @@
|
||||
"NotificationTriggersHelpText": "Επιλέξτε ποια συμβάντα θα ενεργοποιήσουν αυτήν την ειδοποίηση",
|
||||
"Started": "Ξεκίνησε",
|
||||
"MassEditor": "Μαζικός Συντάκτης",
|
||||
"UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent που παρέχεται από την εφαρμογή που κάλεσε το API"
|
||||
"UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent που παρέχεται από την εφαρμογή που κάλεσε το API",
|
||||
"SettingsFilterSentryEventsHelpText": "Φιλτράρετε τα γνωστά συμβάντα σφαλμάτων χρήστη από την αποστολή τους ως Analytics",
|
||||
"ThemeHelpText": "Αλλαγή του θέματος διεπαφής χρήστη εφαρμογής, το θέμα «Αυτόματο» θα χρησιμοποιήσει το Θέμα του λειτουργικού σας συστήματος για να ρυθμίσει τη λειτουργία Light ή Dark. Εμπνευσμένο από το Theme.Park",
|
||||
"SettingsFilterSentryEvents": "Φιλτράρισμα συμβάντων Analytics",
|
||||
"SettingsSqlLoggingHelpText": "Καταγράψτε όλα τα ερωτήματα SQL από το Prowlarr",
|
||||
"AppProfileDeleteConfirm": "Είστε βέβαιοι ότι θέλετε να διαγράψετε το {0};",
|
||||
"SettingsConsoleLogLevel": "Επίπεδο καταγραφής κονσόλας",
|
||||
"SettingsLogRotate": "Περιστροφή καταγραφής",
|
||||
"SettingsLogRotateHelpText": "Μέγιστος αριθμός αρχείων καταγραφής που θα διατηρηθούν αποθηκευμένα στο φάκελο καταγραφής",
|
||||
"MinimumSeeders": "Ελάχιστοι σπαρτήρες",
|
||||
"MusicSearchTypes": "Τύποι αναζήτησης μουσικής",
|
||||
"SyncLevel": "Επίπεδο συγχρονισμού",
|
||||
"UnableToLoadDevelopmentSettings": "Δεν είναι δυνατή η φόρτωση των ρυθμίσεων ανάπτυξης",
|
||||
"AreYouSureYouWantToDeleteCategory": "Είστε βέβαιοι ότι θέλετε να διαγράψετε την αντιστοιχισμένη κατηγορία;",
|
||||
"AudioSearch": "Αναζήτηση ήχου",
|
||||
"Auth": "Auth",
|
||||
"BookSearch": "Αναζήτηση βιβλίου",
|
||||
"FullSync": "Πλήρης συγχρονισμός",
|
||||
"IndexerVipCheckExpiringClientMessage": "Τα οφέλη VIP του ευρετηρίου λήγουν σύντομα: {0}",
|
||||
"NotSupported": "Δεν υποστηρίζεται",
|
||||
"Parameters": "Παράμετροι",
|
||||
"Public": "Δημόσιο",
|
||||
"QueryOptions": "Επιλογές ερωτήματος",
|
||||
"SearchIndexers": "Αναζήτηση ευρετηρίων",
|
||||
"SearchType": "Τύπος αναζήτησης",
|
||||
"UnableToLoadApplicationList": "Δεν είναι δυνατή η φόρτωση της λίστας εφαρμογών",
|
||||
"AddRemoveOnly": "Μόνο προσθήκη και αφαίρεση",
|
||||
"ProwlarrSupportsAnyDownloadClient": "Το Prowlarr υποστηρίζει οποιοδήποτε από τα προγράμματα-πελάτες λήψης που αναφέρονται παρακάτω.",
|
||||
"Query": "Ερώτηση",
|
||||
"Redirect": "Διευθύνω πάλιν",
|
||||
"SyncLevelAddRemove": "Μόνο προσθήκη και αφαίρεση: Όταν προστίθενται ή αφαιρούνται ευρετήρια από το Prowlarr, θα ενημερώσει αυτήν την απομακρυσμένη εφαρμογή.",
|
||||
"UISettingsSummary": "Επιλογές με προβλήματα ημερομηνίας, γλώσσας και χρώματος",
|
||||
"AddedToDownloadClient": "Η έκδοση προστέθηκε στον πελάτη",
|
||||
"AddToDownloadClient": "Προσθήκη έκδοσης στον πελάτη λήψης",
|
||||
"IndexerRss": "Ευρετήριο Rss",
|
||||
"SettingsIndexerLogging": "Βελτιωμένη καταγραφή ευρετηρίου",
|
||||
"DeleteIndexerProxy": "Διαγραφή του Indexer Proxy",
|
||||
"Applications": "Εφαρμογές",
|
||||
"DeleteAppProfile": "Διαγραφή προφίλ εφαρμογής",
|
||||
"DeleteApplication": "Διαγραφή Εφαρμογής",
|
||||
"IndexerProxy": "Ευρετήριο μεσολάβησης",
|
||||
"IndexerObsoleteCheckMessage": "Τα ευρετήρια είναι παρωχημένα ή έχουν ενημερωθεί: {0}. Αφαιρέστε και (ή) προσθέστε ξανά στο Prowlarr",
|
||||
"Private": "Ιδιωτικός",
|
||||
"SyncProfiles": "Συγχρονισμός προφίλ",
|
||||
"AuthenticationRequired": "Απαιτείται πιστοποίηση",
|
||||
"BookSearchTypes": "Τύποι αναζήτησης βιβλίων",
|
||||
"ClearHistory": "Καθαρισμός ιστορικού",
|
||||
"ClearHistoryMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε όλο το ιστορικό Prowlarr;",
|
||||
"DeleteClientCategory": "Διαγραφή Κατηγορίας Πελάτη Λήψης",
|
||||
"DownloadClientCategory": "Κατεβάστε την κατηγορία πελάτη",
|
||||
"EditSyncProfile": "Επεξεργασία προφίλ συγχρονισμού",
|
||||
"ElapsedTime": "Χρόνος που παρήλθε",
|
||||
"GrabTitle": "Αρπάξτε τον τίτλο",
|
||||
"IndexerQuery": "Ερώτημα ευρετηρίου",
|
||||
"IndexerSettingsSummary": "Διαμορφώστε διάφορες καθολικές ρυθμίσεις ευρετηρίου, συμπεριλαμβανομένων των διακομιστών μεσολάβησης.",
|
||||
"IndexerSite": "Ιστότοπος ευρετηρίου",
|
||||
"IndexerVipCheckExpiredClientMessage": "Τα προνόμια VIP του ευρετηρίου έχουν λήξει: {0}",
|
||||
"MappedCategories": "Χαρτογραφημένες κατηγορίες",
|
||||
"MovieSearch": "Αναζήτηση ταινίας",
|
||||
"MovieSearchTypes": "Τύποι αναζήτησης ταινιών",
|
||||
"NoSearchResultsFound": "Δεν βρέθηκαν αποτελέσματα αναζήτησης, δοκιμάστε να εκτελέσετε μια νέα αναζήτηση παρακάτω.",
|
||||
"Notifications": "Ειδοποιήσεις",
|
||||
"Notification": "Ειδοποιήσ",
|
||||
"Privacy": "Μυστικότητα",
|
||||
"Proxies": "Proxies",
|
||||
"QueryResults": "Αποτελέσματα ερωτήματος",
|
||||
"RawSearchSupported": "Υποστηρίζεται η ακατέργαστη αναζήτηση",
|
||||
"RedirectHelpText": "Ανακατευθύνετε το εισερχόμενο αίτημα λήψης για το ευρετήριο και περάστε το άρπαγμα απευθείας αντί να μεταφέρετε το αίτημα μέσω Prowlarr",
|
||||
"RestartProwlarr": "Επανεκκινήστε το Prowlarr",
|
||||
"SearchCapabilities": "Δυνατότητες αναζήτησης",
|
||||
"TestAllApps": "Δοκιμάστε όλες τις εφαρμογές",
|
||||
"UnableToLoadIndexerProxies": "Δεν είναι δυνατή η φόρτωση των Proxer Indexer",
|
||||
"AddDownloadClientToProwlarr": "Η προσθήκη ενός προγράμματος-πελάτη λήψης επιτρέπει στο Prowlarr να στέλνει εκδόσεις απευθείας από τη διεπαφή χρήστη ενώ κάνει μια μη αυτόματη αναζήτηση.",
|
||||
"IndexersSelectedInterp": "{0}Επιλέχτηκε ευρετήριο",
|
||||
"Application": "Εφαρμογή",
|
||||
"IndexerAuth": "Indexer Auth",
|
||||
"AddIndexerProxy": "Προσθήκη Indexer Proxy",
|
||||
"Apps": "Εφαρμογές",
|
||||
"Description": "Περιγραφή",
|
||||
"AppProfileInUse": "Προφίλ εφαρμογής σε χρήση",
|
||||
"AppSettingsSummary": "Εφαρμογές και ρυθμίσεις για τη διαμόρφωση του τρόπου με τον οποίο το Prowlarr αλληλεπιδρά με τα προγράμματα PVR",
|
||||
"Category": "Κατηγορία",
|
||||
"DevelopmentSettings": "Ρυθμίσεις ανάπτυξης",
|
||||
"EnabledRedirected": "Ενεργοποιήθηκε, ανακατευθύνθηκε",
|
||||
"EnableIndexer": "Ενεργοποίηση Indexer",
|
||||
"EnableRssHelpText": "Ενεργοποιήστε τη ροή Rss για το Indexer",
|
||||
"Encoding": "Κωδικοποίηση",
|
||||
"FilterPlaceHolder": "Αναζήτηση ευρετηριαστών",
|
||||
"HistoryCleanup": "Εκκαθάριση Ιστορίας",
|
||||
"Id": "id",
|
||||
"IndexerDetails": "Λεπτομέρειες ευρετηρίου",
|
||||
"IndexerInfo": "Πληροφορίες ευρετηρίου",
|
||||
"IndexerName": "Όνομα ευρετηρίου",
|
||||
"IndexerProxies": "Proxer Indexer",
|
||||
"IndexerNoDefCheckMessage": "Τα ευρετήρια δεν έχουν ορισμό και δεν θα λειτουργήσουν: {0}. Αφαιρέστε και (ή) προσθέστε ξανά στο Prowlarr",
|
||||
"SemiPrivate": "Ημι-ιδιωτικό",
|
||||
"SettingsIndexerLoggingHelpText": "Καταγραφή πρόσθετων δεδομένων ευρετηρίου συμπεριλαμβανομένης της απόκρισης",
|
||||
"SearchTypes": "Τύποι αναζήτησης",
|
||||
"Stats": "Στατιστικά στοιχεία",
|
||||
"SettingsLogSql": "Καταγραφή Sql",
|
||||
"SyncProfile": "Συγχρονισμός προφίλ",
|
||||
"TvSearch": "Αναζήτηση τηλεόρασης",
|
||||
"UnableToLoadAppProfiles": "Δεν είναι δυνατή η φόρτωση των προφίλ εφαρμογών",
|
||||
"Url": "Url",
|
||||
"Website": "Δικτυακός τόπος",
|
||||
"IndexerAlreadySetup": "Τουλάχιστον μία παρουσία ευρετηρίου έχει ήδη ρυθμιστεί",
|
||||
"AddNewIndexer": "Προσθήκη νέου ευρετηρίου",
|
||||
"AddSyncProfile": "Προσθήκη προφίλ συγχρονισμού",
|
||||
"Categories": "Κατηγορίες",
|
||||
"TVSearchTypes": "Τύποι αναζήτησης τηλεόρασης",
|
||||
"AppProfileSelectHelpText": "Τα προφίλ εφαρμογών χρησιμοποιούνται για τον έλεγχο των ρυθμίσεων RSS, αυτόματης αναζήτησης και διαδραστικής αναζήτησης στο συγχρονισμό εφαρμογών",
|
||||
"AuthenticationRequiredHelpText": "Αλλαγή για τα οποία απαιτείται έλεγχος ταυτότητας. Μην αλλάζετε αν δεν κατανοήσετε τους κινδύνους.",
|
||||
"AuthenticationRequiredWarning": "Για να αποτρέψει την απομακρυσμένη πρόσβαση χωρίς έλεγχο ταυτότητας, το Prowlarr απαιτεί τώρα να ενεργοποιηθεί ο έλεγχος ταυτότητας. Διαμορφώστε τη μέθοδο ελέγχου ταυτότητας και τα διαπιστευτήριά σας. Μπορείτε προαιρετικά να απενεργοποιήσετε τον έλεγχο ταυτότητας από τοπικές διευθύνσεις. Ανατρέξτε στις Συχνές Ερωτήσεις για πρόσθετες πληροφορίες.",
|
||||
"IndexerHealthCheckNoIndexers": "Δεν υπάρχουν ενεργοποιημένα ευρετήρια, το Prowlarr δεν θα επιστρέψει αποτελέσματα αναζήτησης",
|
||||
"IndexerTagsHelpText": "Χρησιμοποιήστε ετικέτες για να καθορίσετε Διακομιστές μεσολάβησης ευρετηρίου, με ποιες εφαρμογές συγχρονίζεται το ευρετήριο ή απλώς για να οργανώσετε τα ευρετήρια σας.",
|
||||
"MinimumSeedersHelpText": "Ελάχιστοι σπόροι που απαιτούνται από την Εφαρμογή για να αρπάξει ο δείκτης",
|
||||
"ProwlarrSupportsAnyIndexer": "Το Prowlarr υποστηρίζει πολλούς ευρετήρια εκτός από οποιονδήποτε δείκτη που χρησιμοποιεί το πρότυπο Newznab/Torznab χρησιμοποιώντας το 'Generic Newznab' (για usenet) ή το 'Generic Torznab' (για torrents). Αναζήτηση & Επιλέξτε τον ευρετηριαστή σας από παρακάτω.",
|
||||
"SyncAppIndexers": "Συγχρονισμός ευρετηρίων εφαρμογών",
|
||||
"SyncLevelFull": "Πλήρης συγχρονισμός: Θα διατηρήσει πλήρως συγχρονισμένα τα ευρετήρια αυτής της εφαρμογής. Στη συνέχεια, οι αλλαγές που γίνονται στους indexers στο Prowlarr συγχρονίζονται με αυτήν την εφαρμογή. Οποιαδήποτε αλλαγή γίνει σε ευρετήρια απομακρυσμένα σε αυτήν την εφαρμογή θα παρακαμφθεί από τον Prowlarr στον επόμενο συγχρονισμό.",
|
||||
"Remove": "Αφαιρώ",
|
||||
"Replace": "Αντικαθιστώ",
|
||||
"TheLatestVersionIsAlreadyInstalled": "Η τελευταία έκδοση του Prowlarr είναι ήδη εγκατεστημένη"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"AcceptConfirmationModal": "Accept Confirmation Modal",
|
||||
"Actions": "Actions",
|
||||
"Add": "Add",
|
||||
"AddApplication": "Add Application",
|
||||
"AddCustomFilter": "Add Custom Filter",
|
||||
"AddDownloadClient": "Add Download Client",
|
||||
"AddDownloadClientToProwlarr": "Adding a download client allows Prowlarr to send releases direct from the UI while doing a manual search.",
|
||||
"Added": "Added",
|
||||
@@ -322,9 +324,11 @@
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "Branch {0} is not a valid Prowlarr release branch, you will not receive updates",
|
||||
"ReleaseStatus": "Release Status",
|
||||
"Reload": "Reload",
|
||||
"Remove": "Remove",
|
||||
"RemovedFromTaskQueue": "Removed from task queue",
|
||||
"RemoveFilter": "Remove filter",
|
||||
"RemovingTag": "Removing tag",
|
||||
"Replace": "Replace",
|
||||
"Reset": "Reset",
|
||||
"ResetAPIKey": "Reset API Key",
|
||||
"Restart": "Restart",
|
||||
@@ -411,6 +415,7 @@
|
||||
"TestAllApps": "Test All Apps",
|
||||
"TestAllClients": "Test All Clients",
|
||||
"TestAllIndexers": "Test All Indexers",
|
||||
"TheLatestVersionIsAlreadyInstalled": "The latest version of {0} is already installed",
|
||||
"ThemeHelpText": "Change Application UI Theme, 'Auto' Theme will use your OS Theme to set Light or Dark mode. Inspired by {0}",
|
||||
"Time": "Time",
|
||||
"Title": "Title",
|
||||
|
||||
@@ -373,5 +373,8 @@
|
||||
"ApplicationLongTermStatusCheckSingleClientMessage": "Indexers no disponible por errores durando más de 6 horas: {0}",
|
||||
"Ended": "Terminó",
|
||||
"NextExecution": "Siguiente ejecución",
|
||||
"Started": "Iniciado"
|
||||
"Started": "Iniciado",
|
||||
"Remove": "Eliminar",
|
||||
"Replace": "Reemplazar",
|
||||
"TheLatestVersionIsAlreadyInstalled": "La última versión de Prowlarr ya está instalada"
|
||||
}
|
||||
|
||||
1
src/NzbDrone.Core/Localization/Core/es_MX.json
Normal file
1
src/NzbDrone.Core/Localization/Core/es_MX.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
1
src/NzbDrone.Core/Localization/Core/et.json
Normal file
1
src/NzbDrone.Core/Localization/Core/et.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user