Compare commits

...

76 Commits

Author SHA1 Message Date
ta264
0e05d86a58 Fixed: Handle wonky timestamps from Orpheus 2022-04-08 04:14:26 +01:00
bakerboy448
4f83116413 Fixed: (BHD) TMDb Parsing Exception 2022-04-03 12:04:39 -05:00
missingfile
d440bc079f Fixed: MoreThanTV indexer from browse page layout changes (#922) 2022-04-02 16:50:35 -05:00
Qstick
a5c7c6cbcb We don't have two Radarrs 2022-04-02 16:30:29 -05:00
Qstick
710c3d6deb Fix indent from 37c393a659 2022-04-02 15:34:36 -05:00
bakerboy448
1959efbd09 Fixed: (HDBits) Treat 403 as Query Limit 2022-04-02 14:58:58 -05:00
bakerboy448
c4468b9cbb Fixed: (PTP) Treat 403 as Query Limit 2022-04-02 14:58:58 -05:00
bakerboy448
8762d94dda New: (BTN) Rate Limit to 1 Query per 5 Seconds 2022-04-02 14:50:06 -05:00
bakerboy448
e07ea80977 Fixed: (BTN) Handle Query Limit Error 2022-04-02 14:50:06 -05:00
bakerboy448
37c393a659 New: (Lidarr/Radarr/Readarr/Sonarr) Improved Errors 2022-04-02 14:26:03 -05:00
ta264
b4b779df5c Fixed: Loading old commands from database
Fixes #926

(cherry picked from commit 0f87cb72e559a19bddc6c9d4387ec7d9428291f8)
2022-04-02 14:22:00 -05:00
Qstick
1dbf35deb5 Fixed: Cleanup Temp files after backup creation
Fixes #925

(cherry picked from commit 7f0b708cb9f16a0116199625a2a5b4e67049be6a)
2022-04-02 14:22:00 -05:00
Qstick
6ab226c43a Add Support 2022-04-02 14:20:02 -05:00
Weblate
3a896fc43e Translated using Weblate (Finnish)
Currently translated at 97.7% (436 of 446 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translation: Servarr/Prowlarr
2022-03-31 20:53:42 -05:00
bakerboy448
a81d632878 Fixed: Indexer Infobox Error (#920)
* Fixed: Indexer Infobox Error

* fixup! Fixed: Indexer Infobox Error

* fixup! Fixed: Indexer Infobox Error
2022-03-30 09:22:17 -05:00
bakerboy448
cb8c0d4aa7 New: Indexer Description in Add Indexer Modal 2022-03-29 23:24:48 -05:00
bakerboy448
788a5a3e24 Fixed: Missing Translates 2022-03-29 23:24:48 -05:00
bakerboy448
4f056bf228 New: Add Search Capabilities to Indexer API & InfoBox 2022-03-29 23:24:48 -05:00
Robin Dadswell
6ce9c779c1 Fixed: Update from version in logs
Closes #873
Closes #877

(cherry picked from commit bc3e3714b97dec19091e45324d9f2b550f1dbbd5)
(cherry picked from commit b34f4fde1bbe611bb5c886890e3be7cec2b96e6b)
2022-03-29 23:23:04 -05:00
Servarr
32fc2ec365 Automated API Docs update 2022-03-29 23:22:23 -05:00
Weblate
9ddd38a334 Translated using Weblate (Chinese (Simplified) (zh_CN))
Currently translated at 98.5% (422 of 428 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (428 of 428 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: libsu <libsu@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2022-03-29 23:06:34 -05:00
Weblate
b45e5a5e38 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (428 of 428 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2022-03-29 20:21:49 -05:00
Qstick
135efe6d94 Fixed: Validation when testing indexers, connections and download clients
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-03-28 19:55:35 -05:00
Qstick
69de6d18eb Fixed: Prevent delete of last profile 2022-03-25 18:53:29 -05:00
Qstick
627da14a32 New: Load more (page) results on Search UI 2022-03-24 22:19:00 -05:00
Qstick
83bf3620c0 Update webpack packages 2022-03-24 21:02:06 -05:00
Qstick
fa41bf3c58 Frontend Package Updates 2022-03-24 20:30:56 -05:00
Qstick
31e7a101ef Backend Package Updates 2022-03-24 20:30:56 -05:00
Qstick
22f9e9e6b7 Bump dotnet to 6.0.3 2022-03-24 20:30:56 -05:00
Weblate
8834431ba6 Translated using Weblate (Spanish)
Currently translated at 77.5% (332 of 428 strings)

Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translation: Servarr/Prowlarr
2022-03-24 18:47:24 -05:00
bakerboy448
01cc9b3d07 Fixed: (Gazelle) Replace Periods for Space in Search Term
based on Jackett b8b816f953ad9d0508f95a9c31f9ff4385ebdd73
2022-03-23 21:18:39 -05:00
bakerboy448
a0cbe1de5d Fixed: (HDSpace) Replace Periods for Space in Search Term
based on jackett 22efff93e7f1df858b341b3c2756d8204cae6f4a
2022-03-23 21:18:39 -05:00
bakerboy448
75792c0760 Fixed: (Anthelion) Replace Periods for Space in Search Term
based on jackett 22efff93e7f1df858b341b3c2756d8204cae6f4a
2022-03-23 21:18:39 -05:00
bakerboy448
83ca724120 Fixed: (Redacted) Map Categories Comedy & E-Learning Videos to 'Other'
indexer does not actually have movies and tv
2022-03-23 21:16:22 -05:00
bakerboy448
aee6ee1a00 Fixed: No longer require first run as admin on windows (#885)
* Fixed: No longer require first run as admin on windows

(cherry picked from commit 8d9302da7761752189b2d2820915d7d13197ce47)
(cherry picked from commit daa9ee30a2c5fc2511e34167ce642c1bfe2a8b41)

Co-Authored By: ta264 <ta264@users.noreply.github.com>

* Build installer from build.sh

(cherry picked from commit 6db135877a8394244a95142347b99ea651e0cceb)
(cherry picked from commit 86102349c54f7e20f2d1e5ab104505a4ea0d68bc)

* Fixed: (macOS) Prowlarr Bundle Identifier using .video

* fixup! Fixed: No longer require first run as admin on windows

* fixup! Build installer from build.sh

Co-authored-by: ta264 <ta264@users.noreply.github.com>
2022-03-23 20:58:42 -05:00
Weblate
e66c69a292 Translated using Weblate (Chinese (Simplified) (zh_CN))
Currently translated at 98.3% (421 of 428 strings)

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

Currently translated at 98.1% (420 of 428 strings)

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

Currently translated at 98.1% (420 of 428 strings)

Translated using Weblate (French)

Currently translated at 98.5% (422 of 428 strings)

Co-authored-by: EthanChoy <ethanchoy@163.com>
Co-authored-by: KevoM <lilmarsu@gmail.com>
Co-authored-by: Vincent <intelligentvincent@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: wzw1wzw <opinionspirit222@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2022-03-23 19:31:41 -05:00
JigSawFr
930370729b indexer(xthor): moved to YAML definition v5 2022-03-21 19:45:57 -05:00
bakerboy448
2f5b55013a Fixed: '/indexers' URL Base breaking UI navigation
(cherry picked from commit 140547e42a32cbaf80e7a794c5be9bfb574e97a2)
2022-03-21 18:01:39 -05:00
Weblate
cb74fade18 Translated using Weblate (French)
Currently translated at 98.5% (422 of 428 strings)

Co-authored-by: KevoM <lilmarsu@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translation: Servarr/Prowlarr
2022-03-21 18:00:16 -05:00
nitsua
e0da422b0e Fix app settings delete modal not closing and reloading app profiles 2022-03-21 15:31:30 +00:00
Weblate
77caaa2a55 Translated using Weblate (French)
Currently translated at 97.1% (416 of 428 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 1.6% (7 of 428 strings)

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

Currently translated at 95.5% (409 of 428 strings)

Co-authored-by: Ayi <4ayixd@gmail.com>
Co-authored-by: Kakise <sam.taa@icloud.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: 杰森 <john@johnrosen1.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_Hans/
Translation: Servarr/Prowlarr
2022-03-18 14:39:40 -05:00
Qstick
55d03967ec Bump Swashbuckle to 6.3.0 2022-03-14 22:33:08 -05:00
Weblate
684c30893a Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (428 of 428 strings)

Translated using Weblate (Portuguese)

Currently translated at 82.0% (351 of 428 strings)

Translated using Weblate (French)

Currently translated at 96.7% (414 of 428 strings)

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: José Eduardo Veiga <vitruxpt@vitruxbot.com>
Co-authored-by: KevoM <lilmarsu@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2022-03-11 01:46:34 -06:00
bakerboy448
3b51a3a618 fixup! New: (DanishBytes) Move to YML 2022-03-06 21:23:35 -06:00
bakerboy448
09bd8137fc New: (DanishBytes) Move to YML 2022-03-06 21:23:35 -06:00
Weblate
495daf7967 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (Chinese (Traditional) (zh_TW))

Currently translated at 0.4% (2 of 428 strings)

Translated using Weblate (Arabic)

Currently translated at 76.1% (326 of 428 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (428 of 428 strings)

Translated using Weblate (Vietnamese)

Currently translated at 75.9% (325 of 428 strings)

Translated using Weblate (Turkish)

Currently translated at 75.7% (324 of 428 strings)

Translated using Weblate (Thai)

Currently translated at 75.9% (325 of 428 strings)

Translated using Weblate (Swedish)

Currently translated at 93.4% (400 of 428 strings)

Translated using Weblate (Russian)

Currently translated at 77.1% (330 of 428 strings)

Translated using Weblate (Romanian)

Currently translated at 75.9% (325 of 428 strings)

Translated using Weblate (Portuguese)

Currently translated at 78.0% (334 of 428 strings)

Translated using Weblate (Polish)

Currently translated at 75.9% (325 of 428 strings)

Translated using Weblate (Dutch)

Currently translated at 93.6% (401 of 428 strings)

Translated using Weblate (Dutch)

Currently translated at 93.6% (401 of 428 strings)

Translated using Weblate (Korean)

Currently translated at 56.5% (242 of 428 strings)

Translated using Weblate (Japanese)

Currently translated at 75.9% (325 of 428 strings)

Translated using Weblate (Italian)

Currently translated at 80.3% (344 of 428 strings)

Translated using Weblate (Icelandic)

Currently translated at 75.9% (325 of 428 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (428 of 428 strings)

Translated using Weblate (Hindi)

Currently translated at 75.9% (325 of 428 strings)

Translated using Weblate (Hebrew)

Currently translated at 75.9% (325 of 428 strings)

Translated using Weblate (French)

Currently translated at 93.6% (401 of 428 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (428 of 428 strings)

Translated using Weblate (Spanish)

Currently translated at 76.8% (329 of 428 strings)

Translated using Weblate (Greek)

Currently translated at 75.9% (325 of 428 strings)

Translated using Weblate (German)

Currently translated at 100.0% (428 of 428 strings)

Translated using Weblate (Danish)

Currently translated at 75.9% (325 of 428 strings)

Translated using Weblate (Czech)

Currently translated at 76.4% (327 of 428 strings)

Translated using Weblate (Bulgarian)

Currently translated at 70.7% (303 of 428 strings)

Translated using Weblate (Portuguese)

Currently translated at 78.0% (334 of 428 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.3% (421 of 428 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.3% (421 of 428 strings)

Translated using Weblate (German)

Currently translated at 91.3% (391 of 428 strings)

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

Currently translated at 93.6% (401 of 428 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (428 of 428 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (428 of 428 strings)

Added translation using Weblate (Ukrainian)

Added translation using Weblate (Persian)

Added translation using Weblate (Bengali)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Nuno Filipe de Vilhena Santos <nunovilhenasantos@msn.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Rico_Walker <ricardo.walker1203@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Zalhera <tobias.bechen@gmail.com>
Co-authored-by: nopetw <lubduphaur@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/
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/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/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/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/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/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_TW/
Translation: Servarr/Prowlarr
2022-03-06 21:23:19 -06:00
gaizaharduz
bb3821c254 New: (RuTracker.org) add .bet mirror (#876) 2022-03-06 14:21:15 -06:00
bakerboy448
855f8d35f2 Fixed:(pornolab) language formatting 2022-02-28 18:47:50 -06:00
Qstick
15dab381af New: Housekeeper for ApplicationStatus 2022-02-27 23:14:02 -06:00
bakerboy448
9c5a88e2e7 Fixed: Cleanse Tracker api_token from logs 2022-02-27 22:34:37 -06:00
Qstick
80d295cce5 New: (HDTorrents) Add hd-torrents.org as Url option
Fixes #853
2022-02-27 22:27:13 -06:00
Qstick
76afb70b01 New: (Cardigann) Allow JSON filters
Fixes #844
2022-02-27 22:19:19 -06:00
Qstick
c29fba3a2b Fixed: Convert List<HistoryEventTypes> to Int before passing to DB
Fixes #861
2022-02-27 19:10:49 -06:00
Qstick
68e41f0860 Fixed: WhereBuilder for Postgres 2022-02-27 18:09:40 -06:00
Weblate
4c68645175 Translated using Weblate (Finnish)
Currently translated at 100.0% (428 of 428 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (428 of 428 strings)

Added translation using Weblate (Ukrainian)

Added translation using Weblate (Persian)

Added translation using Weblate (Bengali)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translation: Servarr/Prowlarr
2022-02-23 21:45:57 -06:00
ta264
aaef8fb29c Fixed: Make authentication cookie name unique to Prowlarr 2022-02-20 21:38:07 +00:00
Pfuenzle
b2e300b6da Update Categories 2022-02-20 11:09:02 -06:00
bakerboy448
77c840b03a Fixed: Enable response compression over https
(cherry picked from commit cb0ae3603309daae4d6b026de15f26cb4b59096d)
2022-02-16 14:04:38 -06:00
bakerboy448
98c3408909 Fixed: (RuTracker) Update Cats
(cherry picked from jackett commit 7a7144bd9d6b4fc4c1ace4886d3bc62ec37b35e6)

Fixes #836
2022-02-15 19:15:13 -06:00
Zak Saunders
40f6c2e59d Fixed: Clarify App Sync Settings (#847) 2022-02-15 16:35:12 -06:00
James White
64c9bb4231 Set version header to X-Application-Version (missing hyphen)
Not sure if this is intentional or really harms anything but to be consistent with other Arr apps like Sonarr and Radarr, this changes the version header to X-Application-Version.
2022-02-06 21:05:27 -06:00
Qstick
94ef3ea88f Go to http if def exists on def server 2022-02-06 16:11:42 -05:00
bakerboy448
dab4500b16 Fixed: (BHD) Handle API Auth Errors 2022-02-05 19:54:26 -06:00
bakerboy448
d951943c67 Fixed: (Immortalseed) Keywordless Search 2022-02-05 17:46:04 -06:00
Qstick
bba6f9349b Fixed: (Cardigann) TraktId was mapping to TvRageId 2022-02-05 14:16:05 -06:00
Qstick
7e0f88ad7a New: (Cardigann) - Cardigann v4 Support for Genre, Year, and TraktID 2022-02-05 13:30:12 -06:00
bakerboy448
88c6cbf943 New: (Cardigann) - Cardigann v4 Support for categorydesc
based on Jackett 02e43bd6a9838562a14c6bd6ea10cfed5a293d59
based on Jackett 60d2c425e1038a28f54bda658a9f543f3a197a66

Fixes #782
2022-02-05 13:30:12 -06:00
bakerboy448
69d31f96de New: (Cardigann) - Cardigann v4 Add Support for MapTrackerCatDescToNewznab 2022-02-05 13:30:12 -06:00
bakerboy448
a5718ad937 New: (Cardigann) - Cardigann v4 Improved Search Logging 2022-02-05 13:30:12 -06:00
bakerboy448
505d9c151d Fixed: Corrected Query Limit and Grab Limit HelpText 2022-02-05 12:44:13 -06:00
Qstick
a637677ec4 New: (Avistaz) Better error reporting for unauthorized tests
Fixes #822
2022-02-04 22:51:55 -06:00
bakerboy448
a0d5421dc8 Fixed: (Cardigann) Requests Failing for Definitions without LegacyLinks 2022-02-04 13:17:07 -06:00
dependabot[bot]
95f62be50c Bump SharpZipLib from 1.3.1 to 1.3.3 in /src/NzbDrone.Common
Bumps [SharpZipLib](https://github.com/icsharpcode/SharpZipLib) from 1.3.1 to 1.3.3.
- [Release notes](https://github.com/icsharpcode/SharpZipLib/releases)
- [Changelog](https://github.com/icsharpcode/SharpZipLib/blob/master/docs/Changes.txt)
- [Commits](https://github.com/icsharpcode/SharpZipLib/compare/v1.3.1...v1.3.3)

---
updated-dependencies:
- dependency-name: SharpZipLib
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-04 10:23:26 -06:00
Qstick
d7b5100e35 Fixed: (Cardigann) Smarter redirect domain compare
Fixes #808
2022-02-03 23:32:25 -06:00
Qstick
64c1e1fa54 Fixed: (Cardigann) Treat "Refresh" header as redirect
Fixes #812
2022-02-03 23:16:20 -06:00
Qstick
74663ea077 Fixed: (Cardigann) Replace legacy links with default link when making requests 2022-02-03 22:29:46 -06:00
153 changed files with 4746 additions and 4170 deletions

View File

@@ -13,7 +13,9 @@ variables:
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.100'
dotnetVersion: '6.0.201'
innoVersion: '6.2.0'
nodeVersion: '16.x'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
trigger:
@@ -155,7 +157,7 @@ stages:
- task: NodeTool@0
displayName: Set Node.js version
inputs:
versionSpec: '12.x'
versionSpec: $(nodeVersion)
- checkout: self
submodules: true
fetchDepth: 1
@@ -200,16 +202,11 @@ stages:
artifactName: WindowsFrontend
targetPath: _output
displayName: Fetch Frontend
- bash: ./build.sh --packages
displayName: Create Packages
- bash: |
distribution/windows/setup/inno/ISCC.exe distribution/windows/setup/prowlarr.iss //DFramework=net6.0 //DRuntime=win-x86
cp distribution/windows/setup/output/Prowlarr.*windows.net6.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Prowlarr.${BUILDNAME}.windows-core-x86-installer.exe
displayName: Create x86 .NET Core Windows installer
- bash: |
distribution/windows/setup/inno/ISCC.exe distribution/windows/setup/prowlarr.iss //DFramework=net6.0 //DRuntime=win-x64
cp distribution/windows/setup/output/Prowlarr.*windows.net6.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Prowlarr.${BUILDNAME}.windows-core-x64-installer.exe
displayName: Create x64 .NET Core Windows installer
./build.sh --packages --installer
cp distribution/windows/setup/output/Prowlarr.*win-x64.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Prowlarr.${BUILDNAME}.windows-core-x64-installer.exe
cp distribution/windows/setup/output/Prowlarr.*win-x86.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Prowlarr.${BUILDNAME}.windows-core-x86-installer.exe
displayName: Create Installers
- publish: $(Build.ArtifactStagingDirectory)
artifact: 'WindowsInstaller'
displayName: Publish Installer
@@ -706,17 +703,17 @@ stages:
osName: 'Linux'
imageName: 'ubuntu-18.04'
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
failBuild: false
failBuild: true
Mac:
osName: 'Mac'
imageName: 'macos-10.15'
pattern: 'Prowlarr.*.osx-core-x64.tar.gz'
failBuild: false
failBuild: true
Windows:
osName: 'Windows'
imageName: 'windows-2019'
pattern: 'Prowlarr.*.windows-core-x64.zip'
failBuild: false
failBuild: true
pool:
vmImage: $(imageName)
@@ -807,7 +804,7 @@ stages:
- task: NodeTool@0
displayName: Set Node.js version
inputs:
versionSpec: '12.x'
versionSpec: $(nodeVersion)
- checkout: self
submodules: true
fetchDepth: 1
@@ -944,7 +941,7 @@ stages:
- job:
displayName: Discord Notification
pool:
vmImage: 'windows-2019'
vmImage: 'ubuntu-18.04'
steps:
- task: DownloadPipelineArtifact@2
continueOnError: true

View File

@@ -234,6 +234,32 @@ Package()
esac
}
BuildInstaller()
{
local framework="$1"
local runtime="$2"
./_inno/ISCC.exe distribution/windows/setup/prowlarr.iss "//DFramework=$framework" "//DRuntime=$runtime"
}
InstallInno()
{
ProgressStart "Installing portable Inno Setup"
rm -rf _inno
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.0}.exe"
mkdir _inno
./innosetup.exe //portable=1 //silent //currentuser //dir=.\\_inno
rm innosetup.exe
ProgressEnd "Installed portable Inno Setup"
}
RemoveInno()
{
rm -rf _inno
}
PackageTests()
{
local framework="$1"
@@ -265,6 +291,7 @@ if [ $# -eq 0 ]; then
BACKEND=YES
FRONTEND=YES
PACKAGES=YES
INSTALLER=NO
LINT=YES
ENABLE_BSD=NO
fi
@@ -300,6 +327,10 @@ case $key in
PACKAGES=YES
shift # past argument
;;
--installer)
INSTALLER=YES
shift # past argument
;;
--lint)
LINT=YES
shift # past argument
@@ -383,3 +414,11 @@ then
Package "$FRAMEWORK" "$RID"
fi
fi
if [ "$INSTALLER" = "YES" ];
then
InstallInno
BuildInstaller "net6.0" "win-x64"
BuildInstaller "net6.0" "win-x86"
RemoveInno
fi

View File

@@ -15,7 +15,7 @@
<key>CFBundleIconFile</key>
<string>prowlarr.icns</string>
<key>CFBundleIdentifier</key>
<string>com.osx.prowlarr.video</string>
<string>com.osx.prowlarr.com</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>

View File

@@ -1,3 +0,0 @@
#SET BUILD_NUMBER=1
#SET branch=develop
inno\ISCC.exe prowlarr.iss

View File

@@ -1,336 +0,0 @@
; *** Inno Setup version 5.5.3+ English messages ***
;
; To download user-contributed translations of this file, go to:
; http://www.jrsoftware.org/files/istrans/
;
; Note: When translating this text, do not add periods (.) to the end of
; messages that didn't have them already, because on those messages Inno
; Setup adds the periods automatically (appending a period would result in
; two periods being displayed).
[LangOptions]
; The following three entries are very important. Be sure to read and
; understand the '[LangOptions] section' topic in the help file.
LanguageName=English
LanguageID=$0409
LanguageCodePage=0
; If the language you are translating to requires special font faces or
; sizes, uncomment any of the following entries and change them accordingly.
;DialogFontName=
;DialogFontSize=8
;WelcomeFontName=Verdana
;WelcomeFontSize=12
;TitleFontName=Arial
;TitleFontSize=29
;CopyrightFontName=Arial
;CopyrightFontSize=8
[Messages]
; *** Application titles
SetupAppTitle=Setup
SetupWindowTitle=Setup - %1
UninstallAppTitle=Uninstall
UninstallAppFullTitle=%1 Uninstall
; *** Misc. common
InformationTitle=Information
ConfirmTitle=Confirm
ErrorTitle=Error
; *** SetupLdr messages
SetupLdrStartupMessage=This will install %1. Do you wish to continue?
LdrCannotCreateTemp=Unable to create a temporary file. Setup aborted
LdrCannotExecTemp=Unable to execute file in the temporary directory. Setup aborted
; *** Startup error messages
LastErrorMessage=%1.%n%nError %2: %3
SetupFileMissing=The file %1 is missing from the installation directory. Please correct the problem or obtain a new copy of the program.
SetupFileCorrupt=The setup files are corrupted. Please obtain a new copy of the program.
SetupFileCorruptOrWrongVer=The setup files are corrupted, or are incompatible with this version of Setup. Please correct the problem or obtain a new copy of the program.
InvalidParameter=An invalid parameter was passed on the command line:%n%n%1
SetupAlreadyRunning=Setup is already running.
WindowsVersionNotSupported=This program does not support the version of Windows your computer is running.
WindowsServicePackRequired=This program requires %1 Service Pack %2 or later.
NotOnThisPlatform=This program will not run on %1.
OnlyOnThisPlatform=This program must be run on %1.
OnlyOnTheseArchitectures=This program can only be installed on versions of Windows designed for the following processor architectures:%n%n%1
MissingWOW64APIs=The version of Windows you are running does not include functionality required by Setup to perform a 64-bit installation. To correct this problem, please install Service Pack %1.
WinVersionTooLowError=This program requires %1 version %2 or later.
WinVersionTooHighError=This program cannot be installed on %1 version %2 or later.
AdminPrivilegesRequired=You must be logged in as an administrator when installing this program.
PowerUserPrivilegesRequired=You must be logged in as an administrator or as a member of the Power Users group when installing this program.
SetupAppRunningError=Setup has detected that %1 is currently running.%n%nPlease close all instances of it now, then click OK to continue, or Cancel to exit.
UninstallAppRunningError=Uninstall has detected that %1 is currently running.%n%nPlease close all instances of it now, then click OK to continue, or Cancel to exit.
; *** Misc. errors
ErrorCreatingDir=Setup was unable to create the directory "%1"
ErrorTooManyFilesInDir=Unable to create a file in the directory "%1" because it contains too many files
; *** Setup common messages
ExitSetupTitle=Exit Setup
ExitSetupMessage=Setup is not complete. If you exit now, the program will not be installed.%n%nYou may run Setup again at another time to complete the installation.%n%nExit Setup?
AboutSetupMenuItem=&About Setup...
AboutSetupTitle=About Setup
AboutSetupMessage=%1 version %2%n%3%n%n%1 home page:%n%4
AboutSetupNote=
TranslatorNote=
; *** Buttons
ButtonBack=< &Back
ButtonNext=&Next >
ButtonInstall=&Install
ButtonOK=OK
ButtonCancel=Cancel
ButtonYes=&Yes
ButtonYesToAll=Yes to &All
ButtonNo=&No
ButtonNoToAll=N&o to All
ButtonFinish=&Finish
ButtonBrowse=&Browse...
ButtonWizardBrowse=B&rowse...
ButtonNewFolder=&Make New Folder
; *** "Select Language" dialog messages
SelectLanguageTitle=Select Setup Language
SelectLanguageLabel=Select the language to use during the installation:
; *** Common wizard text
ClickNext=Click Next to continue, or Cancel to exit Setup.
BeveledLabel=
BrowseDialogTitle=Browse For Folder
BrowseDialogLabel=Select a folder in the list below, then click OK.
NewFolderName=New Folder
; *** "Welcome" wizard page
WelcomeLabel1=Welcome to the [name] Setup Wizard
WelcomeLabel2=This will install [name/ver] on your computer.%n%nIt is recommended that you close all other applications before continuing.
; *** "Password" wizard page
WizardPassword=Password
PasswordLabel1=This installation is password protected.
PasswordLabel3=Please provide the password, then click Next to continue. Passwords are case-sensitive.
PasswordEditLabel=&Password:
IncorrectPassword=The password you entered is not correct. Please try again.
; *** "License Agreement" wizard page
WizardLicense=License Agreement
LicenseLabel=Please read the following important information before continuing.
LicenseLabel3=Please read the following License Agreement. You must accept the terms of this agreement before continuing with the installation.
LicenseAccepted=I &accept the agreement
LicenseNotAccepted=I &do not accept the agreement
; *** "Information" wizard pages
WizardInfoBefore=Information
InfoBeforeLabel=Please read the following important information before continuing.
InfoBeforeClickLabel=When you are ready to continue with Setup, click Next.
WizardInfoAfter=Information
InfoAfterLabel=Please read the following important information before continuing.
InfoAfterClickLabel=When you are ready to continue with Setup, click Next.
; *** "User Information" wizard page
WizardUserInfo=User Information
UserInfoDesc=Please enter your information.
UserInfoName=&User Name:
UserInfoOrg=&Organization:
UserInfoSerial=&Serial Number:
UserInfoNameRequired=You must enter a name.
; *** "Select Destination Location" wizard page
WizardSelectDir=Select Destination Location
SelectDirDesc=Where should [name] be installed?
SelectDirLabel3=Setup will install [name] into the following folder.
SelectDirBrowseLabel=To continue, click Next. If you would like to select a different folder, click Browse.
DiskSpaceMBLabel=At least [mb] MB of free disk space is required.
CannotInstallToNetworkDrive=Setup cannot install to a network drive.
CannotInstallToUNCPath=Setup cannot install to a UNC path.
InvalidPath=You must enter a full path with drive letter; for example:%n%nC:\APP%n%nor a UNC path in the form:%n%n\\server\share
InvalidDrive=The drive or UNC share you selected does not exist or is not accessible. Please select another.
DiskSpaceWarningTitle=Not Enough Disk Space
DiskSpaceWarning=Setup requires at least %1 KB of free space to install, but the selected drive only has %2 KB available.%n%nDo you want to continue anyway?
DirNameTooLong=The folder name or path is too long.
InvalidDirName=The folder name is not valid.
BadDirName32=Folder names cannot include any of the following characters:%n%n%1
DirExistsTitle=Folder Exists
DirExists=The folder:%n%n%1%n%nalready exists. Would you like to install to that folder anyway?
DirDoesntExistTitle=Folder Does Not Exist
DirDoesntExist=The folder:%n%n%1%n%ndoes not exist. Would you like the folder to be created?
; *** "Select Components" wizard page
WizardSelectComponents=Select Components
SelectComponentsDesc=Which components should be installed?
SelectComponentsLabel2=Select the components you want to install; clear the components you do not want to install. Click Next when you are ready to continue.
FullInstallation=Full installation
; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language)
CompactInstallation=Compact installation
CustomInstallation=Custom installation
NoUninstallWarningTitle=Components Exist
NoUninstallWarning=Setup has detected that the following components are already installed on your computer:%n%n%1%n%nDeselecting these components will not uninstall them.%n%nWould you like to continue anyway?
ComponentSize1=%1 KB
ComponentSize2=%1 MB
ComponentsDiskSpaceMBLabel=Current selection requires at least [mb] MB of disk space.
; *** "Select Additional Tasks" wizard page
WizardSelectTasks=Select Additional Tasks
SelectTasksDesc=Which additional tasks should be performed?
SelectTasksLabel2=Select the additional tasks you would like Setup to perform while installing [name], then click Next.
; *** "Select Start Menu Folder" wizard page
WizardSelectProgramGroup=Select Start Menu Folder
SelectStartMenuFolderDesc=Where should Setup place the program's shortcuts?
SelectStartMenuFolderLabel3=Setup will create the program's shortcuts in the following Start Menu folder.
SelectStartMenuFolderBrowseLabel=To continue, click Next. If you would like to select a different folder, click Browse.
MustEnterGroupName=You must enter a folder name.
GroupNameTooLong=The folder name or path is too long.
InvalidGroupName=The folder name is not valid.
BadGroupName=The folder name cannot include any of the following characters:%n%n%1
NoProgramGroupCheck2=&Don't create a Start Menu folder
; *** "Ready to Install" wizard page
WizardReady=Ready to Install
ReadyLabel1=Setup is now ready to begin installing [name] on your computer.
ReadyLabel2a=Click Install to continue with the installation, or click Back if you want to review or change any settings.
ReadyLabel2b=Click Install to continue with the installation.
ReadyMemoUserInfo=User information:
ReadyMemoDir=Destination location:
ReadyMemoType=Setup type:
ReadyMemoComponents=Selected components:
ReadyMemoGroup=Start Menu folder:
ReadyMemoTasks=Additional tasks:
; *** "Preparing to Install" wizard page
WizardPreparing=Preparing to Install
PreparingDesc=Setup is preparing to install [name] on your computer.
PreviousInstallNotCompleted=The installation/removal of a previous program was not completed. You will need to restart your computer to complete that installation.%n%nAfter restarting your computer, run Setup again to complete the installation of [name].
CannotContinue=Setup cannot continue. Please click Cancel to exit.
ApplicationsFound=The following applications are using files that need to be updated by Setup. It is recommended that you allow Setup to automatically close these applications.
ApplicationsFound2=The following applications are using files that need to be updated by Setup. It is recommended that you allow Setup to automatically close these applications. After the installation has completed, Setup will attempt to restart the applications.
CloseApplications=&Automatically close the applications
DontCloseApplications=&Do not close the applications
ErrorCloseApplications=Setup was unable to automatically close all applications. It is recommended that you close all applications using files that need to be updated by Setup before continuing.
; *** "Installing" wizard page
WizardInstalling=Installing
InstallingLabel=Please wait while Setup installs [name] on your computer.
; *** "Setup Completed" wizard page
FinishedHeadingLabel=Completing the [name] Setup Wizard
FinishedLabelNoIcons=Setup has finished installing [name] on your computer.
FinishedLabel=Setup has finished installing [name] on your computer. The application may be launched by selecting the installed icons.
ClickFinish=Click Finish to exit Setup.
FinishedRestartLabel=To complete the installation of [name], Setup must restart your computer. Would you like to restart now?
FinishedRestartMessage=To complete the installation of [name], Setup must restart your computer.%n%nWould you like to restart now?
ShowReadmeCheck=Yes, I would like to view the README file
YesRadio=&Yes, restart the computer now
NoRadio=&No, I will restart the computer later
; used for example as 'Run MyProg.exe'
RunEntryExec=Run %1
; used for example as 'View Readme.txt'
RunEntryShellExec=View %1
; *** "Setup Needs the Next Disk" stuff
ChangeDiskTitle=Setup Needs the Next Disk
SelectDiskLabel2=Please insert Disk %1 and click OK.%n%nIf the files on this disk can be found in a folder other than the one displayed below, enter the correct path or click Browse.
PathLabel=&Path:
FileNotInDir2=The file "%1" could not be located in "%2". Please insert the correct disk or select another folder.
SelectDirectoryLabel=Please specify the location of the next disk.
; *** Installation phase messages
SetupAborted=Setup was not completed.%n%nPlease correct the problem and run Setup again.
EntryAbortRetryIgnore=Click Retry to try again, Ignore to proceed anyway, or Abort to cancel installation.
; *** Installation status messages
StatusClosingApplications=Closing applications...
StatusCreateDirs=Creating directories...
StatusExtractFiles=Extracting files...
StatusCreateIcons=Creating shortcuts...
StatusCreateIniEntries=Creating INI entries...
StatusCreateRegistryEntries=Creating registry entries...
StatusRegisterFiles=Registering files...
StatusSavingUninstall=Saving uninstall information...
StatusRunProgram=Finishing installation...
StatusRestartingApplications=Restarting applications...
StatusRollback=Rolling back changes...
; *** Misc. errors
ErrorInternal2=Internal error: %1
ErrorFunctionFailedNoCode=%1 failed
ErrorFunctionFailed=%1 failed; code %2
ErrorFunctionFailedWithMessage=%1 failed; code %2.%n%3
ErrorExecutingProgram=Unable to execute file:%n%1
; *** Registry errors
ErrorRegOpenKey=Error opening registry key:%n%1\%2
ErrorRegCreateKey=Error creating registry key:%n%1\%2
ErrorRegWriteKey=Error writing to registry key:%n%1\%2
; *** INI errors
ErrorIniEntry=Error creating INI entry in file "%1".
; *** File copying errors
FileAbortRetryIgnore=Click Retry to try again, Ignore to skip this file (not recommended), or Abort to cancel installation.
FileAbortRetryIgnore2=Click Retry to try again, Ignore to proceed anyway (not recommended), or Abort to cancel installation.
SourceIsCorrupted=The source file is corrupted
SourceDoesntExist=The source file "%1" does not exist
ExistingFileReadOnly=The existing file is marked as read-only.%n%nClick Retry to remove the read-only attribute and try again, Ignore to skip this file, or Abort to cancel installation.
ErrorReadingExistingDest=An error occurred while trying to read the existing file:
FileExists=The file already exists.%n%nWould you like Setup to overwrite it?
ExistingFileNewer=The existing file is newer than the one Setup is trying to install. It is recommended that you keep the existing file.%n%nDo you want to keep the existing file?
ErrorChangingAttr=An error occurred while trying to change the attributes of the existing file:
ErrorCreatingTemp=An error occurred while trying to create a file in the destination directory:
ErrorReadingSource=An error occurred while trying to read the source file:
ErrorCopying=An error occurred while trying to copy a file:
ErrorReplacingExistingFile=An error occurred while trying to replace the existing file:
ErrorRestartReplace=RestartReplace failed:
ErrorRenamingTemp=An error occurred while trying to rename a file in the destination directory:
ErrorRegisterServer=Unable to register the DLL/OCX: %1
ErrorRegSvr32Failed=RegSvr32 failed with exit code %1
ErrorRegisterTypeLib=Unable to register the type library: %1
; *** Post-installation errors
ErrorOpeningReadme=An error occurred while trying to open the README file.
ErrorRestartingComputer=Setup was unable to restart the computer. Please do this manually.
; *** Uninstaller messages
UninstallNotFound=File "%1" does not exist. Cannot uninstall.
UninstallOpenError=File "%1" could not be opened. Cannot uninstall
UninstallUnsupportedVer=The uninstall log file "%1" is in a format not recognized by this version of the uninstaller. Cannot uninstall
UninstallUnknownEntry=An unknown entry (%1) was encountered in the uninstall log
ConfirmUninstall=Are you sure you want to completely remove %1 and all of its components?
UninstallOnlyOnWin64=This installation can only be uninstalled on 64-bit Windows.
OnlyAdminCanUninstall=This installation can only be uninstalled by a user with administrative privileges.
UninstallStatusLabel=Please wait while %1 is removed from your computer.
UninstalledAll=%1 was successfully removed from your computer.
UninstalledMost=%1 uninstall complete.%n%nSome elements could not be removed. These can be removed manually.
UninstalledAndNeedsRestart=To complete the uninstallation of %1, your computer must be restarted.%n%nWould you like to restart now?
UninstallDataCorrupted="%1" file is corrupted. Cannot uninstall
; *** Uninstallation phase messages
ConfirmDeleteSharedFileTitle=Remove Shared File?
ConfirmDeleteSharedFile2=The system indicates that the following shared file is no longer in use by any programs. Would you like for Uninstall to remove this shared file?%n%nIf any programs are still using this file and it is removed, those programs may not function properly. If you are unsure, choose No. Leaving the file on your system will not cause any harm.
SharedFileNameLabel=File name:
SharedFileLocationLabel=Location:
WizardUninstalling=Uninstall Status
StatusUninstalling=Uninstalling %1...
; *** Shutdown block reasons
ShutdownBlockReasonInstallingApp=Installing %1.
ShutdownBlockReasonUninstallingApp=Uninstalling %1.
; The custom messages below aren't used by Setup itself, but if you make
; use of them in your scripts, you'll want to translate them.
[CustomMessages]
NameAndVersion=%1 version %2
AdditionalIcons=Additional icons:
CreateDesktopIcon=Create a &desktop icon
CreateQuickLaunchIcon=Create a &Quick Launch icon
ProgramOnTheWeb=%1 on the Web
UninstallProgram=Uninstall %1
LaunchProgram=Launch %1
AssocFileExtension=&Associate %1 with the %2 file extension
AssocingFileExtension=Associating %1 with the %2 file extension...
AutoStartProgramGroupDescription=Startup:
AutoStartProgram=Automatically start %1
AddonHostProgramNotFound=%1 could not be located in the folder you selected.%n%nDo you want to continue anyway?

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -3,13 +3,12 @@
#define AppName "Prowlarr"
#define AppPublisher "Team Prowlarr"
#define AppURL "https://prowlarr.video/"
#define ForumsURL "https://forums.prowlarr.video/"
#define AppURL "https://prowlarr.com/"
#define ForumsURL "https://prowlarr.com/discord/"
#define AppExeName "Prowlarr.exe"
#define BaseVersion GetEnv('MAJORVERSION')
#define BuildNumber GetEnv('MINORVERSION')
#define BuildVersion GetEnv('PROWLARRVERSION')
#define BranchName GetEnv('BUILD_SOURCEBRANCHNAME')
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
@@ -22,15 +21,15 @@ AppPublisher={#AppPublisher}
AppPublisherURL={#AppURL}
AppSupportURL={#ForumsURL}
AppUpdatesURL={#AppURL}
DefaultDirName={commonappdata}\Prowlarr\bin
DefaultDirName={commonappdata}\Prowlarr
DisableDirPage=yes
DefaultGroupName={#AppName}
DisableProgramGroupPage=yes
OutputBaseFilename=Prowlarr.{#BranchName}.{#BuildVersion}.windows.{#Framework}
OutputBaseFilename=Prowlarr.{#BuildVersion}.{#Runtime}
SolidCompression=yes
AppCopyright=Creative Commons 3.0 License
AllowUNCPath=False
UninstallDisplayIcon={app}\Prowlarr.exe
UninstallDisplayIcon={app}\bin\Prowlarr.exe
DisableReadyPage=True
CompressionThreads=2
Compression=lzma2/normal
@@ -38,6 +37,7 @@ AppContact={#ForumsURL}
VersionInfoVersion={#BaseVersion}.{#BuildNumber}
SetupLogging=yes
OutputDir=output
WizardStyle=modern
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
@@ -48,28 +48,31 @@ Name: "windowsService"; Description: "Install Windows Service (Starts when the c
Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
[Dirs]
Name: "{app}"; Permissions: users-modify
[Files]
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Prowlarr\Prowlarr.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Prowlarr\*"; Excludes: "Prowlarr.Update"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Prowlarr\Prowlarr.exe"; DestDir: "{app}\bin"; Flags: ignoreversion
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Prowlarr\*"; Excludes: "Prowlarr.Update"; DestDir: "{app}\bin"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Parameters: "/icon"
Name: "{commondesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Parameters: "/icon"; Tasks: desktopIcon
Name: "{userstartup}\{#AppName}"; Filename: "{app}\Prowlarr.exe"; WorkingDir: "{app}"; Tasks: startupShortcut
Name: "{group}\{#AppName}"; Filename: "{app}\bin\{#AppExeName}"; Parameters: "/icon"
Name: "{commondesktop}\{#AppName}"; Filename: "{app}\bin\{#AppExeName}"; Parameters: "/icon"; Tasks: desktopIcon
Name: "{userstartup}\{#AppName}"; Filename: "{app}\bin\Prowlarr.exe"; WorkingDir: "{app}\bin"; Tasks: startupShortcut
[InstallDelete]
Name: "{app}"; Type: filesandordirs
Name: "{app}\bin"; Type: filesandordirs
[Run]
Filename: "{app}\Prowlarr.Console.exe"; StatusMsg: "Removing previous Windows Service"; Parameters: "/u /exitimmediately"; Flags: runhidden waituntilterminated;
Filename: "{app}\Prowlarr.Console.exe"; Description: "Enable Access from Other Devices"; StatusMsg: "Enabling Remote access"; Parameters: "/registerurl /exitimmediately"; Flags: postinstall runascurrentuser runhidden waituntilterminated; Tasks: startupShortcut none;
Filename: "{app}\Prowlarr.Console.exe"; StatusMsg: "Installing Windows Service"; Parameters: "/i /exitimmediately"; Flags: runhidden waituntilterminated; Tasks: windowsService
Filename: "{app}\Prowlarr.exe"; Description: "Open Prowlarr Web UI"; Flags: postinstall skipifsilent nowait; Tasks: windowsService;
Filename: "{app}\Prowlarr.exe"; Description: "Start Prowlarr"; Flags: postinstall skipifsilent nowait; Tasks: startupShortcut none;
Filename: "{app}\bin\Prowlarr.Console.exe"; StatusMsg: "Removing previous Windows Service"; Parameters: "/u /exitimmediately"; Flags: runhidden waituntilterminated;
Filename: "{app}\bin\Prowlarr.Console.exe"; Description: "Enable Access from Other Devices"; StatusMsg: "Enabling Remote access"; Parameters: "/registerurl /exitimmediately"; Flags: postinstall runascurrentuser runhidden waituntilterminated; Tasks: startupShortcut none;
Filename: "{app}\bin\Prowlarr.Console.exe"; StatusMsg: "Installing Windows Service"; Parameters: "/i /exitimmediately"; Flags: runhidden waituntilterminated; Tasks: windowsService
Filename: "{app}\bin\Prowlarr.exe"; Description: "Open Prowlarr Web UI"; Flags: postinstall skipifsilent nowait; Tasks: windowsService;
Filename: "{app}\bin\Prowlarr.exe"; Description: "Start Prowlarr"; Flags: postinstall skipifsilent nowait; Tasks: startupShortcut none;
[UninstallRun]
Filename: "{app}\prowlarr.console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist
Filename: "{app}\bin\prowlarr.console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist
[Code]
function PrepareToInstall(var NeedsRestart: Boolean): String;

View File

@@ -27,11 +27,11 @@ dotnet clean $slnFile -c Release
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
dotnet new tool-manifest
dotnet tool install --version 6.2.3 Swashbuckle.AspNetCore.Cli
dotnet tool install --version 6.3.0 Swashbuckle.AspNetCore.Cli
dotnet tool run swagger tofile --output ./src/Prowlarr.Api.V1/openapi.json "$outputFolder/net6.0/$RUNTIME/prowlarr.console.dll" v1 &
sleep 10
sleep 30
kill %1

View File

@@ -47,10 +47,6 @@ class Link extends Component {
el = 'a';
linkProps.href = to;
linkProps.target = target || '_self';
} else if (to.startsWith(`${window.Prowlarr.urlBase}/`)) {
el = RouterLink;
linkProps.to = to;
linkProps.target = target;
} else {
el = RouterLink;
linkProps.to = `${window.Prowlarr.urlBase}/${to.replace(/^\//, '')}`;

View File

@@ -37,7 +37,8 @@ function ModalError(props) {
{translate('Close')}
</Button>
</ModalFooter>
</ModalContent>);
</ModalContent>
);
}
ModalError.propTypes = {

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Scrollbars } from 'react-custom-scrollbars';
import { Scrollbars } from 'react-custom-scrollbars-2';
import { scrollDirections } from 'Helpers/Props';
import styles from './OverlayScroller.css';

View File

@@ -37,6 +37,12 @@ const columns = [
isSortable: true,
isVisible: true
},
{
name: 'description',
label: translate('Description'),
isSortable: false,
isVisible: true
},
{
name: 'privacy',
label: translate('Privacy'),
@@ -136,12 +142,12 @@ class AddIndexerModalContent extends Component {
return true;
});
const errorMessage = getErrorMessage(error, 'Unable to load indexers');
const errorMessage = getErrorMessage(error, translate('UnableToLoadIndexers'));
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Add Indexer
{translate('AddIndexer')}
</ModalHeader>
<ModalBody

View File

@@ -32,6 +32,7 @@ class SelectIndexerRow extends Component {
privacy,
name,
language,
description,
isExistingIndexer
} = this.props;
@@ -61,6 +62,10 @@ class SelectIndexerRow extends Component {
{language}
</TableRowCell>
<TableRowCell>
{description}
</TableRowCell>
<TableRowCell>
{translate(firstCharToUpper(privacy))}
</TableRowCell>
@@ -74,6 +79,7 @@ SelectIndexerRow.propTypes = {
protocol: PropTypes.string.isRequired,
privacy: PropTypes.string.isRequired,
language: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
implementation: PropTypes.string.isRequired,
onIndexerSelect: PropTypes.func.isRequired,
isExistingIndexer: PropTypes.bool.isRequired

View File

@@ -4,6 +4,7 @@ import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import DescriptionListItemDescription from 'Components/DescriptionList/DescriptionListItemDescription';
import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionListItemTitle';
import FieldSet from 'Components/FieldSet';
import Link from 'Components/Link/Link';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
@@ -20,6 +21,7 @@ function IndexerInfoModalContent(props) {
language,
indexerUrls,
protocol,
capabilities,
onModalClose
} = props;
@@ -30,41 +32,78 @@ function IndexerInfoModalContent(props) {
</ModalHeader>
<ModalBody>
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Id')}
data={id}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Description')}
data={description ? description : '-'}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Encoding')}
data={encoding ? encoding : '-'}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Language')}
data={language ?? '-'}
/>
<DescriptionListItemTitle>Indexer Site</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to={indexerUrls[0]}>{indexerUrls[0]}</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
<DescriptionListItemDescription>
{`${window.location.origin}${window.Prowlarr.urlBase}/${id}/api`}
</DescriptionListItemDescription>
</DescriptionList>
<FieldSet legend={translate('IndexerDetails')}>
<div className={styles.groups}>
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Id')}
data={id}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Description')}
data={description ? description : '-'}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Encoding')}
data={encoding ? encoding : '-'}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Language')}
data={language ?? '-'}
/>
<DescriptionListItemTitle>{translate('IndexerSite')}</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to={indexerUrls[0]}>{indexerUrls[0]}</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
<DescriptionListItemDescription>
{`${window.location.origin}${window.Prowlarr.urlBase}/${id}/api`}
</DescriptionListItemDescription>
</DescriptionList>
</div>
</FieldSet>
<FieldSet legend={translate('SearchCapabilities')}>
<div className={styles.groups}>
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('RawSearchSupported')}
data={capabilities.supportsRawSearch ? translate('Yes') : translate('No')}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('SearchTypes')}
data={capabilities.searchParams.length === 0 ? translate('NotSupported') : capabilities.searchParams[0]}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('TVSearchTypes')}
data={capabilities.tvSearchParams.length === 0 ? translate('NotSupported') : capabilities.tvSearchParams.join(', ')}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('MovieSearchTypes')}
data={capabilities.movieSearchParams.length === 0 ? translate('NotSupported') : capabilities.movieSearchParams.join(', ')}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('BookSearchTypes')}
data={capabilities.bookSearchParams.length === 0 ? translate('NotSupported') : capabilities.bookSearchParams.join(', ')}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('MusicSearchTypes')}
data={capabilities.musicSearchParams.length === 0 ? translate('NotSupported') : capabilities.musicSearchParams.join(', ')}
/>
</DescriptionList>
</div>
</FieldSet>
</ModalBody>
</ModalContent>
</ModalContent >
);
}
@@ -76,6 +115,7 @@ IndexerInfoModalContent.propTypes = {
language: PropTypes.string.isRequired,
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
protocol: PropTypes.string.isRequired,
capabilities: PropTypes.object.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -248,7 +248,7 @@ class QueryParameterModal extends Component {
onSelectionChange={this.onInputSelectionChange}
/>
<Button onPress={onModalClose}>
Close
{translate('Close')}
</Button>
</ModalFooter>
</ModalContent>

View File

@@ -37,7 +37,10 @@ class SearchFooter extends Component {
searchingReleases: false,
searchQuery: defaultSearchQuery || '',
searchIndexerIds: defaultIndexerIds,
searchCategories: defaultCategories
searchCategories: defaultCategories,
searchLimit: 100,
searchOffset: 0,
newSearch: true
};
}
@@ -115,11 +118,28 @@ class SearchFooter extends Component {
};
onSearchPress = () => {
this.props.onSearchPress(this.state.searchQuery, this.state.searchIndexerIds, this.state.searchCategories, this.state.searchType);
const {
searchLimit,
searchOffset,
searchQuery,
searchIndexerIds,
searchCategories,
searchType
} = this.state;
this.props.onSearchPress(searchQuery, searchIndexerIds, searchCategories, searchType, searchLimit, searchOffset);
this.setState({ searchOffset: searchOffset + 100, newSearch: false });
};
onSearchInputChange = ({ value }) => {
this.setState({ searchQuery: value });
this.setState({ searchQuery: value, newSearch: true, searchOffset: 0 });
};
onInputChange = ({ name, value }) => {
this.props.onInputChange({ name, value });
this.setState({ newSearch: true, searchOffset: 0 });
};
//
@@ -141,6 +161,7 @@ class SearchFooter extends Component {
searchQuery,
searchIndexerIds,
searchCategories,
newSearch,
isQueryParameterModalOpen,
queryModalOptions,
searchType
@@ -206,7 +227,7 @@ class SearchFooter extends Component {
name='searchIndexerIds'
value={searchIndexerIds}
isDisabled={isFetching}
onChange={onInputChange}
onChange={this.onInputChange}
/>
</div>
@@ -220,7 +241,7 @@ class SearchFooter extends Component {
name='searchCategories'
value={searchCategories}
isDisabled={isFetching}
onChange={onInputChange}
onChange={this.onInputChange}
/>
</div>
@@ -243,7 +264,7 @@ class SearchFooter extends Component {
isDisabled={isFetching || !hasIndexers || selectedCount === 0}
onPress={onBulkGrabPress}
>
{translate('Grab Releases')}
{translate('GrabReleases')}
</SpinnerButton>
}
@@ -253,7 +274,7 @@ class SearchFooter extends Component {
isDisabled={isFetching || !hasIndexers}
onPress={this.onSearchPress}
>
{translate('Search')}
{newSearch ? translate('Search') : translate('More')}
</SpinnerButton>
</div>
</div>

View File

@@ -196,8 +196,8 @@ class SearchIndex extends Component {
this.setState({ jumpToCharacter });
};
onSearchPress = (query, indexerIds, categories, type) => {
this.props.onSearchPress({ query, indexerIds, categories, type });
onSearchPress = (query, indexerIds, categories, type, limit, offset) => {
this.props.onSearchPress({ query, indexerIds, categories, type, limit, offset });
};
onBulkGrabPress = () => {

View File

@@ -5,13 +5,14 @@ import createAllIndexersSelector from './createAllIndexersSelector';
function createProfileInUseSelector(profileProp) {
return createSelector(
(state, { id }) => id,
(state) => state.settings.appProfiles.items,
createAllIndexersSelector(),
(id, indexers) => {
(id, profiles, indexers) => {
if (!id) {
return false;
}
if (_.some(indexers, { [profileProp]: id })) {
if (_.some(indexers, { [profileProp]: id }) || profiles.length <= 1) {
return true;
}

View File

@@ -25,17 +25,17 @@
"not chrome < 60"
],
"dependencies": {
"@fortawesome/fontawesome-free": "5.15.3",
"@fortawesome/fontawesome-svg-core": "1.2.35",
"@fortawesome/free-regular-svg-icons": "5.15.3",
"@fortawesome/free-solid-svg-icons": "5.15.3",
"@fortawesome/react-fontawesome": "0.1.14",
"@microsoft/signalr": "6.0.0",
"@sentry/browser": "6.15.0",
"@sentry/integrations": "6.15.0",
"chart.js": "3.2.0",
"@fortawesome/fontawesome-free": "6.1.1",
"@fortawesome/fontawesome-svg-core": "6.1.1",
"@fortawesome/free-regular-svg-icons": "6.1.1",
"@fortawesome/free-solid-svg-icons": "6.1.1",
"@fortawesome/react-fontawesome": "0.1.18",
"@microsoft/signalr": "6.0.3",
"@sentry/browser": "6.19.2",
"@sentry/integrations": "6.19.2",
"chart.js": "3.7.1",
"classnames": "2.3.1",
"clipboard": "2.0.8",
"clipboard": "2.0.10",
"connected-react-router": "6.9.1",
"element-class": "0.2.2",
"filesize": "6.3.0",
@@ -48,13 +48,13 @@
"moment": "2.29.1",
"mousetrap": "1.6.5",
"normalize.css": "8.0.1",
"prop-types": "15.7.2",
"qs": "6.10.1",
"prop-types": "15.8.1",
"qs": "6.10.3",
"react": "17.0.2",
"react-addons-shallow-compare": "15.6.3",
"react-async-script": "1.2.0",
"react-autosuggest": "10.1.0",
"react-custom-scrollbars": "4.2.1",
"react-custom-scrollbars-2": "4.4.0",
"react-dnd": "14.0.4",
"react-dnd-html5-backend": "14.0.2",
"react-dnd-multi-backend": "6.0.2",
@@ -78,41 +78,41 @@
"reselect": "4.0.0"
},
"devDependencies": {
"@babel/core": "7.16.0",
"@babel/eslint-parser": "7.16.3",
"@babel/plugin-proposal-class-properties": "7.16.0",
"@babel/plugin-proposal-decorators": "7.16.4",
"@babel/plugin-proposal-export-default-from": "7.16.0",
"@babel/plugin-proposal-export-namespace-from": "7.16.0",
"@babel/plugin-proposal-function-sent": "7.16.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.16.0",
"@babel/plugin-proposal-numeric-separator": "7.16.0",
"@babel/plugin-proposal-optional-chaining": "7.16.0",
"@babel/plugin-proposal-throw-expressions": "7.16.0",
"@babel/core": "7.17.8",
"@babel/eslint-parser": "7.17.0",
"@babel/plugin-proposal-class-properties": "7.16.7",
"@babel/plugin-proposal-decorators": "7.17.8",
"@babel/plugin-proposal-export-default-from": "7.16.7",
"@babel/plugin-proposal-export-namespace-from": "7.16.7",
"@babel/plugin-proposal-function-sent": "7.16.7",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7",
"@babel/plugin-proposal-numeric-separator": "7.16.7",
"@babel/plugin-proposal-optional-chaining": "7.16.7",
"@babel/plugin-proposal-throw-expressions": "7.16.7",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.16.4",
"@babel/preset-react": "7.16.0",
"autoprefixer": "10.2.5",
"babel-loader": "8.2.3",
"@babel/preset-env": "7.16.11",
"@babel/preset-react": "7.16.7",
"autoprefixer": "10.4.4",
"babel-loader": "8.2.4",
"babel-plugin-inline-classnames": "2.0.1",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
"core-js": "3.11.0",
"css-loader": "6.5.1",
"eslint": "8.3.0",
"core-js": "3.21.1",
"css-loader": "6.7.1",
"eslint": "8.11.0",
"eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.25.3",
"eslint-plugin-react": "7.27.1",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-react": "7.29.4",
"eslint-plugin-simple-import-sort": "7.0.0",
"esprint": "3.1.0",
"esprint": "3.3.0",
"file-loader": "6.2.0",
"filemanager-webpack-plugin": "6.1.7",
"html-webpack-plugin": "5.5.0",
"loader-utils": "^3.0.0",
"mini-css-extract-plugin": "2.4.5",
"postcss": "8.3.11",
"mini-css-extract-plugin": "2.6.0",
"postcss": "8.4.12",
"postcss-color-function": "4.1.0",
"postcss-loader": "6.2.0",
"postcss-mixins": "8.1.0",
"postcss-loader": "6.2.1",
"postcss-mixins": "9.0.2",
"postcss-nested": "5.0.6",
"postcss-simple-vars": "6.0.3",
"postcss-url": "10.1.3",
@@ -121,11 +121,11 @@
"run-sequence": "2.2.1",
"streamqueue": "1.1.2",
"style-loader": "3.3.1",
"stylelint": "14.1.0",
"stylelint": "14.6.0",
"stylelint-order": "5.0.0",
"url-loader": "4.1.1",
"webpack": "5.64.2",
"webpack-cli": "4.9.1",
"webpack": "5.70.0",
"webpack-cli": "4.9.2",
"webpack-livereload-plugin": "3.0.2"
}
}

View File

@@ -94,10 +94,10 @@
<!-- Standard testing packages -->
<ItemGroup Condition="'$(TestProject)'=='true'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NunitXml.TestLogger" Version="3.0.97" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
<PackageReference Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" />
</ItemGroup>

View File

@@ -20,7 +20,7 @@ namespace NzbDrone.Automation.Test
public abstract class AutomationTest
{
private NzbDroneRunner _runner;
protected RemoteWebDriver driver;
protected WebDriver driver;
public AutomationTest()
{

View File

@@ -1,16 +1,15 @@
using System;
using System.Threading;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using OpenQA.Selenium.Support.UI;
namespace NzbDrone.Automation.Test.PageModel
{
public class PageBase
{
private readonly RemoteWebDriver _driver;
private readonly WebDriver _driver;
public PageBase(RemoteWebDriver driver)
public PageBase(WebDriver driver)
{
_driver = driver;
driver.Manage().Window.Maximize();

View File

@@ -3,8 +3,8 @@
<TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Selenium.Support" Version="3.141.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="90.0.4430.2400" />
<PackageReference Include="Selenium.Support" Version="4.1.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="99.0.4844.5100" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />

View File

@@ -23,6 +23,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"https://hd-space.org/index.php?page=login: uid=mySecret&pwd=mySecret")]
[TestCase(@"https://beyond-hd.me/api/torrents/2b51db35e1912ffc138825a12b9933d2")]
[TestCase(@"Req: [POST] https://www3.yggtorrent.nz/user/login: id=mySecret&pass=mySecret&ci_csrf_token=2b51db35e1912ffc138825a12b9933d2")]
[TestCase(@"https://torrentseeds.org/api/torrents/filter?api_token=2b51db35e1912ffc138825a12b9933d2&name=&sortField=created_at&sortDirection=desc&perPage=100&page=1")]
//Indexer Responses

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using NzbDrone.Common.EnvironmentInfo;
@@ -24,7 +24,8 @@ namespace NzbDrone.Common.Disk
"/boot",
"/lib",
"/sbin",
"/proc"
"/proc",
"/usr/bin"
};
}
}

View File

@@ -75,7 +75,7 @@ namespace NzbDrone.Common.Http
do
{
request.Url += new HttpUri(response.Headers.GetSingleValue("Location"));
request.Url = new HttpUri(response.RedirectUrl);
autoRedirectChain.Add(request.Url.ToString());
_logger.Trace("Redirected to {0}", request.Url);

View File

@@ -9,7 +9,7 @@ namespace NzbDrone.Common.Http
{
public class HttpResponse
{
private static readonly Regex RegexSetCookie = new Regex("^(.*?)=(.*?)(?:;|$)", RegexOptions.Compiled);
private static readonly Regex RegexRefresh = new Regex("^(.*?url)=(.*?)(?:;|$)", RegexOptions.Compiled);
public HttpResponse(HttpRequest request, HttpHeader headers, CookieCollection cookies, byte[] binaryData, long elapsedTime = 0, HttpStatusCode statusCode = HttpStatusCode.OK)
{
@@ -67,7 +67,8 @@ namespace NzbDrone.Common.Http
StatusCode == HttpStatusCode.MovedPermanently ||
StatusCode == HttpStatusCode.RedirectMethod ||
StatusCode == HttpStatusCode.TemporaryRedirect ||
StatusCode == HttpStatusCode.Found;
StatusCode == HttpStatusCode.Found ||
Headers.ContainsKey("Refresh");
public string RedirectUrl
{
@@ -76,6 +77,20 @@ namespace NzbDrone.Common.Http
var newUrl = Headers["Location"];
if (newUrl == null)
{
newUrl = Headers["Refresh"];
if (newUrl == null)
{
return string.Empty;
}
var match = RegexRefresh.Match(newUrl);
if (match.Success)
{
return (Request.Url += new HttpUri(match.Groups[2].Value)).FullUri;
}
return string.Empty;
}

View File

@@ -11,7 +11,7 @@ namespace NzbDrone.Common.Instrumentation
private static readonly Regex[] CleansingRules = new[]
{
// Url
new Regex(@"(?<=[?&: ;])(apikey|(?:access[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pwd)=(?<secret>[^&=]+?)(?= |&|$|<)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=[?&: ;])(apikey|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pwd)=(?<secret>[^&=]+?)(?= |&|$|<)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"rss\.torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"rss\.torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),

View File

@@ -4,14 +4,14 @@
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DryIoc.dll" Version="4.8.1" />
<PackageReference Include="DryIoc.dll" Version="4.8.8" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.2" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NLog" Version="4.7.9" />
<PackageReference Include="Sentry" Version="3.8.3" />
<PackageReference Include="SharpZipLib" Version="1.3.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NLog" Version="4.7.14" />
<PackageReference Include="Sentry" Version="3.15.0" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />

View File

@@ -0,0 +1,54 @@
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Applications;
using NzbDrone.Core.Housekeeping.Housekeepers;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
{
[TestFixture]
public class CleanupOrphanedApplicationFixture : DbTest<CleanupOrphanedApplicationStatus, ApplicationStatus>
{
private ApplicationDefinition _application;
[SetUp]
public void Setup()
{
_application = Builder<ApplicationDefinition>.CreateNew()
.BuildNew();
}
private void GivenApplication()
{
Db.Insert(_application);
}
[Test]
public void should_delete_orphaned_applicationstatus()
{
var status = Builder<ApplicationStatus>.CreateNew()
.With(h => h.ProviderId = _application.Id)
.BuildNew();
Db.Insert(status);
Subject.Clean();
AllStoredModels.Should().BeEmpty();
}
[Test]
public void should_not_delete_unorphaned_applicationstatus()
{
GivenApplication();
var status = Builder<ApplicationStatus>.CreateNew()
.With(h => h.ProviderId = _application.Id)
.BuildNew();
Db.Insert(status);
Subject.Clean();
AllStoredModels.Should().HaveCount(1);
AllStoredModels.Should().Contain(h => h.ProviderId == _application.Id);
}
}
}

View File

@@ -0,0 +1,56 @@
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.Flood;
using NzbDrone.Core.Housekeeping.Housekeepers;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
{
[TestFixture]
public class CleanupOrphanedDownloadClientStatusFixture : DbTest<CleanupOrphanedDownloadClientStatus, DownloadClientStatus>
{
private DownloadClientDefinition _downloadClient;
[SetUp]
public void Setup()
{
_downloadClient = Builder<DownloadClientDefinition>.CreateNew()
.With(c => c.Settings = new FloodSettings())
.BuildNew();
}
private void GivenClient()
{
Db.Insert(_downloadClient);
}
[Test]
public void should_delete_orphaned_downloadclientstatus()
{
var status = Builder<DownloadClientStatus>.CreateNew()
.With(h => h.ProviderId = _downloadClient.Id)
.BuildNew();
Db.Insert(status);
Subject.Clean();
AllStoredModels.Should().BeEmpty();
}
[Test]
public void should_not_delete_unorphaned_downloadclientstatus()
{
GivenClient();
var status = Builder<DownloadClientStatus>.CreateNew()
.With(h => h.ProviderId = _downloadClient.Id)
.BuildNew();
Db.Insert(status);
Subject.Clean();
AllStoredModels.Should().HaveCount(1);
AllStoredModels.Should().Contain(h => h.ProviderId == _downloadClient.Id);
}
}
}

View File

@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Applications;
using NzbDrone.Core.Housekeeping.Housekeepers;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.ThingiProvider.Status;
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
{
[TestFixture]
public class FixFutureApplicationStatusTimesFixture : CoreTest<FixFutureApplicationStatusTimes>
{
[Test]
public void should_set_disabled_till_when_its_too_far_in_the_future()
{
var disabledTillTime = EscalationBackOff.Periods[1];
var applicationStatuses = Builder<ApplicationStatus>.CreateListOfSize(5)
.All()
.With(t => t.DisabledTill = DateTime.UtcNow.AddDays(5))
.With(t => t.InitialFailure = DateTime.UtcNow.AddDays(-5))
.With(t => t.MostRecentFailure = DateTime.UtcNow.AddDays(-5))
.With(t => t.EscalationLevel = 1)
.BuildListOfNew();
Mocker.GetMock<IApplicationStatusRepository>()
.Setup(s => s.All())
.Returns(applicationStatuses);
Subject.Clean();
Mocker.GetMock<IApplicationStatusRepository>()
.Verify(v => v.UpdateMany(
It.Is<List<ApplicationStatus>>(i => i.All(
s => s.DisabledTill.Value <= DateTime.UtcNow.AddMinutes(disabledTillTime)))));
}
[Test]
public void should_set_initial_failure_when_its_in_the_future()
{
var applicationStatuses = Builder<ApplicationStatus>.CreateListOfSize(5)
.All()
.With(t => t.DisabledTill = DateTime.UtcNow.AddDays(-5))
.With(t => t.InitialFailure = DateTime.UtcNow.AddDays(5))
.With(t => t.MostRecentFailure = DateTime.UtcNow.AddDays(-5))
.With(t => t.EscalationLevel = 1)
.BuildListOfNew();
Mocker.GetMock<IApplicationStatusRepository>()
.Setup(s => s.All())
.Returns(applicationStatuses);
Subject.Clean();
Mocker.GetMock<IApplicationStatusRepository>()
.Verify(v => v.UpdateMany(
It.Is<List<ApplicationStatus>>(i => i.All(
s => s.InitialFailure.Value <= DateTime.UtcNow))));
}
[Test]
public void should_set_most_recent_failure_when_its_in_the_future()
{
var applicationStatuses = Builder<ApplicationStatus>.CreateListOfSize(5)
.All()
.With(t => t.DisabledTill = DateTime.UtcNow.AddDays(-5))
.With(t => t.InitialFailure = DateTime.UtcNow.AddDays(-5))
.With(t => t.MostRecentFailure = DateTime.UtcNow.AddDays(5))
.With(t => t.EscalationLevel = 1)
.BuildListOfNew();
Mocker.GetMock<IApplicationStatusRepository>()
.Setup(s => s.All())
.Returns(applicationStatuses);
Subject.Clean();
Mocker.GetMock<IApplicationStatusRepository>()
.Verify(v => v.UpdateMany(
It.Is<List<ApplicationStatus>>(i => i.All(
s => s.MostRecentFailure.Value <= DateTime.UtcNow))));
}
[Test]
public void should_not_change_statuses_when_times_are_in_the_past()
{
var indexerStatuses = Builder<ApplicationStatus>.CreateListOfSize(5)
.All()
.With(t => t.DisabledTill = DateTime.UtcNow.AddDays(-5))
.With(t => t.InitialFailure = DateTime.UtcNow.AddDays(-5))
.With(t => t.MostRecentFailure = DateTime.UtcNow.AddDays(-5))
.With(t => t.EscalationLevel = 0)
.BuildListOfNew();
Mocker.GetMock<IApplicationStatusRepository>()
.Setup(s => s.All())
.Returns(indexerStatuses);
Subject.Clean();
Mocker.GetMock<IApplicationStatusRepository>()
.Verify(v => v.UpdateMany(
It.Is<List<ApplicationStatus>>(i => i.Count == 0)));
}
}
}

View File

@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Download;
using NzbDrone.Core.Housekeeping.Housekeepers;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.ThingiProvider.Status;
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
{
[TestFixture]
public class FixFutureDownloadClientStatusTimesFixture : CoreTest<FixFutureDownloadClientStatusTimes>
{
[Test]
public void should_set_disabled_till_when_its_too_far_in_the_future()
{
var disabledTillTime = EscalationBackOff.Periods[1];
var clientStatuses = Builder<DownloadClientStatus>.CreateListOfSize(5)
.All()
.With(t => t.DisabledTill = DateTime.UtcNow.AddDays(5))
.With(t => t.InitialFailure = DateTime.UtcNow.AddDays(-5))
.With(t => t.MostRecentFailure = DateTime.UtcNow.AddDays(-5))
.With(t => t.EscalationLevel = 1)
.BuildListOfNew();
Mocker.GetMock<IDownloadClientStatusRepository>()
.Setup(s => s.All())
.Returns(clientStatuses);
Subject.Clean();
Mocker.GetMock<IDownloadClientStatusRepository>()
.Verify(v => v.UpdateMany(
It.Is<List<DownloadClientStatus>>(i => i.All(
s => s.DisabledTill.Value <= DateTime.UtcNow.AddMinutes(disabledTillTime)))));
}
[Test]
public void should_set_initial_failure_when_its_in_the_future()
{
var clientStatuses = Builder<DownloadClientStatus>.CreateListOfSize(5)
.All()
.With(t => t.DisabledTill = DateTime.UtcNow.AddDays(-5))
.With(t => t.InitialFailure = DateTime.UtcNow.AddDays(5))
.With(t => t.MostRecentFailure = DateTime.UtcNow.AddDays(-5))
.With(t => t.EscalationLevel = 1)
.BuildListOfNew();
Mocker.GetMock<IDownloadClientStatusRepository>()
.Setup(s => s.All())
.Returns(clientStatuses);
Subject.Clean();
Mocker.GetMock<IDownloadClientStatusRepository>()
.Verify(v => v.UpdateMany(
It.Is<List<DownloadClientStatus>>(i => i.All(
s => s.InitialFailure.Value <= DateTime.UtcNow))));
}
[Test]
public void should_set_most_recent_failure_when_its_in_the_future()
{
var clientStatuses = Builder<DownloadClientStatus>.CreateListOfSize(5)
.All()
.With(t => t.DisabledTill = DateTime.UtcNow.AddDays(-5))
.With(t => t.InitialFailure = DateTime.UtcNow.AddDays(-5))
.With(t => t.MostRecentFailure = DateTime.UtcNow.AddDays(5))
.With(t => t.EscalationLevel = 1)
.BuildListOfNew();
Mocker.GetMock<IDownloadClientStatusRepository>()
.Setup(s => s.All())
.Returns(clientStatuses);
Subject.Clean();
Mocker.GetMock<IDownloadClientStatusRepository>()
.Verify(v => v.UpdateMany(
It.Is<List<DownloadClientStatus>>(i => i.All(
s => s.MostRecentFailure.Value <= DateTime.UtcNow))));
}
[Test]
public void should_not_change_statuses_when_times_are_in_the_past()
{
var clientStatuses = Builder<DownloadClientStatus>.CreateListOfSize(5)
.All()
.With(t => t.DisabledTill = DateTime.UtcNow.AddDays(-5))
.With(t => t.InitialFailure = DateTime.UtcNow.AddDays(-5))
.With(t => t.MostRecentFailure = DateTime.UtcNow.AddDays(-5))
.With(t => t.EscalationLevel = 0)
.BuildListOfNew();
Mocker.GetMock<IDownloadClientStatusRepository>()
.Setup(s => s.All())
.Returns(clientStatuses);
Subject.Clean();
Mocker.GetMock<IDownloadClientStatusRepository>()
.Verify(v => v.UpdateMany(
It.Is<List<DownloadClientStatus>>(i => i.Count == 0)));
}
}
}

View File

@@ -3,7 +3,7 @@
<TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.0.90" />
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="NBuilder" Version="6.1.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="YamlDotNet" Version="11.2.1" />

View File

@@ -116,6 +116,12 @@ namespace NzbDrone.Core.Applications.Lidarr
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Lidarr cannot connect to Prowlarr");
}
if (ex.Response.StatusCode == HttpStatusCode.SeeOther)
{
_logger.Error(ex, "Lidarr returned redirect and is invalid");
return new ValidationFailure("BaseUrl", "Lidarr url is invalid, Prowlarr cannot connect to Lidarr - are you missing a url base?");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
}

View File

@@ -116,6 +116,12 @@ namespace NzbDrone.Core.Applications.Radarr
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Radarr cannot connect to Prowlarr");
}
if (ex.Response.StatusCode == HttpStatusCode.SeeOther)
{
_logger.Error(ex, "Radarr returned redirect and is invalid");
return new ValidationFailure("BaseUrl", "Radarr url is invalid, Prowlarr cannot connect to Radarr - are you missing a url base?");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
}

View File

@@ -116,6 +116,12 @@ namespace NzbDrone.Core.Applications.Readarr
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Readarr cannot connect to Prowlarr");
}
if (ex.Response.StatusCode == HttpStatusCode.SeeOther)
{
_logger.Error(ex, "Readarr returned redirect and is invalid");
return new ValidationFailure("BaseUrl", "Readarr url is invalid, Prowlarr cannot connect to Readarr - are you missing a url base?");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
}

View File

@@ -116,6 +116,18 @@ namespace NzbDrone.Core.Applications.Sonarr
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Sonarr cannot connect to Prowlarr");
}
if (ex.Response.StatusCode == HttpStatusCode.SeeOther)
{
_logger.Error(ex, "Sonarr returned redirect and is invalid");
return new ValidationFailure("BaseUrl", "Sonarr url is invalid, Prowlarr cannot connect to Sonarr - are you missing a url base?");
}
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
{
_logger.Error(ex, "Sonarr not found");
return new ValidationFailure("BaseUrl", "Sonarr url is invalid, Prowlarr cannot connect to Sonarr. Is Sonarr running and accessible? Sonarr v2 is not supported.");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
}

View File

@@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
namespace NzbDrone.Core.Applications.Whisparr
{
public class Whisparr : ApplicationBase<WhisparrSettings>
{
public override string Name => "Whisparr";
private readonly IWhisparrV3Proxy _whisparrV3Proxy;
private readonly ICached<List<WhisparrIndexer>> _schemaCache;
private readonly IConfigFileProvider _configFileProvider;
public Whisparr(ICacheManager cacheManager, IWhisparrV3Proxy whisparrV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, Logger logger)
: base(appIndexerMapService, logger)
{
_schemaCache = cacheManager.GetCache<List<WhisparrIndexer>>(GetType());
_whisparrV3Proxy = whisparrV3Proxy;
_configFileProvider = configFileProvider;
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();
var testIndexer = new IndexerDefinition
{
Id = 0,
Name = "Test",
Protocol = DownloadProtocol.Usenet,
Capabilities = new IndexerCapabilities()
};
foreach (var cat in NewznabStandardCategory.AllCats)
{
testIndexer.Capabilities.Categories.AddCategoryMapping(1, cat);
}
try
{
failures.AddIfNotNull(_whisparrV3Proxy.TestConnection(BuildWhisparrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Whisparr"));
}
return new ValidationResult(failures);
}
public override List<AppIndexerMap> GetIndexerMappings()
{
var indexers = _whisparrV3Proxy.GetIndexers(Settings)
.Where(i => i.Implementation == "Newznab" || i.Implementation == "Torznab");
var mappings = new List<AppIndexerMap>();
foreach (var indexer in indexers)
{
if ((string)indexer.Fields.FirstOrDefault(x => x.Name == "apiKey")?.Value == _configFileProvider.ApiKey)
{
var match = AppIndexerRegex.Match((string)indexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value);
if (match.Groups["indexer"].Success && int.TryParse(match.Groups["indexer"].Value, out var indexerId))
{
//Add parsed mapping if it's mapped to a Indexer in this Prowlarr instance
mappings.Add(new AppIndexerMap { RemoteIndexerId = indexer.Id, IndexerId = indexerId });
}
}
}
return mappings;
}
public override void AddIndexer(IndexerDefinition indexer)
{
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
{
var radarrIndexer = BuildWhisparrIndexer(indexer, indexer.Protocol);
var remoteIndexer = _whisparrV3Proxy.AddIndexer(radarrIndexer, Settings);
_appIndexerMapService.Insert(new AppIndexerMap { AppId = Definition.Id, IndexerId = indexer.Id, RemoteIndexerId = remoteIndexer.Id });
}
}
public override void RemoveIndexer(int indexerId)
{
var appMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
var indexerMapping = appMappings.FirstOrDefault(m => m.IndexerId == indexerId);
if (indexerMapping != null)
{
//Remove Indexer remotely and then remove the mapping
_whisparrV3Proxy.RemoveIndexer(indexerMapping.RemoteIndexerId, Settings);
_appIndexerMapService.Delete(indexerMapping.Id);
}
}
public override void UpdateIndexer(IndexerDefinition indexer)
{
_logger.Debug("Updating indexer {0} [{1}]", indexer.Name, indexer.Id);
var appMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
var indexerMapping = appMappings.FirstOrDefault(m => m.IndexerId == indexer.Id);
var radarrIndexer = BuildWhisparrIndexer(indexer, indexer.Protocol, indexerMapping?.RemoteIndexerId ?? 0);
var remoteIndexer = _whisparrV3Proxy.GetIndexer(indexerMapping.RemoteIndexerId, Settings);
if (remoteIndexer != null)
{
_logger.Debug("Remote indexer found, syncing with current settings");
if (!radarrIndexer.Equals(remoteIndexer))
{
_whisparrV3Proxy.UpdateIndexer(radarrIndexer, Settings);
}
}
else
{
_appIndexerMapService.Delete(indexerMapping.Id);
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
{
_logger.Debug("Remote indexer not found, re-adding {0} to Whisparr", indexer.Name);
radarrIndexer.Id = 0;
var newRemoteIndexer = _whisparrV3Proxy.AddIndexer(radarrIndexer, Settings);
_appIndexerMapService.Insert(new AppIndexerMap { AppId = Definition.Id, IndexerId = indexer.Id, RemoteIndexerId = newRemoteIndexer.Id });
}
else
{
_logger.Debug("Remote indexer not found for {0}, skipping re-add to Radarr due to indexer capabilities", indexer.Name);
}
}
}
private WhisparrIndexer BuildWhisparrIndexer(IndexerDefinition indexer, DownloadProtocol protocol, int id = 0)
{
var cacheKey = $"{Settings.BaseUrl}";
var schemas = _schemaCache.Get(cacheKey, () => _whisparrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
var newznab = schemas.Where(i => i.Implementation == "Newznab").First();
var torznab = schemas.Where(i => i.Implementation == "Torznab").First();
var schema = protocol == DownloadProtocol.Usenet ? newznab : torznab;
var whisparrIndexer = new WhisparrIndexer
{
Id = id,
Name = $"{indexer.Name} (Prowlarr)",
EnableRss = indexer.Enable && indexer.AppProfile.Value.EnableRss,
EnableAutomaticSearch = indexer.Enable && indexer.AppProfile.Value.EnableAutomaticSearch,
EnableInteractiveSearch = indexer.Enable && indexer.AppProfile.Value.EnableInteractiveSearch,
Priority = indexer.Priority,
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
ConfigContract = schema.ConfigContract,
Fields = schema.Fields,
};
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
return whisparrIndexer;
}
}
}

View File

@@ -0,0 +1,22 @@
namespace NzbDrone.Core.Applications.Whisparr
{
public class WhisparrField
{
public int Order { get; set; }
public string Name { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public string HelpText { get; set; }
public string HelpLink { get; set; }
public object Value { get; set; }
public string Type { get; set; }
public bool Advanced { get; set; }
public string Section { get; set; }
public string Hidden { get; set; }
public WhisparrField Clone()
{
return (WhisparrField)MemberwiseClone();
}
}
}

View File

@@ -0,0 +1,44 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
namespace NzbDrone.Core.Applications.Whisparr
{
public class WhisparrIndexer
{
public int Id { get; set; }
public bool EnableRss { get; set; }
public bool EnableAutomaticSearch { get; set; }
public bool EnableInteractiveSearch { get; set; }
public int Priority { get; set; }
public string Name { get; set; }
public string ImplementationName { get; set; }
public string Implementation { get; set; }
public string ConfigContract { get; set; }
public string InfoLink { get; set; }
public HashSet<int> Tags { get; set; }
public List<WhisparrField> Fields { get; set; }
public bool Equals(WhisparrIndexer other)
{
if (ReferenceEquals(null, other))
{
return false;
}
var baseUrl = (string)Fields.FirstOrDefault(x => x.Name == "baseUrl").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value;
var apiPath = (string)Fields.FirstOrDefault(x => x.Name == "apiPath").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
var apiKey = (string)Fields.FirstOrDefault(x => x.Name == "apiKey").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiKey").Value;
var cats = JToken.DeepEquals((JArray)Fields.FirstOrDefault(x => x.Name == "categories").Value, (JArray)other.Fields.FirstOrDefault(x => x.Name == "categories").Value);
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
other.Name == Name &&
other.Implementation == Implementation &&
other.Priority == Priority &&
other.Id == Id &&
apiKey && apiPath && baseUrl && cats;
}
}
}

View File

@@ -0,0 +1,46 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Applications.Whisparr
{
public class WhisparrSettingsValidator : AbstractValidator<WhisparrSettings>
{
public WhisparrSettingsValidator()
{
RuleFor(c => c.BaseUrl).IsValidUrl();
RuleFor(c => c.ProwlarrUrl).IsValidUrl();
RuleFor(c => c.ApiKey).NotEmpty();
RuleFor(c => c.SyncCategories).NotEmpty();
}
}
public class WhisparrSettings : IApplicationSettings
{
private static readonly WhisparrSettingsValidator Validator = new WhisparrSettingsValidator();
public WhisparrSettings()
{
SyncCategories = new[] { 6000, 6010, 6020, 6030, 6040, 6045, 6050, 6070, 6080, 6090 };
}
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Whisparr sees it, including http(s)://, port, and urlbase if needed", Placeholder = "http://localhost:9696")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Whisparr Server", HelpText = "URL used to connect to Whisparr server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:6969")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Whisparr in Settings/General")]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]
public IEnumerable<int> SyncCategories { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -0,0 +1,7 @@
namespace NzbDrone.Core.Applications.Whisparr
{
public class WhisparrStatus
{
public string Version { get; set; }
}
}

View File

@@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using FluentValidation.Results;
using Newtonsoft.Json;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Applications.Whisparr
{
public interface IWhisparrV3Proxy
{
WhisparrIndexer AddIndexer(WhisparrIndexer indexer, WhisparrSettings settings);
List<WhisparrIndexer> GetIndexers(WhisparrSettings settings);
WhisparrIndexer GetIndexer(int indexerId, WhisparrSettings settings);
List<WhisparrIndexer> GetIndexerSchema(WhisparrSettings settings);
void RemoveIndexer(int indexerId, WhisparrSettings settings);
WhisparrIndexer UpdateIndexer(WhisparrIndexer indexer, WhisparrSettings settings);
ValidationFailure TestConnection(WhisparrIndexer indexer, WhisparrSettings settings);
}
public class WhisparrV3Proxy : IWhisparrV3Proxy
{
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
public WhisparrV3Proxy(IHttpClient httpClient, Logger logger)
{
_httpClient = httpClient;
_logger = logger;
}
public WhisparrStatus GetStatus(WhisparrSettings settings)
{
var request = BuildRequest(settings, "/api/v3/system/status", HttpMethod.Get);
return Execute<WhisparrStatus>(request);
}
public List<WhisparrIndexer> GetIndexers(WhisparrSettings settings)
{
var request = BuildRequest(settings, "/api/v3/indexer", HttpMethod.Get);
return Execute<List<WhisparrIndexer>>(request);
}
public WhisparrIndexer GetIndexer(int indexerId, WhisparrSettings settings)
{
try
{
var request = BuildRequest(settings, $"/api/v3/indexer/{indexerId}", HttpMethod.Get);
return Execute<WhisparrIndexer>(request);
}
catch (HttpException ex)
{
if (ex.Response.StatusCode != HttpStatusCode.NotFound)
{
throw;
}
}
return null;
}
public void RemoveIndexer(int indexerId, WhisparrSettings settings)
{
var request = BuildRequest(settings, $"/api/v3/indexer/{indexerId}", HttpMethod.Delete);
_httpClient.Execute(request);
}
public List<WhisparrIndexer> GetIndexerSchema(WhisparrSettings settings)
{
var request = BuildRequest(settings, "/api/v3/indexer/schema", HttpMethod.Get);
return Execute<List<WhisparrIndexer>>(request);
}
public WhisparrIndexer AddIndexer(WhisparrIndexer indexer, WhisparrSettings settings)
{
var request = BuildRequest(settings, "/api/v3/indexer", HttpMethod.Post);
request.SetContent(indexer.ToJson());
return Execute<WhisparrIndexer>(request);
}
public WhisparrIndexer UpdateIndexer(WhisparrIndexer indexer, WhisparrSettings settings)
{
var request = BuildRequest(settings, $"/api/v3/indexer/{indexer.Id}", HttpMethod.Put);
request.SetContent(indexer.ToJson());
return Execute<WhisparrIndexer>(request);
}
public ValidationFailure TestConnection(WhisparrIndexer indexer, WhisparrSettings settings)
{
var request = BuildRequest(settings, $"/api/v3/indexer/test", HttpMethod.Post);
request.SetContent(indexer.ToJson());
try
{
Execute<WhisparrIndexer>(request);
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
{
_logger.Error(ex, "API Key is invalid");
return new ValidationFailure("ApiKey", "API Key is invalid");
}
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Error(ex, "Prowlarr URL is invalid");
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Whisparr cannot connect to Prowlarr");
}
if (ex.Response.StatusCode == HttpStatusCode.SeeOther)
{
_logger.Error(ex, "Whisparr returned redirect and is invalid");
return new ValidationFailure("BaseUrl", "Whisparr url is invalid, Prowlarr cannot connect to Whisparr - are you missing a url base?");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("", "Unable to send test message");
}
return null;
}
private HttpRequest BuildRequest(WhisparrSettings settings, string resource, HttpMethod method)
{
var baseUrl = settings.BaseUrl.TrimEnd('/');
var request = new HttpRequestBuilder(baseUrl).Resource(resource)
.SetHeader("X-Api-Key", settings.ApiKey)
.Build();
request.Headers.ContentType = "application/json";
request.Method = method;
request.AllowAutoRedirect = true;
return request;
}
private TResource Execute<TResource>(HttpRequest request)
where TResource : new()
{
var response = _httpClient.Execute(request);
var results = JsonConvert.DeserializeObject<TResource>(response.Content);
return results;
}
}
}

View File

@@ -90,6 +90,8 @@ namespace NzbDrone.Core.Backup
_archiveService.CreateZip(backupPath, _diskProvider.GetFiles(_backupTempFolder, SearchOption.TopDirectoryOnly));
Cleanup();
_logger.ProgressDebug("Backup zip created");
}

View File

@@ -66,7 +66,7 @@ namespace NzbDrone.Core.Datastore
_updateSql = GetUpdateSql(_properties);
}
protected virtual SqlBuilder Builder() => new SqlBuilder();
protected virtual SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType);
protected virtual List<TModel> Query(SqlBuilder builder) => _database.Query<TModel>(builder).ToList();

View File

@@ -0,0 +1,19 @@
using System;
using System.Data;
using Dapper;
namespace NzbDrone.Core.Datastore.Converters
{
public class DapperTimeSpanConverter : SqlMapper.TypeHandler<TimeSpan>
{
public override void SetValue(IDbDataParameter parameter, TimeSpan value)
{
parameter.Value = value.ToString();
}
public override TimeSpan Parse(object value)
{
return TimeSpan.Parse((string)value);
}
}
}

View File

@@ -42,21 +42,21 @@ namespace NzbDrone.Core.Datastore
public static SqlBuilder Where<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
{
var wb = new WhereBuilder(filter, true, builder.Sequence);
var wb = GetWhereBuilder(builder.DatabaseType, filter, true, builder.Sequence);
return builder.Where(wb.ToString(), wb.Parameters);
}
public static SqlBuilder OrWhere<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
{
var wb = new WhereBuilder(filter, true, builder.Sequence);
var wb = GetWhereBuilder(builder.DatabaseType, filter, true, builder.Sequence);
return builder.OrWhere(wb.ToString(), wb.Parameters);
}
public static SqlBuilder Join<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
{
var wb = new WhereBuilder(filter, false, builder.Sequence);
var wb = GetWhereBuilder(builder.DatabaseType, filter, false, builder.Sequence);
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
@@ -65,7 +65,7 @@ namespace NzbDrone.Core.Datastore
public static SqlBuilder LeftJoin<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
{
var wb = new WhereBuilder(filter, false, builder.Sequence);
var wb = GetWhereBuilder(builder.DatabaseType, filter, false, builder.Sequence);
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
@@ -138,6 +138,18 @@ namespace NzbDrone.Core.Datastore
return sb.ToString();
}
private static WhereBuilder GetWhereBuilder(DatabaseType databaseType, Expression filter, bool requireConcrete, int seq)
{
if (databaseType == DatabaseType.PostgreSQL)
{
return new WhereBuilderPostgres(filter, requireConcrete, seq);
}
else
{
return new WhereBuilderSqlite(filter, requireConcrete, seq);
}
}
private static Dictionary<string, object> ToDictionary(this DynamicParameters dynamicParams)
{
var argsDictionary = new Dictionary<string, object>();

View File

@@ -8,9 +8,17 @@ namespace NzbDrone.Core.Datastore
public class SqlBuilder
{
private readonly Dictionary<string, Clauses> _data = new Dictionary<string, Clauses>();
private readonly DatabaseType _databaseType;
public SqlBuilder(DatabaseType databaseType)
{
_databaseType = databaseType;
}
public int Sequence { get; private set; }
public DatabaseType DatabaseType => _databaseType;
public Template AddTemplate(string sql, dynamic parameters = null) =>
new Template(this, sql, parameters);

View File

@@ -183,7 +183,7 @@ namespace NzbDrone.Core.Datastore
(db, parent) =>
{
var id = childIdSelector(parent);
return db.Query<TChild>(new SqlBuilder().Where<TChild>(x => x.Id == id)).SingleOrDefault();
return db.Query<TChild>(new SqlBuilder(db.DatabaseType).Where<TChild>(x => x.Id == id)).SingleOrDefault();
},
parent => childIdSelector(parent) > 0);
}

View File

@@ -105,6 +105,7 @@ namespace NzbDrone.Core.Datastore
SqlMapper.RemoveTypeMap(typeof(DateTime));
SqlMapper.AddTypeHandler(new DapperUtcConverter());
SqlMapper.AddTypeHandler(new DapperTimeSpanConverter());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<Dictionary<string, string>>());
SqlMapper.AddTypeHandler(new CookieConverter());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<int>>());

View File

@@ -1,389 +1,9 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Dapper;
namespace NzbDrone.Core.Datastore
{
public class WhereBuilder : ExpressionVisitor
public abstract class WhereBuilder : ExpressionVisitor
{
protected StringBuilder _sb;
private const DbType EnumerableMultiParameter = (DbType)(-1);
private readonly string _paramNamePrefix;
private readonly bool _requireConcreteValue = false;
private int _paramCount = 0;
private bool _gotConcreteValue = false;
public WhereBuilder(Expression filter, bool requireConcreteValue, int seq)
{
_paramNamePrefix = string.Format("Clause{0}", seq + 1);
_requireConcreteValue = requireConcreteValue;
_sb = new StringBuilder();
Parameters = new DynamicParameters();
if (filter != null)
{
Visit(filter);
}
}
public DynamicParameters Parameters { get; private set; }
private string AddParameter(object value, DbType? dbType = null)
{
_gotConcreteValue = true;
_paramCount++;
var name = _paramNamePrefix + "_P" + _paramCount;
Parameters.Add(name, value, dbType);
return '@' + name;
}
protected override Expression VisitBinary(BinaryExpression expression)
{
_sb.Append('(');
Visit(expression.Left);
_sb.AppendFormat(" {0} ", Decode(expression));
Visit(expression.Right);
_sb.Append(')');
return expression;
}
protected override Expression VisitMethodCall(MethodCallExpression expression)
{
var method = expression.Method.Name;
switch (expression.Method.Name)
{
case "Contains":
ParseContainsExpression(expression);
break;
case "StartsWith":
ParseStartsWith(expression);
break;
case "EndsWith":
ParseEndsWith(expression);
break;
default:
var msg = string.Format("'{0}' expressions are not yet implemented in the where clause expression tree parser.", method);
throw new NotImplementedException(msg);
}
return expression;
}
protected override Expression VisitMemberAccess(MemberExpression expression)
{
var tableName = expression?.Expression?.Type != null ? TableMapping.Mapper.TableNameMapping(expression.Expression.Type) : null;
var gotValue = TryGetRightValue(expression, out var value);
// Only use the SQL condition if the expression didn't resolve to an actual value
if (tableName != null && !gotValue)
{
_sb.Append($"\"{tableName}\".\"{expression.Member.Name}\"");
}
else
{
if (value != null)
{
// string is IEnumerable<Char> but we don't want to pick up that case
var type = value.GetType();
var typeInfo = type.GetTypeInfo();
var isEnumerable =
type != typeof(string) && (
typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
(typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>)));
var paramName = isEnumerable ? AddParameter(value, EnumerableMultiParameter) : AddParameter(value);
_sb.Append(paramName);
}
else
{
_gotConcreteValue = true;
_sb.Append("NULL");
}
}
return expression;
}
protected override Expression VisitConstant(ConstantExpression expression)
{
if (expression.Value != null)
{
var paramName = AddParameter(expression.Value);
_sb.Append(paramName);
}
else
{
_gotConcreteValue = true;
_sb.Append("NULL");
}
return expression;
}
private bool TryGetConstantValue(Expression expression, out object result)
{
result = null;
if (expression is ConstantExpression constExp)
{
result = constExp.Value;
return true;
}
return false;
}
private bool TryGetPropertyValue(MemberExpression expression, out object result)
{
result = null;
if (expression.Expression is MemberExpression nested)
{
// Value is passed in as a property on a parent entity
var container = (nested.Expression as ConstantExpression)?.Value;
if (container == null)
{
return false;
}
var entity = GetFieldValue(container, nested.Member);
result = GetFieldValue(entity, expression.Member);
return true;
}
return false;
}
private bool TryGetVariableValue(MemberExpression expression, out object result)
{
result = null;
// Value is passed in as a variable
if (expression.Expression is ConstantExpression nested)
{
result = GetFieldValue(nested.Value, expression.Member);
return true;
}
return false;
}
private bool TryGetRightValue(Expression expression, out object value)
{
value = null;
if (TryGetConstantValue(expression, out value))
{
return true;
}
var memberExp = expression as MemberExpression;
if (TryGetPropertyValue(memberExp, out value))
{
return true;
}
if (TryGetVariableValue(memberExp, out value))
{
return true;
}
return false;
}
private object GetFieldValue(object entity, MemberInfo member)
{
if (member.MemberType == MemberTypes.Field)
{
return (member as FieldInfo).GetValue(entity);
}
if (member.MemberType == MemberTypes.Property)
{
return (member as PropertyInfo).GetValue(entity);
}
throw new ArgumentException(string.Format("WhereBuilder could not get the value for {0}.{1}.", entity.GetType().Name, member.Name));
}
private bool IsNullVariable(Expression expression)
{
if (expression.NodeType == ExpressionType.Constant &&
TryGetConstantValue(expression, out var constResult) &&
constResult == null)
{
return true;
}
if (expression.NodeType == ExpressionType.MemberAccess &&
expression is MemberExpression member &&
((TryGetPropertyValue(member, out var result) && result == null) ||
(TryGetVariableValue(member, out result) && result == null)))
{
return true;
}
return false;
}
private string Decode(BinaryExpression expression)
{
if (IsNullVariable(expression.Right))
{
switch (expression.NodeType)
{
case ExpressionType.Equal: return "IS";
case ExpressionType.NotEqual: return "IS NOT";
}
}
switch (expression.NodeType)
{
case ExpressionType.AndAlso: return "AND";
case ExpressionType.And: return "AND";
case ExpressionType.Equal: return "=";
case ExpressionType.GreaterThan: return ">";
case ExpressionType.GreaterThanOrEqual: return ">=";
case ExpressionType.LessThan: return "<";
case ExpressionType.LessThanOrEqual: return "<=";
case ExpressionType.NotEqual: return "<>";
case ExpressionType.OrElse: return "OR";
case ExpressionType.Or: return "OR";
default: throw new NotSupportedException(string.Format("{0} statement is not supported", expression.NodeType.ToString()));
}
}
private void ParseContainsExpression(MethodCallExpression expression)
{
var list = expression.Object;
if (list != null && (list.Type == typeof(string)))
{
ParseStringContains(expression);
return;
}
ParseEnumerableContains(expression);
}
private void ParseEnumerableContains(MethodCallExpression body)
{
// Fish out the list and the item to compare
// It's in a different form for arrays and Lists
var list = body.Object;
Expression item;
if (list != null)
{
// Generic collection
item = body.Arguments[0];
}
else
{
// Static method
// Must be Enumerable.Contains(source, item)
if (body.Method.DeclaringType != typeof(Enumerable) || body.Arguments.Count != 2)
{
throw new NotSupportedException("Unexpected form of Enumerable.Contains");
}
list = body.Arguments[0];
item = body.Arguments[1];
}
_sb.Append('(');
Visit(item);
_sb.Append(" IN ");
// hardcode the integer list if it exists to bypass parameter limit
if (item.Type == typeof(int) && TryGetRightValue(list, out var value))
{
var items = (IEnumerable<int>)value;
_sb.Append('(');
_sb.Append(string.Join(", ", items));
_sb.Append(')');
_gotConcreteValue = true;
}
else
{
Visit(list);
}
_sb.Append(')');
}
private void ParseStringContains(MethodCallExpression body)
{
_sb.Append('(');
Visit(body.Object);
_sb.Append(" LIKE '%' || ");
Visit(body.Arguments[0]);
_sb.Append(" || '%')");
}
private void ParseStartsWith(MethodCallExpression body)
{
_sb.Append('(');
Visit(body.Object);
_sb.Append(" LIKE ");
Visit(body.Arguments[0]);
_sb.Append(" || '%')");
}
private void ParseEndsWith(MethodCallExpression body)
{
_sb.Append('(');
Visit(body.Object);
_sb.Append(" LIKE '%' || ");
Visit(body.Arguments[0]);
_sb.Append(')');
}
public override string ToString()
{
var sql = _sb.ToString();
if (_requireConcreteValue && !_gotConcreteValue)
{
var e = new InvalidOperationException("WhereBuilder requires a concrete condition");
e.Data.Add("sql", sql);
throw e;
}
return sql;
}
public DynamicParameters Parameters { get; protected set; }
}
}

View File

@@ -0,0 +1,374 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Dapper;
namespace NzbDrone.Core.Datastore
{
public class WhereBuilderPostgres : WhereBuilder
{
protected StringBuilder _sb;
private const DbType EnumerableMultiParameter = (DbType)(-1);
private readonly string _paramNamePrefix;
private readonly bool _requireConcreteValue = false;
private int _paramCount = 0;
private bool _gotConcreteValue = false;
public WhereBuilderPostgres(Expression filter, bool requireConcreteValue, int seq)
{
_paramNamePrefix = string.Format("Clause{0}", seq + 1);
_requireConcreteValue = requireConcreteValue;
_sb = new StringBuilder();
Parameters = new DynamicParameters();
if (filter != null)
{
Visit(filter);
}
}
private string AddParameter(object value, DbType? dbType = null)
{
_gotConcreteValue = true;
_paramCount++;
var name = _paramNamePrefix + "_P" + _paramCount;
Parameters.Add(name, value, dbType);
return '@' + name;
}
protected override Expression VisitBinary(BinaryExpression expression)
{
_sb.Append('(');
Visit(expression.Left);
_sb.AppendFormat(" {0} ", Decode(expression));
Visit(expression.Right);
_sb.Append(')');
return expression;
}
protected override Expression VisitMethodCall(MethodCallExpression expression)
{
var method = expression.Method.Name;
switch (expression.Method.Name)
{
case "Contains":
ParseContainsExpression(expression);
break;
case "StartsWith":
ParseStartsWith(expression);
break;
case "EndsWith":
ParseEndsWith(expression);
break;
default:
var msg = string.Format("'{0}' expressions are not yet implemented in the where clause expression tree parser.", method);
throw new NotImplementedException(msg);
}
return expression;
}
protected override Expression VisitMemberAccess(MemberExpression expression)
{
var tableName = expression?.Expression?.Type != null ? TableMapping.Mapper.TableNameMapping(expression.Expression.Type) : null;
var gotValue = TryGetRightValue(expression, out var value);
// Only use the SQL condition if the expression didn't resolve to an actual value
if (tableName != null && !gotValue)
{
_sb.Append($"\"{tableName}\".\"{expression.Member.Name}\"");
}
else
{
if (value != null)
{
// string is IEnumerable<Char> but we don't want to pick up that case
var type = value.GetType();
var typeInfo = type.GetTypeInfo();
var isEnumerable =
type != typeof(string) && (
typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
(typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>)));
var paramName = isEnumerable ? AddParameter(value, EnumerableMultiParameter) : AddParameter(value);
_sb.Append(paramName);
}
else
{
_gotConcreteValue = true;
_sb.Append("NULL");
}
}
return expression;
}
protected override Expression VisitConstant(ConstantExpression expression)
{
if (expression.Value != null)
{
var paramName = AddParameter(expression.Value);
_sb.Append(paramName);
}
else
{
_gotConcreteValue = true;
_sb.Append("NULL");
}
return expression;
}
private bool TryGetConstantValue(Expression expression, out object result)
{
result = null;
if (expression is ConstantExpression constExp)
{
result = constExp.Value;
return true;
}
return false;
}
private bool TryGetPropertyValue(MemberExpression expression, out object result)
{
result = null;
if (expression.Expression is MemberExpression nested)
{
// Value is passed in as a property on a parent entity
var container = (nested.Expression as ConstantExpression)?.Value;
if (container == null)
{
return false;
}
var entity = GetFieldValue(container, nested.Member);
result = GetFieldValue(entity, expression.Member);
return true;
}
return false;
}
private bool TryGetVariableValue(MemberExpression expression, out object result)
{
result = null;
// Value is passed in as a variable
if (expression.Expression is ConstantExpression nested)
{
result = GetFieldValue(nested.Value, expression.Member);
return true;
}
return false;
}
private bool TryGetRightValue(Expression expression, out object value)
{
value = null;
if (TryGetConstantValue(expression, out value))
{
return true;
}
var memberExp = expression as MemberExpression;
if (TryGetPropertyValue(memberExp, out value))
{
return true;
}
if (TryGetVariableValue(memberExp, out value))
{
return true;
}
return false;
}
private object GetFieldValue(object entity, MemberInfo member)
{
if (member.MemberType == MemberTypes.Field)
{
return (member as FieldInfo).GetValue(entity);
}
if (member.MemberType == MemberTypes.Property)
{
return (member as PropertyInfo).GetValue(entity);
}
throw new ArgumentException(string.Format("WhereBuilder could not get the value for {0}.{1}.", entity.GetType().Name, member.Name));
}
private bool IsNullVariable(Expression expression)
{
if (expression.NodeType == ExpressionType.Constant &&
TryGetConstantValue(expression, out var constResult) &&
constResult == null)
{
return true;
}
if (expression.NodeType == ExpressionType.MemberAccess &&
expression is MemberExpression member &&
((TryGetPropertyValue(member, out var result) && result == null) ||
(TryGetVariableValue(member, out result) && result == null)))
{
return true;
}
return false;
}
private string Decode(BinaryExpression expression)
{
if (IsNullVariable(expression.Right))
{
switch (expression.NodeType)
{
case ExpressionType.Equal: return "IS";
case ExpressionType.NotEqual: return "IS NOT";
}
}
switch (expression.NodeType)
{
case ExpressionType.AndAlso: return "AND";
case ExpressionType.And: return "AND";
case ExpressionType.Equal: return "=";
case ExpressionType.GreaterThan: return ">";
case ExpressionType.GreaterThanOrEqual: return ">=";
case ExpressionType.LessThan: return "<";
case ExpressionType.LessThanOrEqual: return "<=";
case ExpressionType.NotEqual: return "<>";
case ExpressionType.OrElse: return "OR";
case ExpressionType.Or: return "OR";
default: throw new NotSupportedException(string.Format("{0} statement is not supported", expression.NodeType.ToString()));
}
}
private void ParseContainsExpression(MethodCallExpression expression)
{
var list = expression.Object;
if (list != null && (list.Type == typeof(string)))
{
ParseStringContains(expression);
return;
}
ParseEnumerableContains(expression);
}
private void ParseEnumerableContains(MethodCallExpression body)
{
// Fish out the list and the item to compare
// It's in a different form for arrays and Lists
var list = body.Object;
Expression item;
if (list != null)
{
// Generic collection
item = body.Arguments[0];
}
else
{
// Static method
// Must be Enumerable.Contains(source, item)
if (body.Method.DeclaringType != typeof(Enumerable) || body.Arguments.Count != 2)
{
throw new NotSupportedException("Unexpected form of Enumerable.Contains");
}
list = body.Arguments[0];
item = body.Arguments[1];
}
_sb.Append('(');
Visit(item);
_sb.Append(" = ANY (");
Visit(list);
_sb.Append("))");
}
private void ParseStringContains(MethodCallExpression body)
{
_sb.Append('(');
Visit(body.Object);
_sb.Append(" LIKE '%' || ");
Visit(body.Arguments[0]);
_sb.Append(" || '%')");
}
private void ParseStartsWith(MethodCallExpression body)
{
_sb.Append('(');
Visit(body.Object);
_sb.Append(" LIKE ");
Visit(body.Arguments[0]);
_sb.Append(" || '%')");
}
private void ParseEndsWith(MethodCallExpression body)
{
_sb.Append('(');
Visit(body.Object);
_sb.Append(" LIKE '%' || ");
Visit(body.Arguments[0]);
_sb.Append(')');
}
public override string ToString()
{
var sql = _sb.ToString();
if (_requireConcreteValue && !_gotConcreteValue)
{
var e = new InvalidOperationException("WhereBuilder requires a concrete condition");
e.Data.Add("sql", sql);
throw e;
}
return sql;
}
}
}

View File

@@ -0,0 +1,387 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Dapper;
namespace NzbDrone.Core.Datastore
{
public class WhereBuilderSqlite : WhereBuilder
{
protected StringBuilder _sb;
private const DbType EnumerableMultiParameter = (DbType)(-1);
private readonly string _paramNamePrefix;
private readonly bool _requireConcreteValue = false;
private int _paramCount = 0;
private bool _gotConcreteValue = false;
public WhereBuilderSqlite(Expression filter, bool requireConcreteValue, int seq)
{
_paramNamePrefix = string.Format("Clause{0}", seq + 1);
_requireConcreteValue = requireConcreteValue;
_sb = new StringBuilder();
Parameters = new DynamicParameters();
if (filter != null)
{
Visit(filter);
}
}
private string AddParameter(object value, DbType? dbType = null)
{
_gotConcreteValue = true;
_paramCount++;
var name = _paramNamePrefix + "_P" + _paramCount;
Parameters.Add(name, value, dbType);
return '@' + name;
}
protected override Expression VisitBinary(BinaryExpression expression)
{
_sb.Append('(');
Visit(expression.Left);
_sb.AppendFormat(" {0} ", Decode(expression));
Visit(expression.Right);
_sb.Append(')');
return expression;
}
protected override Expression VisitMethodCall(MethodCallExpression expression)
{
var method = expression.Method.Name;
switch (expression.Method.Name)
{
case "Contains":
ParseContainsExpression(expression);
break;
case "StartsWith":
ParseStartsWith(expression);
break;
case "EndsWith":
ParseEndsWith(expression);
break;
default:
var msg = string.Format("'{0}' expressions are not yet implemented in the where clause expression tree parser.", method);
throw new NotImplementedException(msg);
}
return expression;
}
protected override Expression VisitMemberAccess(MemberExpression expression)
{
var tableName = expression?.Expression?.Type != null ? TableMapping.Mapper.TableNameMapping(expression.Expression.Type) : null;
var gotValue = TryGetRightValue(expression, out var value);
// Only use the SQL condition if the expression didn't resolve to an actual value
if (tableName != null && !gotValue)
{
_sb.Append($"\"{tableName}\".\"{expression.Member.Name}\"");
}
else
{
if (value != null)
{
// string is IEnumerable<Char> but we don't want to pick up that case
var type = value.GetType();
var typeInfo = type.GetTypeInfo();
var isEnumerable =
type != typeof(string) && (
typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
(typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>)));
var paramName = isEnumerable ? AddParameter(value, EnumerableMultiParameter) : AddParameter(value);
_sb.Append(paramName);
}
else
{
_gotConcreteValue = true;
_sb.Append("NULL");
}
}
return expression;
}
protected override Expression VisitConstant(ConstantExpression expression)
{
if (expression.Value != null)
{
var paramName = AddParameter(expression.Value);
_sb.Append(paramName);
}
else
{
_gotConcreteValue = true;
_sb.Append("NULL");
}
return expression;
}
private bool TryGetConstantValue(Expression expression, out object result)
{
result = null;
if (expression is ConstantExpression constExp)
{
result = constExp.Value;
return true;
}
return false;
}
private bool TryGetPropertyValue(MemberExpression expression, out object result)
{
result = null;
if (expression.Expression is MemberExpression nested)
{
// Value is passed in as a property on a parent entity
var container = (nested.Expression as ConstantExpression)?.Value;
if (container == null)
{
return false;
}
var entity = GetFieldValue(container, nested.Member);
result = GetFieldValue(entity, expression.Member);
return true;
}
return false;
}
private bool TryGetVariableValue(MemberExpression expression, out object result)
{
result = null;
// Value is passed in as a variable
if (expression.Expression is ConstantExpression nested)
{
result = GetFieldValue(nested.Value, expression.Member);
return true;
}
return false;
}
private bool TryGetRightValue(Expression expression, out object value)
{
value = null;
if (TryGetConstantValue(expression, out value))
{
return true;
}
var memberExp = expression as MemberExpression;
if (TryGetPropertyValue(memberExp, out value))
{
return true;
}
if (TryGetVariableValue(memberExp, out value))
{
return true;
}
return false;
}
private object GetFieldValue(object entity, MemberInfo member)
{
if (member.MemberType == MemberTypes.Field)
{
return (member as FieldInfo).GetValue(entity);
}
if (member.MemberType == MemberTypes.Property)
{
return (member as PropertyInfo).GetValue(entity);
}
throw new ArgumentException(string.Format("WhereBuilder could not get the value for {0}.{1}.", entity.GetType().Name, member.Name));
}
private bool IsNullVariable(Expression expression)
{
if (expression.NodeType == ExpressionType.Constant &&
TryGetConstantValue(expression, out var constResult) &&
constResult == null)
{
return true;
}
if (expression.NodeType == ExpressionType.MemberAccess &&
expression is MemberExpression member &&
((TryGetPropertyValue(member, out var result) && result == null) ||
(TryGetVariableValue(member, out result) && result == null)))
{
return true;
}
return false;
}
private string Decode(BinaryExpression expression)
{
if (IsNullVariable(expression.Right))
{
switch (expression.NodeType)
{
case ExpressionType.Equal: return "IS";
case ExpressionType.NotEqual: return "IS NOT";
}
}
switch (expression.NodeType)
{
case ExpressionType.AndAlso: return "AND";
case ExpressionType.And: return "AND";
case ExpressionType.Equal: return "=";
case ExpressionType.GreaterThan: return ">";
case ExpressionType.GreaterThanOrEqual: return ">=";
case ExpressionType.LessThan: return "<";
case ExpressionType.LessThanOrEqual: return "<=";
case ExpressionType.NotEqual: return "<>";
case ExpressionType.OrElse: return "OR";
case ExpressionType.Or: return "OR";
default: throw new NotSupportedException(string.Format("{0} statement is not supported", expression.NodeType.ToString()));
}
}
private void ParseContainsExpression(MethodCallExpression expression)
{
var list = expression.Object;
if (list != null && (list.Type == typeof(string)))
{
ParseStringContains(expression);
return;
}
ParseEnumerableContains(expression);
}
private void ParseEnumerableContains(MethodCallExpression body)
{
// Fish out the list and the item to compare
// It's in a different form for arrays and Lists
var list = body.Object;
Expression item;
if (list != null)
{
// Generic collection
item = body.Arguments[0];
}
else
{
// Static method
// Must be Enumerable.Contains(source, item)
if (body.Method.DeclaringType != typeof(Enumerable) || body.Arguments.Count != 2)
{
throw new NotSupportedException("Unexpected form of Enumerable.Contains");
}
list = body.Arguments[0];
item = body.Arguments[1];
}
_sb.Append('(');
Visit(item);
_sb.Append(" IN ");
// hardcode the integer list if it exists to bypass parameter limit
if (item.Type == typeof(int) && TryGetRightValue(list, out var value))
{
var items = (IEnumerable<int>)value;
_sb.Append('(');
_sb.Append(string.Join(", ", items));
_sb.Append(')');
_gotConcreteValue = true;
}
else
{
Visit(list);
}
_sb.Append(')');
}
private void ParseStringContains(MethodCallExpression body)
{
_sb.Append('(');
Visit(body.Object);
_sb.Append(" LIKE '%' || ");
Visit(body.Arguments[0]);
_sb.Append(" || '%')");
}
private void ParseStartsWith(MethodCallExpression body)
{
_sb.Append('(');
Visit(body.Object);
_sb.Append(" LIKE ");
Visit(body.Arguments[0]);
_sb.Append(" || '%')");
}
private void ParseEndsWith(MethodCallExpression body)
{
_sb.Append('(');
Visit(body.Object);
_sb.Append(" LIKE '%' || ");
Visit(body.Arguments[0]);
_sb.Append(')');
}
public override string ToString()
{
var sql = _sb.ToString();
if (_requireConcreteValue && !_gotConcreteValue)
{
var e = new InvalidOperationException("WhereBuilder requires a concrete condition");
e.Data.Add("sql", sql);
throw e;
}
return sql;
}
}
}

View File

@@ -100,11 +100,13 @@ namespace NzbDrone.Core.History
public int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes)
{
var builder = new SqlBuilder()
var intEvents = eventTypes.Select(t => (int)t).ToList();
var builder = new SqlBuilder(_database.DatabaseType)
.SelectCount()
.Where<History>(x => x.IndexerId == indexerId)
.Where<History>(x => x.Date >= date)
.Where<History>(x => eventTypes.Contains(x.EventType));
.Where<History>(x => intEvents.Contains((int)x.EventType));
var sql = builder.AddPageCountTemplate(typeof(History));

View File

@@ -0,0 +1,27 @@
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
{
public class CleanupOrphanedApplicationStatus : IHousekeepingTask
{
private readonly IMainDatabase _database;
public CleanupOrphanedApplicationStatus(IMainDatabase database)
{
_database = database;
}
public void Clean()
{
var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""ApplicationStatus""
WHERE ""Id"" IN (
SELECT ""ApplicationStatus"".""Id"" FROM ""ApplicationStatus""
LEFT OUTER JOIN ""Applications""
ON ""ApplicationStatus"".""ProviderId"" = ""Applications"".""Id""
WHERE ""Applications"".""Id"" IS NULL)");
}
}
}

View File

@@ -91,10 +91,12 @@ namespace NzbDrone.Core.IndexerSearch
r.IndexerFlags == null ? null : from f in r.IndexerFlags select GetNabElement("tag", f.Name, protocol),
r.Languages == null ? null : from c in r.Languages select GetNabElement("language", c.Id, protocol),
r.Subs == null ? null : from c in r.Subs select GetNabElement("subs", c.Id, protocol),
r.Genres == null ? null : GetNabElement("genre", string.Join(", ", r.Genres), protocol),
GetNabElement("rageid", r.TvRageId, protocol),
GetNabElement("tvdbid", r.TvdbId, protocol),
GetNabElement("imdb", r.ImdbId.ToString("D7"), protocol),
GetNabElement("tmdbid", r.TmdbId, protocol),
GetNabElement("traktid", r.TraktId, protocol),
GetNabElement("seeders", t.Seeders, protocol),
GetNabElement("files", r.Files, protocol),
GetNabElement("grabs", r.Grabs, protocol),

View File

@@ -28,7 +28,7 @@ namespace NzbDrone.Core.IndexerVersions
/* Update Service will fall back if version # does not exist for an indexer per Ta */
private const string DEFINITION_BRANCH = "master";
private const int DEFINITION_VERSION = 3;
private const int DEFINITION_VERSION = 5;
//Used when moving yml to C#
private readonly List<string> _defintionBlocklist = new List<string>()
@@ -204,7 +204,7 @@ namespace NzbDrone.Core.IndexerVersions
var dbDefs = _versionService.All();
//Check to ensure it's in versioned defs before we go to web
if (dbDefs.Count > 0 && dbDefs.Any(x => x.File == fileKey))
if (dbDefs.Count > 0 && !dbDefs.Any(x => x.File == fileKey))
{
throw new ArgumentNullException(nameof(fileKey));
}

View File

@@ -48,14 +48,17 @@ namespace NzbDrone.Core.Indexers.Definitions
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "Anime Series");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio, "Anime Musik/OST");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.PCGames, "Anime Spiele");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.XXX, "Hentai");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.PCGames, "Spiele Linux");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.XXX, "Anime Hentai");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.PCGames, "Software");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.Other, "Sonstiges");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.Movies, "Filme");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.TV, "Serien");
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.PCGames, "Spiele");
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.Audio, "Musik");
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.BooksComics, "Mangas");
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.Movies, "Cartoon Filme");
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.TV, "Cartoon Serie");
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.XXX, "H-Manga / Doujinshi");
return caps;
}

View File

@@ -148,7 +148,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{ "order_way", "desc" },
{ "action", "basic" },
{ "searchsubmit", "1" },
{ "searchstr", imdbId.IsNotNullOrWhiteSpace() ? imdbId : term }
{ "searchstr", imdbId.IsNotNullOrWhiteSpace() ? imdbId : term.Replace(".", " ") }
};
var catList = Capabilities.Categories.MapTorznabCapsToTrackers(categories);

View File

@@ -46,6 +46,11 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public List<AvistazRelease> Data { get; set; }
}
public class AvistazErrorResponse
{
public string Message { get; set; }
}
public class AvistazIdInfo
{
public string Tmdb { get; set; }

View File

@@ -89,6 +89,22 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{
await GetToken();
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
{
_logger.Warn(ex, "Unauthorized request to indexer");
var jsonResponse = new HttpResponse<AvistazErrorResponse>(ex.Response);
return new ValidationFailure(string.Empty, jsonResponse.Resource?.Message ?? "Unauthorized request to indexer");
}
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");
}
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to connect to indexer");

View File

@@ -182,18 +182,23 @@ namespace NzbDrone.Core.Indexers.Definitions
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<TorrentInfo>();
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
var indexerHttpResponse = indexerResponse.HttpResponse;
if (indexerHttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerHttpResponse.StatusCode} code from API request");
}
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
if (!indexerHttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
{
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerHttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
}
var jsonResponse = new HttpResponse<BeyondHDResponse>(indexerResponse.HttpResponse);
if (indexerResponse.Content.ContainsIgnoreCase("Invalid API Key"))
{
throw new IndexerAuthException("API Key invalid or not authorized");
}
var jsonResponse = new HttpResponse<BeyondHDResponse>(indexerHttpResponse);
foreach (var row in jsonResponse.Resource.Results)
{
@@ -213,7 +218,7 @@ namespace NzbDrone.Core.Indexers.Definitions
Grabs = row.Grabs,
Seeders = row.Seeders,
ImdbId = ParseUtil.GetImdbID(row.ImdbId).GetValueOrDefault(),
TmdbId = row.TmdbId.IsNullOrWhiteSpace() ? 0 : ParseUtil.CoerceInt(row.TmdbId.Split("/")[1]),
TmdbId = row.TmdbId.IsNullOrWhiteSpace() ? 0 : (int)ParseUtil.CoerceLong(row.TmdbId.Split("/")[1]),
Peers = row.Leechers + row.Seeders,
DownloadVolumeFactor = row.Freeleech || row.Limited ? 0 : row.Promo75 ? 0.25 : row.Promo50 ? 0.5 : row.Promo25 ? 0.75 : 1,
UploadVolumeFactor = 1,

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Http;
@@ -16,6 +17,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
public override bool SupportsSearch => true;
public override int PageSize => 100;
public override IndexerCapabilities Capabilities => SetCapabilities();
public override TimeSpan RateLimit => TimeSpan.FromSeconds(5);
public override string[] IndexerUrls => new string[] { "http://api.broadcasthe.net/" };
public override string Description => "BroadcasTheNet (BTN) is an invite-only torrent tracker focused on TV shows";

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model;
@@ -25,8 +26,9 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var results = new List<ReleaseInfo>();
var indexerHttpResponse = indexerResponse.HttpResponse;
switch (indexerResponse.HttpResponse.StatusCode)
switch (indexerHttpResponse.StatusCode)
{
case HttpStatusCode.Unauthorized:
throw new IndexerAuthException("API Key invalid or not authorized");
@@ -35,25 +37,30 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
case HttpStatusCode.ServiceUnavailable:
throw new RequestLimitReachedException(indexerResponse, "Cannot do more than 150 API requests per hour.");
default:
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
if (indexerHttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse, "Indexer API call returned an unexpected StatusCode [{0}]", indexerResponse.HttpResponse.StatusCode);
throw new IndexerException(indexerResponse, "Indexer API call returned an unexpected StatusCode [{0}]", indexerHttpResponse.StatusCode);
}
break;
}
if (indexerResponse.HttpResponse.Headers.ContentType != null && indexerResponse.HttpResponse.Headers.ContentType.Contains("text/html"))
if (indexerHttpResponse.Headers.ContentType != null && indexerHttpResponse.Headers.ContentType.Contains("text/html"))
{
throw new IndexerException(indexerResponse, "Indexer responded with html content. Site is likely blocked or unavailable.");
}
if (indexerResponse.Content.ContainsIgnoreCase("Call Limit Exceeded"))
{
throw new RequestLimitReachedException(indexerResponse, "Cannot do more than 150 API requests per hour.");
}
if (indexerResponse.Content == "Query execution was interrupted")
{
throw new IndexerException(indexerResponse, "Indexer API returned an internal server error");
}
JsonRpcResponse<BroadcastheNetTorrents> jsonResponse = new HttpResponse<JsonRpcResponse<BroadcastheNetTorrents>>(indexerResponse.HttpResponse).Resource;
JsonRpcResponse<BroadcastheNetTorrents> jsonResponse = new HttpResponse<JsonRpcResponse<BroadcastheNetTorrents>>(indexerHttpResponse).Resource;
if (jsonResponse.Error != null || jsonResponse.Result == null)
{

View File

@@ -48,6 +48,9 @@ namespace NzbDrone.Core.Indexers.Cardigann
protected static readonly Regex _LogicFunctionRegex = new Regex(
$@"\b({string.Join("|", _SupportedLogicFunctions.Select(Regex.Escape))})(?:\s+(\(?\.[^\)\s]+\)?|""[^""]+"")){{2,}}");
// Matches CSS selectors for the JSON parser
protected static readonly Regex _jsonSelectorRegex = new Regex(@"\:(?<filter>.+?)\((?<key>.+?)\)(?=:|\z)", RegexOptions.Compiled);
public CardigannSettings Settings { get; set; }
public CardigannBase(IConfigService configService,
@@ -234,13 +237,20 @@ namespace NzbDrone.Core.Indexers.Cardigann
if (selector.Selector != null)
{
var selector_Selector = ApplyGoTemplateText(selector.Selector.TrimStart('.'), variables);
var selection = parentObj.SelectToken(selector_Selector);
var selectorSelector = ApplyGoTemplateText(selector.Selector.TrimStart('.'), variables);
selectorSelector = JsonParseFieldSelector(parentObj, selectorSelector);
JToken selection = null;
if (selectorSelector != null)
{
selection = parentObj.SelectToken(selectorSelector);
}
if (selection == null)
{
if (required)
{
throw new Exception(string.Format("Selector \"{0}\" didn't match {1}", selector_Selector, parentObj.ToString()));
throw new Exception(string.Format("Selector \"{0}\" didn't match {1}", selectorSelector, parentObj.ToString()));
}
return null;
@@ -375,6 +385,22 @@ namespace NzbDrone.Core.Indexers.Cardigann
return results;
}
public ICollection<IndexerCategory> MapTrackerCatDescToNewznab(string trackerCategoryDesc)
{
if (string.IsNullOrWhiteSpace(trackerCategoryDesc))
{
return new List<IndexerCategory>();
}
var cats = _categoryMapping
.Where(m =>
!string.IsNullOrWhiteSpace(m.TrackerCategoryDesc) &&
string.Equals(m.TrackerCategoryDesc, trackerCategoryDesc, StringComparison.InvariantCultureIgnoreCase))
.Select(c => NewznabStandardCategory.AllCats.FirstOrDefault(n => n.Id == c.NewzNabCategory) ?? new IndexerCategory { Id = c.NewzNabCategory })
.ToList();
return cats;
}
protected delegate string TemplateTextModifier(string str);
protected string ApplyGoTemplateText(string template, Dictionary<string, object> variables = null, TemplateTextModifier modifier = null)
@@ -816,5 +842,108 @@ namespace NzbDrone.Core.Indexers.Cardigann
{
return new Uri(currentUrl ?? new Uri(SiteLink), path);
}
protected string ResolveSiteLink()
{
var settingsBaseUrl = Settings?.BaseUrl;
var defaultLink = _definition.Links.First();
if (settingsBaseUrl == null)
{
return defaultLink;
}
if (_definition?.Legacylinks?.Contains(settingsBaseUrl) ?? false)
{
_logger.Trace("Changing legacy site link from {0} to {1}", settingsBaseUrl, defaultLink);
return defaultLink;
}
return settingsBaseUrl;
}
protected JArray JsonParseRowsSelector(JToken parsedJson, string rowSelector)
{
var selector = rowSelector.Split(':')[0];
var rowsObj = parsedJson.SelectToken(selector).Value<JArray>();
return new JArray(rowsObj.Where(t =>
JsonParseFieldSelector(t.Value<JObject>(), rowSelector.Remove(0, selector.Length)) != null));
}
private string JsonParseFieldSelector(JToken parsedJson, string rowSelector)
{
var selector = rowSelector.Split(':')[0];
JToken parsedObject;
if (string.IsNullOrWhiteSpace(selector))
{
parsedObject = parsedJson;
}
else if (parsedJson.SelectToken(selector) != null)
{
parsedObject = parsedJson.SelectToken(selector);
}
else
{
return null;
}
foreach (Match match in _jsonSelectorRegex.Matches(rowSelector))
{
var filter = match.Result("${filter}");
var key = match.Result("${key}");
Match innerMatch;
switch (filter)
{
case "has":
innerMatch = _jsonSelectorRegex.Match(key);
if (innerMatch.Success)
{
if (JsonParseFieldSelector(parsedObject, key) == null)
{
return null;
}
}
else
{
if (parsedObject.SelectToken(key) == null)
{
return null;
}
}
break;
case "not":
innerMatch = _jsonSelectorRegex.Match(key);
if (innerMatch.Success)
{
if (JsonParseFieldSelector(parsedObject, key) != null)
{
return null;
}
}
else
{
if (parsedObject.SelectToken(key) != null)
{
return null;
}
}
break;
case "contains":
if (!parsedObject.ToString().Contains(key))
{
return null;
}
break;
default:
_logger.Error(string.Format("CardigannIndexer ({0}): Unsupported selector: {1}", _definition.Id, rowSelector));
continue;
}
}
return selector;
}
}
}

View File

@@ -151,6 +151,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 class SearchPathBlock : RequestBlock
@@ -200,8 +201,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
public class ResponseBlock
{
public string Type { get; set; }
public string Attribute { get; set; }
public bool Multiple { get; set; }
public string NoResultsMessage { get; set; }
}
}

View File

@@ -21,7 +21,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
{
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
protected override string SiteLink => Settings?.BaseUrl ?? _definition.Links.First();
protected override string SiteLink => ResolveSiteLink();
public CardigannParser(IConfigService configService,
CardigannDefinition definition,
@@ -83,16 +83,16 @@ namespace NzbDrone.Core.Indexers.Cardigann
}
}
var rowsObj = parsedJson.SelectToken(search.Rows.Selector);
if (rowsObj == null)
var rowsArray = JsonParseRowsSelector(parsedJson, search.Rows.Selector);
if (rowsArray == null)
{
throw new IndexerException(indexerResponse, "Error Parsing Rows Selector");
}
foreach (var row in rowsObj.Value<JArray>())
foreach (var row in rowsArray)
{
var selObj = request.SearchPath.Response.Attribute != null ? row.SelectToken(request.SearchPath.Response.Attribute).Value<JToken>() : row;
var mulRows = request.SearchPath.Response.Multiple == true ? selObj.Values<JObject>() : new List<JObject> { selObj.Value<JObject>() };
var selObj = search.Rows.Attribute != null ? row.SelectToken(search.Rows.Attribute).Value<JToken>() : row;
var mulRows = search.Rows.Multiple ? selObj.Values<JObject>() : new List<JObject> { selObj.Value<JObject>() };
foreach (var mulRow in mulRows)
{
@@ -464,6 +464,22 @@ namespace NzbDrone.Core.Indexers.Cardigann
}
}
value = release.Categories.ToString();
break;
case "categorydesc":
var catsDesc = MapTrackerCatDescToNewznab(value);
if (catsDesc.Any())
{
if (release.Categories == null || fieldModifiers.Contains("noappend"))
{
release.Categories = catsDesc;
}
else
{
release.Categories = release.Categories.Union(catsDesc).ToList();
}
}
value = release.Categories.ToString();
break;
case "size":
@@ -545,6 +561,13 @@ namespace NzbDrone.Core.Indexers.Cardigann
release.TvRageId = (int)ParseUtil.CoerceLong(rageID);
value = release.TvRageId.ToString();
break;
case "traktid":
var traktIDRegEx = new Regex(@"(\d+)", RegexOptions.Compiled);
var traktIDMatch = traktIDRegEx.Match(value);
var traktID = traktIDMatch.Groups[1].Value;
release.TraktId = (int)ParseUtil.CoerceLong(traktID);
value = release.TraktId.ToString();
break;
case "tvdbid":
var tvdbIdRegEx = new Regex(@"(\d+)", RegexOptions.Compiled);
var tvdbIdMatch = tvdbIdRegEx.Match(value);
@@ -561,6 +584,14 @@ namespace NzbDrone.Core.Indexers.Cardigann
value = release.PosterUrl;
break;
case "genre":
release.Genres = release.Genres.Union(value.Split(',')).ToList();
value = release.Genres.ToString();
break;
case "year":
release.Year = ParseUtil.CoerceInt(value);
value = release.Year.ToString();
break;
case "author":
release.Author = value;
break;

View File

@@ -8,13 +8,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
public Dictionary<string, object> Variables { get; private set; }
public SearchPathBlock SearchPath { get; private set; }
public CardigannRequest(string url, HttpAccept httpAccept, Dictionary<string, object> variables, SearchPathBlock searchPath)
: base(url, httpAccept)
{
Variables = variables;
SearchPath = searchPath;
}
public CardigannRequest(HttpRequest httpRequest, Dictionary<string, object> variables, SearchPathBlock searchPath)
: base(httpRequest)
{

View File

@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
public IDictionary<string, string> Cookies { get; set; }
protected HttpResponse landingResult;
protected IHtmlDocument landingResultDocument;
protected override string SiteLink => Settings?.BaseUrl ?? _definition.Links.First();
protected override string SiteLink => ResolveSiteLink();
public CardigannRequestGenerator(IConfigService configService,
CardigannDefinition definition,
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
_logger.Trace("Getting search");
_logger.Trace("Getting Movie search");
var pageableRequests = new IndexerPageableRequestChain();
@@ -61,6 +61,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
_logger.Trace("Getting Music search");
var pageableRequests = new IndexerPageableRequestChain();
var variables = GetQueryVariableDefaults(searchCriteria);
@@ -77,6 +79,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
_logger.Trace("Getting TV search");
var pageableRequests = new IndexerPageableRequestChain();
var variables = GetQueryVariableDefaults(searchCriteria);
@@ -99,6 +103,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
_logger.Trace("Getting Book search");
var pageableRequests = new IndexerPageableRequestChain();
var variables = GetQueryVariableDefaults(searchCriteria);
@@ -113,6 +119,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
_logger.Trace("Getting Basic search");
var pageableRequests = new IndexerPageableRequestChain();
var variables = GetQueryVariableDefaults(searchCriteria);
@@ -647,10 +655,13 @@ namespace NzbDrone.Core.Indexers.Cardigann
protected string GetRedirectDomainHint(string requestUrl, string redirectUrl)
{
if (requestUrl.StartsWith(SiteLink) && !redirectUrl.StartsWith(SiteLink))
var siteLinkUri = new HttpUri(SiteLink);
var requestUri = new HttpUri(requestUrl);
var redirectUri = new HttpUri(redirectUrl);
if (requestUri.Host.StartsWith(siteLinkUri.Host) && !redirectUri.Host.StartsWith(siteLinkUri.Host))
{
var uri = new HttpUri(redirectUrl);
return uri.Scheme + "://" + uri.Host + "/";
return redirectUri.Scheme + "://" + redirectUri.Host + "/";
}
return null;

View File

@@ -19,6 +19,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete("Moved to YML")]
public class DanishBytes : TorrentIndexerBase<DanishBytesSettings>
{
public override string Name => "DanishBytes";

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace NzbDrone.Core.Indexers.Gazelle
{
@@ -59,6 +60,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
public int TotalSeeders { get; set; }
public int TotalSnatched { get; set; }
public long MaxSize { get; set; }
[JsonConverter(typeof(GazelleTimestampConverter))]
public long GroupTime { get; set; }
public List<GazelleTorrent> Torrents { get; set; }
public bool IsFreeLeech { get; set; }

View File

@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
if (!string.IsNullOrWhiteSpace(searchString))
{
parameters += string.Format("&searchstr={0}", searchString);
parameters += string.Format("&searchstr={0}", searchString.Replace(".", " "));
}
if (categories != null)

View File

@@ -0,0 +1,41 @@
using System;
using Newtonsoft.Json;
namespace NzbDrone.Core.Indexers.Gazelle
{
public class GazelleTimestampConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteNull();
}
else
{
writer.WriteValue(value);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (objectType == typeof(long))
{
return Convert.ToInt64(reader.Value);
}
if (objectType == typeof(string))
{
var date = DateTimeOffset.Parse(reader.Value.ToString());
return date.ToUnixTimeSeconds();
}
throw new JsonSerializationException("Can't convert type " + existingValue.GetType().FullName + " to timestamp");
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(long) || objectType == typeof(string);
}
}
}

View File

@@ -23,29 +23,29 @@ namespace NzbDrone.Core.Indexers.HDBits
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
var indexerHttpResponse = indexerResponse.HttpResponse;
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
if (indexerHttpResponse.StatusCode == HttpStatusCode.Forbidden)
{
throw new IndexerException(indexerResponse,
"Unexpected response status {0} code from API request",
indexerResponse.HttpResponse.StatusCode);
throw new RequestLimitReachedException(indexerResponse, "HDBits Query Limit Reached. Please try again later.");
}
if (indexerHttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse, "Unexpected response status {0} code from API request", indexerHttpResponse.StatusCode);
}
var jsonResponse = JsonConvert.DeserializeObject<HDBitsResponse>(indexerResponse.Content);
if (jsonResponse.Status != StatusCode.Success)
{
throw new IndexerException(indexerResponse,
"HDBits API request returned status code {0}: {1}",
jsonResponse.Status,
jsonResponse.Message ?? string.Empty);
throw new IndexerException(indexerResponse, "HDBits API request returned status code {0}: {1}", jsonResponse.Status, jsonResponse.Message ?? string.Empty);
}
var responseData = jsonResponse.Data as JArray;
if (responseData == null)
{
throw new IndexerException(indexerResponse,
"Indexer API call response missing result data");
throw new IndexerException(indexerResponse, "Indexer API call response missing result data");
}
var queryResults = responseData.ToObject<TorrentQueryResponse[]>();

View File

@@ -171,7 +171,7 @@ namespace NzbDrone.Core.Indexers.Definitions
else
{
queryCollection.Add("options", "0");
queryCollection.Add("search", term);
queryCollection.Add("search", term.Replace(".", " "));
}
searchUrl += queryCollection.GetQueryString();

View File

@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public override string Name => "HD-Torrents";
public override string[] IndexerUrls => new string[] { "https://hdts.ru/" };
public override string[] IndexerUrls => new string[] { "https://hdts.ru/", "https://hd-torrents.org/" };
public override string Description => "HD-Torrents is a private torrent website with HD torrents and strict rules on their content.";
private string LoginUrl => Settings.BaseUrl + "login.php";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;

View File

@@ -170,16 +170,21 @@ namespace NzbDrone.Core.Indexers.Definitions
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
{
var searchUrl = Settings.BaseUrl + "browse.php";
var searchUrl = Settings.BaseUrl + "browse.php?";
if (term.IsNotNullOrWhiteSpace())
{
searchUrl += string.Format("?do=search&keywords={0}&search_type=t_name&category=0&include_dead_torrents=no", WebUtility.UrlEncode(term));
searchUrl += string.Format("do=search&keywords={0}&search_type=t_name&category=0&include_dead_torrents=no", WebUtility.UrlEncode(term));
}
if (categories != null && categories.Length > 0)
{
searchUrl += "&selectedcats2=" + string.Join(",", Capabilities.Categories.MapTorznabCapsToTrackers(categories));
if (term.IsNotNullOrWhiteSpace())
{
searchUrl += "&";
}
searchUrl += "selectedcats2=" + string.Join(",", Capabilities.Categories.MapTorznabCapsToTrackers(categories));
}
var request = new IndexerRequest(searchUrl, HttpAccept.Html);

View File

@@ -150,14 +150,14 @@ public class MoreThanTVRequestGenerator : IIndexerRequestGenerator
qc.Add("filter_cat[2]", "1"); // SD Movies
break;
case TvSearchCriteria:
qc.Add("filter_cat[3]", "1"); // HD EPISODE
qc.Add("filter_cat[3]", "1"); // HD Episode
qc.Add("filter_cat[4]", "1"); // SD Episode
qc.Add("filter_cat[5]", "1"); // HD Season
qc.Add("filter_cat[6]", "1"); // SD Season
break;
}
return $"{Settings.BaseUrl}torrents.php?{qc.GetQueryString()}";
return $"{Settings.BaseUrl}torrents/browse?{qc.GetQueryString()}";
}
private string GetSearchString(string input)
@@ -188,28 +188,25 @@ public class MoreThanTVParser : IParseIndexerResponse
foreach (var torrent in torrents)
{
// Parse required data
var torrentGroup = torrent.QuerySelectorAll("table a[href^=\"/torrents.php?action=download\"]");
foreach (var downloadAnchor in torrentGroup)
var downloadAnchor = torrent.QuerySelector("span a[href^=\"/torrents.php?action=download\"]");
var title = downloadAnchor.ParentElement.ParentElement.ParentElement.QuerySelector("a[class=\"overlay_torrent\"]").TextContent.Trim();
title = CleanUpTitle(title);
var category = torrent.QuerySelector(".cats_col div").GetAttribute("title");
// default to Other
var indexerCategory = NewznabStandardCategory.Other;
if (movies.Any(category.Contains))
{
var title = downloadAnchor.ParentElement.ParentElement.ParentElement.TextContent.Trim();
title = CleanUpTitle(title);
var category = torrent.QuerySelector(".cats_col div").GetAttribute("title");
// default to Other
var indexerCategory = NewznabStandardCategory.Other;
if (movies.Any(category.Contains))
{
indexerCategory = NewznabStandardCategory.Movies;
}
else if (tv.Any(category.Contains))
{
indexerCategory = NewznabStandardCategory.TV;
}
releases.Add(GetReleaseInfo(torrent, downloadAnchor, title, indexerCategory));
indexerCategory = NewznabStandardCategory.Movies;
}
else if (tv.Any(category.Contains))
{
indexerCategory = NewznabStandardCategory.TV;
}
releases.Add(GetReleaseInfo(torrent, downloadAnchor, title, indexerCategory));
}
return releases;
@@ -231,7 +228,7 @@ public class MoreThanTVParser : IParseIndexerResponse
private ReleaseInfo GetReleaseInfo(IElement row, IElement downloadAnchor, string title, IndexerCategory category)
{
// count from bottom
const int FILES_COL = 8;
const int FILES_COL = 7;
/*const int COMMENTS_COL = 7;*/
const int DATE_COL = 6;
const int FILESIZE_COL = 5;

View File

@@ -25,26 +25,32 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
var indexerHttpResponse = indexerResponse.HttpResponse;
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
if (indexerHttpResponse.StatusCode != HttpStatusCode.OK)
{
// Remove cookie cache
if (indexerResponse.HttpResponse.HasHttpRedirect && indexerResponse.HttpResponse.RedirectUrl
if (indexerHttpResponse.HasHttpRedirect && indexerHttpResponse.RedirectUrl
.ContainsIgnoreCase("login.php"))
{
CookiesUpdater(null, null);
throw new IndexerException(indexerResponse, "We are being redirected to the PTP login page. Most likely your session expired or was killed. Try testing the indexer in the settings.");
throw new IndexerAuthException("We are being redirected to the PTP login page. Most likely your session expired or was killed. Try testing the indexer in the settings.");
}
if (indexerHttpResponse.StatusCode == HttpStatusCode.Forbidden)
{
throw new RequestLimitReachedException(indexerResponse, "PTP Query Limit Reached. Please try again later.");
}
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
}
if (indexerResponse.HttpResponse.Headers.ContentType != HttpAccept.Json.Value)
if (indexerHttpResponse.Headers.ContentType != HttpAccept.Json.Value)
{
if (indexerResponse.HttpResponse.Request.Url.Path.ContainsIgnoreCase("login.php"))
if (indexerHttpResponse.Request.Url.Path.ContainsIgnoreCase("login.php"))
{
CookiesUpdater(null, null);
throw new IndexerException(indexerResponse, "We are currently on the login page. Most likely your session expired or was killed. Try testing the indexer in the settings.");
throw new IndexerAuthException("We are currently on the login page. Most likely your session expired or was killed. Try testing the indexer in the settings.");
}
// Remove cookie cache

View File

@@ -26,7 +26,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override string[] IndexerUrls => new string[] { "https://pornolab.net/" };
private string LoginUrl => Settings.BaseUrl + "forum/login.php";
public override string Description => "PornoLab is a Semi-Private Russian site for Adult content";
public override string Language => "ru-ru";
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;

View File

@@ -73,8 +73,8 @@ namespace NzbDrone.Core.Indexers.Definitions
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.PC, "Applications");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.BooksEBook, "E-Books");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.AudioAudiobook, "Audiobooks");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.MoviesOther, "E-Learning Videos");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TVOther, "Comedy");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Other, "E-Learning Videos");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.Other, "Comedy");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.BooksComics, "Comics");
return caps;

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@ using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions.Xthor
{
[Obsolete("Moved to YML for Cardigann v5")]
public class Xthor : TorrentIndexerBase<XthorSettings>
{
public override string Name => "Xthor";

View File

@@ -14,10 +14,10 @@ namespace NzbDrone.Core.Indexers
{
private static readonly IndexerCommonSettingsValidator Validator = new IndexerCommonSettingsValidator();
[FieldDefinition(1, Type = FieldType.Number, Label = "Query Limit", HelpText = "The number of queries per day Prowlarr will allow to the site", Advanced = true)]
[FieldDefinition(1, Type = FieldType.Number, Label = "Query Limit", HelpText = "The number of queries within a rolling 24 hour period Prowlarr will allow to the site", Advanced = true)]
public int? QueryLimit { get; set; }
[FieldDefinition(2, Type = FieldType.Number, Label = "Grab Limit", HelpText = "The number of grabs per day Prowlarr will allow to the site", Advanced = true)]
[FieldDefinition(2, Type = FieldType.Number, Label = "Grab Limit", HelpText = "The number of grabs within a rolling 24 hour period Prowlarr will allow to the site", Advanced = true)]
public int? GrabLimit { get; set; }
}
}

View File

@@ -4,11 +4,8 @@
"About": "نبدة عن",
"AcceptConfirmationModal": "نموذج قبول التأكيد",
"Actions": "أجراءات",
"MinutesHundredTwenty": "120 دقيقة: {0}",
"MonoVersion": "نسخة أحادية",
"MoreInfo": "مزيد من المعلومات",
"BranchUpdateMechanism": "يستخدم الفرع بواسطة آلية التحديث الخارجية",
"CloneIndexer": "مفهرس استنساخ",
"Added": "مضاف",
"AddIndexer": "أضف مفهرس",
"AddingTag": "مضيفا العلامة",
@@ -18,7 +15,6 @@
"AreYouSureYouWantToResetYourAPIKey": "هل أنت متأكد أنك تريد إعادة تعيين مفتاح API الخاص بك؟",
"Authentication": "المصادقة",
"BackupIntervalHelpText": "الفاصل الزمني بين النسخ الاحتياطية التلقائية",
"Languages": "اللغات",
"BackupNow": "اعمل نسخة احتياطية الان",
"BackupRetentionHelpText": "سيتم تنظيف النسخ الاحتياطية التلقائية الأقدم من فترة الاحتفاظ تلقائيًا",
"Backups": "النسخ الاحتياطية",
@@ -32,18 +28,11 @@
"DeleteApplicationMessageText": "هل تريد بالتأكيد حذف الإشعار \"{0}\"؟",
"DeleteDownloadClient": "حذف Download Client",
"DeleteDownloadClientMessageText": "هل أنت متأكد أنك تريد حذف برنامج التنزيل \"{0}\"؟",
"DeleteIndexer": "حذف المفهرس",
"DeleteIndexerMessageText": "هل أنت متأكد أنك تريد حذف المفهرس \"{0}\"؟",
"EnableMediaInfoHelpText": "استخراج معلومات الفيديو مثل الدقة ووقت التشغيل ومعلومات الترميز من الملفات. يتطلب هذا من Radarr قراءة أجزاء من الملف والتي قد تسبب نشاطًا كبيرًا على القرص أو الشبكة أثناء عمليات الفحص.",
"Filename": "اسم الملف",
"HomePage": "الصفحة الرئيسية",
"IndexerProxyStatusCheckSingleClientMessage": "المفهرسات غير متاحة بسبب الإخفاقات: {0}",
"Language": "لغة",
"MinutesNinety": "90 دقيقة: {0}",
"Usenet": "يوزنت",
"MinutesSixty": "60 دقيقة: {0}",
"MonoVersionCheckUpgradeRecommendedMessage": "يتم دعم الإصدار Mono المثبت حاليًا {0} ولكن يوصى بالترقية إلى {1}.",
"MovieDetailsNextMovie": "تفاصيل الفيلم: الفيلم القادم",
"MovieIndexScrollTop": "فهرس الفيلم: قم بالتمرير لأعلى",
"New": "جديد",
"NoLinks": "لا روابط",
@@ -66,7 +55,6 @@
"ProxyPasswordHelpText": "ما عليك سوى إدخال اسم مستخدم وكلمة مرور إذا كان أحدهما مطلوبًا. اتركها فارغة وإلا.",
"ProxyUsernameHelpText": "ما عليك سوى إدخال اسم مستخدم وكلمة مرور إذا كان أحدهما مطلوبًا. اتركها فارغة وإلا.",
"PtpOldSettingsCheckMessage": "مفهرسات PassThePopcorn التالية بها إعدادات مهملة ويجب تحديثها: {0}",
"QualitySettings": "إعدادات الجودة",
"Queue": "طابور",
"ReadTheWikiForMoreInformation": "اقرأ Wiki لمزيد من المعلومات",
"Reddit": "رديت",
@@ -83,19 +71,12 @@
"UnableToAddANewDownloadClientPleaseTryAgain": "غير قادر على إضافة عميل تنزيل جديد ، يرجى المحاولة مرة أخرى.",
"UnableToAddANewIndexerPleaseTryAgain": "غير قادر على إضافة مفهرس جديد ، يرجى المحاولة مرة أخرى.",
"UnableToLoadBackups": "تعذر تحميل النسخ الاحتياطية",
"UnableToLoadIndexers": "تعذر تحميل المفهرسات",
"UnableToLoadQualityDefinitions": "تعذر تحميل تعريفات الجودة",
"UnsavedChanges": "التغييرات غير المحفوظة",
"UpdateCheckUINotWritableMessage": "لا يمكن تثبيت التحديث لأن مجلد واجهة المستخدم '{0}' غير قابل للكتابة بواسطة المستخدم '{1}'",
"UpdateScriptPathHelpText": "المسار إلى برنامج نصي مخصص يأخذ حزمة تحديث مستخرجة ويتعامل مع ما تبقى من عملية التحديث",
"Username": "اسم المستخدم",
"Warn": "حذر",
"Yesterday": "في الامس",
"EnableColorImpairedMode": "تفعيل وضع ضعف الألوان",
"EnableColorImpairedModeHelpText": "تم تغيير النمط للسماح للمستخدمين الذين يعانون من ضعف الألوان بتمييز المعلومات المشفرة بالألوان بشكل أفضل",
"EnableCompletedDownloadHandlingHelpText": "استيراد التنزيلات المكتملة تلقائيًا من عميل التنزيل",
"EnabledHelpText": "قم بتمكين هذه القائمة للاستخدام في Radarr",
"EnableHelpText": "تفعيل إنشاء ملف البيانات الوصفية لنوع البيانات الوصفية هذا",
"EnableInteractiveSearch": "تمكين البحث التفاعلي",
"Source": "مصدر",
"SSLCertPassword": "كلمة مرور شهادة SSL",
@@ -107,19 +88,15 @@
"CouldNotConnectSignalR": "تعذر الاتصال بـ SignalR ، لن يتم تحديث واجهة المستخدم",
"Dates": "تواريخ",
"DBMigration": "ترحيل DB",
"DelayProfile": "ملف التأخير",
"Delete": "حذف",
"Details": "تفاصيل",
"Donations": "التبرعات",
"DownloadClient": "تحميل العميل",
"EnableAutoHelpText": "في حالة التمكين ، ستتم إضافة الأفلام تلقائيًا إلى Radarr من هذه القائمة",
"IndexerStatusCheckAllClientMessage": "جميع المفهرسات غير متوفرة بسبب الفشل",
"IndexerStatusCheckSingleClientMessage": "المفهرسات غير متاحة بسبب الإخفاقات: {0}",
"Info": "معلومات",
"Interval": "فترة",
"Manual": "كتيب",
"MaximumLimits": "الحدود القصوى",
"NoLimitForAnyRuntime": "لا يوجد حد لأي وقت تشغيل",
"General": "جنرال لواء",
"GeneralSettings": "الاعدادات العامة",
"Grabbed": "اقتطف",
@@ -168,7 +145,6 @@
"IndexerProxyStatusCheckAllClientMessage": "جميع المفهرسات غير متوفرة بسبب الفشل",
"NoChange": "لا تغيير",
"NoLogFiles": "لا توجد ملفات سجل",
"NoMinimumForAnyRuntime": "لا يوجد حد أدنى لأي وقت تشغيل",
"NoTagsHaveBeenAddedYet": "لم يتم إضافة أي علامات حتى الان",
"NotificationTriggers": "مشغلات الإخطار",
"NoUpdatesAreAvailable": "لا توجد تحديثات متوفرة",
@@ -195,10 +171,8 @@
"Clear": "واضح",
"ClientPriority": "أولوية العميل",
"Tomorrow": "غدا",
"DownloadClientCheckUnableToCommunicateMessage": "تعذر الاتصال بـ {0}.",
"Torrent": "السيول",
"Torrents": "السيول",
"ExistingMovies": "فيلم (أفلام) موجود",
"ExistingTag": "علامة موجودة",
"Failed": "فشل",
"FeatureRequests": "طلبات مخصصة",
@@ -210,23 +184,18 @@
"History": "التاريخ",
"Hostname": "اسم المضيف",
"IgnoredAddresses": "العناوين التي تم تجاهلها",
"Importing": "استيراد",
"UILanguage": "لغة واجهة المستخدم",
"UILanguageHelpText": "اللغة التي سيستخدمها Radarr لواجهة المستخدم",
"UILanguageHelpTextWarning": "يلزم إعادة تحميل المتصفح",
"KeyboardShortcuts": "اختصارات لوحة المفاتيح",
"LogFiles": "ملفات الدخول",
"LogLevelTraceHelpTextWarning": "يجب تمكين تسجيل التتبع مؤقتًا فقط",
"MonoTlsCheckMessage": "لا يزال الحل البديل لـ Radarr Mono 4.x tls ممكّنًا ، فجرّب إزالة MONO_TLS_PROVIDER = خيار البيئة القديمة",
"Movies": "أفلام",
"Name": "اسم",
"NetCore": ".شبكة",
"Password": "كلمه السر",
"PreferredSize": "الحجم المفضل",
"Proxy": "الوكيل",
"ProxyCheckResolveIpMessage": "فشل حل عنوان IP لمضيف الخادم الوكيل المكون {0}",
"ProxyType": "نوع الوكيل",
"QualityDefinitions": "تعريفات الجودة",
"ReleaseStatus": "حالة الإصدار",
"Search": "بحث",
"SettingsShortDateFormat": "تنسيق التاريخ القصير",
@@ -254,16 +223,11 @@
"DownloadClients": "تحميل العملاء",
"DownloadClientStatusCheckAllClientMessage": "جميع عملاء التنزيل غير متاحين بسبب الفشل",
"DownloadClientStatusCheckSingleClientMessage": "برامج التنزيل غير متاحة بسبب الإخفاقات: {0}",
"DownloadClientUnavailable": "عميل التنزيل غير متوفر",
"Downloading": "جارى التحميل",
"Edit": "تعديل",
"EditIndexer": "تحرير المفهرس",
"Enable": "ممكن",
"EnableAutomaticAdd": "تمكين إضافة تلقائية",
"EnableAutomaticSearch": "تمكين البحث التلقائي",
"EnableAutomaticSearchHelpTextWarning": "سيتم استخدامها عند استخدام البحث التفاعلي",
"Enabled": "ممكن",
"EnableInteractiveSearchHelpTextWarning": "البحث غير معتمد مع هذا المفهرس",
"IndexerPriority": "أولوية المفهرس",
"Port": "ميناء",
"SettingsTimeFormat": "تنسيق الوقت",
@@ -292,12 +256,10 @@
"SettingsLongDateFormat": "تنسيق التاريخ الطويل",
"ConnectionLost": "انقطع الاتصال",
"Connections": "روابط",
"MovieDetailsPreviousMovie": "تفاصيل الفيلم: الفيلم السابق",
"MovieIndexScrollBottom": "فهرس الفيلم: التمرير لأسفل",
"NoBackupsAreAvailable": "لا توجد نسخ احتياطية متاحة",
"NoChanges": "لا تغيرات",
"NoLeaveIt": "لا ، اتركها",
"Pending": "قيد الانتظار",
"PendingChangesDiscardChanges": "تجاهل التغييرات واترك",
"RSS": "RSS",
"System": "النظام",
@@ -328,7 +290,6 @@
"Mechanism": "آلية",
"Message": "رسالة",
"MIA": "MIA",
"MinimumLimits": "الحد الأدنى",
"RefreshMovie": "تحديث الفيلم",
"EnableAutomaticSearchHelpText": "سيتم استخدامه عند إجراء عمليات البحث التلقائي عبر واجهة المستخدم أو بواسطة Radarr",
"Status": "الحالة",
@@ -336,7 +297,6 @@
"ApplyTagsHelpTexts4": "استبدال: استبدل العلامات بالعلامات التي تم إدخالها (لا تدخل أي علامات لمسح جميع العلامات)",
"AuthenticationMethodHelpText": "طلب اسم المستخدم وكلمة المرور للوصول إلى Radarr",
"Automatic": "تلقائي",
"DownloadClientCheckNoneAvailableMessage": "لا يوجد عميل تنزيل متاح",
"Mode": "الوضع",
"Options": "خيارات",
"Ok": "موافق",
@@ -347,7 +307,6 @@
"RestartRequiredHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول",
"Restore": "استعادة",
"RestoreBackup": "استرجاع النسخة الاحتياطية",
"Restrictions": "قيود",
"Result": "نتيجة",
"Retention": "احتفاظ",
"SaveChanges": "حفظ التغييرات",
@@ -358,5 +317,12 @@
"Test": "اختبار",
"TestAll": "اختبار الكل",
"TestAllClients": "اختبر كل العملاء",
"UnableToAddANewApplicationPleaseTryAgain": "تعذر إضافة إشعار جديد ، يرجى المحاولة مرة أخرى."
"UnableToAddANewApplicationPleaseTryAgain": "تعذر إضافة إشعار جديد ، يرجى المحاولة مرة أخرى.",
"MaintenanceRelease": "إصدار الصيانة: إصلاحات الأخطاء والتحسينات الأخرى. راجع Github Commit History لمزيد من التفاصيل",
"Filters": "منقي",
"HistoryCleanupDaysHelpText": "اضبط على 0 لتعطيل التنظيف التلقائي",
"HistoryCleanupDaysHelpTextWarning": "سيتم تنظيف الملفات الموجودة في سلة المحذوفات الأقدم من عدد الأيام المحدد تلقائيًا",
"OnGrab": "عند الاستيلاء",
"OnHealthIssue": "في قضية الصحة",
"TestAllIndexers": "اختبار كافة المفهرسات"
}

View File

@@ -42,11 +42,9 @@
"Delete": "Изтрий",
"DeleteDownloadClient": "Изтриване на клиент за изтегляне",
"DeleteDownloadClientMessageText": "Наистина ли искате да изтриете клиента за изтегляне '{0}'?",
"DeleteIndexer": "Изтрийте Indexer",
"DeleteNotification": "Изтриване на известието",
"DeleteNotificationMessageText": "Наистина ли искате да изтриете известието '{0}'?",
"Donations": "Дарения",
"DownloadClientCheckUnableToCommunicateMessage": "Не може да се комуникира с {0}.",
"About": "относно",
"AcceptConfirmationModal": "Приемете модал за потвърждение",
"AddDownloadClient": "Добавяне на клиент за изтегляне",
@@ -66,20 +64,15 @@
"Connections": "Връзки",
"CouldNotConnectSignalR": "Не можах да се свържа със SignalR, потребителският интерфейс няма да се актуализира",
"DeleteBackupMessageText": "Наистина ли искате да изтриете резервното копие '{0}'?",
"DeleteIndexerMessageText": "Наистина ли искате да изтриете индексатора '{0}'?",
"DeleteTag": "Изтриване на маркера",
"DeleteTagMessageText": "Наистина ли искате да изтриете маркера '{0}'?",
"DownloadClientCheckNoneAvailableMessage": "Няма наличен клиент за изтегляне",
"IndexerProxyStatusCheckAllClientMessage": "Всички списъци са недостъпни поради неуспехи",
"LastWriteTime": "Време за последно писане",
"Columns": "Колони",
"EnableRss": "Активиране на RSS",
"Downloading": "Изтегляне",
"IndexerProxyStatusCheckSingleClientMessage": "Списъци, недостъпни поради неуспехи: {0}",
"Languages": "Езици",
"LogLevel": "Log Level",
"MovieIndexScrollTop": "Индекс на филма: Превъртете отгоре",
"Movies": "Филми",
"Queue": "Опашка",
"ReadTheWikiForMoreInformation": "Прочетете Wiki за повече информация",
"Reddit": "Reddit",
@@ -89,7 +82,6 @@
"RemovedFromTaskQueue": "Премахнато от опашката на задачите",
"RemoveFilter": "Отстранете филтъра",
"RestoreBackup": "Възстанови архива",
"Restrictions": "Ограничения",
"Result": "Резултат",
"Save": "Запазете",
"SaveChanges": "Запазите промените",
@@ -114,7 +106,6 @@
"UnableToAddANewIndexerPleaseTryAgain": "Не може да се добави нов индексатор, моля, опитайте отново.",
"UnableToAddANewIndexerProxyPleaseTryAgain": "Не може да се добави нов индексатор, моля, опитайте отново.",
"UnableToAddANewNotificationPleaseTryAgain": "Не може да се добави ново известие, моля, опитайте отново.",
"UnableToLoadQualityDefinitions": "Определенията за качество не могат да се заредят",
"UpdateAutomaticallyHelpText": "Автоматично изтегляне и инсталиране на актуализации. Все още ще можете да инсталирате от System: Updates",
"UpdateCheckStartupTranslocationMessage": "Не може да се инсталира актуализация, защото стартовата папка „{0}“ е в папка за преместване на приложения.",
"ReleaseStatus": "Състояние на освобождаване",
@@ -129,7 +120,6 @@
"StartupDirectory": "Стартова директория",
"Disabled": "хора с увреждания",
"EditIndexer": "Редактиране на Indexer",
"EnableAutomaticSearchHelpTextWarning": "Ще се използва, когато се използва интерактивно търсене",
"Enabled": "Активирано",
"General": "Общ",
"Grabbed": "Грабната",
@@ -148,21 +138,17 @@
"Language": "Език",
"LogLevelTraceHelpTextWarning": "Регистрацията на проследяване трябва да бъде разрешена само временно",
"Manual": "Ръчно",
"MaximumLimits": "Максимални граници",
"Mechanism": "Механизъм",
"Message": "Съобщение",
"MIA": "МВР",
"MinutesHundredTwenty": "120 минути: {0}",
"NoChange": "Няма промяна",
"NoChanges": "Без промени",
"NoLeaveIt": "Не, оставете го",
"NoLogFiles": "Няма регистрационни файлове",
"NoMinimumForAnyRuntime": "Няма минимум за всяко време на изпълнение",
"RestartRequiredHelpTextWarning": "Изисква рестартиране, за да влезе в сила",
"SettingsShortDateFormat": "Формат на кратка дата",
"SSLCertPath": "SSL Cert Path",
"URLBase": "URL база",
"CloneIndexer": "Clone Indexer",
"CloneProfile": "Профил на клониране",
"EnableInteractiveSearch": "Активирайте интерактивно търсене",
"TagIsNotUsedAndCanBeDeleted": "Етикетът не се използва и може да бъде изтрит",
@@ -175,7 +161,6 @@
"UnableToLoadBackups": "Архивите не могат да се заредят",
"AllIndexersHiddenDueToFilter": "Всички филми са скрити поради приложен филтър.",
"Level": "Ниво",
"MinimumLimits": "Минимални лимити",
"ApplicationStatusCheckAllClientMessage": "Всички списъци са недостъпни поради неуспехи",
"ApplicationStatusCheckSingleClientMessage": "Списъци, недостъпни поради неуспехи: {0}",
"Close": "Близо",
@@ -204,7 +189,6 @@
"PackageVersion": "Версия на пакета",
"PageSize": "Размер на страницата",
"PendingChangesStayReview": "Останете и прегледайте промените",
"PreferredSize": "Предпочитан размер",
"Presets": "Предварителни настройки",
"Priority": "Приоритет",
"PrioritySettings": "Приоритет",
@@ -212,13 +196,8 @@
"SettingsLongDateFormat": "Формат с дълга дата",
"SettingsShowRelativeDates": "Показване на относителни дати",
"UnableToLoadDownloadClients": "Клиентите за изтегляне не могат да се заредят",
"DelayProfile": "Профил за забавяне",
"Logging": "Регистрация",
"Exception": "Изключение",
"MinutesNinety": "90 минути: {0}",
"MinutesSixty": "60 минути: {0}",
"MovieDetailsNextMovie": "Подробности за филма: Следващ филм",
"MovieDetailsPreviousMovie": "Подробности за филма: Предишен филм",
"MovieIndexScrollBottom": "Индекс на филма: Превъртане отдолу",
"Name": "Име",
"New": "Ново",
@@ -233,12 +212,8 @@
"ProxyCheckResolveIpMessage": "Неуспешно разрешаване на IP адреса за конфигурирания прокси хост {0}",
"ProxyPasswordHelpText": "Трябва само да въведете потребителско име и парола, ако е необходимо. В противен случай ги оставете празни.",
"ProxyType": "Тип прокси",
"ExistingMovies": "Съществуващи филми",
"ExistingTag": "Съществуващ маркер",
"ProxyUsernameHelpText": "Трябва само да въведете потребителско име и парола, ако е необходимо. В противен случай ги оставете празни.",
"QualityDefinitions": "Определения за качество",
"QualitySettings": "Настройки за качество",
"Importing": "Импортиране",
"ReleaseBranchCheckOfficialBranchMessage": "Клон {0} не е валиден клон за издаване на Radarr, няма да получавате актуализации",
"IncludeHealthWarningsHelpText": "Включете здравни предупреждения",
"SaveSettings": "Запазване на настройките",
@@ -275,8 +250,6 @@
"UpdateCheckStartupNotWritableMessage": "Не може да се инсталира актуализация, тъй като стартовата папка „{0}“ не може да се записва от потребителя „{1}“.",
"UpdateCheckUINotWritableMessage": "Не може да се инсталира актуализация, защото папката „{0}“ на потребителския интерфейс не може да се записва от потребителя „{1}“.",
"Enable": "Активиране",
"EnableColorImpairedMode": "Активирайте режима с увредени цветове",
"EnableColorImpairedModeHelpText": "Променен стил, за да позволи на потребителите с увредени цветове да разграничат по-добре информацията, кодирана в цвят",
"EventType": "Тип на събитието",
"Failed": "Се провали",
"FeatureRequests": "Заявки за функции",
@@ -298,10 +271,6 @@
"Indexers": "Индексатори",
"IndexerStatusCheckAllClientMessage": "Всички индексатори са недостъпни поради грешки",
"Mode": "Режим",
"MonoTlsCheckMessage": "Работното решение на Radarr Mono 4.x tls все още е активирано, помислете за премахване на MONO_TLS_PROVIDER = опция за наследствена среда",
"MonoVersion": "Моно версия",
"MonoVersionCheckUpgradeRecommendedMessage": "Понастоящем инсталираната моно версия {0} се поддържа, но се препоръчва надстройка до {1}.",
"NoLimitForAnyRuntime": "Няма ограничение за всяко време на изпълнение",
"TagsSettingsSummary": "Вижте всички тагове и как се използват. Неизползваните маркери могат да бъдат премахнати",
"TestAllClients": "Тествайте всички клиенти",
"UnableToAddANewDownloadClientPleaseTryAgain": "Не може да се добави нов клиент за изтегляне, моля, опитайте отново.",
@@ -315,17 +284,9 @@
"DownloadClientSettings": "Изтеглете настройките на клиента",
"DownloadClientStatusCheckAllClientMessage": "Всички клиенти за изтегляне са недостъпни поради неуспехи",
"DownloadClientStatusCheckSingleClientMessage": "Клиентите за изтегляне са недостъпни поради грешки: {0}",
"DownloadClientUnavailable": "Клиентът за изтегляне не е наличен",
"Edit": "редактиране",
"EnableAutoHelpText": "Ако е активирано, Филмите ще бъдат автоматично добавени към Radarr от този списък",
"EnableAutomaticAdd": "Активирайте автоматичното добавяне",
"EnableAutomaticSearch": "Активирайте автоматичното търсене",
"EnableAutomaticSearchHelpText": "Ще се използва, когато се извършват автоматични търсения чрез потребителския интерфейс или от Radarr",
"EnableCompletedDownloadHandlingHelpText": "Автоматично импортирайте завършени изтегляния от клиент за изтегляне",
"EnabledHelpText": "Активирайте този списък за използване в Radarr",
"EnableHelpText": "Активирайте създаването на файл с метаданни за този тип метаданни",
"EnableInteractiveSearchHelpTextWarning": "Търсенето не се поддържа с този индексатор",
"EnableMediaInfoHelpText": "Извличайте видео информация като резолюция, време на работа и информация за кодеци от файлове. Това изисква от Radarr да чете части от файла, които могат да причинят висока активност на диска или мрежата по време на сканиране.",
"EnableSSL": "Активирайте SSL",
"EnableSslHelpText": " Изисква рестартиране, изпълнено като администратор, за да влезе в сила",
"Error": "Грешка",
@@ -350,12 +311,17 @@
"PageSizeHelpText": "Брой елементи за показване на всяка страница",
"Password": "Парола",
"Peers": "Връстници",
"Pending": "В очакване",
"PendingChangesDiscardChanges": "Изхвърлете промените и оставете",
"PriorityHelpText": "Приоритизирайте множество клиенти за изтегляне. Round-Robin се използва за клиенти със същия приоритет.",
"Protocol": "Протокол",
"UnableToLoadGeneralSettings": "Не може да се заредят общи настройки",
"UnableToLoadHistory": "Историята не може да се зареди",
"UnableToLoadIndexers": "Индексаторите не могат да се заредят",
"UnableToLoadNotifications": "Известията не могат да се заредят"
"UnableToLoadNotifications": "Известията не могат да се заредят",
"MaintenanceRelease": "Издание за поддръжка: поправки на грешки и други подобрения. Вижте История на комисиите на Github за повече подробности",
"Filters": "Филтър",
"HistoryCleanupDaysHelpText": "Задайте на 0, за да деактивирате автоматичното почистване",
"HistoryCleanupDaysHelpTextWarning": "Файловете в кошчето, по-стари от избрания брой дни, ще се почистват автоматично",
"OnGrab": "На Граб",
"OnHealthIssue": "По здравен въпрос",
"TestAllIndexers": "Тествайте всички индексатори"
}

View File

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

View File

@@ -4,7 +4,6 @@
"Actions": "Accions",
"AcceptConfirmationModal": "Accepta el mètode de confirmació",
"About": "Quant a",
"Movies": "Pel·lícula",
"New": "Nou",
"Reload": "Recarregar",
"Queue": "Cua",

View File

@@ -6,19 +6,16 @@
"About": "O",
"Component": "Součástka",
"Info": "Info",
"Languages": "Jazyky",
"LogFiles": "Záznam souborů",
"PrioritySettings": "Přednost",
"Logs": "Protokoly",
"ProxyBypassFilterHelpText": "Použijte ',' jako oddělovač a '*.' jako zástupný znak pro subdomény",
"QualityDefinitions": "Definice kvality",
"SaveSettings": "Uložit nastavení",
"Scheduled": "Naplánováno",
"ScriptPath": "Cesta skriptu",
"SetTags": "Nastavit značky",
"Settings": "Nastavení",
"StartTypingOrSelectAPathBelow": "Začněte psát nebo vyberte cestu níže",
"UnableToLoadIndexers": "Nelze načíst indexery",
"Usenet": "Usenet",
"AddDownloadClient": "Přidat staženého klienta",
"Backups": "Zálohy",
@@ -26,10 +23,8 @@
"MovieIndexScrollBottom": "Rejstřík filmů: Posun dolů",
"ProxyType": "Typ serveru proxy",
"Reddit": "Reddit",
"DownloadClientUnavailable": "Stahovací klient není k dispozici",
"ErrorLoadingContents": "Chyba při načítání obsahu",
"IndexerLongTermStatusCheckAllClientMessage": "Všechny indexery nejsou k dispozici z důvodu selhání po dobu delší než 6 hodin",
"MonoVersion": "Mono verze",
"RemovedFromTaskQueue": "Odebráno z fronty úkolů",
"ResetAPIKey": "Resetovat klíč API",
"SSLCertPassword": "Heslo SSL Cert",
@@ -49,7 +44,6 @@
"DownloadClientSettings": "Stáhněte si nastavení klienta",
"DownloadClientStatusCheckAllClientMessage": "Všichni klienti pro stahování nejsou kvůli chybám k dispozici",
"DownloadClientStatusCheckSingleClientMessage": "Stahování klientů není k dispozici z důvodu selhání: {0}",
"Downloading": "Stahování",
"Folder": "Složka",
"Grabs": "Urvat",
"HealthNoIssues": "Žádné problémy s vaší konfigurací",
@@ -68,12 +62,10 @@
"Level": "Úroveň",
"LogLevel": "Úroveň protokolu",
"Manual": "Manuál",
"MaximumLimits": "Maximální limity",
"Message": "Zpráva",
"MIA": "MIA",
"Mode": "Režim",
"NoTagsHaveBeenAddedYet": "Zatím nebyly přidány žádné značky",
"NoMinimumForAnyRuntime": "Žádné minimum za běhu",
"Ok": "OK",
"SendAnonymousUsageData": "Odesílejte anonymní údaje o používání",
"UnselectAll": "Odznačit vše",
@@ -91,10 +83,6 @@
"Branch": "Větev",
"BranchUpdate": "Pobočka, která se má použít k aktualizaci Radarr",
"EditIndexer": "Upravit indexátor",
"EnableColorImpairedModeHelpText": "Upravený styl umožňující uživatelům s barevným postižením lépe rozlišovat barevně kódované informace",
"EnableCompletedDownloadHandlingHelpText": "Automaticky importovat dokončená stahování z klienta pro stahování",
"EnabledHelpText": "Povolte tento seznam pro použití v Radarru",
"EnableHelpText": "Povolit vytváření souborů metadat pro tento typ metadat",
"ForMoreInformationOnTheIndividualDownloadClients": "Další informace o jednotlivých klientech pro stahování získáte kliknutím na informační tlačítka.",
"General": "Všeobecné",
"ApplyTagsHelpTexts3": "Odebrat: Odebere zadané značky",
@@ -103,7 +91,6 @@
"ConnectionLost": "Spojení ztraceno",
"ConnectSettings": "Připojit nastavení",
"Custom": "Zvyk",
"EnableAutomaticAdd": "Povolit automatické přidání",
"Error": "Chyba",
"Failed": "Selhalo",
"FeatureRequests": "Žádosti o funkce",
@@ -127,8 +114,6 @@
"ApplyTags": "Použít značky",
"MoreInfo": "Více informací",
"System": "Systém",
"EnableAutomaticSearchHelpTextWarning": "Bude použito při použití interaktivního vyhledávání",
"EnableColorImpairedMode": "Aktivujte režim se sníženou barevností",
"Enabled": "Povoleno",
"IgnoredAddresses": "Ignorované adresy",
"AcceptConfirmationModal": "Přijměte potvrzení Modal",
@@ -138,16 +123,7 @@
"LaunchBrowserHelpText": " Otevřete webový prohlížeč a při spuštění aplikace přejděte na domovskou stránku Radarr.",
"Logging": "Protokolování",
"Mechanism": "Mechanismus",
"MinutesNinety": "90 minut: {0}",
"MinutesSixty": "60 minut: {0}",
"MonoTlsCheckMessage": "Řešení Radarr Mono 4.x tls je stále povoleno, zvažte odebrání MONO_TLS_PROVIDER = možnost staršího prostředí",
"MonoVersionCheckUpgradeRecommendedMessage": "Aktuálně nainstalovaná mono verze {0} je podporována, ale doporučuje se upgradovat na {1}.",
"MovieDetailsNextMovie": "Detaily filmu: Další film",
"MovieDetailsPreviousMovie": "Detaily filmu: Předchozí film",
"Movies": "Filmy",
"NoLimitForAnyRuntime": "Žádné omezení za běhu",
"NoLinks": "Žádné odkazy",
"PreferredSize": "Preferovaná velikost",
"Presets": "Předvolby",
"Priority": "Přednost",
"PriorityHelpText": "Upřednostněte více klientů pro stahování. Round-Robin se používá pro klienty se stejnou prioritou.",
@@ -160,7 +136,6 @@
"ProxyPasswordHelpText": "Musíte pouze zadat uživatelské jméno a heslo, pokud je požadováno. Jinak je nechte prázdné.",
"ProxyUsernameHelpText": "Musíte pouze zadat uživatelské jméno a heslo, pokud je požadováno. Jinak je nechte prázdné.",
"PtpOldSettingsCheckMessage": "Následující indexovače PassThePopcorn mají zastaralá nastavení a měla by být aktualizována: {0}",
"QualitySettings": "Nastavení kvality",
"Queue": "Fronta",
"ReadTheWikiForMoreInformation": "Další informace najdete na Wiki",
"Refresh": "Obnovit",
@@ -193,7 +168,6 @@
"UnableToAddANewIndexerPleaseTryAgain": "Nelze přidat nový indexer, zkuste to znovu.",
"UnableToAddANewIndexerProxyPleaseTryAgain": "Nelze přidat nový indexer, zkuste to znovu.",
"UnableToLoadNotifications": "Nelze načíst oznámení",
"UnableToLoadQualityDefinitions": "Nelze načíst definice kvality",
"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",
@@ -226,7 +200,6 @@
"Restart": "Restartujte",
"RestartNow": "Restartovat nyní",
"RestoreBackup": "Obnovit zálohu",
"Restrictions": "Omezení",
"RSSIsNotSupportedWithThisIndexer": "RSS není u tohoto indexeru podporováno",
"Save": "Uložit",
"SSLCertPasswordHelpText": "Heslo pro soubor pfx",
@@ -235,8 +208,6 @@
"UnableToLoadBackups": "Nelze načíst zálohy",
"UnableToLoadDownloadClients": "Nelze načíst klienty pro stahování",
"UnableToLoadGeneralSettings": "Nelze načíst obecná nastavení",
"DeleteIndexer": "Odstranit indexer",
"DeleteIndexerMessageText": "Opravdu chcete odstranit indexer „{0}“?",
"DeleteNotification": "Smazat oznámení",
"EnableAutomaticSearch": "Povolit automatické vyhledávání",
"EnableInteractiveSearchHelpText": "Bude použito při použití interaktivního vyhledávání",
@@ -245,8 +216,6 @@
"Interval": "Interval",
"KeyboardShortcuts": "Klávesové zkratky",
"Language": "Jazyk",
"MinimumLimits": "Minimální limity",
"MinutesHundredTwenty": "120 minut: {0}",
"NoLogFiles": "Žádné soubory protokolu",
"NoBackupsAreAvailable": "Nejsou k dispozici žádné zálohy",
"NoChanges": "Žádné změny",
@@ -278,7 +247,6 @@
"ChangeHasNotBeenSavedYet": "Změna ještě nebyla uložena",
"Clear": "Průhledná",
"ClientPriority": "Priorita klienta",
"CloneIndexer": "Klonovat indexátor",
"CloneProfile": "Klonovat profil",
"Close": "Zavřít",
"ConnectionLostAutomaticMessage": "Radarr se pokusí připojit automaticky, nebo můžete kliknout na znovu načíst níže.",
@@ -287,7 +255,6 @@
"Date": "datum",
"Dates": "Termíny",
"DBMigration": "Migrace databáze",
"DelayProfile": "Zpožděný profil",
"Delete": "Vymazat",
"DeleteApplicationMessageText": "Opravdu chcete smazat oznámení „{0}“?",
"DeleteBackup": "Odstranit zálohu",
@@ -296,25 +263,18 @@
"DeleteTagMessageText": "Opravdu chcete smazat značku „{0}“?",
"Discord": "Svár",
"DownloadClient": "Stáhnout klienta",
"DownloadClientCheckNoneAvailableMessage": "Není k dispozici žádný klient pro stahování",
"DownloadClientCheckUnableToCommunicateMessage": "S uživatelem {0} nelze komunikovat.",
"DownloadClients": "Stáhnout klienty",
"Edit": "Upravit",
"Enable": "Umožnit",
"EnableAutoHelpText": "Pokud je tato možnost povolena, filmy budou automaticky přidány do Radarru z tohoto seznamu",
"EnableAutomaticSearchHelpText": "Použije se, když se automatické vyhledávání provádí pomocí uživatelského rozhraní nebo Radarr",
"EnableInteractiveSearch": "Povolit interaktivní vyhledávání",
"EnableInteractiveSearchHelpTextWarning": "Vyhledávání není u tohoto indexeru podporováno",
"EnableMediaInfoHelpText": "Extrahujte ze souborů informace o videu, jako je rozlišení, runtime a informace o kodeku. To vyžaduje, aby Radarr četl části souboru, což může během skenování způsobit vysokou aktivitu disku nebo sítě.",
"EnableSSL": "Povolit SSL",
"EnableSslHelpText": " Vyžaduje restartování spuštěné jako správce, aby se projevilo",
"Events": "Události",
"EventType": "Typ události",
"Exception": "Výjimka",
"ExistingMovies": "Stávající filmy",
"ExistingTag": "Stávající značka",
"IllRestartLater": "Restartuji později",
"Importing": "Import",
"IndexerLongTermStatusCheckSingleClientMessage": "Indexery nedostupné z důvodu selhání po dobu delší než 6 hodin: {0}",
"IndexerStatusCheckSingleClientMessage": "Indexery nedostupné z důvodu selhání: {0}",
"SettingsTimeFormat": "Časový formát",
@@ -335,7 +295,6 @@
"PageSizeHelpText": "Počet položek, které se mají zobrazit na každé stránce",
"Password": "Heslo",
"Peers": "Vrstevníci",
"Pending": "čekající",
"PendingChangesDiscardChanges": "Zahodit změny a odejít",
"PortNumber": "Číslo portu",
"Result": "Výsledek",
@@ -357,5 +316,14 @@
"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"
"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.",
"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",
"OnGrab": "Chyť",
"OnHealthIssue": "K otázce zdraví",
"TestAllIndexers": "Vyzkoušejte všechny indexery"
}

View File

@@ -1,6 +1,5 @@
{
"LastWriteTime": "Sidste Skrive Tid",
"Languages": "Sprog",
"Language": "Sprog",
"KeyboardShortcuts": "Keyboard Genveje",
"Info": "Information",
@@ -26,8 +25,6 @@
"DownloadClientStatusCheckAllClientMessage": "Alle download klienter er utilgængelige på grund af fejl",
"DownloadClientsSettingsSummary": "Download klienter, download håndtering og remote path mappings",
"DownloadClients": "Download Klienter",
"DownloadClientCheckUnableToCommunicateMessage": "Ude af stand til at kommunikere med {0}.",
"DownloadClientCheckNoneAvailableMessage": "Ingen download klient tilgængelig",
"DownloadClient": "Download Klient",
"Details": "Detaljer",
"Delete": "Slet",
@@ -59,10 +56,7 @@
"Add": "Tilføj",
"AddDownloadClient": "Tilføj downloadklient",
"DBMigration": "DB Migration",
"DelayProfile": "Udskyd Profiler",
"MIA": "MIA",
"MovieDetailsNextMovie": "Filmoplysninger: Næste film",
"MovieDetailsPreviousMovie": "Filmdetaljer: Forrige film",
"ResetAPIKey": "Nulstil API-nøgle",
"SettingsTimeFormat": "Tidsformat",
"SystemTimeCheckMessage": "Systemtiden er slukket mere end 1 dag. Planlagte opgaver kører muligvis ikke korrekt, før tiden er rettet",
@@ -109,13 +103,10 @@
"BypassProxyForLocalAddresses": "Bypass-proxy til lokale adresser",
"CancelPendingTask": "Er du sikker på, at du vil annullere denne afventende opgave?",
"CertificateValidation": "Validering af certifikat",
"CloneIndexer": "Klonindekser",
"CloseCurrentModal": "Luk Nuværende Modal",
"CouldNotConnectSignalR": "Kunne ikke oprette forbindelse til SignalR, UI opdateres ikke",
"DeleteApplicationMessageText": "Er du sikker på, at du vil slette underretningen '{0}'?",
"DeleteDownloadClientMessageText": "Er du sikker på, at du vil slette downloadklienten '{0}'?",
"DeleteIndexer": "Slet Indexer",
"DeleteIndexerMessageText": "Er du sikker på, at du vil slette indeksøren '{0}'?",
"DeleteIndexerProxyMessageText": "Er du sikker på, at du vil slette tagget '{0}'?",
"DeleteNotification": "Slet underretning",
"DeleteNotificationMessageText": "Er du sikker på, at du vil slette underretningen '{0}'?",
@@ -125,27 +116,16 @@
"Docker": "Docker",
"Donations": "Donationer",
"DownloadClientSettings": "Download klientindstillinger",
"DownloadClientUnavailable": "Downloadklienten er ikke tilgængelig",
"Downloading": "Downloader",
"EditIndexer": "Rediger indekser",
"Enable": "Aktiver",
"EnableAutoHelpText": "Hvis det er aktiveret, føjes film automatisk til Radarr fra denne liste",
"EnableAutomaticAdd": "Aktivér automatisk tilføjelse",
"EnableAutomaticSearch": "Aktivér automatisk søgning",
"EnableAutomaticSearchHelpText": "Bruges, når der foretages automatiske søgninger via brugergrænsefladen eller af Radarr",
"EnableAutomaticSearchHelpTextWarning": "Bruges, når der bruges interaktiv søgning",
"EnableColorImpairedMode": "Aktivér farve-nedsat tilstand",
"EnableColorImpairedModeHelpText": "Ændret stil for at give farvehæmmede brugere bedre at skelne mellem farvekodede oplysninger",
"EnableCompletedDownloadHandlingHelpText": "Importer automatisk afsluttede downloads fra downloadklienten",
"Enabled": "Aktiveret",
"EnableHelpText": "Aktivér oprettelse af metadatafiler for denne metadatatype",
"EnableInteractiveSearch": "Aktivér interaktiv søgning",
"EnableMediaInfoHelpText": "Uddrag videoinformation såsom opløsning, runtime og codec-oplysninger fra filer. Dette kræver, at Radarr læser dele af filen, som kan forårsage høj disk- eller netværksaktivitet under scanninger.",
"EnableRss": "Aktivér RSS",
"EnableSslHelpText": " Kræver genstart, der kører som administrator for at træde i kraft",
"ErrorLoadingContents": "Fejl ved indlæsning af indhold",
"Exception": "Undtagelse",
"ExistingMovies": "Eksisterende film",
"ExistingTag": "Eksisterende mærke",
"FeatureRequests": "Funktionsanmodninger",
"Filter": "Filter",
@@ -158,7 +138,6 @@
"Hostname": "Værtsnavn",
"IgnoredAddresses": "Ignorerede adresser",
"IllRestartLater": "Jeg genstarter senere",
"Importing": "Importerer",
"IncludeHealthWarningsHelpText": "Inkluder sundhedsadvarsler",
"Indexer": "Indekser",
"IndexerLongTermStatusCheckAllClientMessage": "Alle indeksatorer er ikke tilgængelige på grund af fejl i mere end 6 timer",
@@ -173,17 +152,9 @@
"LogLevelTraceHelpTextWarning": "Sporlogning bør kun aktiveres midlertidigt",
"Logs": "Logfiler",
"Manual": "brugervejledning",
"MaximumLimits": "Maksimale grænser",
"Mechanism": "Mekanisme",
"Message": "Besked",
"MinimumLimits": "Minimumsgrænser",
"MinutesHundredTwenty": "120 minutter: {0}",
"MinutesNinety": "90 minutter: {0}",
"MinutesSixty": "60 minutter: {0}",
"Mode": "Mode",
"MonoTlsCheckMessage": "Radarr Mono 4.x tls-løsning er stadig aktiveret, overvej at fjerne MONO_TLS_PROVIDER = ældre miljømulighed",
"MonoVersion": "Mono-version",
"MonoVersionCheckUpgradeRecommendedMessage": "Aktuelt installeret Mono-version {0} understøttes, men det anbefales at opgradere til {1}.",
"MovieIndexScrollBottom": "Filmindeks: Rul ned",
"MovieIndexScrollTop": "Filmindeks: Scroll Top",
"Name": "Navn",
@@ -191,23 +162,19 @@
"NoBackupsAreAvailable": "Ingen sikkerhedskopier er tilgængelige",
"NoChanges": "Ingen ændringer",
"NoLeaveIt": "Nej, lad det være",
"NoLimitForAnyRuntime": "Ingen grænse for nogen runtime",
"NoLinks": "Ingen links",
"NoLogFiles": "Ingen logfiler",
"NoMinimumForAnyRuntime": "Intet minimum for enhver driftstid",
"NoTagsHaveBeenAddedYet": "Der er ikke tilføjet nogen tags endnu",
"NotificationTriggers": "Meddelelsesudløsere",
"OnHealthIssueHelpText": "Om sundhedsspørgsmål",
"OpenThisModal": "Åbn denne modal",
"PackageVersion": "Pakkeversion",
"PageSize": "Sidestørrelse",
"Pending": "Verserende",
"PendingChangesDiscardChanges": "Kassér ændringer og gå ud",
"PendingChangesMessage": "Du har ikke gemte ændringer. Er du sikker på, at du vil forlade denne side?",
"PendingChangesStayReview": "Bliv og gennemgå ændringer",
"Port": "Havn",
"PortNumber": "Portnummer",
"PreferredSize": "Foretrukken størrelse",
"Priority": "Prioritet",
"Protocol": "Protokol",
"ProxyBypassFilterHelpText": "Brug ',' som en separator og '*.' som et jokertegn for underdomæner",
@@ -217,8 +184,6 @@
"ProxyPasswordHelpText": "Du skal kun indtaste et brugernavn og en adgangskode, hvis der kræves et. Lad dem være tomme ellers.",
"ProxyUsernameHelpText": "Du skal kun indtaste et brugernavn og en adgangskode, hvis der kræves et. Lad dem være tomme ellers.",
"PtpOldSettingsCheckMessage": "Følgende PassThePopcorn-indeksatorer har forældede indstillinger og skal opdateres: {0}",
"QualityDefinitions": "Kvalitetsdefinitioner",
"QualitySettings": "Kvalitetsindstillinger",
"ReadTheWikiForMoreInformation": "Læs Wiki for mere information",
"Reddit": "Reddit",
"Refresh": "Opdater",
@@ -275,7 +240,6 @@
"UnableToAddANewIndexerPleaseTryAgain": "Kunne ikke tilføje en ny indekser. Prøv igen.",
"UnableToLoadBackups": "Kunne ikke indlæse sikkerhedskopier",
"UnableToLoadGeneralSettings": "Kan ikke indlæse generelle indstillinger",
"UnableToLoadIndexers": "Kan ikke indlæse indeksatorer",
"UnableToLoadNotifications": "Kunne ikke indlæse meddelelser",
"UnableToLoadTags": "Kan ikke indlæse tags",
"UnableToLoadUISettings": "UI-indstillingerne kunne ikke indlæses",
@@ -297,7 +261,6 @@
"ReleaseStatus": "Frigør status",
"Restore": "Gendan",
"RestoreBackup": "Gendan sikkerhedskopi",
"Restrictions": "Begrænsninger",
"RSS": "RSS",
"RSSIsNotSupportedWithThisIndexer": "RSS understøttes ikke med denne indekseringsenhed",
"Save": "Gemme",
@@ -310,12 +273,10 @@
"StartupDirectory": "Startmappe",
"Status": "Status",
"UnableToLoadDownloadClients": "Kunne ikke indlæse downloadklienter",
"UnableToLoadQualityDefinitions": "Kunne ikke indlæse kvalitetsdefinitioner",
"UpdateCheckStartupTranslocationMessage": "Kan ikke installere opdatering, fordi startmappen '{0}' er i en App Translocation-mappe.",
"UpdateMechanismHelpText": "Brug Radarrs indbyggede opdatering eller et script",
"View": "Udsigt",
"Warn": "Advare",
"Movies": "Film",
"AddingTag": "Tilføjer tag",
"ApplicationStatusCheckAllClientMessage": "Alle lister er utilgængelige på grund af fejl",
"ApplicationStatusCheckSingleClientMessage": "Lister utilgængelige på grund af fejl: {0}",
@@ -340,7 +301,6 @@
"ClientPriority": "Kundens prioritet",
"CloneProfile": "Klonprofil",
"EnableInteractiveSearchHelpText": "Bruges, når der bruges interaktiv søgning",
"EnableInteractiveSearchHelpTextWarning": "Søgning understøttes ikke med denne indekser",
"UISettings": "UI-indstillinger",
"NoUpdatesAreAvailable": "Ingen opdateringer er tilgængelige",
"OAuthPopupMessage": "Pop-ups blokeres af din browser",
@@ -357,9 +317,15 @@
"AppDataDirectory": "AppData-bibliotek",
"CertificateValidationHelpText": "Skift, hvor streng HTTPS-certificering er",
"ChangeHasNotBeenSavedYet": "Ændring er endnu ikke gemt",
"EnabledHelpText": "Aktivér denne liste til brug i Radarr",
"ConnectSettings": "Forbind indstillinger",
"DeleteBackup": "Slet sikkerhedskopi",
"DeleteBackupMessageText": "Er du sikker på, at du vil slette sikkerhedskopien '{0}'?",
"DeleteDownloadClient": "Slet Download Client"
"DeleteDownloadClient": "Slet Download Client",
"MaintenanceRelease": "Vedligeholdelsesfrigivelse: fejlrettelser og andre forbedringer. Se Github Commit History for flere detaljer",
"Filters": "Filter",
"HistoryCleanupDaysHelpText": "Sæt til 0 for at deaktivere automatisk oprydning",
"HistoryCleanupDaysHelpTextWarning": "Filer i papirkurven, der er ældre end det valgte antal dage, renses automatisk",
"OnGrab": "On Grab",
"OnHealthIssue": "Om sundhedsspørgsmål",
"TestAllIndexers": "Test alle indeksører"
}

View File

@@ -6,16 +6,14 @@
"Backup": "Backups",
"BackupNow": "Jetzt sichern",
"Clear": "Leeren",
"Connect": "Verbindungen",
"Connect": "Benachrichtigungen",
"Connections": "Verbindungen",
"CustomFilters": "Filter anpassen",
"Date": "Datum",
"Dates": "Termine",
"Delete": "Löschen",
"DownloadClientCheckNoneAvailableMessage": "Es ist kein Downloader verfügbar",
"DownloadClientCheckUnableToCommunicateMessage": "Kommunikation mit {0} nicht möglich.",
"DownloadClientStatusCheckAllClientMessage": "Alle Downloader sind aufgrund von Fehlern nicht verfügbar",
"DownloadClientStatusCheckSingleClientMessage": "Downloader aufgrund von Fehlern nicht verfügbar: {0}",
"DownloadClientStatusCheckAllClientMessage": "Alle Download Clients sind aufgrund von Fehlern nicht verfügbar",
"DownloadClientStatusCheckSingleClientMessage": "Download Clients aufgrund von Fehlern nicht verfügbar: {0}",
"DownloadClients": "Downloader",
"Edit": "Bearbeiten",
"Events": "Events",
@@ -30,13 +28,9 @@
"IndexerStatusCheckAllClientMessage": "Alle Indexer sind aufgrund von Fehlern nicht verfügbar",
"IndexerStatusCheckSingleClientMessage": "Indexer aufgrund von Fehlern nicht verfügbar: {0}",
"Indexers": "Indexer",
"Languages": "Sprachen",
"LogFiles": "Protokolle",
"Logging": "Protokollierung",
"MonoNotNetCoreCheckMessage": "Bitte aktualisieren Sie auf die .NET Core-Version von Prowlarr",
"MonoTlsCheckMessage": "Der Workaround für Prowlarr Mono 4.x TLS ist weiterhin aktiviert. Entferne möglicherweise die Option MONO_TLS_PROVIDER=legacy",
"MoreInfo": "Mehr Infos",
"Movies": "Filme",
"NoChange": "Keine Änderung",
"NoChanges": "Keine Änderungen",
"Options": "Optionen",
@@ -45,13 +39,10 @@
"ProxyCheckFailedToTestMessage": "Proxy konnte nicht getestet werden: {0}",
"ProxyCheckResolveIpMessage": "Fehler beim Auflösen der IP-Adresse für den konfigurierten Proxy-Host {0}",
"PtpOldSettingsCheckMessage": "Die folgenden PassThePopcorn-Indexer haben veraltete Einstellungen und sollten aktualisiert werden: {0}",
"QualityDefinitions": "Qualitätsdefinitionen",
"Queue": "Warteschlange",
"Refresh": "Aktualisieren",
"ReleaseBranchCheckOfficialBranchMessage": "Zweig {0} ist kein gültiger Prowlarr-Release-Zweig. Sie erhalten keine Updates",
"ReleaseBranchCheckPreviousVersionMessage": "Zweig {0} ist für eine frühere Version von Prowlarr. Setzen Sie den Zweig für weitere Aktualisierungen auf 'nightly'",
"RestoreBackup": "Backup einspielen",
"Restrictions": "Beschränkungen",
"SaveChanges": "Änderungen speichern",
"Scheduled": "Geplant",
"Search": "Suche",
@@ -74,16 +65,16 @@
"Updates": "Updates",
"View": "Ansicht",
"Language": "Sprache",
"UISettingsSummary": "Einstellungen für Kalender, Datumsformat und Farbbeeinträchtigung",
"UISettingsSummary": "Optionen für Datum, Sprache und Farbbeinträchtigungen",
"TagsSettingsSummary": "Alle Tags und deren Benutzung anzeigen. Unbenutzte Tags können entfernt werden",
"Size": "Größe",
"ReleaseStatus": "Releasestatus",
"Protocol": "Protokoll",
"LastWriteTime": "Zuletzt beschrieben",
"Indexer": "Indexer",
"DownloadClientsSettingsSummary": "Downloader, Downloadverarbeitung und Remote-Pfadzuordnungen",
"DownloadClientsSettingsSummary": "Download der Client-Konfigurationen für die Integration in die Prowlarr UI-Suche",
"Grabbed": "Erfasste",
"GeneralSettingsSummary": "Port, SSL, Benutzername/Passwort, Proxy, Statistik und Updates",
"GeneralSettingsSummary": "Port, SSL, Benutzername/Passwort, Proxy, Analytik und Updates",
"Filename": "Dateiname",
"Failed": "Fehlgeschlagen",
"EventType": "Event Typ",
@@ -140,11 +131,8 @@
"PendingChangesStayReview": "Auf der Seite bleiben",
"PendingChangesMessage": "Es gibt noch ungespeicherte Änderungen, bist du sicher, dass du die Seite verlassen möchtest?",
"PendingChangesDiscardChanges": "Änderungen verwerfen und schließen",
"MonoVersionCheckUpgradeRecommendedMessage": "Die momentane installierte Mono Version {0} wird unterstützt aber es wird empfohlen auf {1} zu updaten.",
"ExistingMovies": "Vorhandene Filme",
"UpdateAutomaticallyHelpText": "Updates automatisch herunteraden und installieren. Es kann weiterhin unter \"System -> Updates\" ein manuelles Update angestoßen werden",
"UrlBaseHelpText": "Für Reverse-Proxy-Unterstützung. Die Standardeinstellung leer",
"EnableAutoHelpText": "Wenn aktiviert werden Filme dieser Liste automatisch hinzugefügt",
"AppDataDirectory": "AppData Ordner",
"ApplyTags": "Tags setzen",
"Authentication": "Authentifizierung",
@@ -163,17 +151,11 @@
"CloneProfile": "Profil kopieren",
"DeleteBackup": "Backup löschen",
"DeleteDownloadClient": "Downloader löschen",
"DeleteIndexer": "Indexer löschen",
"DeleteTag": "Tag löschen",
"Docker": "Docker",
"DownloadClientSettings": "Downloader Einstellungen",
"Enable": "Aktivieren",
"EnableAutomaticAdd": "Automatisch hinzufügen",
"EnableAutomaticSearch": "Automatisch suchen",
"EnableColorImpairedMode": "Farbbeeinträchtigter Modus aktivieren",
"EnableCompletedDownloadHandlingHelpText": "Importiere fertige Downloads vom Downloader automatisch",
"EnabledHelpText": "Aktiviere diese Liste",
"EnableHelpText": "Metadaten Dateien erstellen für diesen Metadata Typ",
"EnableInteractiveSearch": "Interaktive Suche",
"EnableSSL": "SSL",
"EnableSslHelpText": " Erfordert einen Neustart als Administrator",
@@ -182,21 +164,17 @@
"Hostname": "Hostname",
"IgnoredAddresses": "Ignorierte Adressen",
"IllRestartLater": "Später neustarten",
"Importing": "Importiere",
"IncludeHealthWarningsHelpText": "Zustandswarnung",
"IndexerFlags": "Indexer Flags",
"IndexerFlags": "Indexer-Flags",
"Interval": "Intervall",
"LogLevel": "Log Level",
"Logs": "Logs",
"Logs": "Protokolle",
"Mechanism": "Verfahren",
"MIA": "MIA",
"MinimumLimits": "Mindest Grenzen",
"Mode": "Modus",
"MonoVersion": "Mono Version",
"NetCore": ".NET Core",
"New": "Neu",
"NoLeaveIt": "Nein, nicht ändern",
"NoLimitForAnyRuntime": "Keine Begrenzung der Laufzeiten",
"NotificationTriggers": "Benachrichtigungs Auslöser",
"OnHealthIssueHelpText": "Zustandsproblem",
"OpenBrowserOnStart": "Browser beim Start öffnen",
@@ -205,11 +183,9 @@
"Password": "Passwort",
"Port": "Port",
"PortNumber": "Port Nummer",
"PreferredSize": "Bevorzugte Größe",
"ProxyBypassFilterHelpText": "Verwende ',' als Trennzeichen und '*.' als Platzhalter für Subdomains",
"ProxyType": "Proxy Typ",
"ProxyUsernameHelpText": "Nur wenn ein Benutzername und Passwort erforderlich ist, muss es eingegeben werden. Ansonsten leer lassen.",
"QualitySettings": "Qualitäts Einstellungen",
"RefreshMovie": "Film aktualisieren",
"RemovedFromTaskQueue": "Aus der Aufgabenwarteschlage entfernt",
"RemoveFilter": "Filter entfernen",
@@ -227,25 +203,20 @@
"SuggestTranslationChange": "Schlage eine Übersetzung vor",
"TagsHelpText": "Wird auf Filme mit mindestens einem passenden Tag angewandt",
"TestAllClients": "Alle testen",
"UpdateMechanismHelpText": "Benutze den Built-In Updater oder ein Script",
"UpdateMechanismHelpText": "Benutze Prowlarr's Built-In Updater oder ein Script",
"UpdateScriptPathHelpText": "Pfad zu einem benutzerdefinierten Skript, das ein extrahiertes Update-Paket übernimmt und den Rest des Update-Prozesses abwickelt",
"Uptime": "Laufzeit",
"URLBase": "URL Base",
"URLBase": "URL-Basis",
"Torrents": "Torrents",
"UISettings": "Benutzeroberflächen Einstellungen",
"UnableToLoadDownloadClients": "Downloader konnten nicht geladen werden",
"UnableToLoadIndexers": "Indexer konnten nicht geladen werden",
"UnableToLoadNotifications": "Benachrichtigungen konnten nicht geladen werden",
"UnableToLoadQualityDefinitions": "Qualitätsdefinitionen konnten nicht geladen werden",
"UnableToLoadTags": "Tags konnten nicht geladen werden",
"Usenet": "Usenet",
"UseProxy": "Proxy benutzen",
"Username": "Benutzername",
"Version": "Version",
"YesCancel": "Ja, abbrechen",
"EnableColorImpairedModeHelpText": "Alternativer Stil, um farbbeeinträchtigten Benutzern eine bessere Unterscheidung farbcodierter Informationen zu ermöglichen",
"EnableMediaInfoHelpText": "Videoinformationen wie Auflösung, Laufzeit und Codec aus Datien erkennen. Dazu ist es erforderlich, dass Prowlarr Teile der Datei liest, was zu hoher Festplatten- oder Netzwerkaktivität während der Scans führen kann.",
"NoMinimumForAnyRuntime": "Kein Minimum für Laufzeiten",
"AnalyticsEnabledHelpText": "Sende anonyme Nutzungs- und Fehlerinformationen an die Server von Prowlarr. Dazu gehören Informationen über Browser, welche Seiten der Prowlarr-Weboberfläche aufgerufen wurden, Fehlerberichte sowie Betriebssystem- und Laufzeitversion. Wir werden diese Informationen verwenden, um Funktionen und Fehlerbehebungen zu priorisieren.",
"RestartRequiredHelpTextWarning": "Erfordert einen Neustart",
"ApiKey": "API-Schlüssel",
@@ -258,10 +229,7 @@
"LaunchBrowserHelpText": " Öffne die Startseite von Prowlarr im Webbrowser nach dem Start.",
"ReadTheWikiForMoreInformation": "Lese das Wiki für mehr Informationen",
"BackupFolderHelpText": "Relative Pfade befinden sich unter Prowlarrs AppData Ordner",
"DelayProfile": "Verzögerungsprofil",
"MaximumLimits": "Maximale Grenzen",
"DeleteNotification": "Benachrichtigung löschen",
"CloneIndexer": "Indexer kopieren",
"ConnectSettings": "Eintellungen für Verbindungen",
"TagCannotBeDeletedWhileInUse": "Kann während der Benutzung nicht gelöscht werden",
"SSLCertPathHelpText": "Pfad zur PFX Datei",
@@ -269,20 +237,14 @@
"ShownClickToHide": "Angezeigt, zum verstecken klicken",
"RSSIsNotSupportedWithThisIndexer": "RSS wird von diesem Indexer nicht unterstützt",
"RemovingTag": "Tag entfernen",
"Pending": "Ausstehend",
"Manual": "Manuell",
"LogLevelTraceHelpTextWarning": "Trace logging sollte nur kurzzeitig aktiviert werden",
"HiddenClickToShow": "Versteckt, klicken zum anzeigen",
"ExistingTag": "Vorhandener Tag",
"EnableInteractiveSearchHelpTextWarning": "Der Indexer unterstützt keine Suchen",
"EnableInteractiveSearchHelpText": "Wird bei der manuellen Suche benutzt",
"EnableAutomaticSearchHelpTextWarning": "Wird für die manuelle Suche benutzt",
"EnableAutomaticSearchHelpText": "Wird für automatische Suchen genutzt die vom Benutzer oder von Prowlarr gestartet werden",
"Downloading": "Lädt herunter",
"DownloadClientUnavailable": "Downloader ist nicht verfügbar",
"DeleteTagMessageText": "Tag '{0}' wirklich löschen?",
"DeleteNotificationMessageText": "Benachrichtigung '{0}' wirklich löschen?",
"DeleteIndexerMessageText": "Indexer '{0}' wirklich löschen?",
"DeleteDownloadClientMessageText": "Downloader '{0}' wirklich löschen?",
"DeleteBackupMessageText": "Backup '{0}' wirkich löschen?",
"CancelPendingTask": "Diese laufende Aufgabe wirklich abbrechen?",
@@ -306,10 +268,7 @@
"NoTagsHaveBeenAddedYet": "Es wurden noch keine Tags erstellt",
"NoLogFiles": "Keine Log-Dateien",
"NoBackupsAreAvailable": "Es sind keine Backups vorhanden",
"MinutesSixty": "60 Minuten: {0}",
"MinutesNinety": "90 Minuten: {0}",
"MinutesHundredTwenty": "120 Minuten: {0}",
"MaintenanceRelease": "Wartungsupdate",
"MaintenanceRelease": "Wartung: Fehlerbehebung und andere Verbesserungen. Siehe Github Commit History für weitere Details",
"ForMoreInformationOnTheIndividualDownloadClients": "Für mehr Infomationen klicke auf die Info-Knöpfe.",
"FilterPlaceHolder": "Filme suchen",
"Exception": "Ausnahme",
@@ -323,7 +282,7 @@
"UILanguage": "Oberflächen Sprache ( UI Language )",
"Priority": "Priorität",
"InteractiveSearch": "Interaktive Suche",
"IndexerPriorityHelpText": "Indexer Priorität von 1 (höchste) bis 50 (niedrigste). Standart: 25.",
"IndexerPriorityHelpText": "Indexer Priorität von 1 (höchste) bis 50 (niedrigste). Standard: 25.",
"IndexerPriority": "Priorität",
"EditIndexer": "Indexer bearbeiten",
"Disabled": "Deaktiviert",
@@ -333,8 +292,6 @@
"OpenThisModal": "Dieses Modal öffnen",
"MovieIndexScrollTop": "Filmindex: Nach oben scrollen",
"MovieIndexScrollBottom": "Filmindex: Nach unten scrollen",
"MovieDetailsPreviousMovie": "Film Details: Vorheriger Film",
"MovieDetailsNextMovie": "Film Details: Nächster Film",
"FocusSearchBox": "Suchbox fokussieren",
"CloseCurrentModal": "Momentanes Modal schließen",
"AcceptConfirmationModal": "Bestätigung akzeptieren Modal",
@@ -382,7 +339,7 @@
"EditAppProfile": "App-Profil bearbeiten",
"Wiki": "Wiki",
"RSS": "RSS",
"RedirectHelpText": "Leiten Sie eingehende Download-Anfragen für den Indexer um, statt Proxying mit Prowlarr",
"RedirectHelpText": "Eingehende Download-Anfragen für den Indexer umleiten, anstatt Proxying mit Prowlarr",
"Redirect": "Umleiten",
"Reddit": "Reddit",
"HomePage": "Startseite",
@@ -396,8 +353,8 @@
"UnableToLoadAppProfiles": "App-Profile können nicht geladen werden",
"UnableToAddANewAppProfilePleaseTryAgain": "Es kann kein neues Anwendungsprofil hinzugefügt werden. Bitte versuchen Sie es erneut.",
"UnableToAddANewApplicationPleaseTryAgain": "Es kann keine neue Anwendung hinzugefügt werden. Bitte versuchen Sie es erneut.",
"SyncLevelFull": "Vollständige Synchronisierung: Hält diese App vollständig synchron. In Prowlarr vorgenommene Änderungen werden dann mit dieser App synchronisiert. Alle aus der Ferne vorgenommenen Änderungen werden von Prowlarr bei der nächsten Synchronisierung überschrieben.",
"SyncLevelAddRemove": "Nur hinzufügen und entfernen: Wenn es in Prowlarr hinzugefügt oder entfernt wird, aktualisiert es diese Remote-App.",
"SyncLevelFull": "Vollständige Synchronisierung: Hält die Indexer dieser App vollständig synchronisiert. Änderungen an Indexern in Prowlarr werden mit dieser App synchronisiert. Jede Änderung die and Indexern dieser App gemacht werden, wird von Prowlarr bei der nächsten Synchronisierung überschrieben.",
"SyncLevelAddRemove": "Nur hinzufügen und entfernen: Wenn Indexer zu Prowlarr hinzugefügt oder entfernt werden, wird diese Remote-App aktualisiert.",
"SyncLevel": "Sync-Level",
"Privacy": "Privatsphäre",
"Presets": "Voreinstellungen",
@@ -429,5 +386,45 @@
"AudioSearch": "Audio Suche",
"AddIndexerProxy": "Indexer Proxy hinzufügen",
"AppSettingsSummary": "Anwendungen und Einstellungen um zu konfigurieren, wie Prowlarr mit deinen PVR Programmen interagiert",
"DeleteIndexerProxy": "Indexer Proxy löschen"
"DeleteIndexerProxy": "Indexer Proxy löschen",
"Notification": "Benachrichtigungen",
"Filters": "Filter",
"Notifications": "Benachrichtigungen",
"HistoryCleanupDaysHelpText": "Auf 0 setzen um das automatische leeren des Papierkorbs zu deaktivieren",
"HistoryCleanupDaysHelpTextWarning": "Datien im Papierkorb die älter sind als der gewählte Wert, werden endgültig gelöscht",
"OnApplicationUpdate": "Bei Anwendungsaktualisierung",
"OnApplicationUpdateHelpText": "Bei Anwendungsaktualisierung",
"OnGrab": "Bei Erfassung",
"OnHealthIssue": "Bei Zustandsproblem",
"TestAllIndexers": "Alle testen",
"UserAgentProvidedByTheAppThatCalledTheAPI": "UserAgent von der App welcher die API aufgerufen hat",
"BookSearch": "Buch Suche",
"Id": "Id",
"IndexerProxies": "Indexer-Proxies",
"IndexerTagsHelpText": "Benutze Tags, um Indexer-Proxies zu spezifizieren oder um Indexer zu organisieren.",
"MovieSearch": "Film Suche",
"QueryOptions": "Abfrage-Optionen",
"Categories": "Kategorien",
"Database": "Datenbank",
"IndexerNoDefCheckMessage": "Indexer haben keine Definition und werden nicht funktionieren: {0}. Bitte entferne und (oder) füge diese neu zu Prowlarr hinzu",
"MassEditor": "Masseneditor",
"Private": "Privat",
"TvSearch": "TV Suche",
"Url": "Url",
"Auth": "Authentifizierung",
"HistoryCleanup": "Verlaufsbereinigung",
"IndexerAlreadySetup": "Mindestens eine Indexer Instanz ist bereits eingerichtet",
"IndexerInfo": "Indexer-Info",
"IndexerProxy": "Indexer-Proxy",
"IndexerSettingsSummary": "Konfiguration verschiedener globaler Indexer Einstellungen, einschließlich Proxies.",
"IndexerVipCheckExpiredClientMessage": "Die VIP Indexer Vorteile sind abgelaufen: {0}",
"IndexerVipCheckExpiringClientMessage": "Die Indexer VIP Vorteile verfallen bald: {0}",
"Proxies": "Proxies",
"Public": "Öffentlich",
"QueryResults": "Abfrageergebnisse",
"SearchType": "Suchtyp",
"SemiPrivate": "Halbprivat",
"UnableToLoadApplicationList": "Anwendungsliste kann nicht geladen werden",
"UnableToLoadIndexerProxies": "Indexer-Proxies können nicht geladen werden",
"Website": "Webseite"
}

View File

@@ -1,7 +1,5 @@
{
"DownloadClients": "Προγράμματα Λήψης",
"DownloadClientCheckUnableToCommunicateMessage": "Αδύνατο να επικοινωνήσει με {0}.",
"DownloadClientCheckNoneAvailableMessage": "Δεν υπάρχει διαθέσιμο πρόγραμμα Λήψης",
"DownloadClient": "Πρόγραμμα Λήψης",
"Details": "Λεπτομέρειες",
"Delete": "Διαγραφή",
@@ -61,7 +59,6 @@
"IllRestartLater": "Θα επανεκκινήσω αργότερα",
"Protocol": "Πρωτόκολλο",
"Reddit": "Reddit",
"Restrictions": "Περιορισμοί",
"Result": "Αποτέλεσμα",
"Retention": "Κράτηση",
"RSS": "RSS",
@@ -77,16 +74,13 @@
"ApplyTagsHelpTexts2": "Προσθήκη: Προσθέστε τις ετικέτες στην υπάρχουσα λίστα ετικετών",
"ApplyTagsHelpTexts3": "Αφαίρεση: Καταργήστε τις καταχωρημένες ετικέτες",
"AreYouSureYouWantToResetYourAPIKey": "Είστε βέβαιοι ότι θέλετε να επαναφέρετε το κλειδί API σας;",
"DeleteIndexer": "Διαγραφή ευρετηρίου",
"NoChange": "Καμία αλλαγή",
"Port": "Λιμάνι",
"PortNumber": "Αριθμός θύρας",
"PreferredSize": "Προτιμώμενο μέγεθος",
"IndexerStatusCheckAllClientMessage": "Όλοι οι δείκτες δεν είναι διαθέσιμοι λόγω αστοχιών",
"IndexerStatusCheckSingleClientMessage": "Τα ευρετήρια δεν είναι διαθέσιμα λόγω αστοχιών: {0}",
"KeyboardShortcuts": "Συντομεύσεις πληκτρολογίου",
"Language": "Γλώσσα",
"MonoTlsCheckMessage": "Η λύση Radarr Mono 4.x tls είναι ακόμα ενεργοποιημένη, εξετάστε το ενδεχόμενο να καταργήσετε το MONO_TLS_PROVIDER = επιλογή περιβάλλοντος παλαιού τύπου",
"PriorityHelpText": "Προτεραιότητα πολλαπλών πελατών λήψης. Το Round-Robin χρησιμοποιείται για πελάτες με την ίδια προτεραιότητα.",
"PrioritySettings": "Προτεραιότητα",
"Reset": "Επαναφορά",
@@ -103,11 +97,6 @@
"ConnectSettings": "Σύνδεση ρυθμίσεων",
"Disabled": "άτομα με ειδικές ανάγκες",
"Manual": "Εγχειρίδιο",
"MaximumLimits": "Μέγιστα όρια",
"MinutesSixty": "60 λεπτά: {0}",
"MovieDetailsNextMovie": "Λεπτομέρειες ταινίας: Επόμενη ταινία",
"QualityDefinitions": "Ορισμοί ποιότητας",
"QualitySettings": "Ρυθμίσεις ποιότητας",
"SetTags": "Ορισμός ετικετών",
"ShowSearch": "Εμφάνιση αναζήτησης",
"Status": "Κατάσταση",
@@ -123,7 +112,6 @@
"IndexerPriority": "Προτεραιότητα ευρετηρίου",
"Indexers": "Ευρετήρια",
"Info": "Πληροφορίες",
"Languages": "Γλώσσες",
"LastWriteTime": "Τελευταία ώρα εγγραφής",
"Level": "Επίπεδο",
"LogLevel": "Επίπεδο καταγραφής",
@@ -143,24 +131,16 @@
"CloneProfile": "Προφίλ κλώνου",
"CloseCurrentModal": "Κλείσιμο τρέχοντος modal",
"DBMigration": "Μετεγκατάσταση DB",
"DelayProfile": "Προφίλ χρονοκαθυστέρησης",
"DeleteApplicationMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε την ειδοποίηση \"{0}\";",
"DeleteBackupMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε το αντίγραφο ασφαλείας \"{0}\";",
"DeleteIndexerMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε το ευρετήριο \"{0}\";",
"DeleteIndexerProxyMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τη λίστα \"{0}\";",
"DeleteNotificationMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε την ειδοποίηση \"{0}\";",
"DeleteTagMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε την ετικέτα \"{0}\";",
"DownloadClientUnavailable": "Ο πελάτης λήψης δεν είναι διαθέσιμος",
"EnableAutoHelpText": "Εάν ενεργοποιηθεί, οι Ταινίες θα προστεθούν αυτόματα στο Radarr από αυτήν τη λίστα",
"EnableAutomaticAdd": "Ενεργοποίηση αυτόματης προσθήκης",
"EnableAutomaticSearch": "Ενεργοποίηση αυτόματης αναζήτησης",
"EnableAutomaticSearchHelpText": "Θα χρησιμοποιηθεί όταν πραγματοποιούνται αυτόματες αναζητήσεις μέσω του περιβάλλοντος χρήστη ή του Radarr",
"EnableColorImpairedModeHelpText": "Τροποποιημένο στυλ για να επιτρέπεται στους χρήστες με προβλήματα χρώματος να διακρίνουν καλύτερα τις πληροφορίες με χρωματική κωδικοποίηση",
"EnableMediaInfoHelpText": "Εξαγωγή πληροφοριών βίντεο, όπως ανάλυση, χρόνος εκτέλεσης και πληροφορίες κωδικοποιητή από αρχεία. Αυτό απαιτεί από τον Radarr να διαβάσει τμήματα του αρχείου που ενδέχεται να προκαλέσουν υψηλή δραστηριότητα δίσκου ή δικτύου κατά τη διάρκεια των σαρώσεων.",
"EnableSslHelpText": " Απαιτείται επανεκκίνηση ως διαχειριστής για να τεθεί σε ισχύ",
"Error": "Λάθος",
"ErrorLoadingContents": "Σφάλμα κατά τη φόρτωση περιεχομένων",
"ExistingMovies": "Υφιστάμενες ταινίες",
"GeneralSettings": "Γενικές Ρυθμίσεις",
"Grabs": "Αρπάζω",
"HealthNoIssues": "Δεν υπάρχουν προβλήματα με τη διαμόρφωσή σας",
@@ -177,29 +157,19 @@
"LogLevelTraceHelpTextWarning": "Η καταγραφή ιχνών πρέπει να ενεργοποιηθεί προσωρινά",
"Mechanism": "Μηχανισμός",
"Message": "Μήνυμα",
"MinimumLimits": "Ελάχιστα όρια",
"MinutesHundredTwenty": "120 λεπτά: {0}",
"MinutesNinety": "90 λεπτά: {0}",
"MonoVersion": "Μονο έκδοση",
"MonoVersionCheckUpgradeRecommendedMessage": "Η τρέχουσα εγκατεστημένη έκδοση Mono {0} υποστηρίζεται, αλλά συνιστάται η αναβάθμιση σε {1}.",
"MovieDetailsPreviousMovie": "Λεπτομέρειες ταινίας: Προηγούμενη ταινία",
"MovieIndexScrollBottom": "Ευρετήριο ταινιών: Κύλιση κάτω",
"MovieIndexScrollTop": "Ευρετήριο ταινιών: Κύλιση στην κορυφή",
"Movies": "Κινηματογράφος",
"Name": "Ονομα",
"New": "Νέος",
"NoBackupsAreAvailable": "Δεν υπάρχουν διαθέσιμα αντίγραφα ασφαλείας",
"NoLeaveIt": "Όχι, άσε το",
"NoLimitForAnyRuntime": "Δεν υπάρχει όριο για οποιοδήποτε χρόνο εκτέλεσης",
"NoLinks": "Χωρίς συνδέσμους",
"NoLogFiles": "Δεν υπάρχουν αρχεία καταγραφής",
"NoMinimumForAnyRuntime": "Χωρίς ελάχιστο για κάθε χρόνο εκτέλεσης",
"NoTagsHaveBeenAddedYet": "Δεν έχουν προστεθεί ετικέτες ακόμη",
"NotificationTriggers": "Ενεργοποιήσεις ειδοποίησης",
"PageSizeHelpText": "Αριθμός στοιχείων προς εμφάνιση σε κάθε σελίδα",
"Password": "Κωδικός πρόσβασης",
"Peers": "Ομότιμοι",
"Pending": "εκκρεμής",
"PendingChangesMessage": "Έχετε μη αποθηκευμένες αλλαγές, είστε βέβαιοι ότι θέλετε να αποχωρήσετε από αυτήν τη σελίδα;",
"PendingChangesStayReview": "Παραμείνετε και ελέγξτε τις αλλαγές",
"Presets": "Προεπιλογές",
@@ -257,7 +227,6 @@
"UnableToAddANewIndexerPleaseTryAgain": "Δεν είναι δυνατή η προσθήκη νέου ευρετηρίου, δοκιμάστε ξανά.",
"UnableToLoadGeneralSettings": "Δεν είναι δυνατή η φόρτωση των γενικών ρυθμίσεων",
"UnableToLoadNotifications": "Δεν είναι δυνατή η φόρτωση ειδοποιήσεων",
"UnableToLoadQualityDefinitions": "Δεν είναι δυνατή η φόρτωση των ορισμών ποιότητας",
"UnableToLoadUISettings": "Δεν είναι δυνατή η φόρτωση των ρυθμίσεων διεπαφής χρήστη",
"UnsavedChanges": "Μη αποθηκευμένες αλλαγές",
"UpdateCheckStartupTranslocationMessage": "Δεν είναι δυνατή η εγκατάσταση της ενημέρωσης επειδή ο φάκελος εκκίνησης \"{0}\" βρίσκεται σε ένα φάκελο \"Μετατόπιση εφαρμογών\".",
@@ -272,19 +241,16 @@
"Warn": "Προειδοποιώ",
"YesCancel": "Ναι, Ακύρωση",
"Exception": "Εξαίρεση",
"Importing": "Εισαγωγή",
"IncludeHealthWarningsHelpText": "Συμπεριλάβετε προειδοποιήσεις για την υγεία",
"Security": "Ασφάλεια",
"Tasks": "Καθήκοντα",
"UnableToLoadBackups": "Δεν είναι δυνατή η φόρτωση αντιγράφων ασφαλείας",
"UnableToLoadDownloadClients": "Δεν είναι δυνατή η φόρτωση πελατών λήψης",
"UnableToLoadIndexers": "Δεν είναι δυνατή η φόρτωση του ευρετηρίου",
"UpdateMechanismHelpText": "Χρησιμοποιήστε το ενσωματωμένο πρόγραμμα ενημέρωσης του Radarr ή ένα σενάριο",
"AnalyticsEnabledHelpText": "Στείλτε ανώνυμες πληροφορίες χρήσης και σφάλματος στους διακομιστές του Radarr. Αυτό περιλαμβάνει πληροφορίες στο πρόγραμμα περιήγησής σας, ποιες σελίδες Radarr WebUI χρησιμοποιείτε, αναφορά σφαλμάτων καθώς και έκδοση λειτουργικού συστήματος και χρόνου εκτέλεσης. Θα χρησιμοποιήσουμε αυτές τις πληροφορίες για να δώσουμε προτεραιότητα σε λειτουργίες και διορθώσεις σφαλμάτων.",
"AppDataDirectory": "Κατάλογος AppData",
"BindAddress": "Δεσμευμένη διεύθυνση",
"EnableRss": "Ενεργοποίηση RSS",
"EnableInteractiveSearchHelpTextWarning": "Η αναζήτηση δεν υποστηρίζεται με αυτό το ευρετήριο",
"IndexerFlags": "Σημαίες ευρετηρίου",
"IndexerLongTermStatusCheckAllClientMessage": "Όλοι οι δείκτες δεν είναι διαθέσιμοι λόγω αστοχιών για περισσότερο από 6 ώρες",
"InteractiveSearch": "Διαδραστική αναζήτηση",
@@ -314,14 +280,12 @@
"CertificateValidation": "Επικύρωση πιστοποιητικού",
"CertificateValidationHelpText": "Αλλάξτε πόσο αυστηρή είναι η επικύρωση πιστοποίησης HTTPS",
"ClientPriority": "Προτεραιότητα πελάτη",
"CloneIndexer": "Δείκτης κλώνου",
"DeleteBackup": "Διαγραφή αντιγράφων ασφαλείας",
"DeleteDownloadClient": "Διαγραφή προγράμματος-πελάτη λήψης",
"DeleteDownloadClientMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον πελάτη λήψης \"{0}\";",
"DeleteNotification": "Διαγραφή ειδοποίησης",
"Docker": "Λιμενεργάτης",
"DownloadClientSettings": "Λήψη ρυθμίσεων πελάτη",
"Downloading": "Λήψη",
"EnableInteractiveSearch": "Ενεργοποίηση διαδραστικής αναζήτησης",
"SuggestTranslationChange": "Προτείνετε αλλαγή μετάφρασης",
"System": "Σύστημα",
@@ -354,11 +318,14 @@
"DeleteTag": "Διαγραφή ετικέτας",
"EditIndexer": "Επεξεργασία ευρετηρίου",
"Enable": "επιτρέπω",
"EnableAutomaticSearchHelpTextWarning": "Θα χρησιμοποιηθεί όταν χρησιμοποιείται διαδραστική αναζήτηση",
"EnableColorImpairedMode": "Ενεργοποίηση λειτουργίας με προβλήματα χρώματος",
"EnableCompletedDownloadHandlingHelpText": "Αυτόματη εισαγωγή ολοκληρωμένων λήψεων από τον πελάτη λήψης",
"Enabled": "Ενεργοποιήθηκε",
"EnabledHelpText": "Ενεργοποιήστε αυτήν τη λίστα για χρήση στο Radarr",
"EnableHelpText": "Ενεργοποίηση δημιουργίας αρχείων μεταδεδομένων για αυτόν τον τύπο μεταδεδομένων",
"EnableInteractiveSearchHelpText": "Θα χρησιμοποιηθεί όταν χρησιμοποιείται διαδραστική αναζήτηση"
"EnableInteractiveSearchHelpText": "Θα χρησιμοποιηθεί όταν χρησιμοποιείται διαδραστική αναζήτηση",
"OnGrab": "Στο Grab",
"Filters": "Φίλτρο",
"HistoryCleanupDaysHelpText": "Ορίστε σε 0 για να απενεργοποιήσετε τον αυτόματο καθαρισμό",
"HistoryCleanupDaysHelpTextWarning": "Τα αρχεία στον κάδο ανακύκλωσης παλαιότερα από τον επιλεγμένο αριθμό ημερών θα καθαρίζονται αυτόματα",
"OnHealthIssue": "Σχετικά με το θέμα της υγείας",
"TestAllIndexers": "Δοκιμάστε όλους τους δείκτες",
"MaintenanceRelease": "Έκδοση συντήρησης: επιδιορθώσεις σφαλμάτων και άλλες βελτιώσεις. Δείτε το Github Commit History για περισσότερες λεπτομέρειες",
"ConnectionLostMessage": "Το Radarr έχασε τη σύνδεσή του με το backend και θα χρειαστεί να επαναφορτωθεί για να αποκαταστήσει τη λειτουργικότητά του."
}

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