Compare commits

..

44 Commits

Author SHA1 Message Date
Qstick
0984976378 Bump DryIoc, YamlDotNet, AngleSharp 2023-02-18 21:12:08 -06:00
Qstick
fcb3c96455 Call async methods when in an async method 2023-02-18 15:03:35 -06:00
Qstick
acf7a425b5 Add global analyzer config 2023-02-18 15:03:35 -06:00
Qstick
da898fe958 Remove Non-Failing Rules 2023-02-18 15:03:35 -06:00
Qstick
5bb3ea0806 Remove unnecessary assignments to default type value
The .NET runtime initializes all fields of reference types to their default values before running the constructor. In most cases, explicitly initializing a field to its default value in a constructor is redundant, adding maintenance costs and potentially degrading performance
2023-02-18 15:03:35 -06:00
Qstick
b41cb80e33 Use const where appropriate
The value of a const field is computed at compile time and stored in the metadata, which improves run-time performance when it is compared to a static readonly field.
2023-02-18 15:03:35 -06:00
Qstick
a39341be4b Enable all analyzers to default back to our rules 2023-02-18 15:03:35 -06:00
Weblate
27b3d8618a Translated using Weblate (Ukrainian)
Currently translated at 73.3% (349 of 476 strings)

Translated using Weblate (Slovak)

Currently translated at 22.6% (108 of 476 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 23.5% (112 of 476 strings)

Translated using Weblate (Catalan)

Currently translated at 74.5% (355 of 476 strings)

Translated using Weblate (Chinese (Simplified) (zh_CN))

Currently translated at 99.5% (474 of 476 strings)

Translated using Weblate (Arabic)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (476 of 476 strings)

Translated using Weblate (Vietnamese)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Turkish)

Currently translated at 71.4% (340 of 476 strings)

Translated using Weblate (Thai)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Swedish)

Currently translated at 86.3% (411 of 476 strings)

Translated using Weblate (Russian)

Currently translated at 76.4% (364 of 476 strings)

Translated using Weblate (Romanian)

Currently translated at 72.0% (343 of 476 strings)

Translated using Weblate (Portuguese)

Currently translated at 79.2% (377 of 476 strings)

Translated using Weblate (Polish)

Currently translated at 74.1% (353 of 476 strings)

Translated using Weblate (Dutch)

Currently translated at 88.4% (421 of 476 strings)

Translated using Weblate (Korean)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Japanese)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Italian)

Currently translated at 99.3% (473 of 476 strings)

Translated using Weblate (Icelandic)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.5% (474 of 476 strings)

Translated using Weblate (Hindi)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Hebrew)

Currently translated at 81.3% (387 of 476 strings)

Translated using Weblate (French)

Currently translated at 98.5% (469 of 476 strings)

Translated using Weblate (Finnish)

Currently translated at 99.1% (472 of 476 strings)

Translated using Weblate (Spanish)

Currently translated at 77.9% (371 of 476 strings)

Translated using Weblate (Greek)

Currently translated at 99.5% (474 of 476 strings)

Translated using Weblate (German)

Currently translated at 98.1% (467 of 476 strings)

Translated using Weblate (Danish)

Currently translated at 74.5% (355 of 476 strings)

Translated using Weblate (Czech)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Spanish)

Currently translated at 77.9% (371 of 476 strings)

Translated using Weblate (Arabic)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Bulgarian)

Currently translated at 67.0% (319 of 476 strings)

Translated using Weblate (Spanish)

Currently translated at 77.9% (371 of 476 strings)

Translated using Weblate (Croatian)

Currently translated at 18.2% (87 of 476 strings)

Translated using Weblate (Croatian)

Currently translated at 18.2% (87 of 476 strings)

Translated using Weblate (Ukrainian)

Currently translated at 73.3% (349 of 476 strings)

Translated using Weblate (Slovak)

Currently translated at 22.6% (108 of 476 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 23.5% (112 of 476 strings)

Translated using Weblate (Catalan)

Currently translated at 74.5% (355 of 476 strings)

Translated using Weblate (Catalan)

Currently translated at 74.5% (355 of 476 strings)

Translated using Weblate (Chinese (Simplified) (zh_CN))

Currently translated at 99.5% (474 of 476 strings)

Translated using Weblate (Arabic)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (476 of 476 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (476 of 476 strings)

Translated using Weblate (Vietnamese)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Turkish)

Currently translated at 71.4% (340 of 476 strings)

Translated using Weblate (Thai)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Swedish)

Currently translated at 86.3% (411 of 476 strings)

Translated using Weblate (Russian)

Currently translated at 76.4% (364 of 476 strings)

Translated using Weblate (Romanian)

Currently translated at 72.0% (343 of 476 strings)

Translated using Weblate (Portuguese)

Currently translated at 79.2% (377 of 476 strings)

Translated using Weblate (Polish)

Currently translated at 74.1% (353 of 476 strings)

Translated using Weblate (Dutch)

Currently translated at 88.4% (421 of 476 strings)

Translated using Weblate (Korean)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Japanese)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Italian)

Currently translated at 99.3% (473 of 476 strings)

Translated using Weblate (Icelandic)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.5% (474 of 476 strings)

Translated using Weblate (Hindi)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Hebrew)

Currently translated at 81.3% (387 of 476 strings)

Translated using Weblate (French)

Currently translated at 98.5% (469 of 476 strings)

Translated using Weblate (Finnish)

Currently translated at 99.1% (472 of 476 strings)

Translated using Weblate (Spanish)

Currently translated at 77.7% (370 of 476 strings)

Translated using Weblate (Greek)

Currently translated at 99.5% (474 of 476 strings)

Translated using Weblate (German)

Currently translated at 98.1% (467 of 476 strings)

Translated using Weblate (Danish)

Currently translated at 74.5% (355 of 476 strings)

Translated using Weblate (Czech)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Bulgarian)

Currently translated at 67.0% (319 of 476 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Qstick <qstick@gmail.com>
Co-authored-by: TheHrle <Hpranjkovic@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: dtalens <databio@gmail.com>
Co-authored-by: fiego14 <alvaross_96@hotmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-02-18 14:39:38 -06:00
Bogdan
550b9b58df Fixed: (TorrentIndexerBase) Validate downloaded torrent data 2023-02-18 22:13:33 +02:00
Bogdan
035ad33b72 Fixed: (Nebulance) Prevent redirect to login page when downloading torrent files 2023-02-18 21:20:17 +02:00
Qstick
85f8e0c451 Update MagnetLinkBuilder public trackers
8ba874e69d
2023-02-18 11:49:34 -06:00
bakerboy448
ea2061a7d3 fixup!
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2023-02-18 19:24:47 +02:00
Bakerboy448
ea6d01a49b Fixed: (RarBG) Handle HTTP 200 Rate Limiting False Positive
Fixes: #1277
Finishes: #1169
Related: #1380

partially reverts 5cc044aa8f
2023-02-18 19:24:47 +02:00
Bogdan
252cd97e35 Fixed: (SpeedAppBase) Add pagination 2023-02-18 19:08:02 +02:00
Bogdan
a8ea05af07 Fixed: (Nebulance) Add SupportsRedirect since their API is stateless 2023-02-18 19:01:37 +02:00
Bogdan
24d6a0cb06 Fixed: (UI) Remedy for external link regression 2023-02-18 19:01:23 +02:00
Bakerboy448
8e1771b5a9 Fixed: Improved Indexer HTTP Validation Failure Messaging 2023-02-17 11:23:59 +02:00
Bogdan
d767a82e84 Fixed: (RuTracker) Add "Use Magnet Links" and "Add RUS to title" options 2023-02-17 07:12:57 +02:00
Bogdan
76bfd29f23 New: Add UniOtaku 2023-02-17 07:12:21 +02:00
Bogdan
c923982711 New: (AudioBookBay) Migrate to C# 2023-02-17 07:11:28 +02:00
Bogdan
f03a64f9ac Fixed: (Shazbat) Fix Guid 2023-02-15 06:09:06 +02:00
Bogdan
e713e58e83 Fixed: (ImmortalSeed) Set RateLimit to 5 2023-02-15 06:08:47 +02:00
Bogdan
4fb5d3432b Fixed: (FileList) Switch to Basic Auth 2023-02-13 02:28:02 +02:00
Qstick
a31b107a90 Fix some UI translated strings 2023-02-12 18:22:43 -06:00
Qstick
f91ffb8328 New: (Localization) 7 New Languages 2023-02-12 17:57:22 -06:00
Weblate
a3ba070296 Added translation using Weblate (Tamil)
Added translation using Weblate (Indonesian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Serbian)

Added translation using Weblate (Croatian)

Added translation using Weblate (Bosnian)

Added translation using Weblate (Spanish (Mexico))

Co-authored-by: Weblate <noreply@weblate.org>
2023-02-12 17:38:07 -06:00
Qstick
bccb0bd5c8 Bump version to 1.2.1 2023-02-11 13:25:32 -06:00
Weblate
4517f271c4 Translated using Weblate (Greek)
Currently translated at 100.0% (471 of 471 strings)

Translated using Weblate (Ukrainian)

Currently translated at 73.4% (346 of 471 strings)

Translated using Weblate (Russian)

Currently translated at 76.6% (361 of 471 strings)

Translated using Weblate (Dutch)

Currently translated at 88.7% (418 of 471 strings)

Translated using Weblate (Hebrew)

Currently translated at 81.5% (384 of 471 strings)

Translated using Weblate (French)

Currently translated at 98.9% (466 of 471 strings)

Translated using Weblate (Greek)

Currently translated at 75.5% (356 of 471 strings)

Translated using Weblate (Danish)

Currently translated at 74.7% (352 of 471 strings)

Translated using Weblate (French)

Currently translated at 97.0% (457 of 471 strings)

Translated using Weblate (French)

Currently translated at 97.0% (457 of 471 strings)

Translated using Weblate (French)

Currently translated at 97.0% (457 of 471 strings)

Translated using Weblate (French)

Currently translated at 96.1% (453 of 471 strings)

Translated using Weblate (French)

Currently translated at 96.1% (453 of 471 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (471 of 471 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: KevoM <lilmarsu@gmail.com>
Co-authored-by: Vasilis Ieropoulos <kirav96@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: louismaxx <lmdupouy@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translation: Servarr/Prowlarr
2023-02-11 11:28:14 -06:00
Qstick
2ae2a0b184 Delete azuresync.yml 2023-02-11 10:35:07 -06:00
Bogdan
b5e43e7a1a Fixed: (Cardigann) Show redirect url when the response has errors 2023-02-11 08:28:38 +02:00
Bogdan
3a52048dc2 Fixed: (UI) Check for non-array indexerUrls 2023-02-11 08:27:16 +02:00
Bogdan
8b898733ab Fixed: (RuTracker/Toloka) Clean title 2023-02-10 06:46:02 +02:00
Bogdan
f99a2e1164 Fixed: Standardize dashes/single quotes in search term, ignore artist if "VA" 2023-02-10 06:44:39 +02:00
Bogdan
306209fcc2 Fixed: Simplify DateTime alteration 2023-02-09 13:13:16 +02:00
Bogdan
5d09c2b5fa Fixed: (Shazbat) Simplify conditions for CheckIfLoginNeeded 2023-02-09 12:48:39 +02:00
Bogdan
41a9d2d732 New: Add Shazbat 2023-02-09 07:05:29 +02:00
Bogdan
49b120ba55 Revert "Fixed: (Redacted/Orpheus/Libble/SecretCinema) Add SupportsRawSearch"
This reverts commit c46b7c5e4b.
2023-02-08 06:28:05 +02:00
Qstick
a88fc34a78 Fixed: Settings fail to save for some auth setups 2023-02-06 23:12:20 -06:00
Bogdan
c46b7c5e4b Fixed: (Redacted/Orpheus/Libble/SecretCinema) Add SupportsRawSearch 2023-02-07 07:00:27 +02:00
Bogdan
94c45541ae Fixed: (Anidub/Animedia) Use rate limit in sub-requests 2023-02-07 06:59:48 +02:00
Bogdan
f8082047a5 Fixed: (HttpIndexerBase) Catch HttpRequestException/TaskCanceledException 2023-02-05 20:00:10 +02:00
Qstick
011fd57f7d Fixed: Handle null IEnumerable field values in SchemaBuilder 2023-02-05 10:37:35 -06:00
Bogdan
6c35c3fc6f Fixed: (ImmortalSeed/XSpeeds) Sitewide Freeleech 2023-02-05 01:34:36 +02:00
Qstick
5da02c49eb Bump version to 1.2.0 2023-02-04 15:07:01 -06:00
141 changed files with 2600 additions and 947 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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)'

View File

@@ -50,7 +50,7 @@ function CustomFiltersModalContent(props) {
<div className={styles.addButtonContainer}>
<Button onPress={onAddCustomFilter}>
Add Custom Filter
{translate('AddCustomFilter')}
</Button>
</div>
</ModalBody>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}

View File

@@ -28,7 +28,7 @@ class AddApplicationModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Add Application
{translate('AddApplication')}
</ModalHeader>
<ModalBody>

View File

@@ -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>
}

View File

@@ -106,7 +106,7 @@ class IndexerProxy extends Component {
kind={kinds.DISABLED}
outline={true}
>
Disabled
{translate('Disabled')}
</Label> :
null
}

View File

@@ -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
},

View File

@@ -25,7 +25,7 @@ const columns = [
},
{
name: 'size',
label: 'Size',
label: translate('Size'),
isVisible: true
},
{

View File

@@ -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
View File

@@ -0,0 +1,3 @@
is_global = true
dotnet_diagnostic.CA1014.severity = none

View File

@@ -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>

View File

@@ -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;
}
}
}

View File

@@ -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
{

View File

@@ -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
}

View File

@@ -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" />

View File

@@ -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\"";

View File

@@ -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

View File

@@ -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]

View File

@@ -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" />

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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)
{

View File

@@ -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)
{

View File

@@ -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;
}

View File

@@ -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()

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
{

View File

@@ -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)
{

View File

@@ -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)

View File

@@ -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));

View File

@@ -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)

View File

@@ -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");
}

View File

@@ -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));

View File

@@ -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.");
}

View 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; }
}

View File

@@ -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;

View File

@@ -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.");
}

View File

@@ -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('(');

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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
{

View File

@@ -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;
}
}

View File

@@ -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.");
}

View File

@@ -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.");
}

View File

@@ -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;

View File

@@ -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.");
}

View File

@@ -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.");
}

View File

@@ -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;
}

View File

@@ -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()),

View File

@@ -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

View File

@@ -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");
}
}
}

View File

@@ -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.");
}

View File

@@ -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(),

View File

@@ -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;

View File

@@ -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
{

View File

@@ -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");
}

View File

@@ -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");
}

View File

@@ -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);

View File

@@ -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(),

View File

@@ -40,7 +40,7 @@ namespace NzbDrone.Core.Indexers.Definitions
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q,
},
}
};
caps.Categories.AddCategoryMapping(401, NewznabStandardCategory.Movies, "Movies");

View File

@@ -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");
}

View File

@@ -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; }
}
}

View File

@@ -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)

View 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));
}
}

View File

@@ -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");

View File

@@ -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;
}

View File

@@ -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.");
}

View File

@@ -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.");
}

View File

@@ -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

View File

@@ -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.");
}

View File

@@ -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");
}
}
}

View 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; }
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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;

View File

@@ -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)

View File

@@ -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)

View File

@@ -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;
}
}
}
}

View File

@@ -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}"
}

View File

@@ -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}"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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 είναι ήδη εγκατεστημένη"
}

View File

@@ -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",

View File

@@ -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"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
{}

Some files were not shown because too many files have changed in this diff Show More