Compare commits

..

105 Commits

Author SHA1 Message Date
Qstick
0c45eb68fa Newznab to Yml 2022-12-17 17:53:37 -06:00
Servarr
38ba810ae8 Automated API Docs update 2022-12-17 14:18:15 -06:00
Bakerboy448
4e3f460a24 Fixed: (Avistaz Family) Correct Age Parsing
Co-authored-by: Qstick <qstick@gmail.com>
2022-12-17 14:12:23 -06:00
Qstick
0d918a0aa9 New: Define multiple mapped categories for Download Clients
Fixes #170
2022-12-17 14:11:09 -06:00
Qstick
a110412665 Fixed: Stats failing of all indexer events are failures
Fixes #1231
2022-12-17 10:27:14 -06:00
Weblate
6c97f1b6ee Translated using Weblate (Italian)
Currently translated at 100.0% (464 of 464 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (464 of 464 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (464 of 464 strings)

Translated using Weblate (German)

Currently translated at 100.0% (464 of 464 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (464 of 464 strings)

Translated using Weblate (Dutch)

Currently translated at 89.6% (416 of 464 strings)

Translated using Weblate (Dutch)

Currently translated at 89.6% (416 of 464 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Mipiaceanutella <remix-polity-0l@icloud.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Robin Flikkema <robin@robinflikkema.nl>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: kamperzoid <nick@kamper.be>
Co-authored-by: reloxx <reloxx@interia.pl>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translation: Servarr/Prowlarr
2022-12-11 12:47:35 -06:00
Qstick
470779ead2 Bump version to 0.4.11 2022-12-11 12:46:58 -06:00
Erik Persson
b371f2d913 New: Added setting to not include animebytes synonyms 2022-12-08 17:54:57 -06:00
Qstick
3ff3452e2d Handling for Obsolete API Endpoints 2022-12-08 17:50:18 -06:00
Qstick
df13537e29 Fixed: Use route Id for PUT requests if not passed in body 2022-12-08 17:48:33 -06:00
Qstick
5d2fefde8f Fixed: Correct Attribute compare for Id validation 2022-12-08 17:46:49 -06:00
Qstick
ffb3f83324 Simplify X-Forwarded-For handling
This happens in asp.net middleware now

Co-Authored-By: ta264 <ta264@users.noreply.github.com>
2022-12-08 17:45:14 -06:00
Qstick
1c125733b2 New: Improve IPAddress.IsLocal method
Co-Authored-By: ta264 <ta264@users.noreply.github.com>
2022-12-08 17:45:04 -06:00
Mark McDowall
2af7fac15e New: IPv6 support for connections/indexers/download clients 2022-12-08 17:44:24 -06:00
Mark McDowall
f172d17ecc Fixed: Improve Bind Address validation and help text 2022-12-08 17:42:55 -06:00
Zak Saunders
c69843931e New: Auto theme option to match OS theme
Co-authored-by: Qstick <qstick@gmail.com>
2022-12-08 17:40:59 -06:00
bakerboy448
cd3e99ad87 Fixed: Indexer Error handling improvements (#1172)
* Fixed: Indexer Error handling improvements

* fixup! Fixed: Indexer Error handling improvements
2022-12-01 21:30:27 -06:00
Qstick
1cce39b404 Fix Orpheus Tests 2022-11-29 20:15:23 -06:00
Qstick
9b46ab73e4 Fixed: (Orpheus) Parse date from epoch or date time string 2022-11-29 19:42:35 -06:00
Mark McDowall
a352c053ab Fixed: Publish ApplicationStartingEvent during startup
(cherry picked from commit 5400bce1295bdc4198d2cfe0b9258bbb7ccf0852)

Fixes #1199
2022-11-26 10:17:05 -06:00
Weblate
b33e45d266 Translated using Weblate (Slovak)
Currently translated at 23.0% (107 of 464 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 23.9% (111 of 464 strings)

Translated using Weblate (Catalan)

Currently translated at 75.6% (351 of 464 strings)

Translated using Weblate (Arabic)

Currently translated at 73.0% (339 of 464 strings)

Translated using Weblate (Vietnamese)

Currently translated at 73.0% (339 of 464 strings)

Translated using Weblate (Turkish)

Currently translated at 72.8% (338 of 464 strings)

Translated using Weblate (Thai)

Currently translated at 73.0% (339 of 464 strings)

Translated using Weblate (Swedish)

Currently translated at 88.1% (409 of 464 strings)

Translated using Weblate (Russian)

Currently translated at 77.5% (360 of 464 strings)

Translated using Weblate (Romanian)

Currently translated at 73.2% (340 of 464 strings)

Translated using Weblate (Portuguese)

Currently translated at 80.8% (375 of 464 strings)

Translated using Weblate (Polish)

Currently translated at 75.6% (351 of 464 strings)

Translated using Weblate (Dutch)

Currently translated at 88.7% (412 of 464 strings)

Translated using Weblate (Korean)

Currently translated at 73.0% (339 of 464 strings)

Translated using Weblate (Japanese)

Currently translated at 73.0% (339 of 464 strings)

Translated using Weblate (Italian)

Currently translated at 98.0% (455 of 464 strings)

Translated using Weblate (Icelandic)

Currently translated at 73.0% (339 of 464 strings)

Translated using Weblate (Hindi)

Currently translated at 73.0% (339 of 464 strings)

Translated using Weblate (Hebrew)

Currently translated at 74.5% (346 of 464 strings)

Translated using Weblate (French)

Currently translated at 96.7% (449 of 464 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (464 of 464 strings)

Translated using Weblate (Spanish)

Currently translated at 79.3% (368 of 464 strings)

Translated using Weblate (Greek)

Currently translated at 72.8% (338 of 464 strings)

Translated using Weblate (German)

Currently translated at 100.0% (464 of 464 strings)

Translated using Weblate (German)

Currently translated at 100.0% (464 of 464 strings)

Translated using Weblate (Danish)

Currently translated at 72.8% (338 of 464 strings)

Translated using Weblate (Czech)

Currently translated at 73.0% (339 of 464 strings)

Translated using Weblate (Czech)

Currently translated at 73.0% (339 of 464 strings)

Translated using Weblate (Bulgarian)

Currently translated at 68.3% (317 of 464 strings)

Translated using Weblate (German)

Currently translated at 100.0% (464 of 464 strings)

Translated using Weblate (German)

Currently translated at 100.0% (464 of 464 strings)

Translated using Weblate (German)

Currently translated at 99.3% (461 of 464 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Don-Chris <Chr_Sch@t-online.de>
Co-authored-by: Tordai, Ralph <ralph_t@posteo.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Zalhera <tobias.bechen@gmail.com>
Co-authored-by: marapavelka <mara.pavelka@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/vi/
Translation: Servarr/Prowlarr
2022-11-22 19:58:33 -06:00
bakerboy448
817d61de91 Fixed: (SpeedApp) Migrate Legacy URL without slash 2022-11-22 19:57:18 -06:00
Rumplin
c7e5cc6462 Removed suspicious URL in the default definitions (#1208)
* Removed suspicious URL in the default definitions

Revert "Removed suspicious URL in the default definitions"

This reverts commit e26853f9aa919cd413b0f8b914ac426f220b9475.

* Update Torznab.cs

Removed suspicious URL from the code. Looks like the site that was originally there doesn't exist anymore and it's hosting malware (HD4Free.xyz).

Co-authored-by: admin <stanislav.ivanov@performit.ie>
2022-11-22 19:56:48 -06:00
Qstick
25596fc2e8 Fixed: Orpheus migration fails on Postgres 2022-11-19 13:59:31 -05:00
Qstick
9ff0b90626 Convert Notifiarr Payload to JSON, Standardize with Webhook (#1194)
* Convert Notifiarr Payload to JSON, Standardize with Webhook

* fixup!
2022-11-13 14:18:44 -05:00
ta264
4f4c011436 Swap Orpheus to API key (#946)
* New: Orpheus uses API key instead of user/pass

* fixup! New: Orpheus uses API key instead of user/pass

Co-authored-by: Qstick <qstick@gmail.com>
2022-11-13 11:07:55 -06:00
Qstick
bd0115931f Bump version to 0.4.10 2022-11-12 22:02:13 -06:00
Qstick
a0d18c546e Bump version to 0.4.9 2022-11-12 20:03:35 -06:00
Qstick
d935b0df82 Fix regression in release analytics service after debounce added
Fixes #1193
2022-11-10 17:39:14 -06:00
Qstick
9e37f69224 Fixed: (RetroFlix) Urls built with double slash
Fixes #1188
Closes #1192
2022-11-10 06:54:20 -06:00
Servarr
2805c4f18b Automated API Docs update 2022-11-07 20:22:57 -06:00
Qstick
dae21f22b9 Bump version to 0.4.8 2022-11-07 19:30:14 -06:00
Qstick
7ddbe09eca New: Base API info endpoint 2022-11-07 19:26:54 -06:00
bakerboy448
90e3c809c3 New: Notifiarr moved from webhook to API
New: Notifiarr Add Instance Name Support

Fixed: Notifiarr - Better HTTP Error Handling

also quiet sentry

move apikey to header from url
(cherry picked from commit 1db690ad39ec103c0f4dc89ac4545801ef95bec7)

Fixed: Improve Notifiarr Exception Handling and Validation Errors

(cherry picked from commit 6aaa024d71b939030950460ae986ada5bbae5ad7)
2022-11-07 19:23:16 -06:00
Weblate
ec8cf5f57a Translated using Weblate (Finnish)
Currently translated at 99.5% (462 of 464 strings)

Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translation: Servarr/Prowlarr
2022-11-07 19:22:26 -06:00
Bakerboy448
f4bbf2f8af Fixed: (Avistaz) Handle 429 Request Limit Reached 2022-11-04 09:55:11 -05:00
bakerboy448
ea98d41472 Update feature_request.yml
[skip ci]
2022-11-04 09:54:04 -05:00
bakerboy448
b8cb0fd291 update bug report template [skip ci] 2022-11-04 09:54:04 -05:00
Bakerboy448
d3dfa620ac Fix confusing session expired test message 2022-11-04 09:53:27 -05:00
Weblate
049668f307 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (464 of 464 strings)

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

Currently translated at 100.0% (464 of 464 strings)

Translated using Weblate (Dutch)

Currently translated at 87.0% (404 of 464 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (464 of 464 strings)

Translated using Weblate (Portuguese)

Currently translated at 78.7% (364 of 462 strings)

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

Currently translated at 2.8% (13 of 462 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Thirrian <matthiaslantermann@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: korax1970 <duxlatronum@gmail.com>
Co-authored-by: libsu <libsu@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
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/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_TW/
Translation: Servarr/Prowlarr
2022-11-04 09:48:54 -05:00
Yukine
c400575aac Fixed: (AnimeBytes) add delimiter to episode release 2022-11-04 09:48:14 -05:00
Yukine
6f122fb2e4 New: (AnimeBytes) add filename support for single episodes 2022-11-04 09:47:52 -05:00
Qstick
a9c210f8e7 Create CODE_OF_CONDUCT.md 2022-11-03 15:58:57 -05:00
ta264
1068ba8915 Use wildcard pattern now we have better bsd agent 2022-11-03 11:10:41 +00:00
ta264
635335d876 Revert "Temp disable BSD Tests"
This reverts commit 438ea380f5.
2022-11-03 11:10:41 +00:00
Qstick
2ed51cd933 Fixed: Nullref on Cardigann without login test 2022-10-30 18:06:19 -05:00
Qstick
b74c46c554 Ignore brotli test on osx 2022-10-30 13:25:34 -05:00
Qstick
7029e0d6ee Enable new Servarr build notifications 2022-10-25 21:59:08 -05:00
Qstick
438ea380f5 Temp disable BSD Tests 2022-10-25 21:54:19 -05:00
Qstick
4eec675d61 Fix Baker Problems 2022-10-25 21:51:48 -05:00
bakerboy448
0a9bd8287f New: Return 429 for Query and Grab Limits 2022-10-25 21:11:13 -05:00
Qstick
b583ac3a97 Fixed: (Cardigann) Rework login required logic
Fixes #1166
2022-10-25 20:21:27 -05:00
bakerboy448
4be41ff3fb fixup! 2022-10-18 10:49:12 -05:00
bakerboy448
b911f8cc08 Fix: (RetroFlix) Update URL to .club
Fixes #1159
2022-10-18 10:49:12 -05:00
Weblate
22face385f Translated using Weblate (Portuguese)
Currently translated at 78.7% (364 of 462 strings)

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

Currently translated at 2.8% (13 of 462 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: korax1970 <duxlatronum@gmail.com>
Co-authored-by: libsu <libsu@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_TW/
Translation: Servarr/Prowlarr
2022-10-14 22:22:40 -05:00
Qstick
3e700b63c2 New: Retry Postgres connection 3 times (with 5 second sleep) on Startup 2022-10-10 22:30:00 -05:00
Qstick
df0b8fc660 And another..... 2022-10-09 19:04:30 -05:00
Qstick
f96dbbfc21 Ensure FS doesn't fail when no proxy 2022-10-09 19:03:53 -05:00
Qstick
4a75f92cb5 Fixed: (FlareSolverr) Send non-auth global proxy when set
Fixes #1142
2022-10-09 18:33:41 -05:00
Qstick
dd05a9dbd4 Obsolete Anthelion C# Indexer 2022-10-09 10:34:40 -05:00
Qstick
e78b8d5346 New: Add long term Application status Healthcheck 2022-10-09 10:15:50 -05:00
Qstick
74a1d95ab7 Update NZBIndex Categories 2022-10-08 22:52:17 -05:00
Qstick
f929a7e62f New: (Indexer) NZBIndex 2022-10-08 22:14:46 -05:00
Qstick
e9e4248af4 New: (Indexer) RetroFlix 2022-10-08 19:18:12 -05:00
Yukine
9e3b43ef12 Fixed: (GreatPosterWall) correctly override Gazelle base method 2022-10-08 18:38:46 -05:00
Qstick
738a690aac Fixed: (Rarbg) Incorrect TVDB param logic
Fixes #1129

Co-Authored-By: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2022-10-08 18:20:41 -05:00
Qstick
3b7c59e9bb Fixed: (Rarbg) More reliable token handling and retry
Fixes #1148
2022-10-08 18:11:22 -05:00
Qstick
b8ca28d955 Fixed: Explicitly forbid redirects on Gazelle search requests
Fixes #1144
2022-10-08 15:31:51 -05:00
Yukine
8797bb7d1c Remove unused Gazelle legacy code 2022-10-04 06:46:20 -05:00
Yukine
be430732f5 Fixed: (GreatPosterWall) move imdb id search to searchstr query param 2022-10-04 06:46:20 -05:00
h96kikh6
e7b1380b85 Fixed: (Indexer) HDSpace - Added new categories
Added new categories: 45 - HDTV 2160 -> Prowlarr: Movies/UHD (2045), 46 - Movies 2160 -> Prowlarr: TV/UHD (5045), 47 - Doc 2160 -> Prowlarr: TV/Documentary (5080), 48 - Animation 2160 -> Prowlarr: TV/Anime (5070), 49 - XXX 2160 -> Prowlarr: XXX/UHD (6045)
2022-10-02 11:08:51 -05:00
Qstick
c29735741c Optimize Indexer updates (v2) 2022-09-29 22:47:00 -05:00
Qstick
f56a13a375 Bump Mailkit to 3.4.1 2022-09-29 22:14:25 -05:00
Qstick
148d8ee249 Bump Sentry to 3.21.0 2022-09-29 22:14:25 -05:00
Qstick
3547028b96 Bump YamlDotNet to 12.0.1 2022-09-29 22:14:25 -05:00
Qstick
e4ffa1873e Fixed: Definition not updating if local file is missing 2022-09-29 22:13:21 -05:00
Qstick
2e85a21576 Fixed: (GazelleGames) Serialization error on empty response
Fixes #1137
2022-09-29 20:14:18 -05:00
Qstick
0a111e7572 Fixed: (Cardigann) Search path redirect
Fixes #1102
2022-09-26 21:13:57 -05:00
Qstick
25217c0ee8 Fixed: TypeError on Keyup in Firefox for IndexerIndex 2022-09-25 20:44:26 -05:00
Qstick
791592927c Purge old PTP Radarr check 2022-09-25 12:13:49 -05:00
Qstick
4137193a60 Fixed: (Avistaz) FL Only should be checkbox 2022-09-25 10:08:12 -05:00
Qstick
99816bfd36 Fix test error due to DryIOC update 2022-09-24 20:24:17 -05:00
Qstick
59e5b5bd52 Set PooledConnectionLifetime to 10 minutes
Setting PooledConnectinLifetime to a defined number will ensure we don't run into DNS refresh issues
2022-09-18 21:45:10 -05:00
Qstick
7fa0a2b33c Bump Swashbuckle to 6.4.0 2022-09-18 21:43:06 -05:00
Qstick
0593ca6b9e Bump DryIoc to 5.2.2 2022-09-18 21:40:32 -05:00
Qstick
06a26b5c87 Fixed: (RarBG) Don't disable indexer on temp rate limit
Fixes #1027
2022-09-18 15:56:31 -05:00
Weblate
dcae6dc151 Translated using Weblate (Slovak)
Currently translated at 12.5% (58 of 462 strings)

Translated using Weblate (Portuguese)

Currently translated at 78.3% (362 of 462 strings)

Translated using Weblate (Portuguese)

Currently translated at 78.3% (362 of 462 strings)

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

Currently translated at 2.8% (13 of 462 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (462 of 462 strings)

Added translation using Weblate (Latvian)

Co-authored-by: Dainel Amendoeira <daniel@amendoeira.eu>
Co-authored-by: Gylesie <github-anon.dasheens@aleeas.com>
Co-authored-by: HiNesslio <chi.lio@shms-mail.ch>
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/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_TW/
Translation: Servarr/Prowlarr
2022-09-18 15:54:43 -05:00
Qstick
04e3ed0ffe Fixed: (Gazelle) Download fails if out of FL tokens
Fixes #1088
2022-09-18 15:39:09 -05:00
Qstick
1ed5ed9179 Bump version to 0.4.7 2022-09-18 14:55:58 -05:00
Qstick
d292d086ee Prevent query failures on Cardigann Indexers 2022-09-12 22:49:28 -05:00
Qstick
f68915c5dd New: Don't query indexers if they don't support query categories 2022-09-12 22:28:07 -05:00
Qstick
01e970e1a7 New: (Avistaz) Genre Search Support
Fixes #1097
2022-09-12 21:27:32 -05:00
Qstick
68df439498 New: (Avistaz) Freeleech Only Setting
#1108
2022-09-12 21:16:05 -05:00
Qstick
33de7ca7ab Fixed: (MoreThanTv) Parsing issue when download url is null
Fixes #1047
2022-09-10 12:50:32 -05:00
Qstick
ae2d9b795b Don't reset request Url when calculating RedirectUrl 2022-09-10 12:24:41 -05:00
Qstick
eadea745f8 Warn on redirect to alt domain when checking if login required 2022-09-10 12:24:41 -05:00
Qstick
f958c4aefa Bump version to 0.4.6 2022-09-08 20:57:06 -05:00
Weblate
4cf9fb0e79 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (462 of 462 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.7% (461 of 462 strings)

Translated using Weblate (French)

Currently translated at 95.6% (442 of 462 strings)

Translated using Weblate (French)

Currently translated at 95.2% (440 of 462 strings)

Translated using Weblate (German)

Currently translated at 99.1% (458 of 462 strings)

Translated using Weblate (French)

Currently translated at 95.2% (440 of 462 strings)

Translated using Weblate (French)

Currently translated at 94.8% (438 of 462 strings)

Translated using Weblate (Spanish)

Currently translated at 77.7% (359 of 462 strings)

Co-authored-by: Fradri <adrien.riotte@live.fr>
Co-authored-by: Gian Klug <gian.klug@ict-scouts.ch>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Mijail Todorovich <mijailtodorovich+git@gmail.com>
Co-authored-by: Thomas Schwery <thomas@schwery.me>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: berrre <zdch6W75@gmail.com>
Co-authored-by: cikyw <cikyw@vomoto.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2022-09-08 20:29:59 -05:00
psylenced
bfa68347e6 Fix: Trace logging postgres cleanse for large json files. 2022-09-08 20:28:05 -05:00
Qstick
f97b35403d Fixed: Indexer proxies not applying to requests
Fixes #1107
2022-09-05 19:55:46 -05:00
Weblate
bf2e057247 Translated using Weblate (Russian)
Currently translated at 76.1% (352 of 462 strings)

Translated using Weblate (Romanian)

Currently translated at 71.8% (332 of 462 strings)

Co-authored-by: AlexR-sf <omg.portal.supp@gmail.com>
Co-authored-by: emanuelsipos <emanuelsipos1@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translation: Servarr/Prowlarr
2022-08-18 23:12:04 -05:00
Qstick
5a278f4e9d Fixed: Set default null value for Genre, Publisher, Douban parameters 2022-08-18 22:30:04 -05:00
Yukine
232a6efd0d New: (Indexer) GreatPosterWall (#1085)
* New: (Indexer) GreatPosterWall

* fix(GreatPosterWall): time is Chinese and not UTC

* feat(GreatPosterWall): add canUseToken check to GetDownloadUrl

* feat(GreatPosterWall): add fileName property as title
2022-08-18 20:04:29 -05:00
Chris
7e01c93b2c Fixed: Regex in log cleanser taking 10+ minutes on messages longer than 100k.
(cherry picked from commit d01bae92bfd68b04c54ab19bafe8af16c68ce711)
2022-08-18 20:03:31 -05:00
Qstick
d58f6551e6 Fixed: Set Units for Seed Time settings 2022-08-18 19:56:13 -05:00
218 changed files with 8689 additions and 2438 deletions

View File

@@ -5,9 +5,9 @@ body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
description: Please search to see if an open or closed issue already exists for the bug you encountered. If a bug exists and is closed note that it may only be fixed in an unstable branch.
options:
- label: I have searched the existing issues
- label: I have searched the existing open and closed issues
required: true
- type: textarea
attributes:

View File

@@ -5,9 +5,9 @@ body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the feature you are requesting.
description: Please search to see if an open or closed issue already exists for the feature you are requesting. If a request exists and is closed note that it may only be fixed in an unstable branch.
options:
- label: I have searched the existing issues
- label: I have searched the existing open and closed issues
required: true
- type: textarea
attributes:

132
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,132 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
<development@prowlarr.com>.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '0.4.5'
majorVersion: '0.4.11'
minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
@@ -748,7 +748,7 @@ stages:
inputs:
buildType: 'current'
artifactName: Packages
itemPattern: '/$(pattern)'
itemPattern: '**/$(pattern)'
targetPath: $(Build.ArtifactStagingDirectory)
- bash: |
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
@@ -1108,4 +1108,5 @@ stages:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
DISCORDCHANNELID: $(discordChannelId)
DISCORDWEBHOOKKEY: $(discordWebhookKey)
DISCORDTHREADID: $(discordThreadId)

View File

@@ -1,55 +0,0 @@
# New Beta Release
Prowlarr v0.2.0.1448 has been released on `develop`
A reminder about the `develop` branch
- **develop - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first. This version will receive updates either weeklyish or bi-weeklyish depending on development.**
# Announcements
- Automated API Documentation Updates recently implemented
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v5](https://github.com/Prowlarr/Prowlarr/pull/823)
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
- This will be fixed with Cardigann v5 and is due to all the Newznab Indexers sharing the same definition.
- https://i.imgur.com/tijCHlk.png
# Additional Commentary
- Lidarr v1 coming to `develop` as beta soon^(tm)
- Readarr official beta on `develop` coming soon^(tm) currently dealing with metadata issues
- [Radarr](https://www.reddit.com/r/radarr/comments/sgrsb3/new_stable_release_master_v4045909/) v4.0.4 released to `master` (stable)
- [Radarr Postgres Database Support coming soon (PR#6873)](https://github.com/radarr/radarr/pull/6873)
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
# Releases
## Native
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
## Docker
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
------------
# Release Notes
## v0.2.0.1448 (changes since v0.2.0.1426)
- Sync Indexers on app start, go to http if not sync'd yet
- Misc definition handling improvements
- Fixed: Updated ruTorrent stopped state helptext
- Fixed: Added missing translate for Database
- Fixed: Download limit check was using the query limit instead of the grab limit.
- Other bug fixes and improvements, see github history

View File

@@ -1,212 +0,0 @@
# New Beta Release
Prowlarr v0.2.0.1678 has been released on `develop`
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`
A reminder about the `develop` and `nightly` branches
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.**
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.**
# Announcements
- Automated API Documentation Updates recently implemented
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v6](https://github.com/Prowlarr/Prowlarr/pull/823)
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
- This will be fixed with Cardigann v6 and is due to all the Newznab Indexers sharing the same definition.
- https://i.imgur.com/tijCHlk.png
# Additional Commentary
- Lidarr v1 coming to `develop` as beta soon^(tm)
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
- [Readarr official beta on `develop` announced](https://www.reddit.com/r/Readarr/comments/sxvj8y/new_beta_release_develop_v0101248/)
- Radarr Postgres Database Support in `nightly`
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
# Releases
## Native
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
## Docker
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
## NAS Packages
- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally
- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally
------------
# Release Notes
## v0.2.0.1678 (changes since v0.2.0.1448)
- Bump moment from 2.29.1 to 2.29.2
- #834 #256 fix for unable to load the Indexes page
- Fix .editorconfig to disallow `this`
- New: MyAnonamouse freeleech support
- Fixed: (BHD) TMDb Parsing Exception
- Fixed: MoreThanTV indexer from browse page layout changes (#922)
- We don't have two Radarrs
- Fix indent from 37c393a659
- Fixed: (HDBits) Treat 403 as Query Limit
- Fixed: (PTP) Treat 403 as Query Limit
- New: (BTN) Rate Limit to 1 Query per 5 Seconds
- Fixed: (BTN) Handle Query Limit Error
- New: (Lidarr/Radarr/Readarr/Sonarr) Improved Errors
- Fixed: Loading old commands from database
- Fixed: Cleanup Temp files after backup creation
- Add Support
- Translated using Weblate (Finnish)
- Fixed: Indexer Infobox Error (#920)
- New: Indexer Description in Add Indexer Modal
- Fixed: Missing Translates
- New: Add Search Capabilities to Indexer API & InfoBox
- Fixed: Update from version in logs
- Automated API Docs update
- Translated using Weblate (Chinese (Simplified) (zh_CN))
- Translated using Weblate (Portuguese (Brazil))
- Fixed: Validation when testing indexers, connections and download clients
- Fixed: Prevent delete of last profile
- New: Load more (page) results on Search UI
- Update webpack packages
- Frontend Package Updates
- Backend Package Updates
- Bump dotnet to 6.0.3
- Translated using Weblate (Spanish)
- Fixed: (Gazelle) Replace Periods for Space in Search Term
- Fixed: (HDSpace) Replace Periods for Space in Search Term
- Fixed: (Anthelion) Replace Periods for Space in Search Term
- Fixed: (Redacted) Map Categories Comedy & E-Learning Videos to 'Other'
- Fixed: No longer require first run as admin on windows (#885)
- Translated using Weblate (Chinese (Simplified) (zh_CN))
- indexer(xthor): moved to YAML definition v5
- Fixed: '/indexers' URL Base breaking UI navigation
- Translated using Weblate (French)
- Fix app settings delete modal not closing and reloading app profiles
- Translated using Weblate (French)
- Bump Swashbuckle to 6.3.0
- Translated using Weblate (Portuguese (Brazil))
- fixup! New: (DanishBytes) Move to YML
- New: (DanishBytes) Move to YML
- Update translation files
- New: (RuTracker.org) add .bet mirror (#876)
- Fixed:(pornolab) language formatting
- New: Housekeeper for ApplicationStatus
- Fixed: Cleanse Tracker api_token from logs
- New: (HDTorrents) Add hd-torrents.org as Url option
- New: (Cardigann) Allow JSON filters
- Fixed: Convert List<HistoryEventTypes> to Int before passing to DB
- Fixed: WhereBuilder for Postgres
- Translated using Weblate (Finnish)
- Fixed: Make authentication cookie name unique to Prowlarr
- Update Categories
- Fixed: Enable response compression over https
- Fixed: (RuTracker) Update Cats
- Fixed: Clarify App Sync Settings (#847)
- Set version header to X-Application-Version (missing hyphen)
- Go to http if def exists on def server
- Fixed: (BHD) Handle API Auth Errors
- Fixed: (Immortalseed) Keywordless Search
- Fixed: (Cardigann) TraktId was mapping to TvRageId
- New: (Cardigann) - Cardigann v4 Support for Genre, Year, and TraktID
- New: (Cardigann) - Cardigann v4 Support for categorydesc
- New: (Cardigann) - Cardigann v4 Add Support for MapTrackerCatDescToNewznab
- New: (Cardigann) - Cardigann v4 Improved Search Logging
- Fixed: Corrected Query Limit and Grab Limit HelpText
- New: (Avistaz) Better error reporting for unauthorized tests
- Fixed: (Cardigann) Requests Failing for Definitions without LegacyLinks
- Bump SharpZipLib from 1.3.1 to 1.3.3 in /src/NzbDrone.Common
- Fixed: (Cardigann) Smarter redirect domain compare
- Fixed: (Cardigann) Treat "Refresh" header as redirect
- Fixed: (Cardigann) Replace legacy links with default link when making requests
- Other bug fixes and improvements, see GitHub history

View File

@@ -1,117 +0,0 @@
# New Beta Release
Prowlarr v0.3.0.1730 has been released on `develop`
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
A reminder about the `develop` and `nightly` branches
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.
# Announcements
- Automated API Documentation Updates recently implemented
- [*Coming Soon* - Better \*Arr App Sync](https://github.com/Prowlarr/Prowlarr/pull/983)
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v6](https://github.com/Prowlarr/Prowlarr/pull/823)
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
- This will be fixed with Cardigann v6 and is due to all the Newznab Indexers sharing the same definition.
- https://i.imgur.com/tijCHlk.png
# Additional Commentary
- Lidarr v1 coming to `develop` as beta soon^(tm)
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
- [Readarr official beta on `develop` announced](https://www.reddit.com/r/Readarr/comments/sxvj8y/new_beta_release_develop_v0101248/)
- Radarr Postgres Database Support in `nightly`
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
# Releases
## Native
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
## Docker
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
## NAS Packages
- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally
- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally
------------
# Release Notes
## v0.3.0.1730 (changes since v0.3.0.1724)
- Fixed: Prevent endless loop when calling IndexerUrls for Torznab
- Deleted translation using Weblate (Chinese (Min Nan))
- Fix some translations
- Other bug fixes and improvements, see GitHub history
## v0.3.0.1724 (changes since v0.2.0.1678)
- Fixed: Prevent endless loop when calling IndexerUrls for Newznab ( #982 )
- Fixed: Default List for Cardigann LegacyLinks
- New: Auto map known legacy BaseUrls for non-Cardigann
- Fixed: (BTN) Move to HTTPS ( #979 )
- Typo for myanonamouse.
- Fixed: (MoreThanTV) Better Response Cleansing ( #928 )
- New: SceneHD Indexer
- Fixed: (MaM) Handle Auth Errors & Session Expiry
- Fixed: Remove Indexer if categories were changed to not include in sync ( #912 )
- Fixed: Sync Indexers on App Edit
- Cleanup Config Values ( #894 )
- Fixed: (Cardigann) Handle json field selector that returns arrays ( #950 )
- New: Schedule refresh and process monitored download tasks at high priority
- Centralise image choice, update to latest images
- Don't return early after re-running checks after startup grace period ( #7147 )
- Fixed: Delay health check notifications on startup
- New: Add date picker for custom filter dates
- Bump Monotorrent to 2.0.5
- Remove old DotNetVersion method and dep
- New: Add backup size information ( #957 )
- Fixed: (BeyondHD) Use TryCoerceInt for tmdbId ( #960 )
- Fixed: (TorrentDay) TV Search returning Series not S/E Results ( #816 )
- Fixed: (CinemaZ and ExoticaZ) Better Log Cleansing
- Fixed: (exoticaz) Category Parsing
- Fixed: (Indexer) HDTorrents search imdbid + season/episode
- Bump version to 0.3.0
- Other bug fixes and improvements, see GitHub history

View File

@@ -1,203 +0,0 @@
# New Beta Release
Prowlarr v0.4.2.1879 has been released on `develop`
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
A reminder about the `develop` and `nightly` branches
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.
# Announcements
- [Prowlarr Cardigann Definitions Schema Versions and Validations created](https://github.com/Prowlarr/indexers#schemas)
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v7](https://github.com/Prowlarr/Prowlarr/pull/823)
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
- This will be fixed with Cardigann v6 and is due to all the Newznab Indexers sharing the same definition.
- https://i.imgur.com/tijCHlk.png
# Additional Commentary
- [Lidarr v1 coming to `master` as recently released](https://www.reddit.com/r/Lidarr/comments/v5fdhi/new_stable_release_master_v1022592/)
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
- [Readarr official beta on `develop` announced](https://www.reddit.com/r/Readarr/comments/sxvj8y/new_beta_release_develop_v0101248/)
- Radarr Postgres Database Support in `nightly` and `develop`
- Prowlarr Postgres Database Support in `nightly` and `develop`
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
- \*Arrs Wiki Contributions welcomed and strongly encouraged, simply auth with GitHub on the wiki and update the page
# Releases
## Native
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
## Docker
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
## NAS Packages
- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally
- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally
------------
# Release Notes
## v0.4.2.1879 (changes since v0.3.0.1730)
- Don't require user agent for IPTorrents
- Fixed: (Applications) ApiPath can be null from -arr in some cases
- ProtectionService Test Fixture
- Fixed: Lidarr null ref when building indexer for sync
- Fixed: Lidarr null ref when building indexer for sync
- Double MultipartBodyLengthLimit for Backup Restore to 256MB
- Fixed: (IPTorrents) Allow UA override for CF
- Fixed: Log Cleanse Indexer Response Logic and Test Cases
- Fixed: Set update executable permissions correctly
- Fixed: Don't call for server notifications on event driven check
- Update file and folder handling methods from Radarr (#1051)
- Running Integration Tests against Postgres Database (#838)
- Updated NLog Version (#7365)
- Add additional link logging to DownloadService
- Fixed: Correctly remove TorrentParadiseMl
- V6 Cardigann Changes (#1045)
- Sliding expiration for auth cookie and a little clean up
- Bump version to 0.4.2
- Update Sentry to 3.18.0
- Update Swashbuckle to 6.3.1
- Bump dotnet to 6.0.6
- Update AngleSharp to 0.17.0
- Remove ShowRSS C# Implementation
- Swallow HTTP issues on analytics call
- Fix NullRef in analytics service
- Bump version to 0.4.1
- Fix Donation Links
- Fix Tooltips in Dark Theme
- Fixed: (AnimeBytes) Cleanse Passkey from response
- Fixed: (Cardigann) Use variables in keywordsfilters block
- New: (BeyondHD) Better status messages for failures
- Fixed: VIP Healthcheck not triggered for expired indexers
- Use DryIoc for Automoqer, drop Unity dependency
- New: Send description element in nab response
- (Filelist) Update help text for pass key (#1039)
- Fixed: (Exoticaz) Category parsing kills search/feed
- New: (PassThePopcorn) Freeleech only option
- Fixed: (Cardigann) Searching with nab Parent should also use Child categories
- Fixed: Better Cleansing of Tracker Announce Keys
- Automated API Docs update
- Update FE dev dependencies
- Ensure .Mono and .Windows projects have all dependencies in build output
- Fixed: (Gazelle) Parse grouptime as long or date
- Fixed: (ExoticaZ) Category Parsing
- Fixed: Input options background color on mobile
- Fixed: Update AltHub API URL (#1010)
- Automated API Docs update
- New: Dark Theme
- New: Move to CSS Variables for Colorings
- New: Native Theme Engine
- diversify chartcolors for doughnut & stackedbar
- Translated using Weblate (Chinese (Simplified) (zh_CN))
- Catch Postgres log connection errors
- Clean lingering Postgres Connections on Close
- New: Instance name in System/Status API endpoint
- New: Instance name for Page Title
- New: Instance Name used for Syslog
- New: Set Instance Name
- Fixed: Use separate guid for download protection
- Fixed: (RuTracker) Support Raw search from apps
- Fixed: Localization for two part language dialects
- Fixed: (AnimeBytes) Handle series synonyms with commas (#984)
- New: Add Lidarr and Readarr DiscographySeedTime Sync
- New: Add Sonarr SeasonSeedTime Sync
- Fixed: Indexer Tags Helptext
- Automated API Docs update
- New: Seed Settings Sync
- New: Only sync indexers with matching app tags
- Indexer Cleanup
- Bump version to 0.4.0
- Bump version to 0.3.1
- Translated using Weblate (Chinese (Simplified) (zh_CN))
- Fixed: Correct User-Agent api logging
- Other bug fixes and improvements, see GitHub history

View File

@@ -1,110 +0,0 @@
# New Beta Release
Prowlarr v0.4.3.1921 has been released on `develop`
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
A reminder about the `develop` and `nightly` branches
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.
# Announcements
- [Prowlarr Cardigann Definitions Schema Versions and Validations created](https://github.com/Prowlarr/indexers#schemas)
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v8](https://github.com/Prowlarr/Prowlarr/pull/823)
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
- This will be fixed with Cardigann v8 and is due to all the Newznab Indexers sharing the same definition.
- https://i.imgur.com/tijCHlk.png
# Additional Commentary
- [Radarr Develop recently released](https://www.reddit.com/r/radarr/comments/w3kik4/new_release_develop_v4206438/)
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
- Radarr Postgres Database Support in `nightly` and `develop`
- Prowlarr Postgres Database Support in `nightly` and `develop`
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
- \*Arrs Wiki Contributions welcomed and strongly encouraged, simply auth with GitHub on the wiki and update the page
# Releases
## Native
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
## Docker
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
## NAS Packages
- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally
- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally
------------
# Release Notes
## v0.4.3.1921 (changes since v0.4.2.1879)
- Fixed: (GazelleGames) Use API instead of scraping
- Translated using Weblate (Hungarian)
- Automated API Docs update
- New: Search by DoubanId
- Fixed: UI Typos (#1072)
- Translated using Weblate (Chinese (Traditional) (zh_TW))
- Update README.md
- Automated API Docs update
- Debounce analytics service
- Fixed: Set Download and Upload Factors from Generic Torznab
- Translated using Weblate (Portuguese (Brazil))
- Translation Improvements
- Cleanup Language and Localization code
- Added translation using Weblate (Lithuanian)
- Fixed: BeyondHD using improperly cased Content-Type header
- Fix NullRef in Cloudflare detection service
- New: (AvistaZ) Parse Languages and Subs, pass in response
- Rework Cloudflare Protection Detection
- New: (FlareSolverr) DDOS Guard Support
- Bump Mailkit to 3.3.0 (#1054)
- New: Add linux-x86 builds
- Remove unused XmlRPC dependency
- Fixed: (Cardigann) Use Indexer Encoding for Form Parameters
- Fixed: (Cardigann) Use Session Cookie when making SimpleCaptchaCall
- Fixed: Delete CustomFilters not handled properly
- Modern HTTP Client (#685)
- Bump version to 0.4.3
- Other bug fixes and improvements, see GitHub history

View File

@@ -1,83 +0,0 @@
# New Beta Release
Prowlarr v0.4.4.1947 has been released on `develop`
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
A reminder about the `develop` and `nightly` branches
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.
# Announcements
- [Prowlarr Cardigann Definitions Schema Versions and Validations created](https://github.com/Prowlarr/indexers#schemas)
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v8](https://github.com/Prowlarr/Prowlarr/pull/823)
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
- This will be fixed with Cardigann v8 and is due to all the Newznab Indexers sharing the same definition.
- https://i.imgur.com/tijCHlk.png
# Additional Commentary
- [Radarr Develop recently released](https://www.reddit.com/r/radarr/comments/w3kik4/new_release_develop_v4206438/)
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
- Radarr Postgres Database Support in `nightly` and `develop`
- Prowlarr Postgres Database Support in `nightly` and `develop`
- Readarr Postgres Database Support in `nightly`
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
- \*Arrs Wiki Contributions welcomed and strongly encouraged, simply auth with GitHub on the wiki and update the page
# Releases
## Native
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
## Docker
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
## NAS Packages
- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally
- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally
------------
# Release Notes
## v0.4.4.1947 (changes since [v0.4.3.1921](https://www.reddit.com/r/prowlarr/comments/wbanhd/new_develop_release_v0431921/))
- Translated using Weblate (Chinese (Simplified) (zh_CN))
- Fixed: Correctly persist FlareSolverr Cookies to ensure it doesn't run on every request
- Fixed: Correctly use FlareSolverr User Agent
- Remove duplicate package NLog.Extensions in Prowlarr.Common
- Fixed: (Cardigann) fix imatch for rows
- Support for digest auth with HttpRequests
- Fixed: (Cardigann) Genre is optional
- Fixed: (Cardigann) Genre Parsing
- Automated API Docs update
- Fixed: (Cardigann) Genre Parsing for Releases
- Fixed: (Cardigann) messy row strdump
- New: (Cardigann) Additional query support
- Bump version to 0.4.4
- Other bug fixes and improvements, see GitHub history

View File

@@ -1,6 +0,0 @@
- [Prowlarr Cardigann Definitions Schema Versions and Validations created](https://github.com/Prowlarr/indexers#schemas)
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v8](https://github.com/Prowlarr/Prowlarr/pull/823)
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
- This will be fixed with Cardigann v8 and is due to all the Newznab Indexers sharing the same definition.
- https://i.imgur.com/tijCHlk.png

View File

@@ -1,6 +0,0 @@
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
A reminder about the `develop` and `nightly` branches
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.

View File

@@ -1,6 +0,0 @@
- **Users who do not wish to be on the alpha `nightly` or beta `develop` testing branches should take advantage of this parity and switch to `master`
A reminder about the `develop` and `nightly` branches
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.**
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.**

View File

@@ -1,7 +0,0 @@
- [Radarr Develop recently released](https://www.reddit.com/r/radarr/comments/w3kik4/new_release_develop_v4206438/)
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
- Radarr Postgres Database Support in `nightly` and `develop`
- Prowlarr Postgres Database Support in `nightly` and `develop`
- Readarr Postgres Database Support in `nightly`
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
- \*Arrs Wiki Contributions welcomed and strongly encouraged, simply auth with GitHub on the wiki and update the page

View File

@@ -16,6 +16,7 @@ import FormInputHelpText from './FormInputHelpText';
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
import InfoInput from './InfoInput';
import KeyValueListInput from './KeyValueListInput';
import NewznabCategorySelectInputConnector from './NewznabCategorySelectInputConnector';
import NumberInput from './NumberInput';
import OAuthInputConnector from './OAuthInputConnector';
import PasswordInput from './PasswordInput';
@@ -68,6 +69,9 @@ function getComponent(type) {
case inputTypes.PATH:
return PathInputConnector;
case inputTypes.CATEGORY_SELECT:
return NewznabCategorySelectInputConnector;
case inputTypes.INDEXER_FLAGS_SELECT:
return IndexerFlagsSelectInputConnector;

View File

@@ -31,7 +31,7 @@ function createMapStateToProps() {
});
return {
value,
value: value || [],
values
};
}

View File

@@ -8,6 +8,7 @@ export const DEVICE = 'device';
export const KEY_VALUE_LIST = 'keyValueList';
export const INFO = 'info';
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
export const CATEGORY_SELECT = 'newznabCategorySelect';
export const NUMBER = 'number';
export const OAUTH = 'oauth';
export const PASSWORD = 'password';
@@ -32,6 +33,7 @@ export const all = [
KEY_VALUE_LIST,
INFO,
MOVIE_MONITORED_SELECT,
CATEGORY_SELECT,
NUMBER,
OAUTH,
PASSWORD,

View File

@@ -221,7 +221,7 @@ class IndexerIndex extends Component {
onKeyUp = (event) => {
const jumpBarItems = this.state.jumpBarItems.order;
if (event.path.length === 4) {
if (event.composedPath && event.composedPath().length === 4) {
if (event.keyCode === keyCodes.HOME && event.ctrlKey) {
this.setState({ jumpToCharacter: jumpBarItems[0] });
}

View File

@@ -0,0 +1,27 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import AddCategoryModalContentConnector from './AddCategoryModalContentConnector';
function AddCategoryModal({ isOpen, onModalClose, ...otherProps }) {
return (
<Modal
size={sizes.MEDIUM}
isOpen={isOpen}
onModalClose={onModalClose}
>
<AddCategoryModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
AddCategoryModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default AddCategoryModal;

View File

@@ -0,0 +1,50 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import AddCategoryModal from './AddCategoryModal';
function createMapDispatchToProps(dispatch, props) {
const section = 'settings.downloadClientCategories';
return {
dispatchClearPendingChanges() {
dispatch(clearPendingChanges({ section }));
}
};
}
class AddCategoryModalConnector extends Component {
//
// Listeners
onModalClose = () => {
this.props.dispatchClearPendingChanges();
this.props.onModalClose();
};
//
// Render
render() {
const {
dispatchClearPendingChanges,
...otherProps
} = this.props;
return (
<AddCategoryModal
{...otherProps}
onModalClose={this.onModalClose}
/>
);
}
}
AddCategoryModalConnector.propTypes = {
onModalClose: PropTypes.func.isRequired,
dispatchClearPendingChanges: PropTypes.func.isRequired
};
export default connect(null, createMapDispatchToProps)(AddCategoryModalConnector);

View File

@@ -0,0 +1,5 @@
.deleteButton {
composes: button from '~Components/Link/Button.css';
margin-right: auto;
}

View File

@@ -0,0 +1,111 @@
import PropTypes from 'prop-types';
import React from 'react';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './AddCategoryModalContent.css';
function AddCategoryModalContent(props) {
const {
advancedSettings,
item,
onInputChange,
onFieldChange,
onCancelPress,
onSavePress,
onDeleteSpecificationPress,
...otherProps
} = props;
const {
id,
clientCategory,
categories
} = item;
return (
<ModalContent onModalClose={onCancelPress}>
<ModalHeader>
{`${id ? 'Edit' : 'Add'} Category`}
</ModalHeader>
<ModalBody>
<Form
{...otherProps}
>
<FormGroup>
<FormLabel>
{translate('DownloadClientCategory')}
</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="clientCategory"
{...clientCategory}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('MappedCategories')}
</FormLabel>
<FormInputGroup
type={inputTypes.CATEGORY_SELECT}
name="categories"
{...categories}
onChange={onInputChange}
/>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
{
id &&
<Button
className={styles.deleteButton}
kind={kinds.DANGER}
onPress={onDeleteSpecificationPress}
>
{translate('Delete')}
</Button>
}
<Button
onPress={onCancelPress}
>
{translate('Cancel')}
</Button>
<SpinnerErrorButton
isSpinning={false}
onPress={onSavePress}
>
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>
);
}
AddCategoryModalContent.propTypes = {
advancedSettings: PropTypes.bool.isRequired,
item: PropTypes.object.isRequired,
onInputChange: PropTypes.func.isRequired,
onFieldChange: PropTypes.func.isRequired,
onCancelPress: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onDeleteSpecificationPress: PropTypes.func
};
export default AddCategoryModalContent;

View File

@@ -0,0 +1,78 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { clearDownloadClientCategoryPending, saveDownloadClientCategory, setDownloadClientCategoryFieldValue, setDownloadClientCategoryValue } from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import AddCategoryModalContent from './AddCategoryModalContent';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.advancedSettings,
createProviderSettingsSelector('downloadClientCategories'),
(advancedSettings, specification) => {
return {
advancedSettings,
...specification
};
}
);
}
const mapDispatchToProps = {
setDownloadClientCategoryValue,
setDownloadClientCategoryFieldValue,
saveDownloadClientCategory,
clearDownloadClientCategoryPending
};
class AddCategoryModalContentConnector extends Component {
//
// Listeners
onInputChange = ({ name, value }) => {
this.props.setDownloadClientCategoryValue({ name, value });
};
onFieldChange = ({ name, value }) => {
this.props.setDownloadClientCategoryFieldValue({ name, value });
};
onCancelPress = () => {
this.props.clearDownloadClientCategoryPending();
this.props.onModalClose();
};
onSavePress = () => {
this.props.saveDownloadClientCategory({ id: this.props.id });
this.props.onModalClose();
};
//
// Render
render() {
return (
<AddCategoryModalContent
{...this.props}
onCancelPress={this.onCancelPress}
onSavePress={this.onSavePress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
/>
);
}
}
AddCategoryModalContentConnector.propTypes = {
id: PropTypes.number,
item: PropTypes.object.isRequired,
setDownloadClientCategoryValue: PropTypes.func.isRequired,
setDownloadClientCategoryFieldValue: PropTypes.func.isRequired,
clearDownloadClientCategoryPending: PropTypes.func.isRequired,
saveDownloadClientCategory: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(AddCategoryModalContentConnector);

View File

@@ -0,0 +1,32 @@
.customFormat {
composes: card from '~Components/Card.css';
width: 300px;
}
.nameContainer {
display: flex;
justify-content: space-between;
}
.name {
@add-mixin truncate;
margin-bottom: 5px;
font-weight: 300;
font-size: 20px;
}
.labels {
display: flex;
flex-wrap: wrap;
margin-top: 5px;
pointer-events: all;
}
.tooltipLabel {
composes: label from '~Components/Label.css';
margin: 0;
border: none;
}

View File

@@ -0,0 +1,111 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Card from 'Components/Card';
import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddCategoryModalConnector from './AddCategoryModalConnector';
import styles from './Category.css';
class Category extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isEditSpecificationModalOpen: false,
isDeleteSpecificationModalOpen: false
};
}
//
// Listeners
onEditSpecificationPress = () => {
this.setState({ isEditSpecificationModalOpen: true });
};
onEditSpecificationModalClose = () => {
this.setState({ isEditSpecificationModalOpen: false });
};
onDeleteSpecificationPress = () => {
this.setState({
isEditSpecificationModalOpen: false,
isDeleteSpecificationModalOpen: true
});
};
onDeleteSpecificationModalClose = () => {
this.setState({ isDeleteSpecificationModalOpen: false });
};
onConfirmDeleteSpecification = () => {
this.props.onConfirmDeleteSpecification(this.props.id);
};
//
// Lifecycle
render() {
const {
id,
clientCategory,
categories
} = this.props;
return (
<Card
className={styles.customFormat}
overlayContent={true}
onPress={this.onEditSpecificationPress}
>
<div className={styles.nameContainer}>
<div className={styles.name}>
{clientCategory}
</div>
</div>
<Label kind={kinds.PRIMARY}>
{`${categories.length} ${categories.length > 1 ? translate('Categories') : translate('Category')}`}
</Label>
<AddCategoryModalConnector
id={id}
isOpen={this.state.isEditSpecificationModalOpen}
onModalClose={this.onEditSpecificationModalClose}
onDeleteSpecificationPress={this.onDeleteSpecificationPress}
/>
<ConfirmModal
isOpen={this.state.isDeleteSpecificationModalOpen}
kind={kinds.DANGER}
title={translate('DeleteClientCategory')}
message={
<div>
<div>
{translate('AreYouSureYouWantToDeleteCategory', [name])}
</div>
</div>
}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteSpecification}
onCancel={this.onDeleteSpecificationModalClose}
/>
</Card>
);
}
}
Category.propTypes = {
id: PropTypes.number.isRequired,
categories: PropTypes.arrayOf(PropTypes.number).isRequired,
clientCategory: PropTypes.string.isRequired,
onConfirmDeleteSpecification: PropTypes.func.isRequired
};
export default Category;

View File

@@ -1,11 +1,14 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Card from 'Components/Card';
import FieldSet from 'Components/FieldSet';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup';
import Icon from 'Components/Icon';
import Button from 'Components/Link/Button';
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@@ -13,12 +16,33 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import { icons, inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddCategoryModalConnector from './Categories/AddCategoryModalConnector';
import Category from './Categories/Category';
import styles from './EditDownloadClientModalContent.css';
class EditDownloadClientModalContent extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isAddCategoryModalOpen: false
};
}
onAddCategoryPress = () => {
this.setState({ isAddCategoryModalOpen: true });
};
onAddCategoryModalClose = () => {
this.setState({ isAddCategoryModalOpen: false });
};
//
// Render
@@ -27,6 +51,7 @@ class EditDownloadClientModalContent extends Component {
advancedSettings,
isFetching,
error,
categories,
isSaving,
isTesting,
saveError,
@@ -37,19 +62,27 @@ class EditDownloadClientModalContent extends Component {
onSavePress,
onTestPress,
onDeleteDownloadClientPress,
onConfirmDeleteCategory,
...otherProps
} = this.props;
const {
isAddCategoryModalOpen
} = this.state;
const {
id,
implementationName,
name,
enable,
priority,
supportsCategories,
fields,
message
} = item;
console.log(supportsCategories);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
@@ -136,6 +169,43 @@ class EditDownloadClientModalContent extends Component {
/>
</FormGroup>
{
supportsCategories.value ?
<FieldSet legend={translate('MappedCategories')}>
<div className={styles.customFormats}>
{
categories.map((tag) => {
return (
<Category
key={tag.id}
{...tag}
onConfirmDeleteSpecification={onConfirmDeleteCategory}
/>
);
})
}
<Card
className={styles.addCategory}
onPress={this.onAddCategoryPress}
>
<div className={styles.center}>
<Icon
name={icons.ADD}
size={25}
/>
</div>
</Card>
</div>
</FieldSet> :
null
}
<AddCategoryModalConnector
isOpen={isAddCategoryModalOpen}
onModalClose={this.onAddCategoryModalClose}
/>
</Form>
}
</ModalBody>
@@ -185,13 +255,15 @@ EditDownloadClientModalContent.propTypes = {
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
isTesting: PropTypes.bool.isRequired,
categories: PropTypes.arrayOf(PropTypes.object),
item: PropTypes.object.isRequired,
onInputChange: PropTypes.func.isRequired,
onFieldChange: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired,
onDeleteDownloadClientPress: PropTypes.func
onDeleteDownloadClientPress: PropTypes.func,
onConfirmDeleteCategory: PropTypes.func.isRequired
};
export default EditDownloadClientModalContent;

View File

@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveDownloadClient, setDownloadClientFieldValue, setDownloadClientValue, testDownloadClient } from 'Store/Actions/settingsActions';
import { deleteDownloadClientCategory, fetchDownloadClientCategories, saveDownloadClient, setDownloadClientFieldValue, setDownloadClientValue, testDownloadClient } from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import EditDownloadClientModalContent from './EditDownloadClientModalContent';
@@ -10,10 +10,12 @@ function createMapStateToProps() {
return createSelector(
(state) => state.settings.advancedSettings,
createProviderSettingsSelector('downloadClients'),
(advancedSettings, downloadClient) => {
(state) => state.settings.downloadClientCategories,
(advancedSettings, downloadClient, categories) => {
return {
advancedSettings,
...downloadClient
...downloadClient,
categories: categories.items
};
}
);
@@ -23,7 +25,9 @@ const mapDispatchToProps = {
setDownloadClientValue,
setDownloadClientFieldValue,
saveDownloadClient,
testDownloadClient
testDownloadClient,
fetchDownloadClientCategories,
deleteDownloadClientCategory
};
class EditDownloadClientModalContentConnector extends Component {
@@ -31,6 +35,14 @@ class EditDownloadClientModalContentConnector extends Component {
//
// Lifecycle
componentDidMount() {
const {
id,
tagsFromId
} = this.props;
this.props.fetchDownloadClientCategories({ id: tagsFromId || id });
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
this.props.onModalClose();
@@ -56,6 +68,10 @@ class EditDownloadClientModalContentConnector extends Component {
this.props.testDownloadClient({ id: this.props.id });
};
onConfirmDeleteCategory = (id) => {
this.props.deleteDownloadClientCategory({ id });
};
//
// Render
@@ -67,6 +83,7 @@ class EditDownloadClientModalContentConnector extends Component {
onTestPress={this.onTestPress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
onConfirmDeleteCategory={this.onConfirmDeleteCategory}
/>
);
}
@@ -74,10 +91,13 @@ class EditDownloadClientModalContentConnector extends Component {
EditDownloadClientModalContentConnector.propTypes = {
id: PropTypes.number,
tagsFromId: PropTypes.number,
isFetching: PropTypes.bool.isRequired,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
item: PropTypes.object.isRequired,
fetchDownloadClientCategories: PropTypes.func.isRequired,
deleteDownloadClientCategory: PropTypes.func.isRequired,
setDownloadClientValue: PropTypes.func.isRequired,
setDownloadClientFieldValue: PropTypes.func.isRequired,
saveDownloadClient: PropTypes.func.isRequired,

View File

@@ -0,0 +1,171 @@
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import createClearReducer from 'Store/Actions/Creators/Reducers/createClearReducer';
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import { createThunk } from 'Store/thunks';
import getNextId from 'Utilities/State/getNextId';
import getProviderState from 'Utilities/State/getProviderState';
import getSectionState from 'Utilities/State/getSectionState';
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
import { removeItem, set, update, updateItem } from '../baseActions';
//
// Variables
const section = 'settings.downloadClientCategories';
//
// Actions Types
export const FETCH_DOWNLOAD_CLIENT_CATEGORIES = 'settings/downloadClientCategories/fetchDownloadClientCategories';
export const FETCH_DOWNLOAD_CLIENT_CATEGORY_SCHEMA = 'settings/downloadClientCategories/fetchDownloadClientCategorySchema';
export const SELECT_DOWNLOAD_CLIENT_CATEGORY_SCHEMA = 'settings/downloadClientCategories/selectDownloadClientCategorySchema';
export const SET_DOWNLOAD_CLIENT_CATEGORY_VALUE = 'settings/downloadClientCategories/setDownloadClientCategoryValue';
export const SET_DOWNLOAD_CLIENT_CATEGORY_FIELD_VALUE = 'settings/downloadClientCategories/setDownloadClientCategoryFieldValue';
export const SAVE_DOWNLOAD_CLIENT_CATEGORY = 'settings/downloadClientCategories/saveDownloadClientCategory';
export const DELETE_DOWNLOAD_CLIENT_CATEGORY = 'settings/downloadClientCategories/deleteDownloadClientCategory';
export const DELETE_ALL_DOWNLOAD_CLIENT_CATEGORY = 'settings/downloadClientCategories/deleteAllDownloadClientCategory';
export const CLEAR_DOWNLOAD_CLIENT_CATEGORIES = 'settings/downloadClientCategories/clearDownloadClientCategories';
export const CLEAR_DOWNLOAD_CLIENT_CATEGORY_PENDING = 'settings/downloadClientCategories/clearDownloadClientCategoryPending';
//
// Action Creators
export const fetchDownloadClientCategories = createThunk(FETCH_DOWNLOAD_CLIENT_CATEGORIES);
export const fetchDownloadClientCategorySchema = createThunk(FETCH_DOWNLOAD_CLIENT_CATEGORY_SCHEMA);
export const selectDownloadClientCategorySchema = createAction(SELECT_DOWNLOAD_CLIENT_CATEGORY_SCHEMA);
export const saveDownloadClientCategory = createThunk(SAVE_DOWNLOAD_CLIENT_CATEGORY);
export const deleteDownloadClientCategory = createThunk(DELETE_DOWNLOAD_CLIENT_CATEGORY);
export const deleteAllDownloadClientCategory = createThunk(DELETE_ALL_DOWNLOAD_CLIENT_CATEGORY);
export const setDownloadClientCategoryValue = createAction(SET_DOWNLOAD_CLIENT_CATEGORY_VALUE, (payload) => {
return {
section,
...payload
};
});
export const setDownloadClientCategoryFieldValue = createAction(SET_DOWNLOAD_CLIENT_CATEGORY_FIELD_VALUE, (payload) => {
return {
section,
...payload
};
});
export const clearDownloadClientCategory = createAction(CLEAR_DOWNLOAD_CLIENT_CATEGORIES);
export const clearDownloadClientCategoryPending = createThunk(CLEAR_DOWNLOAD_CLIENT_CATEGORY_PENDING);
//
// Details
export default {
//
// State
defaultState: {
isPopulated: false,
error: null,
isSchemaFetching: false,
isSchemaPopulated: false,
schemaError: null,
schema: [],
selectedSchema: {},
isSaving: false,
saveError: null,
items: [],
pendingChanges: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_DOWNLOAD_CLIENT_CATEGORIES]: (getState, payload, dispatch) => {
let tags = [];
if (payload.id) {
const cfState = getSectionState(getState(), 'settings.downloadClients', true);
const cf = cfState.items[cfState.itemMap[payload.id]];
tags = cf.categories.map((tag, i) => {
return {
id: i + 1,
...tag
};
});
}
dispatch(batchActions([
update({ section, data: tags }),
set({
section,
isPopulated: true
})
]));
},
[SAVE_DOWNLOAD_CLIENT_CATEGORY]: (getState, payload, dispatch) => {
const {
id,
...otherPayload
} = payload;
const saveData = getProviderState({ id, ...otherPayload }, getState, section, false);
console.log(saveData);
// we have to set id since not actually posting to server yet
if (!saveData.id) {
saveData.id = getNextId(getState().settings.downloadClientCategories.items);
}
dispatch(batchActions([
updateItem({ section, ...saveData }),
set({
section,
pendingChanges: {}
})
]));
},
[DELETE_DOWNLOAD_CLIENT_CATEGORY]: (getState, payload, dispatch) => {
const id = payload.id;
return dispatch(removeItem({ section, id }));
},
[DELETE_ALL_DOWNLOAD_CLIENT_CATEGORY]: (getState, payload, dispatch) => {
return dispatch(set({
section,
items: []
}));
},
[CLEAR_DOWNLOAD_CLIENT_CATEGORY_PENDING]: (getState, payload, dispatch) => {
return dispatch(set({
section,
pendingChanges: {}
}));
}
},
//
// Reducers
reducers: {
[SET_DOWNLOAD_CLIENT_CATEGORY_VALUE]: createSetSettingValueReducer(section),
[SET_DOWNLOAD_CLIENT_CATEGORY_FIELD_VALUE]: createSetProviderFieldValueReducer(section),
[SELECT_DOWNLOAD_CLIENT_CATEGORY_SCHEMA]: (state, { payload }) => {
return selectProviderSchema(state, section, payload, (selectedSchema) => {
return selectedSchema;
});
},
[CLEAR_DOWNLOAD_CLIENT_CATEGORIES]: createClearReducer(section, {
isPopulated: false,
error: null,
items: []
})
}
};

View File

@@ -9,6 +9,7 @@ import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import { createThunk } from 'Store/thunks';
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
import { set } from '../baseActions';
//
// Variables
@@ -90,10 +91,34 @@ export default {
[FETCH_DOWNLOAD_CLIENTS]: createFetchHandler(section, '/downloadclient'),
[FETCH_DOWNLOAD_CLIENT_SCHEMA]: createFetchSchemaHandler(section, '/downloadclient/schema'),
[SAVE_DOWNLOAD_CLIENT]: createSaveProviderHandler(section, '/downloadclient'),
[SAVE_DOWNLOAD_CLIENT]: (getState, payload, dispatch) => {
// move the format tags in as a pending change
const state = getState();
const pendingChanges = state.settings.downloadClients.pendingChanges;
pendingChanges.categories = state.settings.downloadClientCategories.items;
dispatch(set({
section,
pendingChanges
}));
createSaveProviderHandler(section, '/downloadclient')(getState, payload, dispatch);
},
[CANCEL_SAVE_DOWNLOAD_CLIENT]: createCancelSaveProviderHandler(section),
[DELETE_DOWNLOAD_CLIENT]: createRemoveItemHandler(section, '/downloadclient'),
[TEST_DOWNLOAD_CLIENT]: createTestProviderHandler(section, '/downloadclient'),
[TEST_DOWNLOAD_CLIENT]: (getState, payload, dispatch) => {
const state = getState();
const pendingChanges = state.settings.downloadClients.pendingChanges;
pendingChanges.categories = state.settings.downloadClientCategories.items;
dispatch(set({
section,
pendingChanges
}));
createTestProviderHandler(section, '/downloadclient')(getState, payload, dispatch);
},
[CANCEL_TEST_DOWNLOAD_CLIENT]: createCancelTestProviderHandler(section),
[TEST_ALL_DOWNLOAD_CLIENTS]: createTestAllProvidersHandler(section, '/downloadclient')
},

View File

@@ -4,6 +4,7 @@ import createHandleActions from './Creators/createHandleActions';
import applications from './Settings/applications';
import appProfiles from './Settings/appProfiles';
import development from './Settings/development';
import downloadClientCategories from './Settings/downloadClientCategories';
import downloadClients from './Settings/downloadClients';
import general from './Settings/general';
import indexerCategories from './Settings/indexerCategories';
@@ -11,6 +12,7 @@ import indexerProxies from './Settings/indexerProxies';
import notifications from './Settings/notifications';
import ui from './Settings/ui';
export * from './Settings/downloadClientCategories';
export * from './Settings/downloadClients';
export * from './Settings/general';
export * from './Settings/indexerCategories';
@@ -32,6 +34,7 @@ export const section = 'settings';
export const defaultState = {
advancedSettings: false,
downloadClientCategories: downloadClientCategories.defaultState,
downloadClients: downloadClients.defaultState,
general: general.defaultState,
indexerCategories: indexerCategories.defaultState,
@@ -61,6 +64,7 @@ export const toggleAdvancedSettings = createAction(TOGGLE_ADVANCED_SETTINGS);
// Action Handlers
export const actionHandlers = handleThunks({
...downloadClientCategories.actionHandlers,
...downloadClients.actionHandlers,
...general.actionHandlers,
...indexerCategories.actionHandlers,
@@ -81,6 +85,7 @@ export const reducers = createHandleActions({
return Object.assign({}, state, { advancedSettings: !state.advancedSettings });
},
...downloadClientCategories.reducers,
...downloadClients.reducers,
...general.reducers,
...indexerCategories.reducers,

View File

@@ -1,7 +1,11 @@
import * as dark from './dark';
import * as light from './light';
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const auto = defaultDark ? { ...dark } : { ...light };
export default {
auto,
light,
dark
};

View File

@@ -1,105 +0,0 @@
#!/bin/bash
# Generate a Markdown change log of pull requests from commits between two tags
scriptDir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
ghRepo="Prowlarr"
#branch="develop"
#read -r -p "What Repo?: " ghRepo
#read -r -p "What Org?: [Default:$ghRepo]" ghOrg
read -r -p "What Branch? [master|develop|nightly]:" branch
ghOrg=${ghOrg:-$ghRepo}
ghRepoUrl=https://github.com/$ghOrg/$ghRepo
case "${branch}" in
master)
hotioBranch='release'
lsioBranch='latest'
branchType='Stable'
;;
develop)
hotioBranch='testing'
lsioBranch='develop'
branchType='Beta'
;;
nightly)
hotioBranch='nightly'
lsioBranch='nightly'
branchType='Alpha'
;;
esac
baseDir=$(dirname "$scriptDir")
changelogDir="$baseDir/changelogs/"
templateDir="$changelogDir/templates/"
# Get a list of all tags in reverse order
# Assumes the tags are in version format like v1.2.3
gitTags=$(git ls-remote -t --exit-code --refs --sort='-v:refname' "$ghRepoUrl" | sed -E 's/^[[:xdigit:]]+[[:space:]]+refs\/tags\/(.+)/\1/g')
# Make the tags an array
# shellcheck disable=SC2206
tags=($gitTags)
latestTag=${tags[0]}
previousTag=${tags[1]}
# Get a log of commits that occurred between two tags
# See Pretty format placeholders at https://git-scm.com/docs/pretty-formats
# -i -E --grep="(Fixed:|New:)"'
commits=$(git log --pretty=format:' - %s%n' "$previousTag".."$latestTag")
# Store our changelog in a variable to be saved to a file at the end
markdown="# New ${branchType^} Release"
markdown+='\n\n'
markdown+="$ghRepo $latestTag has been released on \`$branch\`"
markdown+='\n\n'
branchmsg=$(cat "$templateDir"/branch-$branch.md)
if [ -n "$branchmsg" ]; then
{
markdown+=$branchmsg
markdown+='\n\n'
}
fi
markdown+="# Announcements"
markdown+='\n\n'
markdown+=$(cat "$templateDir"/announcements.md)
markdown+='\n\n'
markdown+="# Additional Commentary"
markdown+='\n\n'
markdown+=$(cat "$templateDir"/commentary.md)
markdown+='\n\n'
markdown+="# Releases"
markdown+='\n\n'
markdown+="## Native"
markdown+="\n\n"
markdown+="- [GitHub Releases]($ghRepoUrl/releases)"
markdown+="\n\n"
markdown+="- [Wiki Installation Instructions](https://wiki.servarr.com/${ghRepo,,}/installation)"
markdown+="\n\n"
markdown+="## Docker"
markdown+="\n\n"
markdown+="- [hotio/$ghRepo:$hotioBranch](https://hotio.dev/containers/${ghRepo,,})"
markdown+="\n\n"
markdown+="- [lscr.io/linuxserver/$ghRepo:$lsioBranch](https://docs.linuxserver.io/images/docker-${ghRepo,,})"
markdown+="\n\n"
markdown+="## NAS Packages"
markdown+="\n\n"
markdown+="- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally"
markdown+="\n\n"
markdown+="- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally"
markdown+="\n\n"
markdown+="------------"
markdown+="\n\n"
markdown+="# Release Notes"
markdown+="\n\n"
markdown+="## $latestTag (changes since $previousTag)"
markdown+="\n\n"
markdown+="$commits"
markdown+="\n\n"
markdown+=" - Other bug fixes and improvements, see GitHub history"
# Loop over each commit and look for merged pull requests
#for COMMIT in $COMMITS; do
#done
# Save our markdown to a file
mkdir -p "$changelogDir"
echo -e "$markdown" >"$changelogDir/CHANGELOG-$latestTag.md"
exit 0

View File

@@ -0,0 +1,25 @@
using System.Globalization;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Test.ExtensionTests.StringExtensionTests
{
[TestFixture]
public class IsValidIPAddressFixture
{
[TestCase("192.168.0.1")]
[TestCase("::1")]
[TestCase("2001:db8:4006:812::200e")]
public void should_validate_ip_address(string input)
{
input.IsValidIpAddress().Should().BeTrue();
}
[TestCase("sonarr.tv")]
public void should_not_parse_non_ip_address(string input)
{
input.IsValidIpAddress().Should().BeFalse();
}
}
}

View File

@@ -212,6 +212,7 @@ namespace NzbDrone.Common.Test.Http
}
[Test]
[Platform(Exclude = "MacOsX", Reason = "Azure agent update prevents brotli on OSX")]
public void should_execute_get_using_brotli()
{
var request = new HttpRequest($"https://{_httpBinHost}/brotli");

View File

@@ -1,4 +1,4 @@
using FluentAssertions;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Test.Common;
@@ -10,6 +10,7 @@ namespace NzbDrone.Common.Test.Http
[TestCase("abc://my_host.com:8080/root/api/")]
[TestCase("abc://my_host.com:8080//root/api/")]
[TestCase("abc://my_host.com:8080/root//api/")]
[TestCase("abc://[::1]:8080/root//api/")]
public void should_parse(string uri)
{
var newUri = new HttpUri(uri);

View File

@@ -98,15 +98,30 @@ namespace NzbDrone.Common.Test.InstrumentationTests
// Internal
[TestCase(@"[Info] MigrationController: *** Migrating Database=prowlarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")]
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
[TestCase(@"[Info] MigrationController: *** Migrating Database=prowlarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
public void should_clean_message(string message)
{
var cleansedMessage = CleanseLogMessage.Cleanse(message);
cleansedMessage.Should().NotContain("mySecret");
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
cleansedMessage.Should().NotContain("01233210");
}
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
public void should_keep_message(string message)
{
var cleansedMessage = CleanseLogMessage.Cleanse(message);
cleansedMessage.Should().NotContain("mySecret");
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
cleansedMessage.Should().NotContain("01233210");
cleansedMessage.Should().Contain("shouldkeep1");
cleansedMessage.Should().Contain("shouldkeep2");
cleansedMessage.Should().Contain("shouldkeep3");
}
[TestCase(@"Some message (from 32.2.3.5 user agent)")]
[TestCase(@"Auth-Invalidated ip 32.2.3.5")]
[TestCase(@"Auth-Success ip 32.2.3.5")]

View File

@@ -7,34 +7,50 @@ namespace NzbDrone.Common.Extensions
{
public static bool IsLocalAddress(this IPAddress ipAddress)
{
if (ipAddress.IsIPv6LinkLocal)
// Map back to IPv4 if mapped to IPv6, for example "::ffff:1.2.3.4" to "1.2.3.4".
if (ipAddress.IsIPv4MappedToIPv6)
{
return true;
ipAddress = ipAddress.MapToIPv4();
}
// Checks loopback ranges for both IPv4 and IPv6.
if (IPAddress.IsLoopback(ipAddress))
{
return true;
}
// IPv4
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
{
byte[] bytes = ipAddress.GetAddressBytes();
switch (bytes[0])
{
case 10:
case 127:
return true;
case 172:
return bytes[1] < 32 && bytes[1] >= 16;
case 192:
return bytes[1] == 168;
default:
return false;
}
return IsLocalIPv4(ipAddress.GetAddressBytes());
}
// IPv6
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
{
return ipAddress.IsIPv6LinkLocal ||
ipAddress.IsIPv6UniqueLocal ||
ipAddress.IsIPv6SiteLocal;
}
return false;
}
private static bool IsLocalIPv4(byte[] ipv4Bytes)
{
// Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16)
bool IsLinkLocal() => ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
// Class A private range: 10.0.0.0 10.255.255.255 (10.0.0.0/8)
bool IsClassA() => ipv4Bytes[0] == 10;
// Class B private range: 172.16.0.0 172.31.255.255 (172.16.0.0/12)
bool IsClassB() => ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;
// Class C private range: 192.168.0.0 192.168.255.255 (192.168.0.0/16)
bool IsClassC() => ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
return IsLinkLocal() || IsClassA() || IsClassC() || IsClassB();
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
@@ -231,5 +232,30 @@ namespace NzbDrone.Common.Extensions
.Replace("'", "%27")
.Replace("%7E", "~");
}
public static bool IsValidIpAddress(this string value)
{
if (!IPAddress.TryParse(value, out var parsedAddress))
{
return false;
}
if (parsedAddress.Equals(IPAddress.Parse("255.255.255.255")))
{
return false;
}
if (parsedAddress.IsIPv6Multicast)
{
return false;
}
return parsedAddress.AddressFamily == AddressFamily.InterNetwork || parsedAddress.AddressFamily == AddressFamily.InterNetworkV6;
}
public static string ToUrlHost(this string input)
{
return input.Contains(":") ? $"[{input}]" : input;
}
}
}

View File

@@ -99,7 +99,7 @@ namespace NzbDrone.Common.Http.Dispatchers
AddRequestHeaders(requestMessage, request.Headers);
}
var httpClient = GetClient(request.Url);
var httpClient = GetClient(request.Url, request.ProxySettings);
var sw = new Stopwatch();
@@ -154,9 +154,9 @@ namespace NzbDrone.Common.Http.Dispatchers
}
}
protected virtual System.Net.Http.HttpClient GetClient(HttpUri uri)
protected virtual System.Net.Http.HttpClient GetClient(HttpUri uri, HttpProxySettings requestProxy)
{
var proxySettings = _proxySettingsProvider.GetProxySettings(uri);
var proxySettings = requestProxy ?? _proxySettingsProvider.GetProxySettings(uri);
var key = proxySettings?.Key ?? NO_PROXY_KEY;
@@ -174,6 +174,7 @@ namespace NzbDrone.Common.Http.Dispatchers
PreAuthenticate = true,
MaxConnectionsPerServer = 12,
ConnectCallback = onConnect,
PooledConnectionLifetime = TimeSpan.FromMinutes(10),
SslOptions = new SslClientAuthenticationOptions
{
RemoteCertificateValidationCallback = _certificateValidationService.ShouldByPassValidationError

View File

@@ -6,6 +6,7 @@ using System.Net.Http;
using System.Text;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http.Proxy;
namespace NzbDrone.Common.Http
{
@@ -37,7 +38,7 @@ namespace NzbDrone.Common.Http
public HttpMethod Method { get; set; }
public HttpHeader Headers { get; set; }
public Encoding Encoding { get; set; }
public IWebProxy Proxy { get; set; }
public HttpProxySettings ProxySettings { get; set; }
public byte[] ContentData { get; set; }
public string ContentSummary { get; set; }
public ICredentials Credentials { get; set; }

View File

@@ -89,13 +89,13 @@ namespace NzbDrone.Common.Http
if (match.Success)
{
return (Request.Url += new HttpUri(match.Groups[2].Value)).FullUri;
return (Request.Url + new HttpUri(match.Groups[2].Value)).FullUri;
}
return string.Empty;
}
return (Request.Url += new HttpUri(newUrl)).FullUri;
return (Request.Url + new HttpUri(newUrl)).FullUri;
}
}

View File

@@ -8,7 +8,7 @@ namespace NzbDrone.Common.Http
{
public class HttpUri : IEquatable<HttpUri>
{
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+)(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+|\[[[A-F0-9:]+\])(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly string _uri;
public string FullUri => _uri;
@@ -70,6 +70,8 @@ namespace NzbDrone.Common.Http
private void Parse()
{
var parseSuccess = Uri.TryCreate(_uri, UriKind.RelativeOrAbsolute, out var uri);
var match = RegexUri.Match(_uri);
var scheme = match.Groups["scheme"];
@@ -79,7 +81,7 @@ namespace NzbDrone.Common.Http
var query = match.Groups["query"];
var fragment = match.Groups["fragment"];
if (!match.Success || (scheme.Success && !host.Success && path.Success))
if (!parseSuccess || (scheme.Success && !host.Success && path.Success))
{
throw new ArgumentException("Uri didn't match expected pattern: " + _uri);
}

View File

@@ -18,6 +18,7 @@ namespace NzbDrone.Common.Instrumentation
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=authkey = "")(?<secret>[^&=]+?)(?="")", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=beyond-hd\.[a-z]+/api/torrents/)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),

View File

@@ -4,13 +4,13 @@
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DryIoc.dll" Version="4.8.8" />
<PackageReference Include="DryIoc.dll" Version="5.2.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NLog" Version="5.0.1" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Sentry" Version="3.19.0" />
<PackageReference Include="Sentry" Version="3.21.0" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />

View File

@@ -0,0 +1,58 @@
using System;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class orpheus_apiFixture : MigrationTest<orpheus_api>
{
[Test]
public void should_convert_and_disable_orpheus_instance()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Indexers").Row(new
{
Enable = true,
Name = "Orpheus",
Priority = 25,
Added = DateTime.UtcNow,
Implementation = "Orpheus",
Settings = new GazelleIndexerSettings021
{
Username = "some name",
Password = "some pass"
}.ToJson(),
ConfigContract = "GazelleSettings"
});
});
var items = db.Query<IndexerDefinition022>("SELECT \"Id\", \"Enable\", \"ConfigContract\", \"Settings\" FROM \"Indexers\"");
items.Should().HaveCount(1);
items.First().ConfigContract.Should().Be("OrpheusSettings");
items.First().Enable.Should().Be(false);
items.First().Settings.Should().NotContain("username");
items.First().Settings.Should().NotContain("password");
}
}
public class IndexerDefinition022
{
public int Id { get; set; }
public bool Enable { get; set; }
public string ConfigContract { get; set; }
public string Settings { get; set; }
}
public class GazelleIndexerSettings021
{
public string Username { get; set; }
public string Password { get; set; }
}
}

View File

@@ -0,0 +1,4 @@
{
"status": "success",
"response": []
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.History;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.IndexerStats;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerStatsTests
{
public class IndexerStatisticsServiceFixture : CoreTest<IndexerStatisticsService>
{
private IndexerDefinition _indexer;
[SetUp]
public void Setup()
{
_indexer = Builder<IndexerDefinition>.CreateNew().With(x => x.Id = 5).Build();
Mocker.GetMock<IIndexerFactory>()
.Setup(o => o.All())
.Returns(new List<IndexerDefinition> { _indexer });
}
[Test]
public void should_pull_stats_if_all_events_are_failures()
{
var history = new List<History.History>
{
new History.History
{
Date = DateTime.UtcNow.AddHours(-1),
EventType = HistoryEventType.IndexerRss,
Successful = false,
Id = 8,
IndexerId = 5,
Data = new Dictionary<string, string> { { "source", "prowlarr" } }
}
};
Mocker.GetMock<IHistoryService>()
.Setup(o => o.Between(It.IsAny<DateTime>(), It.IsAny<DateTime>()))
.Returns<DateTime, DateTime>((s, f) => history);
var statistics = Subject.IndexerStatistics(DateTime.UtcNow.AddMonths(-1), DateTime.UtcNow);
statistics.IndexerStatistics.Count.Should().Be(1);
statistics.IndexerStatistics.First().AverageResponseTime.Should().Be(0);
}
}
}

View File

@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://avistaz.to/torrent/187240-japan-sinks-people-of-hope-2021-s01e05-720p-nf-web-dl-ddp20-x264-seikel");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-14 23:26:21"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-15 04:26:21"));
torrentInfo.Size.Should().Be(935127615);
torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://exoticaz.to/torrent/64040-ssis-419-my-first-experience-is-yua-mikami-from-the-day-i-lost-my-virginity-i-was-devoted-to-sex");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-06-11 11:04:50"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-06-11 16:04:50"));
torrentInfo.Size.Should().Be(7085405541);
torrentInfo.InfoHash.Should().Be("asdjfiasdf54asd7f4a2sdf544asdf");
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://privatehd.to/torrent/78506-godzilla-2014-2160p-uhd-bluray-remux-hdr-hevc-atmos-triton");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-03-21 00:24:49"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-03-21 05:24:49"));
torrentInfo.Size.Should().Be(69914591044);
torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -12,6 +12,7 @@ using NzbDrone.Core.Indexers.Definitions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
{
@@ -64,5 +65,19 @@ namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
torrentInfo.DownloadVolumeFactor.Should().Be(1);
torrentInfo.UploadVolumeFactor.Should().Be(1);
}
[Test]
public async Task should_not_error_if_empty_response()
{
var recentFeed = ReadAllText(@"Files/Indexers/GazelleGames/recentfeed-empty.json");
Mocker.GetMock<IIndexerHttpClient>()
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new int[] { 2000 } })).Releases;
releases.Should().HaveCount(0);
}
}
}

View File

@@ -15,14 +15,14 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
[TestFixture]
public class NewznabCapabilitiesProviderFixture : CoreTest<NewznabCapabilitiesProvider>
{
private NewznabSettings _settings;
private GenericNewznabSettings _settings;
private IndexerDefinition _definition;
private string _caps;
[SetUp]
public void SetUp()
{
_settings = new NewznabSettings()
_settings = new GenericNewznabSettings()
{
BaseUrl = "http://indxer.local"
};

View File

@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_caps = new IndexerCapabilities();
Mocker.GetMock<INewznabCapabilitiesProvider>()
.Setup(v => v.GetCapabilities(It.IsAny<NewznabSettings>(), It.IsAny<IndexerDefinition>()))
.Setup(v => v.GetCapabilities(It.IsAny<GenericNewznabSettings>(), It.IsAny<IndexerDefinition>()))
.Returns(_caps);
}

View File

@@ -10,7 +10,7 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
{
public class NewznabRequestGeneratorFixture : CoreTest<NewznabRequestGenerator>
public class NewznabRequestGeneratorFixture : CoreTest<GenericNewznabRequestGenerator>
{
private MovieSearchCriteria _movieSearchCriteria;
private TvSearchCriteria _tvSearchCriteria;
@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
[SetUp]
public void SetUp()
{
Subject.Settings = new NewznabSettings()
Subject.Settings = new GenericNewznabSettings()
{
BaseUrl = "http://127.0.0.1:1234/",
ApiKey = "abcd",
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_capabilities = new IndexerCapabilities();
Mocker.GetMock<INewznabCapabilitiesProvider>()
.Setup(v => v.GetCapabilities(It.IsAny<NewznabSettings>(), It.IsAny<IndexerDefinition>()))
.Setup(v => v.GetCapabilities(It.IsAny<GenericNewznabSettings>(), It.IsAny<IndexerDefinition>()))
.Returns(_capabilities);
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions;
using NzbDrone.Core.Indexers.Gazelle;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests.OrpheusTests
{
[TestFixture]
public class OrpheusFixture : CoreTest<Orpheus>
{
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
{
Name = "Orpheus",
Settings = new OrpheusSettings() { Apikey = "somekey" }
};
}
[Test]
public async Task should_parse_recent_feed_from_GazelleGames()
{
var recentFeed = ReadAllText(@"Files/Indexers/Orpheus/recentfeed.json");
Mocker.GetMock<IIndexerHttpClient>()
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new int[] { 2000 } })).Releases;
releases.Should().HaveCount(65);
releases.First().Should().BeOfType<GazelleInfo>();
var torrentInfo = releases.First() as GazelleInfo;
torrentInfo.Title.Should().Be("The Beatles - Abbey Road (1969) [MP3 V2 (VBR)] [BD]");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("https://orpheus.network/ajax.php?action=download&id=1902448");
torrentInfo.InfoUrl.Should().Be("https://orpheus.network/torrents.php?id=466&torrentid=1902448");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-08-08 2:07:39"));
torrentInfo.Size.Should().Be(68296866);
torrentInfo.InfoHash.Should().Be(null);
torrentInfo.MagnetUrl.Should().Be(null);
torrentInfo.Peers.Should().Be(0);
torrentInfo.Seeders.Should().Be(0);
torrentInfo.ImdbId.Should().Be(0);
torrentInfo.TmdbId.Should().Be(0);
torrentInfo.TvdbId.Should().Be(0);
torrentInfo.Languages.Should().HaveCount(0);
torrentInfo.Subs.Should().HaveCount(0);
torrentInfo.DownloadVolumeFactor.Should().Be(1);
torrentInfo.UploadVolumeFactor.Should().Be(1);
}
}
}

View File

@@ -30,7 +30,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
};
Mocker.GetMock<IRarbgTokenProvider>()
.Setup(v => v.GetToken(It.IsAny<RarbgSettings>(), It.IsAny<string>()))
.Setup(v => v.GetToken(It.IsAny<RarbgSettings>()))
.Returns("validtoken");
}

View File

@@ -35,7 +35,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
_caps = new IndexerCapabilities();
Mocker.GetMock<INewznabCapabilitiesProvider>()
.Setup(v => v.GetCapabilities(It.IsAny<NewznabSettings>(), It.IsAny<IndexerDefinition>()))
.Setup(v => v.GetCapabilities(It.IsAny<GenericNewznabSettings>(), It.IsAny<IndexerDefinition>()))
.Returns(_caps);
}

View File

@@ -6,7 +6,7 @@
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="NBuilder" Version="6.1.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="YamlDotNet" Version="11.2.1" />
<PackageReference Include="YamlDotNet" Version="12.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />
@@ -21,7 +21,4 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="Datastore\Migration\" />
</ItemGroup>
</Project>

View File

@@ -206,7 +206,7 @@ namespace NzbDrone.Core.Configuration
public string PostgresMainDb => _postgresOptions?.MainDb ?? GetValue("PostgresMainDb", "prowlarr-main", persist: false);
public string PostgresLogDb => _postgresOptions?.LogDb ?? GetValue("PostgresLogDb", "prowlarr-log", persist: false);
public int PostgresPort => (_postgresOptions?.Port ?? 0) != 0 ? _postgresOptions.Port : GetValueInt("PostgresPort", 5432, persist: false);
public string Theme => GetValue("Theme", "light", persist: false);
public string Theme => GetValue("Theme", "auto", persist: false);
public bool LogSql => GetValueBoolean("LogSql", false, persist: false);
public int LogRotate => GetValueInt("LogRotate", 50, persist: false);
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);

View File

@@ -1,6 +1,7 @@
using System;
using System.Data.Common;
using System.Data.SQLite;
using System.Net.Sockets;
using NLog;
using Npgsql;
using NzbDrone.Common.Disk;
@@ -125,6 +126,37 @@ namespace NzbDrone.Core.Datastore
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/prowlarr/faq#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName);
}
catch (NpgsqlException e)
{
if (e.InnerException is SocketException)
{
var retryCount = 3;
while (true)
{
Logger.Error(e, "Failure to connect to Postgres DB, {0} retries remaining", retryCount);
try
{
_migrationController.Migrate(connectionString, migrationContext);
}
catch (Exception ex)
{
if (--retryCount > 0)
{
System.Threading.Thread.Sleep(5000);
continue;
}
throw new ProwlarrStartupException(ex, "Error creating main database");
}
}
}
else
{
throw new ProwlarrStartupException(e, "Error creating main database");
}
}
catch (Exception e)
{
throw new ProwlarrStartupException(e, "Error creating main database");

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Data;
using FluentMigrator;
using Newtonsoft.Json.Linq;
@@ -21,6 +22,8 @@ namespace NzbDrone.Core.Datastore.Migration
cmd.Transaction = tran;
cmd.CommandText = "SELECT \"Id\", \"Settings\" FROM \"Indexers\" WHERE \"Implementation\" = 'Redacted'";
var updatedIndexers = new List<Indexer008>();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
@@ -45,19 +48,26 @@ namespace NzbDrone.Core.Datastore.Migration
// write new json back to db, switch to new ConfigContract, and disable the indexer
settings = jsonObject.ToJson();
using (var updateCmd = conn.CreateCommand())
updatedIndexers.Add(new Indexer008
{
updateCmd.Transaction = tran;
updateCmd.CommandText = "UPDATE \"Indexers\" SET \"Settings\" = ?, \"ConfigContract\" = ?, \"Enable\" = 0 WHERE \"Id\" = ?";
updateCmd.AddParameter(settings);
updateCmd.AddParameter("RedactedSettings");
updateCmd.AddParameter(id);
updateCmd.ExecuteNonQuery();
}
Id = id,
Settings = settings,
ConfigContract = "RedactedSettings",
Enable = false
});
}
}
}
}
}
public class Indexer008
{
public int Id { get; set; }
public string Settings { get; set; }
public string ConfigContract { get; set; }
public bool Enable { get; set; }
}
}
}

View File

@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Data;
using Dapper;
using FluentMigrator;
using Newtonsoft.Json.Linq;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
using static NzbDrone.Core.Datastore.Migration.redacted_api;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(22)]
public class orpheus_api : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.WithConnection(MigrateToRedactedApi);
}
private void MigrateToRedactedApi(IDbConnection conn, IDbTransaction tran)
{
using (var cmd = conn.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandText = "SELECT \"Id\", \"Settings\" FROM \"Indexers\" WHERE \"Implementation\" = 'Orpheus'";
var updatedIndexers = new List<Indexer008>();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var id = reader.GetInt32(0);
var settings = reader.GetString(1);
if (!string.IsNullOrWhiteSpace(settings))
{
var jsonObject = Json.Deserialize<JObject>(settings);
// Remove username
if (jsonObject.ContainsKey("username"))
{
jsonObject.Remove("username");
}
// Remove password
if (jsonObject.ContainsKey("password"))
{
jsonObject.Remove("password");
}
// write new json back to db, switch to new ConfigContract, and disable the indexer
settings = jsonObject.ToJson();
updatedIndexers.Add(new Indexer008
{
Id = id,
Settings = settings,
ConfigContract = "OrpheusSettings",
Enable = false
});
}
}
}
var updateSql = "UPDATE \"Indexers\" SET \"Settings\" = @Settings, \"ConfigContract\" = @ConfigContract, \"Enable\" = @Enable WHERE \"Id\" = @Id";
conn.Execute(updateSql, updatedIndexers, transaction: tran);
}
}
}
}

View File

@@ -0,0 +1,15 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(023)]
public class download_client_categories : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("DownloadClients")
.AddColumn("Categories").AsString().WithDefaultValue("[]");
}
}
}

View File

@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(24)]
public class newznab_yml : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Update.Table("Indexers").Set(new { Implementation = "GenericNewznab", ConfigContract = "GenericNewznabSettings" }).Where(new { Implementation = "Newznab" });
}
}
}

View File

@@ -60,7 +60,8 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<DownloadClientDefinition>("DownloadClients").RegisterModel()
.Ignore(x => x.ImplementationName)
.Ignore(i => i.Protocol)
.Ignore(d => d.SupportsCategories)
.Ignore(d => d.Protocol)
.Ignore(d => d.Tags);
Mapper.Entity<NotificationDefinition>("Notifications").RegisterModel()
@@ -115,6 +116,7 @@ namespace NzbDrone.Core.Datastore
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<string>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<ReleaseInfo>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<HashSet<int>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<DownloadClientCategory>>());
SqlMapper.AddTypeHandler(new OsPathConverter());
SqlMapper.RemoveTypeMap(typeof(Guid));
SqlMapper.RemoveTypeMap(typeof(Guid?));

View File

@@ -18,6 +18,8 @@ namespace NzbDrone.Core.Download.Clients.Aria2
public override string Name => "Aria2";
public override bool SupportsCategories => false;
public Aria2(IAria2Proxy proxy,
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,

View File

@@ -74,6 +74,8 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
public override string Name => "Torrent Blackhole";
public override bool SupportsCategories => false;
protected override void Test(List<ValidationFailure> failures)
{
failures.AddIfNotNull(TestFolder(Settings.TorrentFolder, "TorrentFolder"));

View File

@@ -45,6 +45,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
}
public override string Name => "Usenet Blackhole";
public override bool SupportsCategories => false;
protected override void Test(List<ValidationFailure> failures)
{

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
@@ -38,9 +39,10 @@ namespace NzbDrone.Core.Download.Clients.Deluge
}
// _proxy.SetTorrentSeedingConfiguration(actualHash, remoteMovie.SeedConfiguration, Settings);
if (Settings.Category.IsNotNullOrWhiteSpace())
var category = GetCategoryForRelease(release) ?? Settings.Category;
if (category.IsNotNullOrWhiteSpace())
{
_proxy.SetTorrentLabel(actualHash, Settings.Category, Settings);
_proxy.SetTorrentLabel(actualHash, category, Settings);
}
if (Settings.Priority == (int)DelugePriority.First)
@@ -61,9 +63,10 @@ namespace NzbDrone.Core.Download.Clients.Deluge
}
// _proxy.SetTorrentSeedingConfiguration(actualHash, release.SeedConfiguration, Settings);
if (Settings.Category.IsNotNullOrWhiteSpace())
var category = GetCategoryForRelease(release) ?? Settings.Category;
if (category.IsNotNullOrWhiteSpace())
{
_proxy.SetTorrentLabel(actualHash, Settings.Category, Settings);
_proxy.SetTorrentLabel(actualHash, category, Settings);
}
if (Settings.Priority == (int)DelugePriority.First)
@@ -75,6 +78,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
}
public override string Name => "Deluge";
public override bool SupportsCategories => true;
protected override void Test(List<ValidationFailure> failures)
{
@@ -139,7 +143,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
private ValidationFailure TestCategory()
{
if (Settings.Category.IsNullOrWhiteSpace())
if (Categories.Count == 0)
{
return null;
}
@@ -156,23 +160,42 @@ namespace NzbDrone.Core.Download.Clients.Deluge
var labels = _proxy.GetAvailableLabels(Settings);
if (Settings.Category.IsNotNullOrWhiteSpace() && !labels.Contains(Settings.Category))
{
_proxy.AddLabel(Settings.Category, Settings);
labels = _proxy.GetAvailableLabels(Settings);
var categories = Categories.Select(c => c.ClientCategory).ToList();
categories.Add(Settings.Category);
if (!labels.Contains(Settings.Category))
foreach (var category in categories)
{
if (category.IsNotNullOrWhiteSpace() && !labels.Contains(category))
{
return new NzbDroneValidationFailure("Category", "Configuration of label failed")
_proxy.AddLabel(category, Settings);
labels = _proxy.GetAvailableLabels(Settings);
if (!labels.Contains(category))
{
DetailedDescription = "Prowlarr was unable to add the label to Deluge."
};
return new NzbDroneValidationFailure("Category", "Configuration of label failed")
{
DetailedDescription = "Prowlarr was unable to add the label to Deluge."
};
}
}
}
return null;
}
protected override void ValidateCategories(List<ValidationFailure> failures)
{
base.ValidateCategories(failures);
foreach (var label in Categories)
{
if (!Regex.IsMatch(label.ClientCategory, "^[-a-z0-9]*$"))
{
failures.AddIfNotNull(new ValidationFailure(string.Empty, "Mapped Categories allowed characters a-z, 0-9 and -"));
}
}
}
private ValidationFailure TestGetTorrents()
{
try

View File

@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
string[] GetAvailablePlugins(DelugeSettings settings);
string[] GetEnabledPlugins(DelugeSettings settings);
string[] GetAvailableLabels(DelugeSettings settings);
DelugeLabel GetLabelOptions(DelugeSettings settings);
DelugeLabel GetLabelOptions(DelugeSettings settings, string label);
void SetTorrentLabel(string hash, string label, DelugeSettings settings);
void SetTorrentConfiguration(string hash, string key, object value, DelugeSettings settings);
void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, DelugeSettings settings);
@@ -158,9 +158,9 @@ namespace NzbDrone.Core.Download.Clients.Deluge
return response;
}
public DelugeLabel GetLabelOptions(DelugeSettings settings)
public DelugeLabel GetLabelOptions(DelugeSettings settings, string label)
{
var response = ProcessRequest<DelugeLabel>(settings, "label.get_options", settings.Category);
var response = ProcessRequest<DelugeLabel>(settings, "label.get_options", label);
return response;
}

View File

@@ -43,7 +43,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
[FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(5, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback Category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
public string Category { get; set; }
[FieldDefinition(6, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing items")]

View File

@@ -18,9 +18,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
.When(c => c.TvDirectory.IsNotNullOrWhiteSpace())
.WithMessage("Cannot start with /");
RuleFor(c => c.TvCategory).Matches(@"^\.?[-a-z]*$", RegexOptions.IgnoreCase).WithMessage("Allowed characters a-z and -");
RuleFor(c => c.Category).Matches(@"^\.?[-a-z]*$", RegexOptions.IgnoreCase).WithMessage("Allowed characters a-z and -");
RuleFor(c => c.TvCategory).Empty()
RuleFor(c => c.Category).Empty()
.When(c => c.TvDirectory.IsNotNullOrWhiteSpace())
.WithMessage("Cannot use Category and Directory");
}
@@ -45,8 +45,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
[FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
public string TvCategory { get; set; }
[FieldDefinition(5, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
public string Category { get; set; }
[FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, HelpText = "Optional shared folder to put downloads into, leave blank to use the default Download Station location")]
public string TvDirectory { get; set; }

View File

@@ -44,6 +44,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
}
public override string Name => "Download Station";
public override bool SupportsCategories => false;
public override ProviderMessage Message => new ProviderMessage("Prowlarr is unable to connect to Download Station if 2-Factor Authentication is enabled on your DSM account", ProviderMessageType.Warning);
@@ -198,7 +199,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
if (downloadDir != null)
{
var sharedFolder = downloadDir.Split('\\', '/')[0];
var fieldName = Settings.TvDirectory.IsNotNullOrWhiteSpace() ? nameof(Settings.TvDirectory) : nameof(Settings.TvCategory);
var fieldName = Settings.TvDirectory.IsNotNullOrWhiteSpace() ? nameof(Settings.TvDirectory) : nameof(Settings.Category);
var folderInfo = _fileStationProxy.GetInfoFileOrDirectory($"/{downloadDir}", Settings);
@@ -311,11 +312,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
return Settings.TvDirectory.TrimStart('/');
}
else if (Settings.TvCategory.IsNotNullOrWhiteSpace())
else if (Settings.Category.IsNotNullOrWhiteSpace())
{
var destDir = GetDefaultDir();
return $"{destDir.TrimEnd('/')}/{Settings.TvCategory}";
return $"{destDir.TrimEnd('/')}/{Settings.Category}";
}
return null;

View File

@@ -42,6 +42,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
}
public override string Name => "Download Station";
public override bool SupportsCategories => false;
public override ProviderMessage Message => new ProviderMessage("Prowlarr is unable to connect to Download Station if 2-Factor Authentication is enabled on your DSM account", ProviderMessageType.Warning);
@@ -101,7 +102,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
if (downloadDir != null)
{
var sharedFolder = downloadDir.Split('\\', '/')[0];
var fieldName = Settings.TvDirectory.IsNotNullOrWhiteSpace() ? nameof(Settings.TvDirectory) : nameof(Settings.TvCategory);
var fieldName = Settings.TvDirectory.IsNotNullOrWhiteSpace() ? nameof(Settings.TvDirectory) : nameof(Settings.Category);
var folderInfo = _fileStationProxy.GetInfoFileOrDirectory($"/{downloadDir}", Settings);
@@ -272,11 +273,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
return Settings.TvDirectory.TrimStart('/');
}
else if (Settings.TvCategory.IsNotNullOrWhiteSpace())
else if (Settings.Category.IsNotNullOrWhiteSpace())
{
var destDir = GetDefaultDir();
return $"{destDir.TrimEnd('/')}/{Settings.TvCategory}";
return $"{destDir.TrimEnd('/')}/{Settings.Category}";
}
return null;

View File

@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Download.Clients.Flood
_proxy = proxy;
}
private static IEnumerable<string> HandleTags(ReleaseInfo release, FloodSettings settings)
private static IEnumerable<string> HandleTags(ReleaseInfo release, FloodSettings settings, string mappedCategory)
{
var result = new HashSet<string>();
@@ -36,6 +36,11 @@ namespace NzbDrone.Core.Download.Clients.Flood
result.UnionWith(settings.Tags);
}
if (mappedCategory != null)
{
result.Add(mappedCategory);
}
if (settings.AdditionalTags.Any())
{
foreach (var additionalTag in settings.AdditionalTags)
@@ -55,18 +60,19 @@ namespace NzbDrone.Core.Download.Clients.Flood
}
public override string Name => "Flood";
public override bool SupportsCategories => true;
public override ProviderMessage Message => new ProviderMessage("Prowlarr is unable to remove torrents that have finished seeding when using Flood", ProviderMessageType.Warning);
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
{
_proxy.AddTorrentByFile(Convert.ToBase64String(fileContent), HandleTags(release, Settings), Settings);
_proxy.AddTorrentByFile(Convert.ToBase64String(fileContent), HandleTags(release, Settings, GetCategoryForRelease(release)), Settings);
return hash;
}
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
{
_proxy.AddTorrentByUrl(magnetLink, HandleTags(release, Settings), Settings);
_proxy.AddTorrentByUrl(magnetLink, HandleTags(release, Settings, GetCategoryForRelease(release)), Settings);
return hash;
}

View File

@@ -27,6 +27,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
}
public override string Name => "Hadouken";
public override bool SupportsCategories => true;
protected override void Test(List<ValidationFailure> failures)
{
@@ -41,14 +42,14 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
{
_proxy.AddTorrentUri(Settings, magnetLink);
_proxy.AddTorrentUri(Settings, magnetLink, GetCategoryForRelease(release) ?? Settings.Category);
return hash.ToUpper();
}
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
{
return _proxy.AddTorrentFile(Settings, fileContent).ToUpper();
return _proxy.AddTorrentFile(Settings, fileContent, GetCategoryForRelease(release) ?? Settings.Category).ToUpper();
}
private ValidationFailure TestConnection()

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Net;
using NLog;
@@ -13,8 +13,8 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
HadoukenSystemInfo GetSystemInfo(HadoukenSettings settings);
HadoukenTorrent[] GetTorrents(HadoukenSettings settings);
IReadOnlyDictionary<string, object> GetConfig(HadoukenSettings settings);
string AddTorrentFile(HadoukenSettings settings, byte[] fileContent);
void AddTorrentUri(HadoukenSettings settings, string torrentUrl);
string AddTorrentFile(HadoukenSettings settings, byte[] fileContent, string label);
void AddTorrentUri(HadoukenSettings settings, string torrentUrl, string label);
void RemoveTorrent(HadoukenSettings settings, string downloadId);
void RemoveTorrentAndData(HadoukenSettings settings, string downloadId);
}
@@ -47,14 +47,14 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
return ProcessRequest<IReadOnlyDictionary<string, object>>(settings, "webui.getSettings");
}
public string AddTorrentFile(HadoukenSettings settings, byte[] fileContent)
public string AddTorrentFile(HadoukenSettings settings, byte[] fileContent, string label)
{
return ProcessRequest<string>(settings, "webui.addTorrent", "file", Convert.ToBase64String(fileContent), new { label = settings.Category });
return ProcessRequest<string>(settings, "webui.addTorrent", "file", Convert.ToBase64String(fileContent), new { label });
}
public void AddTorrentUri(HadoukenSettings settings, string torrentUrl)
public void AddTorrentUri(HadoukenSettings settings, string torrentUrl, string label)
{
ProcessRequest<string>(settings, "webui.addTorrent", "url", torrentUrl, new { label = settings.Category });
ProcessRequest<string>(settings, "webui.addTorrent", "url", torrentUrl, new { label });
}
public void RemoveTorrent(HadoukenSettings settings, string downloadId)

View File

@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox)]
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release.")]
public string Category { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -29,8 +29,9 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
protected override string AddFromNzbFile(ReleaseInfo release, string filename, byte[] fileContents)
{
var priority = Settings.Priority;
var category = GetCategoryForRelease(release) ?? Settings.Category;
var response = _proxy.DownloadNzb(fileContents, filename, priority, Settings);
var response = _proxy.DownloadNzb(fileContents, filename, priority, Settings, category);
if (response == null)
{
@@ -41,6 +42,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
}
public override string Name => "NZBVortex";
public override bool SupportsCategories => true;
protected List<NzbVortexGroup> GetGroups()
{
@@ -111,19 +113,27 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
private ValidationFailure TestCategory()
{
var group = GetGroups().FirstOrDefault(c => c.GroupName == Settings.Category);
var groups = GetGroups();
if (group == null)
foreach (var category in Categories)
{
if (Settings.Category.IsNotNullOrWhiteSpace())
if (!category.ClientCategory.IsNullOrWhiteSpace() && !groups.Any(v => v.GroupName == category.ClientCategory))
{
return new NzbDroneValidationFailure("Category", "Group does not exist")
return new NzbDroneValidationFailure(string.Empty, "Group does not exist")
{
DetailedDescription = "The Group you entered doesn't exist in NzbVortex. Go to NzbVortex to create it."
DetailedDescription = "A mapped category you entered doesn't exist in NzbVortex. Go to NzbVortex to create it."
};
}
}
if (!Settings.Category.IsNullOrWhiteSpace() && !groups.Any(v => v.GroupName == Settings.Category))
{
return new NzbDroneValidationFailure("Category", "Category does not exist")
{
DetailedDescription = "The category you entered doesn't exist in NzbVortex. Go to NzbVortex to create it."
};
}
return null;
}

View File

@@ -13,7 +13,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
{
public interface INzbVortexProxy
{
string DownloadNzb(byte[] nzbData, string filename, int priority, NzbVortexSettings settings);
string DownloadNzb(byte[] nzbData, string filename, int priority, NzbVortexSettings settings, string group);
void Remove(int id, bool deleteData, NzbVortexSettings settings);
NzbVortexVersionResponse GetVersion(NzbVortexSettings settings);
NzbVortexApiVersionResponse GetApiVersion(NzbVortexSettings settings);
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
_authSessionIdCache = cacheManager.GetCache<string>(GetType(), "authCache");
}
public string DownloadNzb(byte[] nzbData, string filename, int priority, NzbVortexSettings settings)
public string DownloadNzb(byte[] nzbData, string filename, int priority, NzbVortexSettings settings, string group)
{
var requestBuilder = BuildRequest(settings).Resource("nzb/add")
.Post()
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
if (settings.Category.IsNotNullOrWhiteSpace())
{
requestBuilder.AddQueryParam("groupname", settings.Category);
requestBuilder.AddQueryParam("groupname", group);
}
requestBuilder.AddFormUpload("name", filename, nzbData, "application/x-nzb");

View File

@@ -47,7 +47,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
[FieldDefinition(3, Label = "API Key", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(4, Label = "Group", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(4, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
public string Category { get; set; }
[FieldDefinition(5, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing items")]

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
protected override string AddFromNzbFile(ReleaseInfo release, string filename, byte[] fileContent)
{
var category = Settings.Category;
var category = GetCategoryForRelease(release) ?? Settings.Category;
var priority = Settings.Priority;
@@ -50,7 +50,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
protected override string AddFromLink(ReleaseInfo release)
{
var category = Settings.Category;
var category = GetCategoryForRelease(release) ?? Settings.Category;
var priority = Settings.Priority;
@@ -66,6 +66,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
}
public override string Name => "NZBGet";
public override bool SupportsCategories => true;
protected IEnumerable<NzbgetCategory> GetCategories(Dictionary<string, string> config)
{
@@ -139,6 +140,18 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
var config = _proxy.GetConfig(Settings);
var categories = GetCategories(config);
foreach (var category in Categories)
{
if (!category.ClientCategory.IsNullOrWhiteSpace() && !categories.Any(v => v.Name == category.ClientCategory))
{
return new NzbDroneValidationFailure(string.Empty, "Category does not exist")
{
InfoLink = _proxy.GetBaseUrl(Settings),
DetailedDescription = "A mapped category you entered doesn't exist in NZBGet. Go to NZBGet to create it."
};
}
}
if (!Settings.Category.IsNullOrWhiteSpace() && !categories.Any(v => v.Name == Settings.Category))
{
return new NzbDroneValidationFailure("Category", "Category does not exist")

View File

@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
public string Category { get; set; }
[FieldDefinition(7, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority for items added from Prowlarr")]

View File

@@ -23,6 +23,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
}
public override string Name => "Pneumatic";
public override bool SupportsCategories => false;
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;

View File

@@ -52,8 +52,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
//var addHasSetShareLimits = setShareLimits && ProxyApiVersion >= new Version(2, 8, 1);
var itemToTop = Settings.Priority == (int)QBittorrentPriority.First;
var forceStart = (QBittorrentState)Settings.InitialState == QBittorrentState.ForceStart;
var category = GetCategoryForRelease(release) ?? Settings.Category;
Proxy.AddTorrentFromUrl(magnetLink, null, Settings);
Proxy.AddTorrentFromUrl(magnetLink, null, Settings, category);
if (itemToTop || forceStart)
{
@@ -100,8 +101,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
//var addHasSetShareLimits = setShareLimits && ProxyApiVersion >= new Version(2, 8, 1);
var itemToTop = Settings.Priority == (int)QBittorrentPriority.First;
var forceStart = (QBittorrentState)Settings.InitialState == QBittorrentState.ForceStart;
var category = GetCategoryForRelease(release) ?? Settings.Category;
Proxy.AddTorrentFromFile(filename, fileContent, null, Settings);
Proxy.AddTorrentFromFile(filename, fileContent, null, Settings, category);
if (itemToTop || forceStart)
{
@@ -167,6 +169,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
}
public override string Name => "qBittorrent";
public override bool SupportsCategories => true;
protected override void Test(List<ValidationFailure> failures)
{
@@ -197,7 +200,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
else if (version < Version.Parse("1.6"))
{
// API version 6 introduced support for labels
if (Settings.Category.IsNotNullOrWhiteSpace())
if (Settings.Category.IsNotNullOrWhiteSpace() || Categories.Count > 0)
{
return new NzbDroneValidationFailure("Category", "Category is not supported")
{
@@ -205,15 +208,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
};
}
}
else if (Settings.Category.IsNullOrWhiteSpace())
{
// warn if labels are supported, but category is not provided
return new NzbDroneValidationFailure("Category", "Category is recommended")
{
IsWarning = true,
DetailedDescription = "Prowlarr will not attempt to import completed downloads without a category."
};
}
}
catch (DownloadClientAuthenticationException ex)
{
@@ -251,7 +245,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
private ValidationFailure TestCategory()
{
if (Settings.Category.IsNullOrWhiteSpace())
if (Settings.Category.IsNullOrWhiteSpace() && Categories.Count == 0)
{
return null;
}
@@ -265,6 +259,23 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
Dictionary<string, QBittorrentLabel> labels = Proxy.GetLabels(Settings);
foreach (var category in Categories)
{
if (category.ClientCategory.IsNotNullOrWhiteSpace() && !labels.ContainsKey(category.ClientCategory))
{
Proxy.AddLabel(category.ClientCategory, Settings);
labels = Proxy.GetLabels(Settings);
if (!labels.ContainsKey(category.ClientCategory))
{
return new NzbDroneValidationFailure(string.Empty, "Configuration of label failed")
{
DetailedDescription = "Prowlarr was unable to add the label to qBittorrent."
};
}
}
}
if (Settings.Category.IsNotNullOrWhiteSpace() && !labels.ContainsKey(Settings.Category))
{
Proxy.AddLabel(Settings.Category, Settings);

View File

@@ -18,8 +18,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
QBittorrentTorrentProperties GetTorrentProperties(string hash, QBittorrentSettings settings);
List<QBittorrentTorrentFile> GetTorrentFiles(string hash, QBittorrentSettings settings);
void AddTorrentFromUrl(string torrentUrl, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings);
void AddTorrentFromFile(string fileName, byte[] fileContent, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings);
void AddTorrentFromUrl(string torrentUrl, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings, string category);
void AddTorrentFromFile(string fileName, byte[] fileContent, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings, string category);
void RemoveTorrent(string hash, bool removeData, QBittorrentSettings settings);
void SetTorrentLabel(string hash, string label, QBittorrentSettings settings);

View File

@@ -113,15 +113,15 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
return response;
}
public void AddTorrentFromUrl(string torrentUrl, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings)
public void AddTorrentFromUrl(string torrentUrl, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings, string category)
{
var request = BuildRequest(settings).Resource("/command/download")
.Post()
.AddFormParameter("urls", torrentUrl);
if (settings.Category.IsNotNullOrWhiteSpace())
if (category.IsNotNullOrWhiteSpace())
{
request.AddFormParameter("category", settings.Category);
request.AddFormParameter("category", category);
}
// Note: ForceStart is handled by separate api call
@@ -143,15 +143,15 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
}
}
public void AddTorrentFromFile(string fileName, byte[] fileContent, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings)
public void AddTorrentFromFile(string fileName, byte[] fileContent, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings, string category)
{
var request = BuildRequest(settings).Resource("/command/upload")
.Post()
.AddFormUpload("torrents", fileName, fileContent);
if (settings.Category.IsNotNullOrWhiteSpace())
if (category.IsNotNullOrWhiteSpace())
{
request.AddFormParameter("category", settings.Category);
request.AddFormParameter("category", category);
}
// Note: ForceStart is handled by separate api call

View File

@@ -119,14 +119,14 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
return response;
}
public void AddTorrentFromUrl(string torrentUrl, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings)
public void AddTorrentFromUrl(string torrentUrl, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings, string category)
{
var request = BuildRequest(settings).Resource("/api/v2/torrents/add")
.Post()
.AddFormParameter("urls", torrentUrl);
if (settings.Category.IsNotNullOrWhiteSpace())
if (category.IsNotNullOrWhiteSpace())
{
request.AddFormParameter("category", settings.Category);
request.AddFormParameter("category", category);
}
// Note: ForceStart is handled by separate api call
@@ -153,15 +153,15 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
}
}
public void AddTorrentFromFile(string fileName, byte[] fileContent, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings)
public void AddTorrentFromFile(string fileName, byte[] fileContent, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings, string category)
{
var request = BuildRequest(settings).Resource("/api/v2/torrents/add")
.Post()
.AddFormUpload("torrents", fileName, fileContent);
if (settings.Category.IsNotNullOrWhiteSpace())
if (category.IsNotNullOrWhiteSpace())
{
request.AddFormParameter("category", settings.Category);
request.AddFormParameter("category", category);
}
// Note: ForceStart is handled by separate api call

View File

@@ -47,7 +47,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
public string Category { get; set; }
[FieldDefinition(7, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing items")]

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
protected override string AddFromNzbFile(ReleaseInfo release, string filename, byte[] fileContent)
{
var category = Settings.Category;
var category = GetCategoryForRelease(release) ?? Settings.Category;
var priority = Settings.Priority;
var response = _proxy.DownloadNzb(fileContent, filename, category, priority, Settings);
@@ -48,7 +48,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
protected override string AddFromLink(ReleaseInfo release)
{
var category = Settings.Category;
var category = GetCategoryForRelease(release) ?? Settings.Category;
var priority = Settings.Priority;
var response = _proxy.DownloadNzbByUrl(release.DownloadUrl, category, priority, Settings);
@@ -62,6 +62,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
}
public override string Name => "SABnzbd";
public override bool SupportsCategories => true;
protected IEnumerable<SabnzbdCategory> GetCategories(SabnzbdConfig config)
{
@@ -260,29 +261,27 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
private ValidationFailure TestCategory()
{
var config = _proxy.GetConfig(Settings);
var category = GetCategories(config).FirstOrDefault((SabnzbdCategory v) => v.Name == Settings.Category);
var categories = GetCategories(config);
if (category != null)
foreach (var category in Categories)
{
if (category.Dir.EndsWith("*"))
if (!category.ClientCategory.IsNullOrWhiteSpace() && !categories.Any(v => v.Name == category.ClientCategory))
{
return new NzbDroneValidationFailure("Category", "Enable Job folders")
return new NzbDroneValidationFailure(string.Empty, "Category does not exist")
{
InfoLink = _proxy.GetBaseUrl(Settings, "config/categories/"),
DetailedDescription = "Prowlarr prefers each download to have a separate folder. With * appended to the Folder/Path SABnzbd will not create these job folders. Go to SABnzbd to fix it."
InfoLink = _proxy.GetBaseUrl(Settings),
DetailedDescription = "A mapped category you entered doesn't exist in Sabnzbd. Go to Sabnzbd to create it."
};
}
}
else
if (!Settings.Category.IsNullOrWhiteSpace() && !categories.Any(v => v.Name == Settings.Category))
{
if (!Settings.Category.IsNullOrWhiteSpace())
return new NzbDroneValidationFailure("Category", "Category does not exist")
{
return new NzbDroneValidationFailure("Category", "Category does not exist")
{
InfoLink = _proxy.GetBaseUrl(Settings, "config/categories/"),
DetailedDescription = "The category you entered doesn't exist in SABnzbd. Go to SABnzbd to create it."
};
}
InfoLink = _proxy.GetBaseUrl(Settings),
DetailedDescription = "The category you entered doesn't exist in Sabnzbd. Go to Sabnzbd to create it."
};
}
if (config.Misc.enable_tv_sorting && ContainsCategory(config.Misc.tv_categories, Settings.Category))

View File

@@ -65,7 +65,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
[FieldDefinition(6, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(7, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(7, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
public string Category { get; set; }
[FieldDefinition(8, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing items")]

View File

@@ -38,5 +38,6 @@ namespace NzbDrone.Core.Download.Clients.Transmission
}
public override string Name => "Transmission";
public override bool SupportsCategories => false;
}
}

View File

@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
public string Category { get; set; }
[FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Transmission location")]

View File

@@ -58,5 +58,6 @@ namespace NzbDrone.Core.Download.Clients.Vuze
}
public override string Name => "Vuze";
public override bool SupportsCategories => false;
}
}

View File

@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
{
var priority = (RTorrentPriority)Settings.Priority;
_proxy.AddTorrentFromUrl(magnetLink, Settings.Category, priority, Settings.Directory, Settings);
_proxy.AddTorrentFromUrl(magnetLink, GetCategoryForRelease(release) ?? Settings.Category, priority, Settings.Directory, Settings);
var tries = 10;
var retryDelay = 500;
@@ -58,7 +58,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
{
var priority = (RTorrentPriority)Settings.Priority;
_proxy.AddTorrentFromFile(filename, fileContent, Settings.Category, priority, Settings.Directory, Settings);
_proxy.AddTorrentFromFile(filename, fileContent, GetCategoryForRelease(release) ?? Settings.Category, priority, Settings.Directory, Settings);
var tries = 10;
var retryDelay = 500;
@@ -73,6 +73,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
}
public override string Name => "rTorrent";
public override bool SupportsCategories => true;
public override ProviderMessage Message => new ProviderMessage("Prowlarr is unable to remove torrents that have finished seeding when using rTorrent", ProviderMessageType.Warning);

View File

@@ -48,7 +48,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional.")]
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional.")]
public string Category { get; set; }
[FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default rTorrent location")]

View File

@@ -38,9 +38,10 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
_proxy.AddTorrentFromUrl(magnetLink, Settings);
//_proxy.SetTorrentSeedingConfiguration(hash, release.SeedConfiguration, Settings);
if (Settings.Category.IsNotNullOrWhiteSpace())
var category = GetCategoryForRelease(release) ?? Settings.Category;
if (GetCategoryForRelease(release).IsNotNullOrWhiteSpace())
{
_proxy.SetTorrentLabel(hash, Settings.Category, Settings);
_proxy.SetTorrentLabel(hash, category, Settings);
}
if (Settings.Priority == (int)UTorrentPriority.First)
@@ -58,9 +59,10 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
_proxy.AddTorrentFromFile(filename, fileContent, Settings);
//_proxy.SetTorrentSeedingConfiguration(hash, release.SeedConfiguration, Settings);
if (Settings.Category.IsNotNullOrWhiteSpace())
var category = GetCategoryForRelease(release) ?? Settings.Category;
if (category.IsNotNullOrWhiteSpace())
{
_proxy.SetTorrentLabel(hash, Settings.Category, Settings);
_proxy.SetTorrentLabel(hash, category, Settings);
}
if (Settings.Priority == (int)UTorrentPriority.First)
@@ -74,40 +76,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
}
public override string Name => "uTorrent";
private List<UTorrentTorrent> GetTorrents()
{
List<UTorrentTorrent> torrents;
var cacheKey = string.Format("{0}:{1}:{2}", Settings.Host, Settings.Port, Settings.Category);
var cache = _torrentCache.Find(cacheKey);
var response = _proxy.GetTorrents(cache == null ? null : cache.CacheID, Settings);
if (cache != null && response.Torrents == null)
{
var removedAndUpdated = new HashSet<string>(response.TorrentsChanged.Select(v => v.Hash).Concat(response.TorrentsRemoved));
torrents = cache.Torrents
.Where(v => !removedAndUpdated.Contains(v.Hash))
.Concat(response.TorrentsChanged)
.ToList();
}
else
{
torrents = response.Torrents;
}
cache = new UTorrentTorrentCache
{
CacheID = response.CacheNumber,
Torrents = torrents
};
_torrentCache.Set(cacheKey, cache, TimeSpan.FromMinutes(15));
return torrents;
}
public override bool SupportsCategories => true;
protected override void Test(List<ValidationFailure> failures)
{

View File

@@ -46,7 +46,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
public string Category { get; set; }
[FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing items")]

View File

@@ -1,14 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
using Org.BouncyCastle.Crypto.Tls;
namespace NzbDrone.Core.Download
{
@@ -50,6 +53,9 @@ namespace NzbDrone.Core.Download
return GetType().Name;
}
protected List<DownloadClientCategory> Categories => ((DownloadClientDefinition)Definition).Categories;
public abstract bool SupportsCategories { get; }
public abstract DownloadProtocol Protocol
{
get;
@@ -57,12 +63,54 @@ namespace NzbDrone.Core.Download
public abstract Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer);
protected string GetCategoryForRelease(ReleaseInfo release)
{
var categories = ((DownloadClientDefinition)Definition).Categories;
if (categories.Count == 0)
{
return null;
}
// Check for direct mapping
var category = categories.FirstOrDefault(x => x.Categories.Intersect(release.Categories.Select(c => c.Id)).Any())?.ClientCategory;
// Check for parent mapping
if (category == null)
{
foreach (var cat in categories)
{
var mappedCat = NewznabStandardCategory.AllCats.Where(x => cat.Categories.Contains(x.Id));
var subCats = mappedCat.SelectMany(x => x.SubCategories);
if (subCats.Intersect(release.Categories).Any())
{
category = cat.ClientCategory;
break;
}
}
}
return category;
}
protected virtual void ValidateCategories(List<ValidationFailure> failures)
{
foreach (var category in ((DownloadClientDefinition)Definition).Categories)
{
if (category.ClientCategory.IsNullOrWhiteSpace())
{
failures.AddIfNotNull(new ValidationFailure(string.Empty, "Category can not be empty"));
}
}
}
public ValidationResult Test()
{
var failures = new List<ValidationFailure>();
try
{
ValidateCategories(failures);
Test(failures);
}
catch (Exception ex)
@@ -97,5 +145,17 @@ namespace NzbDrone.Core.Download
return null;
}
private bool HasConcreteImplementation(string methodName)
{
var method = GetType().GetMethod(methodName);
if (method == null)
{
throw new MissingMethodException(GetType().Name, Name);
}
return !method.DeclaringType.IsAbstract;
}
}
}

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