Compare commits

...

110 Commits

Author SHA1 Message Date
Qstick
e43c650d45 Fixed: Persist Cookies from test on Indexer creation to avoid re-auth 2021-06-23 22:36:31 -04:00
Qstick
c6b6daaf80 Fixed: (Cardigann) DoLogin if search returns HttpError
Fixes #271
2021-06-23 22:05:28 -04:00
bakerboy448
3b42b6a7e0 Fixed: (BHD) Treat Limited Freeleech as Freeleech 2021-06-23 19:05:48 -05:00
Qstick
b6238f469c Fixed: (Orpheus) Use default Gazelle download links 2021-06-23 12:13:29 -04:00
Qstick
ae00c3aa6b Fixed: Cardigann redirect message 2021-06-23 12:13:29 -04:00
bakerboy448
8bf9d1b016 fix port [skip ci] 2021-06-23 01:55:57 -05:00
Qstick
19ed7aa804 Fixed: QueryString parsing for Cardigann failing 2021-06-22 21:12:04 -04:00
Qstick
4d129ada95 Fixed: (Gazelle) Not using freeleech token correctly
Fixes #258
2021-06-22 21:04:23 -04:00
bakerboy448
4ec8ea0e4d fixup bug report to note hidden text [skip ci] 2021-06-22 17:25:52 -05:00
bakerboy448
bd79d3c828 Fixed: Cardigann Indexer Descriptions & Indexer InfoLinks 2021-06-21 23:47:31 -05:00
Qstick
7fb6c539d4 Fixed: Require PID for AvistaZ indexers 2021-06-21 23:18:53 -04:00
Qstick
7f6fa2efbe Fixed: Newznab trying to test caps when disabled
Fixes #226
2021-06-21 23:07:05 -04:00
Qstick
b1727d9d91 Fixed: Set Download/Upload factors for Rarbg
Fixes #234
2021-06-21 23:07:05 -04:00
Qstick
3435d9db6e New: Allow users to use custom ymls 2021-06-21 22:45:09 -04:00
Qstick
8e597c8179 Fixed: Allow redirect and set referer for Anthelion 2021-06-21 21:23:38 -04:00
Qstick
80ec66514e change wiki links from wikjs to wiki endpoint 2021-06-21 21:13:08 -04:00
Weblate
88e677d973 Translated using Weblate (German)
Currently translated at 92.4% (393 of 425 strings)

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

Currently translated at 65.6% (279 of 425 strings)

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

Currently translated at 65.6% (279 of 425 strings)

Translated using Weblate (French)

Currently translated at 100.0% (425 of 425 strings)

Added translation using Weblate (Chinese (Simplified))

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: foXaCe <foxace66@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: vinson512 <vinson512@hotmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2021-06-21 19:56:22 -04:00
bakerboy448
a00f32c508 read me updates [skip ci] 2021-06-21 19:55:46 -04:00
bakerboy448
538db52d16 Fixed: Correct Password and Username field Privacy
Fixed: Consistent help text and settings for Credential Fields
2021-06-21 11:57:55 -05:00
Qstick
1ce7b0e56e New: (Indexer) Newznab preset for NZBNDX api 2021-06-20 17:47:42 -04:00
Yukine
87d91a0f15 Fixed: Support both old and new UNIT3D Id params (#244) 2021-06-19 19:47:08 -04:00
Qstick
61c1e934a5 New: Handle Freeleech flag at IndexerBase
This sets freeleech flag automatically for torrent releases with DownloadVolumeFactor = 0
2021-06-19 17:21:24 -04:00
Qstick
97b09335df fixup Anthelion category parse 2021-06-19 16:42:07 -04:00
Qstick
5e34fd2a9f New: (Indexer) Anthelion 2021-06-19 16:10:20 -04:00
Qstick
7e620bd156 New: (Indexer) ShowRss 2021-06-19 15:56:31 -04:00
Qstick
a97b801b24 New: (Indexer) GazelleGames 2021-06-19 15:48:28 -04:00
bakerboy448
9e64acd407 fix supported wikijs link 2021-06-19 10:36:33 -05:00
Yukine
f72269f91b fix(SpeedApp): correct status code check 2021-06-18 20:00:28 -04:00
Yukine
94d7f768a1 feat(Indexer): add SpeedApp C# indexer 2021-06-18 20:00:28 -04:00
bakerboy448
78cdc78cf9 indexer request method changes [skip ci] 2021-06-18 08:31:15 -05:00
Weblate
2fc1257f42 Translated using Weblate (Chinese (Simplified) (zh_CN))
Currently translated at 51.5% (219 of 425 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.8% (416 of 425 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.8% (416 of 425 strings)

Translated using Weblate (Portuguese)

Currently translated at 82.3% (350 of 425 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (425 of 425 strings)

Translated using Weblate (French)

Currently translated at 98.3% (418 of 425 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: flowcool <a121313b@opayq.com>
Co-authored-by: vinson512 <vinson512@hotmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
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/
Translation: Servarr/Prowlarr
2021-06-17 22:09:01 -04:00
bakerboy448
210311cb38 last fixup... [skip ci] 2021-06-16 13:49:12 -05:00
bakerboy448
9d7ec89314 fixups... [skip ci] 2021-06-16 13:48:25 -05:00
bakerboy448
9c279701a6 update indexer template - no links [skip ci] 2021-06-16 13:46:41 -05:00
bakerboy448
743e2e9b21 Update indexer_request.md
add link to GHI
2021-06-16 12:55:14 -05:00
bakerboy448
4a8daea940 Update indexer_request.md
add link
2021-06-16 12:53:46 -05:00
Qstick
bd90b74c12 Attempt to fix ZonaQ Auth 2021-06-16 00:58:28 -04:00
Qstick
4dff0c075a Log cleanse for passwrd form param 2021-06-16 00:43:54 -04:00
Qstick
b8f57507dd Update README.md 2021-06-16 00:33:24 -04:00
Qstick
61bfa9e7ed New: Better Application Tests 2021-06-16 00:20:42 -04:00
Qstick
bbea256c85 Fixed: Dynamic page size for Cardigann search path paging
Fixes some indexers that have arbitrary paging paths in their search-paths definitions. Set page size based on first request, break out if a request down the line returns less results.
2021-06-15 23:53:52 -04:00
Qstick
5a1186639e Fixed: Cookie set for Cardigann cookie indexers 2021-06-15 23:51:33 -04:00
TheCatLady
a8f2700fe6 Sort average response times 2021-06-15 21:14:11 -04:00
Yukine
eeec505182 Fixed: Remove Camel Case DB Converter for Cookies (#223)
* fix(ProviderRepository): remove DictionaryKeyPolicy CamelCase Naming

* Revert "fix(ProviderRepository): remove DictionaryKeyPolicy CamelCase Naming"

This reverts commit 90c95b240f.

* fix(TableMapping): make own non Camel Case Converter for Cookies
2021-06-15 21:13:37 -04:00
bakerboy448
66dc53b92f Fixed: Remove Defunct Indexer - Usenet - NZBs2GO (#216)
Fixes #214
2021-06-14 15:12:57 -05:00
Qstick
334f3514df Cleanup conversion script issues 2021-06-13 18:26:32 -04:00
Qstick
6ce35f6a24 Fix Nebulance Settings copy paste issues 2021-06-13 17:49:15 -04:00
Qstick
f6a5f887ce New: Convert Shareisland to use API 2021-06-13 01:40:07 -04:00
Qstick
bc90415394 New: (Indexer) Nebulance 2021-06-13 01:10:46 -04:00
Qstick
a8fdd46cd3 Update NewznabController.cs 2021-06-12 19:41:40 -04:00
Qstick
b6a08bdd9e Fixed: Accept ImdbIds in string format on Newznab endpoint 2021-06-12 18:02:10 -04:00
Qstick
59df0351ac New: (Indexer) YTS 2021-06-12 17:11:36 -04:00
Qstick
0fd242cd62 Additional help text for Download Clients 2021-06-12 16:45:19 -04:00
Qstick
ed7c5a937f Fixed: Correctly handle 302 and 303 redirects in HttpClient
Fixes #204
2021-06-12 16:24:48 -04:00
ntldr0
f6906d0f18 Fixed: (Cardigann) fix checkbox configuration (#169)
In ApplyGoTemplateText(), boolean values are resolved by using IsNullOrWhiteSpace().  Since ".False" is neither null or whitespace, ".False" always resolves to true.
2021-06-12 16:23:53 -04:00
Qstick
561563b48c Fixed: (Avistaz) Empty query failing with error
Fixes #192
2021-06-12 15:17:43 -04:00
Servarr
e383036c84 Translations update from Weblate (#148)
* Translated using Weblate (French)

Currently translated at 100.0% (373 of 373 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/

* Translated using Weblate (French)

Currently translated at 100.0% (373 of 373 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/

* Translated using Weblate (German)

Currently translated at 98.9% (369 of 373 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/

* Translated using Weblate (French)

Currently translated at 100.0% (373 of 373 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/

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

Currently translated at 5.0% (19 of 373 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/

* Translated using Weblate (German)

Currently translated at 100.0% (384 of 384 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/

* Translated using Weblate (German)

Currently translated at 100.0% (384 of 384 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/

* Translated using Weblate (German)

Currently translated at 100.0% (384 of 384 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/

* Translated using Weblate (French)

Currently translated at 100.0% (384 of 384 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (384 of 384 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/

* Translated using Weblate (Portuguese)

Currently translated at 75.5% (290 of 384 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (384 of 384 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (417 of 417 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (417 of 417 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/

* Translated using Weblate (Italian)

Currently translated at 81.7% (341 of 417 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/

* Translated using Weblate (Dutch)

Currently translated at 84.1% (351 of 417 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/

* Translated using Weblate (French)

Currently translated at 94.0% (392 of 417 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (417 of 417 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (417 of 417 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/

* Translated using Weblate (Portuguese)

Currently translated at 76.2% (318 of 417 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/

* Translated using Weblate (Portuguese)

Currently translated at 76.2% (318 of 417 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/

* Translated using Weblate (Spanish)

Currently translated at 81.7% (341 of 417 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/

* Translated using Weblate (German)

Currently translated at 94.0% (392 of 417 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/

* Translated using Weblate (German)

Currently translated at 94.0% (392 of 417 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/

* Translated using Weblate (French)

Currently translated at 100.0% (417 of 417 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/

* Translated using Weblate (Portuguese)

Currently translated at 76.2% (318 of 417 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (417 of 417 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/

Co-authored-by: foXaCe <foxace66@gmail.com>
Co-authored-by: Hummingbirdy The Second <hummingbirdy@pm.me>
Co-authored-by: doob187 <amderkum@gmail.com>
Co-authored-by: muihiuwev <muihiuwev@outlook.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
2021-06-12 14:52:55 -04:00
Zippy79
202f439a60 Fixed: Newznab additional parameters (#189) 2021-06-12 14:50:34 -04:00
nitsua
449e60afc0 Fix broken health check level so it is not always "Type" for Notifiarr connect (#202) 2021-06-12 14:49:44 -04:00
Qstick
9675171aff Fixed: (Cardigann) Don't die if no categories can be mapped for a release
Fixes #105
2021-06-12 02:17:17 -04:00
Qstick
bcee5f1754 Fixed: (Torznab) Categories not parsed correctly
Fixes #181
2021-06-12 01:53:22 -04:00
Qstick
d1a3e61979 Fixed: Many torrent indexers use full ImdbId instead of int version
Fixes #192
2021-06-12 01:37:01 -04:00
Qstick
b4a0c272c9 New: (indexer) Newz69
Fixes #198
2021-06-11 23:50:56 -04:00
nitsua
75cbabf716 Add detailed help text for sync levels (#155)
Adjust donation buttons to fit on mobile
Other translation updates
2021-06-11 15:42:32 -04:00
bakerboy448
6612202384 Fixed: App not syncing correct if only Interactive Search enabled (#197)
Fixes #196
2021-06-11 15:42:00 -04:00
HDVinnie
6b52dd6e7a (Indexers) Add Support for Aither via UNIT3D API (#176)
* (Indexers) Add Support for Aither via UNIT3D API

- Added support for Aither via Unit3d API

- Updated IndexerDefinitionUpdateService.cs

* fix: aither definition

* fix: aither definition

* fix: line ending

* Tmp vinnie aithia (#1)

* (Indexers) Add Support for Aither via UNIT3D API

- Added support for Aither via Unit3d API

* fix: aither definition

Signed-off-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>

* !fixup fix: aither definition

Co-authored-by: HDVinnie <hdinnovations@protonmail.com>

* update: Aither.cs

- add language and description

Co-authored-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2021-06-10 17:07:29 -05:00
Robin Dadswell
974ab6387f Fixed: Website Links going to api not site 2021-06-09 15:48:51 -05:00
ta264
9ac435bc41 Fixed: Cardigann redirections after login 2021-06-08 18:21:17 -04:00
ta264
b0f04bb9d7 Fixed: Bad login redirect using a reverse proxy 2021-06-08 14:03:57 +01:00
Lagicrus
52bff3d7bd Fixed: BarChart to have a stepSize multiplier of 1 instead of auto (#163)
* Fixes the BarChart to have a stepSize multiplier of 1 instead of auto

* Moves the size to props as per Discord

* Adds the same behavior to StackedBarChart's
2021-06-08 05:56:25 -04:00
Lagicrus
d965cb3c98 New: Sort 'Add Indexer' by Language (#130)
* New: Sort 'Add Indexer' by Language

* Adds language to propTypes

Updates propTypes to appease lint

* Call translate for labels
2021-06-08 05:20:26 -04:00
bakerboy448
b592a137cf Fixed: Cleanse Token from logs 2021-06-08 03:44:24 -04:00
Lagicrus
b0819c97ed Fixed: Adds a fix to support 3 digit issues (#157)
* Adds a fix to support 3 digit issues

* Fixes mistype with bug fix
2021-06-08 03:43:07 -04:00
Pfuenzle
5802d20b93 (Indexers) Add Newz-Complex 2021-06-07 19:11:16 -04:00
bakerboy448
58e30cc9a6 Update indexer_request.md 2021-06-07 17:36:35 -05:00
bakerboy448
513b9d2324 Fixed: Add missing Translate Keys (#152) 2021-06-07 15:15:11 -05:00
bakerboy448
3ebdc1c1ac Fixed: Develop branch out of date warning
Fixed: Develop branch invalid warning
2021-06-07 14:54:00 -04:00
bakerboy448
e9a5c5f7d2 Fixed: Missing Translate Keys for Redirect & Help Text (#143) 2021-06-07 10:42:01 -05:00
bakerboy448
23c01f7dbe fixup wiki link [skip ci] 2021-06-07 09:41:50 -05:00
bakerboy448
c35864cc7f update contributing [skip ci] 2021-06-07 09:41:28 -05:00
Pfuenzle
e545e0a129 (Indexers) Add Support for Unit3d API / Convert AnimeWorld (#132)
* (Indexers) Add Support for Unit3d API / Convert AnimeWorld

Added support for the Unit3d API for the tracker AnimeWorld.
Tested all the categories, they all returned the correct results

* Update IndexerDefinitionUpdateService.cs

* Added description and language
2021-06-07 09:33:07 -04:00
bakerboy448
1e72944998 Fixed: MaM Helptext clarified
#125
2021-06-06 23:23:31 -04:00
bakerboy448
d234cbda8a Fixed: Cleanse Username prefixed with ': ' from logs
- remove unused var in log cleansing code
2021-06-06 23:19:16 -04:00
Lizandra Candido da Silva
09a9731dae Translated using Weblate (Portuguese)
Currently translated at 88.2% (329 of 373 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
2021-06-06 22:58:14 -04:00
doob187
3b4df1706e Translated using Weblate (German)
Currently translated at 98.6% (368 of 373 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
2021-06-06 22:58:14 -04:00
Lizandra Candido da Silva
20038e4757 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (373 of 373 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
2021-06-06 22:58:14 -04:00
Csaba
c57c77d8e7 Translated using Weblate (Hungarian)
Currently translated at 100.0% (373 of 373 strings)

Translation: Servarr/Prowlarr
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
2021-06-06 22:58:14 -04:00
Qstick
c61c3a9c45 Fixed: (IPTorrent) Cookie not used on grabs 2021-06-06 22:46:38 -04:00
Qstick
d6d418f7b3 New: (IPTorrent) Books/Non-English Category 2021-06-06 22:43:16 -04:00
Qstick
ab5cf45d88 Fixed: Allow Blank Search
#118
2021-06-06 22:37:27 -04:00
Qstick
40d55b915e Fixed: History PropTypes warning 2021-06-06 22:35:15 -04:00
Qstick
4a851c37d5 New: AutoFocus search and Search on 'Enter'
Fixes #113
2021-06-06 22:16:39 -04:00
Qstick
63b6adf0e1 Fixed: Empty should redirect to Base 2021-06-06 21:20:04 -04:00
Qstick
d9e211472b Fixed: Cardigann redirect handling fails for relative location headers 2021-06-06 20:54:46 -04:00
Qstick
fa05dbc642 Fixed: (Rarbg) Tv search doesn't use Season and Ep 2021-06-06 17:54:57 -04:00
bakerboy448
709dc0ee5f fix template for length [skip ci] 2021-06-06 16:12:46 -05:00
bakerboy448
67f26fe185 Fixed: Updates to System > More Info (#67) 2021-06-06 16:11:06 -05:00
bakerboy448
5f4218ae91 add indexer request template [skip ci] (#116) 2021-06-06 16:10:51 -05:00
Qstick
30b54d8340 Fix Filelist unit tests 2021-06-06 13:33:51 -04:00
Qstick
ee6ae386ca Fixed: (Filelist) Unable to add
Fixes #109
2021-06-06 11:48:52 -04:00
Robin Dadswell
b3fb640969 New: App Profile help text on addition and edit of Indexers and other misc translations 2021-06-05 21:58:53 +01:00
Qstick
8b0a8e82b5 Wikijs Links 2021-06-05 16:37:49 -04:00
Robin Dadswell
f25998959e New: Added all Arr donation links 2021-06-05 21:15:06 +01:00
Qstick
f56ce129e6 Fixed: Push to client for Cardigann magnet links 2021-06-05 14:55:43 -04:00
Qstick
ae9930a03f Update ZonaQ.cs 2021-06-05 10:20:54 -04:00
Qstick
85be0be455 Fixed: Push Downloads to client fails for download overrides 2021-06-05 09:32:40 -04:00
Qstick
cf1c44ed75 Fixed: Normalize definitions when serving local and remote 2021-06-04 19:27:50 -04:00
Robin Dadswell
f062fafe82 Fixed: Error when trying to parse the value 'Unknown' as an IP Address 2021-06-05 00:18:12 +01:00
Qstick
1032d8b3ab Fixed: Slash on ProwlarrURL causes App failures 2021-06-04 19:13:01 -04:00
Qstick
99c4ed7dbc Fixed: Host not set for UI Search 2021-06-03 19:40:53 -04:00
199 changed files with 5058 additions and 902 deletions

View File

@@ -7,6 +7,7 @@ assignees: ''
---
<!-- Support Requests will be closed immediately, if you are unsure go to our Reddit or Discord first. Exceptions do not mean you found a bug! -->
<!-- Note: Text between <!- and -> will be hidden -->
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
@@ -33,4 +34,4 @@ assignees: ''
Turn on Trace logs under Settings -> General and wait for the bug to occur again.
**Upload the full log file here (or another site (e.g. pastebin) and link it). Issues will be closed, if they do not include this!**
<!-- Trace logs are named Prowlarr.trace.txt or Prowlarr.trace.#.txt and will contain "trace" in them-->
<!-- Trace logs are named Prowlarr.trace.txt or Prowlarr.trace.#.txt and will contain "trace" in them-->

View File

@@ -1,5 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Indexer Requests
url: https://requests.prowlarr.com/
about: Request new indexers to be added. Vote on existing requests.
- name: Support via Discord
url: https://prowlarr.com/discord
about: Chat with users and devs on support and setup related topics.

View File

@@ -3,15 +3,31 @@
We're always looking for people to help make Prowlarr even better, there are a number of ways to contribute.
## Documentation ##
Setup guides, FAQ, the more information we have on the wiki the better.
Setup guides, FAQ, the more information we have on the [wiki](https://wiki.servarr.com/prowlarr) the better.
## Development ##
See the readme for information on setting up your development environment.
### Tools required ###
- Visual Studio 2019 or higher (https://www.visualstudio.com/vs/). The community version is free and works (https://www.visualstudio.com/downloads/).
- HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc)
- [Git](https://git-scm.com/downloads)
- [NodeJS](https://nodejs.org/en/download/) (Node 12.X.X or higher)
- [Yarn](https://yarnpkg.com/)
- .NET Core 5.0.
### Getting started ###
1. Fork Prowlarr
2. Clone the repository into your development machine. [*info*](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository-from-github)
3. Install the required Node Packages `yarn install`
4. Start gulp to monitor your dev environment for any changes that need post processing using `yarn start` command.
5. Build the project in Visual Studio, Setting startup project to `Prowlarr.Console` and framework to `net5.0`
6. Debug the project in Visual Studio
7. Open http://localhost:9696
### Contributing Code ###
- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Prowlarr/Prowlarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first)
- Rebase from Prowlarr's develop branch, don't merge
- Rebase from Radarr's develop branch, don't merge
- Make meaningful commits, or squash them
- Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements
- Reach out to us on the discord if you have any questions
@@ -20,6 +36,10 @@ See the readme for information on setting up your development environment.
- One feature/bug fix per pull request to keep things clean and easy to understand
- Use 4 spaces instead of tabs, this is the default for VS 2019 and WebStorm (to my knowledge)
### Contributing Indexers ###
- If you're contributing an indexer please phrase your commit as something like: `New: (Indexer) {Indexer Name}`, `New: (Indexer) {Usenet|Torrent} {Indexer Name}`, `New: (Indexer) {Torznab|Newznab} {Indexer Name}`
- If you're updating an indexer please phrase your commit as something like: `Fixed: (Indexer) {Indexer Name} {changes}` e.g. `Fixed: (Indexer) Changed BHD to use API`
### Pull Requesting ###
- Only make pull requests to develop, never master, if you make a PR to master we'll comment on it and close it
- You're probably going to get some comments or questions from us, they will be to ensure consistency and maintainability

View File

@@ -2,7 +2,7 @@
[![Build Status](https://dev.azure.com/Prowlarr/Prowlarr/_apis/build/status/Prowlarr.Prowlarr?branchName=develop)](https://dev.azure.com/Prowlarr/Prowlarr/_build/latest?definitionId=1&branchName=develop)
[![Translated](https://translate.servarr.com/widgets/servarr/-/prowlarr/svg-badge.svg)](https://translate.servarr.com/engage/prowlarr/?utm_source=widget)
[![Docker Pulls](https://img.shields.io/docker/pulls/hotio/prowlarr.svg)](https://wiki.servarr.com/Prowlarr_Installation#Docker)
[![Docker Pulls](https://img.shields.io/docker/pulls/hotio/prowlarr.svg)](https://wiki.servarr.com/prowlarr/installation#docker)
![Github Downloads](https://img.shields.io/github/downloads/Prowlarr/Prowlarr/total.svg)
[![Backers on Open Collective](https://opencollective.com/Prowlarr/backers/badge.svg)](#backers)
[![Sponsors on Open Collective](https://opencollective.com/Prowlarr/sponsors/badge.svg)](#sponsors)
@@ -10,12 +10,14 @@
Prowlarr is a indexer manager/proxy built on the popular arr .net/reactjs base stack to integrate with your various PVR apps. Prowlarr supports both Torrent Trackers and Usenet Indexers. It integrates seamlessly with Sonarr, Radarr, Lidarr, and Readarr offering complete management of your indexers with no per app Indexer setup required (we do it all).
## Major Features Include:
- Usenet support for any Newznab compatible indexer, including Headphones VIP
- Torrent support 400+ trackers & more coming soon
- Usenet support for 24 indexers natively, including Headphones VIP, and support for any Newznab compatible indexer via "Generic Newznab"
- Torrent support for almost 500 trackers & more coming soon
- Torrent support for any Torznab compatible tracker via "Generic Torznab"
- Indexer Sync to Sonarr/Radarr/Readarr/Lidarr, so no manual configuration of the other applications are required
- Indexer History and Statistics
- Manual Searching of Trackers & Indexers at a category level
- Support for pushing releases directly to your download clients from Prowlarr
- Indexer health and status notifications
## Support
Note: Prowlarr is currently early in life, thus bugs should be expected
@@ -23,7 +25,14 @@ Note: Prowlarr is currently early in life, thus bugs should be expected
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://prowlarr.com/discord)
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60)](https://www.reddit.com/r/Prowlarr)
[![GitHub - Bugs and Feature Requests Only](https://img.shields.io/badge/github-issues-red.svg?maxAge=60)](https://github.com/Prowlarr/Prowlarr/issues)
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](https://wiki.servarr.com/Prowlarr)
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](https://wiki.servarr.com/prowlarr)
## Indexers/Trackers
[Supported Indexers](https://wiki.servarr.com/en/prowlarr/supported-indexers)
[Indexer Requests](https://requests.prowlarr.com)
- Request or vote on an existing request for a new tracker/indexer
## Feature Requests

View File

@@ -23,6 +23,16 @@ class BarChart extends Component {
this.myChart = new Chart(this.canvasRef.current, {
type: 'bar',
options: {
x: {
ticks: {
stepSize: this.props.stepSize
}
},
y: {
ticks: {
stepSize: this.props.stepSize
}
},
indexAxis: this.props.horizontal ? 'y' : 'x',
maintainAspectRatio: false,
plugins: {
@@ -64,7 +74,8 @@ BarChart.propTypes = {
horizontal: PropTypes.bool,
legend: PropTypes.bool,
title: PropTypes.string.isRequired,
kind: PropTypes.oneOf(kinds.all).isRequired
kind: PropTypes.oneOf(kinds.all).isRequired,
stepSize: PropTypes.number
};
BarChart.defaultProps = {
@@ -72,7 +83,8 @@ BarChart.defaultProps = {
horizontal: false,
legend: false,
title: '',
kind: kinds.INFO
kind: kinds.INFO,
stepSize: 1
};
export default BarChart;

View File

@@ -16,10 +16,16 @@ class StackedBarChart extends Component {
maintainAspectRatio: false,
scales: {
x: {
stacked: true
stacked: true,
ticks: {
stepSize: this.props.stepSize
}
},
y: {
stacked: true
stacked: true,
ticks: {
stepSize: this.props.stepSize
}
}
},
plugins: {
@@ -63,11 +69,13 @@ class StackedBarChart extends Component {
StackedBarChart.propTypes = {
data: PropTypes.object.isRequired,
title: PropTypes.string.isRequired
title: PropTypes.string.isRequired,
stepSize: PropTypes.number
};
StackedBarChart.defaultProps = {
title: ''
title: '',
stepSize: 1
};
export default StackedBarChart;

View File

@@ -129,7 +129,7 @@ class FileBrowserModalContent extends Component {
className={styles.mappedDrivesWarning}
kind={kinds.WARNING}
>
<Link to="https://wiki.servarr.com/Prowlarr_FAQ#Why_cant_Prowlarr_see_my_files_on_a_remote_server">
<Link to="https://wiki.servarr.com/prowlarr/faq#why-cant-prowlarr-see-my-files-on-a-remote-server">
{translate('MappedDrivesRunningAsService')}
</Link>
</Alert>

View File

@@ -25,7 +25,7 @@ function FormInputHelpText(props) {
isCheckInput && styles.isCheckInput
)}
>
{text}
<div dangerouslySetInnerHTML={{ __html: text }} />
{
link ?

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -73,7 +73,7 @@ class HistoryOptions extends Component {
}
HistoryOptions.propTypes = {
historyCleanupDays: PropTypes.bool.isRequired,
historyCleanupDays: PropTypes.number.isRequired,
dispatchSaveGeneralSettings: PropTypes.func.isRequired
};

View File

@@ -20,19 +20,25 @@ import styles from './AddIndexerModalContent.css';
const columns = [
{
name: 'protocol',
label: 'Protocol',
label: translate('Protocol'),
isSortable: true,
isVisible: true
},
{
name: 'name',
label: 'Name',
label: translate('Name'),
isSortable: true,
isVisible: true
},
{
name: 'language',
label: translate('Language'),
isSortable: true,
isVisible: true
},
{
name: 'privacy',
label: 'Privacy',
label: translate('Privacy'),
isSortable: true,
isVisible: true
}

View File

@@ -26,7 +26,8 @@ class SelectIndexerRow extends Component {
const {
protocol,
privacy,
name
name,
language
} = this.props;
return (
@@ -41,6 +42,10 @@ class SelectIndexerRow extends Component {
{name}
</TableRowCell>
<TableRowCell>
{language}
</TableRowCell>
<TableRowCell>
{privacy}
</TableRowCell>
@@ -53,6 +58,7 @@ SelectIndexerRow.propTypes = {
name: PropTypes.string.isRequired,
protocol: PropTypes.string.isRequired,
privacy: PropTypes.string.isRequired,
language: PropTypes.string.isRequired,
implementation: PropTypes.string.isRequired,
onIndexerSelect: PropTypes.func.isRequired
};

View File

@@ -99,7 +99,7 @@ function EditIndexerModalContent(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="redirect"
helpText={'Redirect incoming download requests for indexer instead of Proxying using Prowlarr'}
helpText={translate('RedirectHelpText')}
isDisabled={!supportsRedirect.value}
{...redirect}
onChange={onInputChange}
@@ -113,6 +113,7 @@ function EditIndexerModalContent(props) {
type={inputTypes.APP_PROFILE_SELECT}
name="appProfileId"
{...appProfileId}
helpText={translate('AppProfileSelectHelpText')}
onChange={onInputChange}
/>
</FormGroup>

View File

@@ -50,7 +50,7 @@ class TagsModalContent extends Component {
render() {
const {
movieTags,
indexerTags,
tagList,
onModalClose
} = this.props;
@@ -108,7 +108,7 @@ class TagsModalContent extends Component {
<div className={styles.result}>
{
movieTags.map((t) => {
indexerTags.map((t) => {
const tag = _.find(tagList, { id: t });
if (!tag) {
@@ -140,7 +140,7 @@ class TagsModalContent extends Component {
return null;
}
if (movieTags.indexOf(t) > -1) {
if (indexerTags.indexOf(t) > -1) {
return null;
}
@@ -179,7 +179,7 @@ class TagsModalContent extends Component {
}
TagsModalContent.propTypes = {
movieTags: PropTypes.arrayOf(PropTypes.number).isRequired,
indexerTags: PropTypes.arrayOf(PropTypes.number).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onModalClose: PropTypes.func.isRequired,
onApplyTagsPress: PropTypes.func.isRequired

View File

@@ -10,15 +10,15 @@ function createMapStateToProps() {
(state, { indexerIds }) => indexerIds,
createAllIndexersSelector(),
createTagsSelector(),
(indexerIds, allMovies, tagList) => {
const movies = _.intersectionWith(allMovies, indexerIds, (s, id) => {
(indexerIds, allIndexers, tagList) => {
const indexers = _.intersectionWith(allIndexers, indexerIds, (s, id) => {
return s.id === id;
});
const movieTags = _.uniq(_.concat(..._.map(movies, 'tags')));
const indexerTags = _.uniq(_.concat(..._.map(indexers, 'tags')));
return {
movieTags,
indexerTags,
tagList
};
}

View File

@@ -4,7 +4,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import withScrollPosition from 'Components/withScrollPosition';
import { testAllIndexers } from 'Store/Actions/indexerActions';
import { saveMovieEditor, setMovieFilter, setMovieSort, setMovieTableOption } from 'Store/Actions/indexerIndexActions';
import { saveIndexerEditor, setMovieFilter, setMovieSort, setMovieTableOption } from 'Store/Actions/indexerIndexActions';
import scrollPositions from 'Store/scrollPositions';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createIndexerClientSideCollectionItemsSelector from 'Store/Selectors/createIndexerClientSideCollectionItemsSelector';
@@ -40,8 +40,8 @@ function createMapDispatchToProps(dispatch, props) {
dispatch(setMovieFilter({ selectedFilterKey }));
},
dispatchSaveMovieEditor(payload) {
dispatch(saveMovieEditor(payload));
dispatchSaveIndexerEditor(payload) {
dispatch(saveIndexerEditor(payload));
},
onTestAllPress() {
@@ -56,7 +56,7 @@ class IndexerIndexConnector extends Component {
// Listeners
onSaveSelected = (payload) => {
this.props.dispatchSaveMovieEditor(payload);
this.props.dispatchSaveIndexerEditor(payload);
}
onScroll = ({ scrollTop }) => {
@@ -79,7 +79,7 @@ class IndexerIndexConnector extends Component {
IndexerIndexConnector.propTypes = {
isSmallScreen: PropTypes.bool.isRequired,
dispatchSaveMovieEditor: PropTypes.func.isRequired,
dispatchSaveIndexerEditor: PropTypes.func.isRequired,
items: PropTypes.arrayOf(PropTypes.object)
};

View File

@@ -248,7 +248,7 @@ class IndexerIndexRow extends Component {
className={styles.externalLink}
name={icons.EXTERNAL_LINK}
title={'Website'}
to={baseUrl}
to={baseUrl.replace('api.', '')}
/>
<IconButton

View File

@@ -19,6 +19,10 @@ function getAverageResponseTimeData(indexerStats) {
};
});
data.sort((a, b) => {
return b.value - a.value;
});
return data;
}

View File

@@ -4,6 +4,7 @@ import React, { Component } from 'react';
import IndexersSelectInputConnector from 'Components/Form/IndexersSelectInputConnector';
import NewznabCategorySelectInputConnector from 'Components/Form/NewznabCategorySelectInputConnector';
import TextInput from 'Components/Form/TextInput';
import keyboardShortcuts from 'Components/keyboardShortcuts';
import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter';
import SearchFooterLabel from './SearchFooterLabel';
@@ -38,9 +39,11 @@ class SearchFooter extends Component {
searchQuery
} = this.state;
if (searchQuery !== '' || searchCategories !== [] || searchIndexerIds !== []) {
if (searchQuery !== '' || searchCategories.length > 0 || searchIndexerIds.length > 0) {
this.onSearchPress();
}
this.props.bindShortcut('enter', this.onSearchPress, { isGlobal: true });
}
componentDidUpdate(prevProps) {
@@ -114,6 +117,7 @@ class SearchFooter extends Component {
<TextInput
name='searchQuery'
autoFocus={true}
value={searchQuery}
isDisabled={isFetching}
onChange={onInputChange}
@@ -181,7 +185,8 @@ SearchFooter.propTypes = {
onSearchPress: PropTypes.func.isRequired,
hasIndexers: PropTypes.bool.isRequired,
onInputChange: PropTypes.func.isRequired,
searchError: PropTypes.object
searchError: PropTypes.object,
bindShortcut: PropTypes.func.isRequired
};
export default SearchFooter;
export default keyboardShortcuts(SearchFooter);

View File

@@ -18,9 +18,9 @@ import translate from 'Utilities/String/translate';
import styles from './EditApplicationModalContent.css';
const syncLevelOptions = [
{ key: 'disabled', value: 'Disabled' },
{ key: 'addOnly', value: 'Add and Remove Only' },
{ key: 'fullSync', value: 'Full Sync' }
{ key: 'disabled', value: translate('Disabled') },
{ key: 'addOnly', value: translate('AddRemoveOnly') },
{ key: 'fullSync', value: translate('FullSync') }
];
function EditApplicationModalContent(props) {
@@ -53,7 +53,7 @@ function EditApplicationModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{`${id ? 'Edit' : 'Add'} Application`}
{`${id ? translate('Edit') : translate('Add')} ${translate('Application')}`}
</ModalHeader>
<ModalBody>
@@ -94,13 +94,13 @@ function EditApplicationModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>{'Sync Level'}</FormLabel>
<FormLabel>{translate('SyncLevel')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
values={syncLevelOptions}
name="syncLevel"
helpText={'Sync Level'}
helpText={`${translate('SyncLevelAddRemove')}<br>${translate('SyncLevelFull')}`}
{...syncLevel}
onChange={onInputChange}
/>

View File

@@ -53,6 +53,9 @@ class AddDownloadClientModalContent extends Component {
<div>
<Alert kind={kinds.INFO}>
<div>
{translate('AddDownloadClientToProwlarr')}
</div>
<div>
{translate('ProwlarrSupportsAnyDownloadClient')}
</div>

View File

@@ -55,7 +55,7 @@ function UpdateSettings(props) {
type={inputTypes.TEXT}
name="branch"
helpText={usingExternalUpdateMechanism ? translate('BranchUpdateMechanism') : translate('BranchUpdate')}
helpLink="https://wiki.servarr.com/Prowlarr_Settings#Updates"
helpLink="https://wiki.servarr.com/prowlarr/settings#updates"
{...branch}
onChange={onInputChange}
readOnly={usingExternalUpdateMechanism}
@@ -92,7 +92,7 @@ function UpdateSettings(props) {
name="updateMechanism"
values={updateOptions}
helpText={translate('UpdateMechanismHelpText')}
helpLink="https://wiki.servarr.com/Prowlarr_Settings#Updates"
helpLink="https://wiki.servarr.com/prowlarr/settings#updates"
onChange={onInputChange}
{...updateMechanism}
/>

View File

@@ -25,8 +25,8 @@ function NotificationEventItems(props) {
<FormLabel>{translate('NotificationTriggers')}</FormLabel>
<div>
<FormInputHelpText
text={translate('NotifcationTriggersHelpText')}
link="https://wiki.servarr.com/Prowlarr_Settings#Connections"
text={translate('NotificationTriggersHelpText')}
link="https://wiki.servarr.com/prowlarr/settings#connections"
/>
<div className={styles.events}>
<div>

View File

@@ -176,7 +176,7 @@ export const SET_MOVIE_SORT = 'indexerIndex/setMovieSort';
export const SET_MOVIE_FILTER = 'indexerIndex/setMovieFilter';
export const SET_MOVIE_VIEW = 'indexerIndex/setMovieView';
export const SET_MOVIE_TABLE_OPTION = 'indexerIndex/setMovieTableOption';
export const SAVE_MOVIE_EDITOR = 'indexerIndex/saveMovieEditor';
export const SAVE_INDEXER_EDITOR = 'indexerIndex/saveIndexerEditor';
export const BULK_DELETE_INDEXERS = 'indexerIndex/bulkDeleteIndexers';
//
@@ -186,14 +186,14 @@ export const setMovieSort = createAction(SET_MOVIE_SORT);
export const setMovieFilter = createAction(SET_MOVIE_FILTER);
export const setMovieView = createAction(SET_MOVIE_VIEW);
export const setMovieTableOption = createAction(SET_MOVIE_TABLE_OPTION);
export const saveMovieEditor = createThunk(SAVE_MOVIE_EDITOR);
export const saveIndexerEditor = createThunk(SAVE_INDEXER_EDITOR);
export const bulkDeleteIndexers = createThunk(BULK_DELETE_INDEXERS);
//
// Action Handlers
export const actionHandlers = handleThunks({
[SAVE_MOVIE_EDITOR]: function(getState, payload, dispatch) {
[SAVE_INDEXER_EDITOR]: function(getState, payload, dispatch) {
dispatch(set({
section,
isSaving: true

View File

@@ -13,7 +13,7 @@ function createHealthCheckSelector() {
source: 'UI',
type: 'warning',
message: translate('CouldNotConnectSignalR'),
wikiUrl: 'https://wiki.servarr.com/Prowlarr_System#Could_not_connect_to_signalR'
wikiUrl: 'https://wiki.servarr.com/prowlarr/system#could-not-connect-to-signalr'
});
}

View File

@@ -0,0 +1,64 @@
import React, { Component } from 'react';
import FieldSet from 'Components/FieldSet';
import Link from 'Components/Link/Link';
import translate from 'Utilities/String/translate';
import styles from '../styles.css';
class Donations extends Component {
//
// Render
render() {
return (
<FieldSet legend={translate('Donations')}>
<div className={styles.logoContainer} title="Radarr">
<Link to="https://opencollective.com/radarr">
<img
className={styles.logo}
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-radarr.png`}
/>
</Link>
</div>
<div className={styles.logoContainer} title="Lidarr">
<Link to="https://opencollective.com/lidarr">
<img
className={styles.logo}
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-lidarr.png`}
/>
</Link>
</div>
<div className={styles.logoContainer} title="Readarr">
<Link to="https://opencollective.com/readarr">
<img
className={styles.logo}
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-readarr.png`}
/>
</Link>
</div>
<div className={styles.logoContainer} title="Prowlarr">
<Link to="https://opencollective.com/prowlarr">
<img
className={styles.logo}
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-prowlarr.png`}
/>
</Link>
</div>
<div className={styles.logoContainer} title="Sonarr">
<Link to="https://opencollective.com/sonarr">
<img
className={styles.logo}
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-sonarr.png`}
/>
</Link>
</div>
</FieldSet>
);
}
}
Donations.propTypes = {
};
export default Donations;

View File

@@ -15,32 +15,32 @@ class MoreInfo extends Component {
return (
<FieldSet legend={translate('MoreInfo')}>
<DescriptionList>
<DescriptionListItemTitle>Home page</DescriptionListItemTitle>
<DescriptionListItemTitle>{translate('HomePage')}</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to="https://prowlarr.com/">prowlarr.com</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>Discord</DescriptionListItemTitle>
<DescriptionListItemTitle>{translate('Wiki')}</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to="https://wiki.servarr.com/prowlarr">wiki.servarr.com/prowlarr</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>{translate('Reddit')}</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to="https://reddit.com/r/prowlarr">r/prowlarr</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>{translate('Discord')}</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to="https://prowlarr.com/discord">prowlarr.com/discord</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>Wiki</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to="https://wiki.servarr.com/Prowlarr">wiki.servarr.com/Prowlarr</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>Donations</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to="https://opencollective.com/prowlarr">opencollective.com/prowlarr</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>Source</DescriptionListItemTitle>
<DescriptionListItemTitle>{translate('Source')}</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to="https://github.com/Prowlarr/Prowlarr/">github.com/Prowlarr/Prowlarr</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>Feature Requests</DescriptionListItemTitle>
<DescriptionListItemTitle>{translate('FeatureRequests')}</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to="https://github.com/Prowlarr/Prowlarr/issues">github.com/Prowlarr/Prowlarr/issues</Link>
</DescriptionListItemDescription>

View File

@@ -3,6 +3,7 @@ import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import translate from 'Utilities/String/translate';
import AboutConnector from './About/AboutConnector';
import Donations from './Donations/Donations';
import HealthConnector from './Health/HealthConnector';
import MoreInfo from './MoreInfo/MoreInfo';
@@ -18,6 +19,7 @@ class Status extends Component {
<HealthConnector />
<AboutConnector />
<MoreInfo />
<Donations />
</PageContentBody>
</PageContent>
);

View File

@@ -0,0 +1,17 @@
.logo {
margin: auto;
padding: 9px;
}
.logoContainer {
display: inline-block;
margin: 0.5em;
width: 50px;
height: 50px;
outline: none;
border: solid 1px #e6e6e6;
border-radius: 0.5em;
background: #f8f8ff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
cursor: pointer;
}

View File

@@ -24,7 +24,7 @@ class UpdateChanges extends Component {
<ul>
{
changes.map((change, index) => {
const checkChange = change.replace(/#\d{4,5}\b/g, (match, contents) => {
const checkChange = change.replace(/#\d{3,5}\b/g, (match, contents) => {
return `[${match}](https://github.com/Prowlarr/Prowlarr/issues/${match.substring(1)})`;
});

View File

@@ -252,7 +252,7 @@
</span>
<a
href="https://wiki.servarr.com/Prowlarr_FAQ#Help_I_have_locked_my_self_out_of_Prowlarr_and_do_not_know_the_password"
href="https://wiki.servarr.com/prowlarr/faq#help-i-have-locked-myself-out"
class="forgot-password"
>Forgot your password?</a
>

View File

@@ -17,6 +17,8 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"https://baconbits.org/feeds.php?feed=torrents_tv&user=12345&auth=2b51db35e1910123321025a12b9933d2&passkey=mySecret&authkey=2b51db35e1910123321025a12b9933d2")]
[TestCase(@"http://127.0.0.1:9117/dl/indexername?jackett_apikey=flwjiefewklfjacketmySecretsdfldskjfsdlk&path=we0re9f0sdfbase64sfdkfjsdlfjk&file=The+Torrent+File+Name.torrent")]
[TestCase(@"http://nzb.su/getnzb/2b51db35e1912ffc138825a12b9933d2.nzb&i=37292&r=2b51db35e1910123321025a12b9933d2")]
[TestCase(@"https://horrorcharnel.org/takeloginhorror.php: username=mySecret&password=mySecret&use_sslvalue==&perm_ssl=1&submitme=X&use_ssl=1&returnto=%2F&captchaSelection=1230456")]
[TestCase(@"https://torrentdb.net/login: _token=2b51db35e1912ffc138825a12b9933d2&username=mySecret&password=mySecret&remember=on")]
// NzbGet
[TestCase(@"{ ""Name"" : ""ControlUsername"", ""Value"" : ""mySecret"" }, { ""Name"" : ""ControlPassword"", ""Value"" : ""mySecret"" }, ")]

View File

@@ -84,6 +84,13 @@ namespace NzbDrone.Common.Http
throw new WebException($"Too many automatic redirections were attempted for {autoRedirectChain.Join(" -> ")}", WebExceptionStatus.ProtocolError);
}
// 302 or 303 should default to GET on redirect even if POST on original
if (response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.RedirectMethod)
{
request.Method = HttpMethod.GET;
request.ContentData = null;
}
response = await ExecuteRequestAsync(request, cookieContainer);
}
while (response.HasHttpRedirect);

View File

@@ -62,6 +62,20 @@ namespace NzbDrone.Common.Http
StatusCode == HttpStatusCode.TemporaryRedirect ||
StatusCode == HttpStatusCode.Found;
public string RedirectUrl
{
get
{
var newUrl = Headers["Location"];
if (newUrl == null)
{
return string.Empty;
}
return (Request.Url += new HttpUri(newUrl)).FullUri;
}
}
public string[] GetCookieHeaders()
{
return Headers.GetValues("Set-Cookie") ?? Array.Empty<string>();

View File

@@ -12,7 +12,7 @@ namespace NzbDrone.Common.Instrumentation
{
// Url
new Regex(@"(?<=\?|&|: |;)(apikey|token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=\?|&)[^=]*?(username|password)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=\?|&| )[^=]*?(_?token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
@@ -77,7 +77,6 @@ namespace NzbDrone.Common.Instrumentation
private static string CleanseRemoteIP(Match match)
{
var group = match.Groups[1];
var valueAll = match.Value;
var valueIP = group.Value;
if (IPAddress.TryParse(valueIP, out var address) && !address.IsLocalAddress())

View File

@@ -0,0 +1,227 @@
using System;
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Download;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Download
{
[TestFixture]
public class DownloadClientProviderFixture : CoreTest<DownloadClientProvider>
{
private List<IDownloadClient> _downloadClients;
private List<DownloadClientStatus> _blockedProviders;
private int _nextId;
[SetUp]
public void SetUp()
{
_downloadClients = new List<IDownloadClient>();
_blockedProviders = new List<DownloadClientStatus>();
_nextId = 1;
Mocker.GetMock<IDownloadClientFactory>()
.Setup(v => v.GetAvailableProviders())
.Returns(_downloadClients);
Mocker.GetMock<IDownloadClientStatusService>()
.Setup(v => v.GetBlockedProviders())
.Returns(_blockedProviders);
}
private Mock<IDownloadClient> WithUsenetClient(int priority = 0)
{
var mock = new Mock<IDownloadClient>(MockBehavior.Default);
mock.SetupGet(s => s.Definition)
.Returns(Builder<DownloadClientDefinition>
.CreateNew()
.With(v => v.Id = _nextId++)
.With(v => v.Priority = priority)
.Build());
_downloadClients.Add(mock.Object);
mock.SetupGet(v => v.Protocol).Returns(DownloadProtocol.Usenet);
return mock;
}
private Mock<IDownloadClient> WithTorrentClient(int priority = 0)
{
var mock = new Mock<IDownloadClient>(MockBehavior.Default);
mock.SetupGet(s => s.Definition)
.Returns(Builder<DownloadClientDefinition>
.CreateNew()
.With(v => v.Id = _nextId++)
.With(v => v.Priority = priority)
.Build());
_downloadClients.Add(mock.Object);
mock.SetupGet(v => v.Protocol).Returns(DownloadProtocol.Torrent);
return mock;
}
private void GivenBlockedClient(int id)
{
_blockedProviders.Add(new DownloadClientStatus
{
ProviderId = id,
DisabledTill = DateTime.UtcNow.AddHours(3)
});
}
[Test]
public void should_roundrobin_over_usenet_client()
{
WithUsenetClient();
WithUsenetClient();
WithUsenetClient();
WithTorrentClient();
var client1 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
var client2 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
var client3 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
var client4 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
var client5 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
client1.Definition.Id.Should().Be(1);
client2.Definition.Id.Should().Be(2);
client3.Definition.Id.Should().Be(3);
client4.Definition.Id.Should().Be(1);
client5.Definition.Id.Should().Be(2);
}
[Test]
public void should_roundrobin_over_torrent_client()
{
WithUsenetClient();
WithTorrentClient();
WithTorrentClient();
WithTorrentClient();
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
client1.Definition.Id.Should().Be(2);
client2.Definition.Id.Should().Be(3);
client3.Definition.Id.Should().Be(4);
client4.Definition.Id.Should().Be(2);
client5.Definition.Id.Should().Be(3);
}
[Test]
public void should_roundrobin_over_protocol_separately()
{
WithUsenetClient();
WithTorrentClient();
WithTorrentClient();
var client1 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
client1.Definition.Id.Should().Be(1);
client2.Definition.Id.Should().Be(2);
client3.Definition.Id.Should().Be(3);
client4.Definition.Id.Should().Be(2);
}
[Test]
public void should_skip_blocked_torrent_client()
{
WithUsenetClient();
WithTorrentClient();
WithTorrentClient();
WithTorrentClient();
GivenBlockedClient(3);
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
client1.Definition.Id.Should().Be(2);
client2.Definition.Id.Should().Be(4);
client3.Definition.Id.Should().Be(2);
client4.Definition.Id.Should().Be(4);
}
[Test]
public void should_not_skip_blocked_torrent_client_if_all_blocked()
{
WithUsenetClient();
WithTorrentClient();
WithTorrentClient();
WithTorrentClient();
GivenBlockedClient(2);
GivenBlockedClient(3);
GivenBlockedClient(4);
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
client1.Definition.Id.Should().Be(2);
client2.Definition.Id.Should().Be(3);
client3.Definition.Id.Should().Be(4);
client4.Definition.Id.Should().Be(2);
}
[Test]
public void should_skip_secondary_prio_torrent_client()
{
WithUsenetClient();
WithTorrentClient(2);
WithTorrentClient();
WithTorrentClient();
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
client1.Definition.Id.Should().Be(3);
client2.Definition.Id.Should().Be(4);
client3.Definition.Id.Should().Be(3);
client4.Definition.Id.Should().Be(4);
}
[Test]
public void should_not_skip_secondary_prio_torrent_client_if_primary_blocked()
{
WithUsenetClient();
WithTorrentClient(2);
WithTorrentClient(2);
WithTorrentClient();
GivenBlockedClient(4);
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
client1.Definition.Id.Should().Be(2);
client2.Definition.Id.Should().Be(3);
client3.Definition.Id.Should().Be(2);
client4.Definition.Id.Should().Be(3);
}
}
}

View File

@@ -0,0 +1,161 @@
using System;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Download;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Download
{
public class DownloadClientStatusServiceFixture : CoreTest<DownloadClientStatusService>
{
private DateTime _epoch;
[SetUp]
public void SetUp()
{
_epoch = DateTime.UtcNow;
Mocker.GetMock<IRuntimeInfo>()
.SetupGet(v => v.StartTime)
.Returns(_epoch - TimeSpan.FromHours(1));
}
private DownloadClientStatus WithStatus(DownloadClientStatus status)
{
Mocker.GetMock<IDownloadClientStatusRepository>()
.Setup(v => v.FindByProviderId(1))
.Returns(status);
Mocker.GetMock<IDownloadClientStatusRepository>()
.Setup(v => v.All())
.Returns(new[] { status });
return status;
}
private void VerifyUpdate()
{
Mocker.GetMock<IDownloadClientStatusRepository>()
.Verify(v => v.Upsert(It.IsAny<DownloadClientStatus>()), Times.Once());
}
private void VerifyNoUpdate()
{
Mocker.GetMock<IDownloadClientStatusRepository>()
.Verify(v => v.Upsert(It.IsAny<DownloadClientStatus>()), Times.Never());
}
[Test]
public void should_not_consider_blocked_within_5_minutes_since_initial_failure()
{
WithStatus(new DownloadClientStatus
{
InitialFailure = _epoch - TimeSpan.FromMinutes(4),
MostRecentFailure = _epoch - TimeSpan.FromSeconds(4),
EscalationLevel = 3
});
Subject.RecordFailure(1);
VerifyUpdate();
var status = Subject.GetBlockedProviders().FirstOrDefault();
status.Should().BeNull();
}
[Test]
public void should_consider_blocked_after_5_minutes_since_initial_failure()
{
WithStatus(new DownloadClientStatus
{
InitialFailure = _epoch - TimeSpan.FromMinutes(6),
MostRecentFailure = _epoch - TimeSpan.FromSeconds(120),
EscalationLevel = 3
});
Subject.RecordFailure(1);
VerifyUpdate();
var status = Subject.GetBlockedProviders().FirstOrDefault();
status.Should().NotBeNull();
}
[Test]
public void should_not_escalate_further_till_after_5_minutes_since_initial_failure()
{
var origStatus = WithStatus(new DownloadClientStatus
{
InitialFailure = _epoch - TimeSpan.FromMinutes(4),
MostRecentFailure = _epoch - TimeSpan.FromSeconds(4),
EscalationLevel = 3
});
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
var status = Subject.GetBlockedProviders().FirstOrDefault();
status.Should().BeNull();
origStatus.EscalationLevel.Should().Be(3);
}
[Test]
public void should_escalate_further_after_5_minutes_since_initial_failure()
{
WithStatus(new DownloadClientStatus
{
InitialFailure = _epoch - TimeSpan.FromMinutes(6),
MostRecentFailure = _epoch - TimeSpan.FromSeconds(120),
EscalationLevel = 3
});
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
var status = Subject.GetBlockedProviders().FirstOrDefault();
status.Should().NotBeNull();
status.EscalationLevel.Should().BeGreaterThan(3);
}
[Test]
public void should_not_escalate_beyond_3_hours()
{
WithStatus(new DownloadClientStatus
{
InitialFailure = _epoch - TimeSpan.FromMinutes(6),
MostRecentFailure = _epoch - TimeSpan.FromSeconds(120),
EscalationLevel = 3
});
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
var status = Subject.GetBlockedProviders().FirstOrDefault();
status.Should().NotBeNull();
status.DisabledTill.Should().HaveValue();
status.DisabledTill.Should().NotBeAfter(_epoch + TimeSpan.FromHours(3.1));
}
}
}

View File

@@ -33,14 +33,8 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
Subject.Check().ShouldBeWarning();
}
[TestCase("Develop")]
[TestCase("develop")]
public void should_return_error_when_branch_is_v1(string branch)
{
GivenValidBranch(branch);
Subject.Check().ShouldBeError();
}
[TestCase("nightly")]
[TestCase("Nightly")]
public void should_return_no_warning_when_branch_valid(string branch)

View File

@@ -1,4 +1,4 @@
using FluentAssertions;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.HealthCheck;
using NzbDrone.Core.Test.Framework;
@@ -10,9 +10,9 @@ namespace NzbDrone.Core.Test.HealthCheck
{
private const string WikiRoot = "https://wiki.servarr.com/";
[TestCase("I blew up because of some weird user mistake", null, WikiRoot + "Prowlarr_System#i_blew_up_because_of_some_weird_user_mistake")]
[TestCase("I blew up because of some weird user mistake", "#my_health_check", WikiRoot + "Prowlarr_System#my_health_check")]
[TestCase("I blew up because of some weird user mistake", "Custom-Page#my_health_check", WikiRoot + "Custom-Page#my_health_check")]
[TestCase("I blew up because of some weird user mistake", null, WikiRoot + "prowlarr/system#i-blew-up-because-of-some-weird-user-mistake")]
[TestCase("I blew up because of some weird user mistake", "#my-health-check", WikiRoot + "prowlarr/system#my-health-check")]
[TestCase("I blew up because of some weird user mistake", "custom-page#my-health-check", WikiRoot + "prowlarr/custom-page#my-health-check")]
public void should_format_wiki_url(string message, string wikiFragment, string expectedUrl)
{
var subject = new NzbDrone.Core.HealthCheck.HealthCheck(typeof(HealthCheckBase), HealthCheckResult.Warning, message, wikiFragment);

View File

@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.FileList;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;
@@ -21,6 +22,33 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
Username = "somename"
};
Subject.Capabilities = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
},
Flags = new List<IndexerFlag>
{
IndexerFlag.FreeLeech
}
};
Subject.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesSD, "Filme SD");
Subject.Capabilities.Categories.AddCategoryMapping(2, NewznabStandardCategory.MoviesDVD, "Filme DVD");
_movieSearchCriteria = new MovieSearchCriteria
{
SearchTerm = "Star Wars",
@@ -38,7 +66,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
[Test]
public void should_use_categories_for_feed()
{
var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new int[] { 1, 2 } });
var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new int[] { NewznabStandardCategory.MoviesSD.Id, NewznabStandardCategory.MoviesDVD.Id } });
results.GetAllTiers().Should().HaveCount(1);
@@ -50,7 +78,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
[Test]
public void should_not_search_by_imdbid_if_not_supported()
{
_movieSearchCriteria.ImdbId = "tt0076759";
_movieSearchCriteria.ImdbId = "0076759";
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.GetAllTiers().Should().HaveCount(1);

View File

@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
_movieSearchCriteria = new MovieSearchCriteria
{
Categories = new int[] { 2000, 2010 },
ImdbId = "tt0076759"
ImdbId = "0076759"
};
}

View File

@@ -48,7 +48,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
_movieSearchCriteria = new MovieSearchCriteria
{
Categories = new int[] { 2000, 2010 },
ImdbId = "tt0076759"
ImdbId = "0076759"
};
}
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
public void should_search_by_imdbid_if_supported()
{
var results = Subject.GetSearchRequests(_movieSearchCriteria);
var imdbQuery = int.Parse(_movieSearchCriteria.ImdbId.Substring(2));
var imdbQuery = int.Parse(_movieSearchCriteria.ImdbId);
results.GetAllTiers().Should().HaveCount(1);

View File

@@ -1,12 +1,13 @@
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Test.IndexerTests
{
public class TestIndexer : HttpIndexerBase<TestIndexerSettings>
public class TestIndexer : UsenetIndexerBase<TestIndexerSettings>
{
public override string Name => "Test Indexer";
public override string BaseUrl => "http://testindexer.com";
@@ -18,8 +19,8 @@ namespace NzbDrone.Core.Test.IndexerTests
public int _supportedPageSize;
public override int PageSize => _supportedPageSize;
public TestIndexer(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
public TestIndexer(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, IValidateNzbs nzbValidationService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, nzbValidationService, logger)
{
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
@@ -31,7 +32,25 @@ namespace NzbDrone.Core.Applications.Lidarr
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_lidarrV1Proxy.Test(Settings));
var testIndexer = new IndexerDefinition
{
Id = 0,
Name = "Test",
Protocol = DownloadProtocol.Usenet,
Capabilities = new IndexerCapabilities()
};
testIndexer.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.Audio);
try
{
failures.AddIfNotNull(_lidarrV1Proxy.TestConnection(BuildLidarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Lidarr"));
}
return new ValidationResult(failures);
}
@@ -146,7 +165,7 @@ namespace NzbDrone.Core.Applications.Lidarr
Fields = schema.Fields,
};
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/";
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Applications.Lidarr
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
other.Name == Name &&
other.Implementation == Implementation &&
other.Priority == Priority &&

View File

@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Applications.Lidarr
public IEnumerable<int> SyncCategories { get; set; }
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Lidarr sees it, including http(s):// and port if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Lidarr sees it, including http(s)://, port, and urlbase if needed")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Lidarr Server", HelpText = "Lidarr server URL, including http(s):// and port if needed")]

View File

@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Applications.Lidarr
List<LidarrIndexer> GetIndexerSchema(LidarrSettings settings);
void RemoveIndexer(int indexerId, LidarrSettings settings);
LidarrIndexer UpdateIndexer(LidarrIndexer indexer, LidarrSettings settings);
ValidationFailure Test(LidarrSettings settings);
ValidationFailure TestConnection(LidarrIndexer indexer, LidarrSettings settings);
}
public class LidarrV1Proxy : ILidarrV1Proxy
@@ -91,11 +91,15 @@ namespace NzbDrone.Core.Applications.Lidarr
return Execute<LidarrIndexer>(request);
}
public ValidationFailure Test(LidarrSettings settings)
public ValidationFailure TestConnection(LidarrIndexer indexer, LidarrSettings settings)
{
var request = BuildRequest(settings, $"/api/v1/indexer/test", HttpMethod.POST);
request.SetContent(indexer.ToJson());
try
{
GetStatus(settings);
Execute<LidarrIndexer>(request);
}
catch (HttpException ex)
{
@@ -105,8 +109,14 @@ namespace NzbDrone.Core.Applications.Lidarr
return new ValidationFailure("ApiKey", "API Key is invalid");
}
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Error(ex, "Prowlarr URL is invalid");
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Lidarr cannot connect to Prowlarr");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("ApiKey", "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
}
catch (Exception ex)
{

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
@@ -31,7 +32,25 @@ namespace NzbDrone.Core.Applications.Radarr
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_radarrV3Proxy.Test(Settings));
var testIndexer = new IndexerDefinition
{
Id = 0,
Name = "Test",
Protocol = DownloadProtocol.Usenet,
Capabilities = new IndexerCapabilities()
};
testIndexer.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies);
try
{
failures.AddIfNotNull(_radarrV3Proxy.TestConnection(BuildRadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Radarr"));
}
return new ValidationResult(failures);
}
@@ -146,7 +165,7 @@ namespace NzbDrone.Core.Applications.Radarr
Fields = schema.Fields,
};
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/";
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Applications.Radarr
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
other.Name == Name &&
other.Implementation == Implementation &&
other.Priority == Priority &&

View File

@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Applications.Radarr
public IEnumerable<int> SyncCategories { get; set; }
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Radarr sees it, including http(s):// and port if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Radarr sees it, including http(s)://, port, and urlbase if needed")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Radarr Server", HelpText = "Radarr server URL, including http(s):// and port if needed")]

View File

@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Applications.Radarr
List<RadarrIndexer> GetIndexerSchema(RadarrSettings settings);
void RemoveIndexer(int indexerId, RadarrSettings settings);
RadarrIndexer UpdateIndexer(RadarrIndexer indexer, RadarrSettings settings);
ValidationFailure Test(RadarrSettings settings);
ValidationFailure TestConnection(RadarrIndexer indexer, RadarrSettings settings);
}
public class RadarrV3Proxy : IRadarrV3Proxy
@@ -91,11 +91,15 @@ namespace NzbDrone.Core.Applications.Radarr
return Execute<RadarrIndexer>(request);
}
public ValidationFailure Test(RadarrSettings settings)
public ValidationFailure TestConnection(RadarrIndexer indexer, RadarrSettings settings)
{
var request = BuildRequest(settings, $"/api/v3/indexer/test", HttpMethod.POST);
request.SetContent(indexer.ToJson());
try
{
GetStatus(settings);
Execute<RadarrIndexer>(request);
}
catch (HttpException ex)
{
@@ -105,8 +109,14 @@ namespace NzbDrone.Core.Applications.Radarr
return new ValidationFailure("ApiKey", "API Key is invalid");
}
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Error(ex, "Prowlarr URL is invalid");
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Radarr cannot connect to Prowlarr");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("ApiKey", "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
}
catch (Exception ex)
{

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
@@ -31,7 +32,25 @@ namespace NzbDrone.Core.Applications.Readarr
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_readarrV1Proxy.Test(Settings));
var testIndexer = new IndexerDefinition
{
Id = 0,
Name = "Test",
Protocol = DownloadProtocol.Usenet,
Capabilities = new IndexerCapabilities()
};
testIndexer.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.Books);
try
{
failures.AddIfNotNull(_readarrV1Proxy.TestConnection(BuildReadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Readarr"));
}
return new ValidationResult(failures);
}
@@ -146,7 +165,7 @@ namespace NzbDrone.Core.Applications.Readarr
Fields = schema.Fields,
};
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/";
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Applications.Readarr
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
other.Name == Name &&
other.Implementation == Implementation &&
other.Priority == Priority &&

View File

@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Applications.Readarr
public IEnumerable<int> SyncCategories { get; set; }
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Readarr sees it, including http(s):// and port if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Readarr sees it, including http(s)://, port, and urlbase if needed")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Readarr Server", HelpText = "Readarr server URL, including http(s):// and port if needed")]

View File

@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Applications.Readarr
List<ReadarrIndexer> GetIndexerSchema(ReadarrSettings settings);
void RemoveIndexer(int indexerId, ReadarrSettings settings);
ReadarrIndexer UpdateIndexer(ReadarrIndexer indexer, ReadarrSettings settings);
ValidationFailure Test(ReadarrSettings settings);
ValidationFailure TestConnection(ReadarrIndexer indexer, ReadarrSettings settings);
}
public class ReadarrV1Proxy : IReadarrV1Proxy
@@ -91,11 +91,15 @@ namespace NzbDrone.Core.Applications.Readarr
return Execute<ReadarrIndexer>(request);
}
public ValidationFailure Test(ReadarrSettings settings)
public ValidationFailure TestConnection(ReadarrIndexer indexer, ReadarrSettings settings)
{
var request = BuildRequest(settings, $"/api/v1/indexer/test", HttpMethod.POST);
request.SetContent(indexer.ToJson());
try
{
GetStatus(settings);
Execute<ReadarrIndexer>(request);
}
catch (HttpException ex)
{
@@ -105,8 +109,14 @@ namespace NzbDrone.Core.Applications.Readarr
return new ValidationFailure("ApiKey", "API Key is invalid");
}
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Error(ex, "Prowlarr URL is invalid");
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Readarr cannot connect to Prowlarr");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("ApiKey", "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
}
catch (Exception ex)
{

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
@@ -31,7 +32,25 @@ namespace NzbDrone.Core.Applications.Sonarr
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_sonarrV3Proxy.Test(Settings));
var testIndexer = new IndexerDefinition
{
Id = 0,
Name = "Test",
Protocol = DownloadProtocol.Usenet,
Capabilities = new IndexerCapabilities()
};
testIndexer.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.TV);
try
{
failures.AddIfNotNull(_sonarrV3Proxy.TestConnection(BuildSonarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Sonarr"));
}
return new ValidationResult(failures);
}
@@ -146,7 +165,7 @@ namespace NzbDrone.Core.Applications.Sonarr
Fields = schema.Fields,
};
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/";
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));

View File

@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Applications.Sonarr
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
other.Name == Name &&
other.Implementation == Implementation &&
other.Priority == Priority &&

View File

@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Applications.Sonarr
public IEnumerable<int> SyncCategories { get; set; }
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Sonarr sees it, including http(s):// and port if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Sonarr sees it, including http(s)://, port, and urlbase if needed")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Sonarr Server", HelpText = "Sonarr server URL, including http(s):// and port if needed")]

View File

@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Applications.Sonarr
List<SonarrIndexer> GetIndexerSchema(SonarrSettings settings);
void RemoveIndexer(int indexerId, SonarrSettings settings);
SonarrIndexer UpdateIndexer(SonarrIndexer indexer, SonarrSettings settings);
ValidationFailure Test(SonarrSettings settings);
ValidationFailure TestConnection(SonarrIndexer indexer, SonarrSettings settings);
}
public class SonarrV3Proxy : ISonarrV3Proxy
@@ -91,11 +91,15 @@ namespace NzbDrone.Core.Applications.Sonarr
return Execute<SonarrIndexer>(request);
}
public ValidationFailure Test(SonarrSettings settings)
public ValidationFailure TestConnection(SonarrIndexer indexer, SonarrSettings settings)
{
var request = BuildRequest(settings, $"/api/v3/indexer/test", HttpMethod.POST);
request.SetContent(indexer.ToJson());
try
{
GetStatus(settings);
Execute<SonarrIndexer>(request);
}
catch (HttpException ex)
{
@@ -105,8 +109,14 @@ namespace NzbDrone.Core.Applications.Sonarr
return new ValidationFailure("ApiKey", "API Key is invalid");
}
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Error(ex, "Prowlarr URL is invalid");
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Sonarr cannot connect to Prowlarr");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("ApiKey", "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
}
catch (Exception ex)
{

View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Data;
using System.Text.Json;
using Dapper;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Datastore.Converters
{
public class CookieConverter : SqlMapper.TypeHandler<IDictionary<string, string>>
{
protected readonly JsonSerializerOptions SerializerSettings;
public CookieConverter()
{
var serializerSettings = new JsonSerializerOptions
{
AllowTrailingCommas = true,
IgnoreNullValues = true,
PropertyNameCaseInsensitive = true,
WriteIndented = true
};
SerializerSettings = serializerSettings;
}
public override void SetValue(IDbDataParameter parameter, IDictionary<string, string> value)
{
parameter.Value = JsonSerializer.Serialize(value, SerializerSettings);
}
public override IDictionary<string, string> Parse(object value)
{
return JsonSerializer.Deserialize<Dictionary<string, string>>((string)value, SerializerSettings);
}
}
}

View File

@@ -108,10 +108,10 @@ namespace NzbDrone.Core.Datastore
if (OsInfo.IsOsx)
{
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/Prowlarr_FAQ#I_use_Prowlarr_on_a_Mac_and_it_suddenly_stopped_working_What_happened", e, fileName);
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/prowlarr/faq#i-use-prowlarr-on-a-mac-and-it-suddenly-stopped-working-what-happened", e, fileName);
}
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);
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 (Exception e)
{

View File

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

View File

@@ -31,9 +31,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IValidateNzbs nzbValidationService,
Logger logger)
: base(httpClient, configService, diskProvider, nzbValidationService, logger)
: base(httpClient, configService, diskProvider, logger)
{
_dsInfoProxy = dsInfoProxy;
_dsTaskProxy = dsTaskProxy;

View File

@@ -20,9 +20,8 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IValidateNzbs nzbValidationService,
Logger logger)
: base(httpClient, configService, diskProvider, nzbValidationService, logger)
: base(httpClient, configService, diskProvider, logger)
{
_proxy = proxy;
}

View File

@@ -25,9 +25,8 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IValidateNzbs nzbValidationService,
Logger logger)
: base(httpClient, configService, diskProvider, nzbValidationService, logger)
: base(httpClient, configService, diskProvider, logger)
{
_proxy = proxy;
}

View File

@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
@@ -14,24 +16,20 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
{
public class Pneumatic : DownloadClientBase<PneumaticSettings>
{
private readonly IHttpClient _httpClient;
public Pneumatic(IHttpClient httpClient,
IConfigService configService,
public Pneumatic(IConfigService configService,
IDiskProvider diskProvider,
Logger logger)
: base(configService, diskProvider, logger)
{
_httpClient = httpClient;
}
public override string Name => "Pneumatic";
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
public override string Download(ReleaseInfo release, bool redirect)
public override async Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer)
{
var url = release.DownloadUrl;
var url = new Uri(release.DownloadUrl);
var title = release.Title;
title = StringUtil.CleanFileName(title);
@@ -40,7 +38,10 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
var nzbFile = Path.Combine(Settings.NzbFolder, title + ".nzb");
_logger.Debug("Downloading NZB from: {0} to: {1}", url, nzbFile);
_httpClient.DownloadFile(url, nzbFile);
var nzbData = await indexer.Download(url);
File.WriteAllBytes(nzbFile, nzbData);
_logger.Debug("NZB Download succeeded, saved to: {0}", nzbFile);

View File

@@ -22,9 +22,8 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IValidateNzbs nzbValidationService,
Logger logger)
: base(httpClient, configService, diskProvider, nzbValidationService, logger)
: base(httpClient, configService, diskProvider, logger)
{
_proxy = proxy;
}

View File

@@ -66,7 +66,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
{
_logger.Debug("rTorrent didn't add the torrent within {0} seconds: {1}.", tries * retryDelay / 1000, filename);
throw new ReleaseDownloadException(release, "Downloading torrent failed");
throw new ReleaseDownloadException("Downloading torrent failed");
}
return hash;

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
@@ -54,7 +55,7 @@ namespace NzbDrone.Core.Download
get;
}
public abstract string Download(ReleaseInfo release, bool redirect);
public abstract Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer);
public ValidationResult Test()
{

View File

@@ -6,7 +6,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Security;
namespace NzbDrone.Core.Indexers
namespace NzbDrone.Core.Download
{
public interface IDownloadMappingService
{

View File

@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Download
{
public interface IDownloadService
{
void SendReportToClient(ReleaseInfo release, string source, string host, bool redirect);
Task SendReportToClient(ReleaseInfo release, string source, string host, bool redirect);
Task<byte[]> DownloadReport(string link, int indexerId, string source, string host, string title);
void RecordRedirect(string link, int indexerId, string source, string host, string title);
}
@@ -49,7 +49,7 @@ namespace NzbDrone.Core.Download
_logger = logger;
}
public void SendReportToClient(ReleaseInfo release, string source, string host, bool redirect)
public async Task SendReportToClient(ReleaseInfo release, string source, string host, bool redirect)
{
var downloadTitle = release.Title;
var downloadClient = _downloadClientProvider.GetDownloadClient(release.DownloadProtocol);
@@ -69,10 +69,12 @@ namespace NzbDrone.Core.Download
_rateLimitService.WaitAndPulse(url.Host, TimeSpan.FromSeconds(2));
}
var indexer = _indexerFactory.GetInstance(_indexerFactory.Get(release.IndexerId));
string downloadClientId;
try
{
downloadClientId = downloadClient.Download(release, redirect);
downloadClientId = await downloadClient.Download(release, redirect, indexer);
_downloadClientStatusService.RecordSuccess(downloadClient.Definition.Id);
_indexerStatusService.RecordSuccess(release.IndexerId);
}

View File

@@ -1,3 +1,4 @@
using System.Threading.Tasks;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
@@ -7,6 +8,6 @@ namespace NzbDrone.Core.Download
public interface IDownloadClient : IProvider
{
DownloadProtocol Protocol { get; }
string Download(ReleaseInfo release, bool redirect);
Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer);
}
}

View File

@@ -8,12 +8,12 @@ namespace NzbDrone.Core.Download
{
public interface IValidateNzbs
{
void Validate(string filename, byte[] fileContent);
void Validate(byte[] fileContent);
}
public class NzbValidationService : IValidateNzbs
{
public void Validate(string filename, byte[] fileContent)
public void Validate(byte[] fileContent)
{
var reader = new StreamReader(new MemoryStream(fileContent));
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Download
if (nzb == null)
{
throw new InvalidNzbException("Invalid NZB: No Root element [{0}]", filename);
throw new InvalidNzbException("Invalid NZB: No Root element");
}
// nZEDb has an bug in their error reporting code spitting out invalid http status codes
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Download
if (!nzb.Name.LocalName.Equals("nzb"))
{
throw new InvalidNzbException("Invalid NZB: Unexpected root element. Expected 'nzb' found '{0}' [{1}]", nzb.Name.LocalName, filename);
throw new InvalidNzbException("Invalid NZB: Unexpected root element. Expected 'nzb' found '{0}'", nzb.Name.LocalName);
}
var ns = nzb.Name.Namespace;
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Download
if (files.Empty())
{
throw new InvalidNzbException("Invalid NZB: No files [{0}]", filename);
throw new InvalidNzbException("Invalid NZB: No files");
}
}
}

View File

@@ -1,5 +1,7 @@
using System;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using MonoTorrent;
using NLog;
using NzbDrone.Common.Disk;
@@ -39,7 +41,7 @@ namespace NzbDrone.Core.Download
protected abstract string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent);
protected abstract string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink);
public override string Download(ReleaseInfo release, bool redirect)
public override async Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer)
{
var torrentInfo = release as TorrentInfo;
@@ -66,7 +68,7 @@ namespace NzbDrone.Core.Download
{
try
{
return DownloadFromWebUrl(release, torrentUrl);
return await DownloadFromWebUrl(release, indexer, torrentUrl);
}
catch (Exception ex)
{
@@ -87,7 +89,7 @@ namespace NzbDrone.Core.Download
}
catch (NotSupportedException ex)
{
throw new ReleaseDownloadException(release, "Magnet not supported by download client. ({0})", ex.Message);
throw new ReleaseDownloadException("Magnet not supported by download client. ({0})", ex.Message);
}
}
}
@@ -103,7 +105,7 @@ namespace NzbDrone.Core.Download
{
if (torrentUrl.IsNullOrWhiteSpace())
{
throw new ReleaseDownloadException(release, "Magnet not supported by download client. ({0})", ex.Message);
throw new ReleaseDownloadException("Magnet not supported by download client. ({0})", ex.Message);
}
_logger.Debug("Magnet not supported by download client, trying torrent. ({0})", ex.Message);
@@ -112,74 +114,31 @@ namespace NzbDrone.Core.Download
if (torrentUrl.IsNotNullOrWhiteSpace())
{
return DownloadFromWebUrl(release, torrentUrl);
return await DownloadFromWebUrl(release, indexer, torrentUrl);
}
}
return null;
}
private string DownloadFromWebUrl(ReleaseInfo release, string torrentUrl)
private async Task<string> DownloadFromWebUrl(ReleaseInfo release, IIndexer indexer, string torrentUrl)
{
byte[] torrentFile = null;
try
torrentFile = await indexer.Download(new Uri(torrentUrl));
// handle magnet URLs
if (torrentFile.Length >= 7
&& torrentFile[0] == 0x6d
&& torrentFile[1] == 0x61
&& torrentFile[2] == 0x67
&& torrentFile[3] == 0x6e
&& torrentFile[4] == 0x65
&& torrentFile[5] == 0x74
&& torrentFile[6] == 0x3a)
{
var request = new HttpRequest(torrentUrl);
request.Headers.Accept = "application/x-bittorrent";
request.AllowAutoRedirect = false;
var response = _httpClient.Get(request);
if (response.StatusCode == HttpStatusCode.MovedPermanently ||
response.StatusCode == HttpStatusCode.Found ||
response.StatusCode == HttpStatusCode.SeeOther)
{
var locationHeader = response.Headers.GetSingleValue("Location");
_logger.Trace("Torrent request is being redirected to: {0}", locationHeader);
if (locationHeader != null)
{
if (locationHeader.StartsWith("magnet:"))
{
return DownloadFromMagnetUrl(release, locationHeader);
}
return DownloadFromWebUrl(release, locationHeader);
}
throw new WebException("Remote website tried to redirect without providing a location.");
}
torrentFile = response.ResponseData;
_logger.Debug("Downloading torrent for release '{0}' finished ({1} bytes from {2})", release.Title, torrentFile.Length, torrentUrl);
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
{
_logger.Error(ex, "Downloading torrent file for release '{0}' failed since it no longer exists ({1})", release.Title, torrentUrl);
throw new ReleaseUnavailableException(release, "Downloading torrent failed", ex);
}
if ((int)ex.Response.StatusCode == 429)
{
_logger.Error("API Grab Limit reached for {0}", torrentUrl);
}
else
{
_logger.Error(ex, "Downloading torrent file for release '{0}' failed ({1})", release.Title, torrentUrl);
}
throw new ReleaseDownloadException(release, "Downloading torrent failed", ex);
}
catch (WebException ex)
{
_logger.Error(ex, "Downloading torrent file for release '{0}' failed ({1})", release.Title, torrentUrl);
throw new ReleaseDownloadException(release, "Downloading torrent failed", ex);
var magnetUrl = Encoding.UTF8.GetString(torrentFile);
return DownloadFromMagnetUrl(release, magnetUrl);
}
var filename = string.Format("{0}.torrent", StringUtil.CleanFileName(release.Title));

View File

@@ -1,9 +1,9 @@
using System.Net;
using System;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
@@ -15,17 +15,14 @@ namespace NzbDrone.Core.Download
where TSettings : IProviderConfig, new()
{
protected readonly IHttpClient _httpClient;
private readonly IValidateNzbs _nzbValidationService;
protected UsenetClientBase(IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IValidateNzbs nzbValidationService,
Logger logger)
: base(configService, diskProvider, logger)
{
_httpClient = httpClient;
_nzbValidationService = nzbValidationService;
}
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
@@ -33,9 +30,9 @@ namespace NzbDrone.Core.Download
protected abstract string AddFromNzbFile(ReleaseInfo release, string filename, byte[] fileContents);
protected abstract string AddFromLink(ReleaseInfo release);
public override string Download(ReleaseInfo release, bool redirect)
public override async Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer)
{
var url = release.DownloadUrl;
var url = new Uri(release.DownloadUrl);
if (redirect)
{
@@ -46,40 +43,7 @@ namespace NzbDrone.Core.Download
byte[] nzbData;
try
{
var request = new HttpRequest(url);
nzbData = _httpClient.Get(request).ResponseData;
_logger.Debug("Downloaded nzb for release '{0}' finished ({1} bytes from {2})", release.Title, nzbData.Length, url);
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
{
_logger.Error(ex, "Downloading nzb file for release '{0}' failed since it no longer exists ({1})", release.Title, url);
throw new ReleaseUnavailableException(release, "Downloading nzb failed", ex);
}
if ((int)ex.Response.StatusCode == 429)
{
_logger.Error("API Grab Limit reached for {0}", url);
}
else
{
_logger.Error(ex, "Downloading nzb for release '{0}' failed ({1})", release.Title, url);
}
throw new ReleaseDownloadException(release, "Downloading nzb failed", ex);
}
catch (WebException ex)
{
_logger.Error(ex, "Downloading nzb for release '{0}' failed ({1})", release.Title, url);
throw new ReleaseDownloadException(release, "Downloading nzb failed", ex);
}
_nzbValidationService.Validate(filename, nzbData);
nzbData = await indexer.Download(url);
_logger.Info("Adding report [{0}] to the queue.", release.Title);
return AddFromNzbFile(release, filename, nzbData);

View File

@@ -1,28 +1,33 @@
using System;
using System;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Exceptions
{
public class DownloadClientRejectedReleaseException : ReleaseDownloadException
{
public ReleaseInfo Release { get; set; }
public DownloadClientRejectedReleaseException(ReleaseInfo release, string message, params object[] args)
: base(release, message, args)
: base(message, args)
{
Release = release;
}
public DownloadClientRejectedReleaseException(ReleaseInfo release, string message)
: base(release, message)
: base(message)
{
Release = release;
}
public DownloadClientRejectedReleaseException(ReleaseInfo release, string message, Exception innerException, params object[] args)
: base(release, message, innerException, args)
: base(message, innerException, args)
{
Release = release;
}
public DownloadClientRejectedReleaseException(ReleaseInfo release, string message, Exception innerException)
: base(release, message, innerException)
: base(message, innerException)
{
Release = release;
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using NzbDrone.Common.Exceptions;
using NzbDrone.Core.Parser.Model;
@@ -6,30 +6,24 @@ namespace NzbDrone.Core.Exceptions
{
public class ReleaseDownloadException : NzbDroneException
{
public ReleaseInfo Release { get; set; }
public ReleaseDownloadException(ReleaseInfo release, string message, params object[] args)
public ReleaseDownloadException(string message, params object[] args)
: base(message, args)
{
Release = release;
}
public ReleaseDownloadException(ReleaseInfo release, string message)
public ReleaseDownloadException(string message)
: base(message)
{
Release = release;
}
public ReleaseDownloadException(ReleaseInfo release, string message, Exception innerException, params object[] args)
public ReleaseDownloadException(string message, Exception innerException, params object[] args)
: base(message, innerException, args)
{
Release = release;
}
public ReleaseDownloadException(ReleaseInfo release, string message, Exception innerException)
public ReleaseDownloadException(string message, Exception innerException)
: base(message, innerException)
{
Release = release;
}
}
}

View File

@@ -1,27 +1,27 @@
using System;
using System;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Exceptions
{
public class ReleaseUnavailableException : ReleaseDownloadException
{
public ReleaseUnavailableException(ReleaseInfo release, string message, params object[] args)
: base(release, message, args)
public ReleaseUnavailableException(string message, params object[] args)
: base(message, args)
{
}
public ReleaseUnavailableException(ReleaseInfo release, string message)
: base(release, message)
public ReleaseUnavailableException(string message)
: base(message)
{
}
public ReleaseUnavailableException(ReleaseInfo release, string message, Exception innerException, params object[] args)
: base(release, message, innerException, args)
public ReleaseUnavailableException(string message, Exception innerException, params object[] args)
: base(message, innerException, args)
{
}
public ReleaseUnavailableException(ReleaseInfo release, string message, Exception innerException)
: base(release, message, innerException)
public ReleaseUnavailableException(string message, Exception innerException)
: base(message, innerException)
{
}
}

View File

@@ -44,14 +44,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
return new HealthCheck(GetType(),
HealthCheckResult.Error,
_localizationService.GetLocalizedString("ApplicationStatusCheckAllClientMessage"),
"#applications_are_unavailable_due_to_failures");
"#applications-are-unavailable-due-to-failures");
}
return new HealthCheck(GetType(),
HealthCheckResult.Warning,
string.Format(_localizationService.GetLocalizedString("ApplicationStatusCheckSingleClientMessage"),
string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))),
"#applications_are_unavailable_due_to_failures");
"#applications-are-unavailable-due-to-failures");
}
}
}

View File

@@ -37,10 +37,10 @@ namespace NzbDrone.Core.HealthCheck.Checks
if (backOffProviders.Count == enabledProviders.Count)
{
return new HealthCheck(GetType(), HealthCheckResult.Error, _localizationService.GetLocalizedString("DownloadClientStatusCheckAllClientMessage"), "#download_clients_are_unavailable_due_to_failures");
return new HealthCheck(GetType(), HealthCheckResult.Error, _localizationService.GetLocalizedString("DownloadClientStatusCheckAllClientMessage"), "#download-clients-are-unavailable-due-to-failures");
}
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("DownloadClientStatusCheckSingleClientMessage"), string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))), "#download_clients_are_unavailable_due_to_failures");
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("DownloadClientStatusCheckSingleClientMessage"), string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))), "#download-clients-are-unavailable-due-to-failures");
}
}
}

View File

@@ -46,14 +46,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
return new HealthCheck(GetType(),
HealthCheckResult.Error,
_localizationService.GetLocalizedString("IndexerLongTermStatusCheckAllClientMessage"),
"#indexers_are_unavailable_due_to_failures");
"#indexers-are-unavailable-due-to-failures");
}
return new HealthCheck(GetType(),
HealthCheckResult.Warning,
string.Format(_localizationService.GetLocalizedString("IndexerLongTermStatusCheckSingleClientMessage"),
string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))),
"#indexers_are_unavailable_due_to_failures");
"#indexers-are-unavailable-due-to-failures");
}
}
}

View File

@@ -44,14 +44,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
return new HealthCheck(GetType(),
HealthCheckResult.Error,
_localizationService.GetLocalizedString("IndexerStatusCheckAllClientMessage"),
"#indexers_are_unavailable_due_to_failures");
"#indexers-are-unavailable-due-to-failures");
}
return new HealthCheck(GetType(),
HealthCheckResult.Warning,
string.Format(_localizationService.GetLocalizedString("IndexerStatusCheckSingleClientMessage"),
string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))),
"#indexers_are_unavailable_due_to_failures");
"#indexers-are-unavailable-due-to-failures");
}
}
}

View File

@@ -55,7 +55,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
HealthCheckResult.Warning,
string.Format(_localizationService.GetLocalizedString("NewznabVipCheckExpiringClientMessage"),
string.Join(", ", expiringProviders.Select(v => v.Definition.Name))),
"#newznab_vip_expiring");
"#newznab-vip-expiring");
}
if (!expiredProviders.Empty())
@@ -64,7 +64,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
HealthCheckResult.Warning,
string.Format(_localizationService.GetLocalizedString("NewznabVipCheckExpiredClientMessage"),
string.Join(", ", expiredProviders.Select(v => v.Definition.Name))),
"#newznab_vip_expired");
"#newznab-vip-expired");
}
return new HealthCheck(GetType());

View File

@@ -38,7 +38,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
HealthCheckResult.Warning,
string.Format(_localizationService.GetLocalizedString("IndexerObsoleteCheckMessage"),
string.Join(", ", oldIndexers.Select(v => v.Name))),
"#indexers_are_obsolete");
"#indexers-are-obsolete");
}
public override bool CheckOnSchedule => false;

View File

@@ -23,12 +23,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
if (!Enum.GetNames(typeof(ReleaseBranches)).Any(x => x.ToLower() == currentBranch))
{
if (currentBranch == "develop")
{
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("ReleaseBranchCheckPreviousVersionMessage"), _configFileService.Branch), "#branch_is_for_a_previous_version");
}
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("ReleaseBranchCheckOfficialBranchMessage"), _configFileService.Branch), "#branch_is_not_a_valid_release_branch");
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("ReleaseBranchCheckOfficialBranchMessage"), _configFileService.Branch), "#branch-is-not-a-valid-release-branch");
}
return new HealthCheck(GetType());
@@ -36,6 +31,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
public enum ReleaseBranches
{
Develop,
Nightly
}
}

View File

@@ -48,7 +48,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
return new HealthCheck(GetType(),
HealthCheckResult.Error,
string.Format(_localizationService.GetLocalizedString("UpdateCheckStartupTranslocationMessage"), startupFolder),
"#cannot_install_update_because_startup_folder_is_in_an_app_translocation_folder.");
"#cannot-install-update-because-startup-folder-is-in-an-app-translocation-folder.");
}
if (!_diskProvider.FolderWritable(startupFolder))
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
return new HealthCheck(GetType(),
HealthCheckResult.Error,
string.Format(_localizationService.GetLocalizedString("UpdateCheckStartupNotWritableMessage"), startupFolder, Environment.UserName),
"#cannot_install_update_because_startup_folder_is_not_writable_by_the_user");
"#cannot-install-update-because-startup-folder-is-not-writable-by-the-user");
}
if (!_diskProvider.FolderWritable(uiFolder))
@@ -64,7 +64,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
return new HealthCheck(GetType(),
HealthCheckResult.Error,
string.Format(_localizationService.GetLocalizedString("UpdateCheckUINotWritableMessage"), uiFolder, Environment.UserName),
"#cannot_install_update_because_ui_folder_is_not_writable_by_the_user");
"#cannot-install-update-because-ui-folder-is-not-writable-by-the-user");
}
}

View File

@@ -34,12 +34,12 @@ namespace NzbDrone.Core.HealthCheck
private static string MakeWikiFragment(string message)
{
return "#" + CleanFragmentRegex.Replace(message.ToLower(), string.Empty).Replace(' ', '_');
return "#" + CleanFragmentRegex.Replace(message.ToLower(), string.Empty).Replace(' ', '-');
}
private static HttpUri MakeWikiUrl(string fragment)
{
return new HttpUri("https://wiki.servarr.com/Prowlarr_System#") + new HttpUri(fragment);
return new HttpUri("https://wiki.servarr.com/prowlarr/system#") + new HttpUri(fragment);
}
}

View File

@@ -119,14 +119,14 @@ namespace NzbDrone.Core.History
if (message.Query is MovieSearchCriteria)
{
history.Data.Add("ImdbId", ((MovieSearchCriteria)message.Query).ImdbId ?? string.Empty);
history.Data.Add("ImdbId", ((MovieSearchCriteria)message.Query).FullImdbId ?? string.Empty);
history.Data.Add("TmdbId", ((MovieSearchCriteria)message.Query).TmdbId?.ToString() ?? string.Empty);
history.Data.Add("TraktId", ((MovieSearchCriteria)message.Query).TraktId?.ToString() ?? string.Empty);
}
if (message.Query is TvSearchCriteria)
{
history.Data.Add("ImdbId", ((TvSearchCriteria)message.Query).ImdbId ?? string.Empty);
history.Data.Add("ImdbId", ((TvSearchCriteria)message.Query).FullImdbId ?? string.Empty);
history.Data.Add("TvdbId", ((TvSearchCriteria)message.Query).TvdbId?.ToString() ?? string.Empty);
history.Data.Add("TraktId", ((TvSearchCriteria)message.Query).TraktId?.ToString() ?? string.Empty);
history.Data.Add("RId", ((TvSearchCriteria)message.Query).RId?.ToString() ?? string.Empty);

View File

@@ -1,4 +1,5 @@
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.IndexerSearch.Definitions
{
@@ -20,5 +21,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
return false;
}
}
public string FullImdbId => ParseUtil.GetFullImdbId(ImdbId);
}
}

View File

@@ -19,6 +19,8 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public string SanitizedTvSearchString => (SanitizedSearchTerm + " " + EpisodeSearchString).Trim();
public string EpisodeSearchString => GetEpisodeSearchString();
public string FullImdbId => ParseUtil.GetFullImdbId(ImdbId);
public override bool RssSearch
{
get

View File

@@ -80,13 +80,13 @@ namespace NzbDrone.Core.IndexerSearch
r.InfoUrl == null ? null : new XElement("comments", r.InfoUrl),
r.PublishDate == DateTime.MinValue ? new XElement("pubDate", XmlDateFormat(DateTime.Now)) : new XElement("pubDate", XmlDateFormat(r.PublishDate)),
new XElement("size", r.Size),
r.Category == null ? null : from c in r.Category select new XElement("category", c.Id),
r.Categories == null ? null : from c in r.Categories select new XElement("category", c.Id),
new XElement(
"enclosure",
new XAttribute("url", r.DownloadUrl ?? t.MagnetUrl ?? string.Empty),
r.Size == null ? null : new XAttribute("length", r.Size),
new XAttribute("type", protocol == DownloadProtocol.Torrent ? "application/x-bittorrent" : "application/x-nzb")),
r.Category == null ? null : from c in r.Category select GetNabElement("category", c.Id, protocol),
r.Categories == null ? null : from c in r.Categories select GetNabElement("category", c.Id, protocol),
r.IndexerFlags == null ? null : from f in r.IndexerFlags select GetNabElement("tag", f.Name, protocol),
GetNabElement("rageid", r.TvRageId, protocol),
GetNabElement("thetvdb", r.TvdbId, protocol),

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Download;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Events;
using NzbDrone.Core.IndexerSearch.Definitions;

View File

@@ -6,6 +6,7 @@ using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Cardigann;
using NzbDrone.Core.Messaging.Commands;
@@ -24,7 +25,7 @@ namespace NzbDrone.Core.IndexerVersions
public class IndexerDefinitionUpdateService : IIndexerDefinitionUpdateService, IExecute<IndexerDefinitionUpdateCommand>
{
private const int DEFINITION_VERSION = 1;
private readonly List<string> _defintionBlacklist = new List<string>() { "blutopia", "beyond-hd", "beyond-hd-oneurl", "hdbits" };
private readonly List<string> _defintionBlacklist = new List<string>() { "aither", "animeworld", "blutopia", "beyond-hd", "beyond-hd-oneurl", "hdbits", "shareisland" };
private readonly IHttpClient _httpClient;
private readonly IAppFolderInfo _appFolderInfo;
@@ -59,6 +60,40 @@ namespace NzbDrone.Core.IndexerVersions
var request = new HttpRequest($"https://indexers.prowlarr.com/master/{DEFINITION_VERSION}");
var response = _httpClient.Get<List<CardigannMetaDefinition>>(request);
indexerList = response.Resource.Where(i => !_defintionBlacklist.Contains(i.File)).ToList();
var definitionFolder = Path.Combine(_appFolderInfo.AppDataFolder, "Definitions", "Custom");
var directoryInfo = new DirectoryInfo(definitionFolder);
if (directoryInfo.Exists)
{
var files = directoryInfo.GetFiles($"*.yml");
foreach (var file in files)
{
_logger.Debug("Loading Custom Cardigann definition " + file.FullName);
try
{
var definitionString = File.ReadAllText(file.FullName);
var definition = _deserializer.Deserialize<CardigannMetaDefinition>(definitionString);
definition.File = Path.GetFileNameWithoutExtension(file.Name);
if (indexerList.Any(i => i.File == definition.File || i.Name == definition.Name))
{
_logger.Warn("Custom Cardigann definition {0} does not have unique file name or Indexer name", file.FullName);
continue;
}
indexerList.Add(definition);
}
catch (Exception e)
{
_logger.Error($"Error while parsing custom Cardigann definition {file.FullName}\n{e}");
}
}
}
}
catch
{
@@ -89,7 +124,8 @@ namespace NzbDrone.Core.IndexerVersions
{
var req = new HttpRequest($"https://indexers.prowlarr.com/master/{DEFINITION_VERSION}/{id}");
var response = _httpClient.Get(req);
return _deserializer.Deserialize<CardigannDefinition>(response.Content);
var definition = _deserializer.Deserialize<CardigannDefinition>(response.Content);
return CleanIndexerDefinition(definition);
}
private CardigannDefinition LoadIndexerDef(string fileKey)
@@ -107,7 +143,7 @@ namespace NzbDrone.Core.IndexerVersions
if (directoryInfo.Exists)
{
var files = directoryInfo.GetFiles($"{fileKey}.yml");
var files = directoryInfo.GetFiles($"{fileKey}.yml", SearchOption.AllDirectories);
if (files.Any())
{
@@ -118,42 +154,7 @@ namespace NzbDrone.Core.IndexerVersions
var definitionString = File.ReadAllText(file.FullName);
var definition = _deserializer.Deserialize<CardigannDefinition>(definitionString);
//defaults
if (definition.Settings == null)
{
definition.Settings = new List<SettingsField>
{
new SettingsField { Name = "username", Label = "Username", Type = "text" },
new SettingsField { Name = "password", Label = "Password", Type = "password" }
};
}
if (definition.Encoding == null)
{
definition.Encoding = "UTF-8";
}
if (definition.Login != null && definition.Login.Method == null)
{
definition.Login.Method = "form";
}
if (definition.Search.Paths == null)
{
definition.Search.Paths = new List<SearchPathBlock>();
}
// convert definitions with a single search Path to a Paths entry
if (definition.Search.Path != null)
{
definition.Search.Paths.Add(new SearchPathBlock
{
Path = definition.Search.Path,
Inheritinputs = true
});
}
return definition;
return CleanIndexerDefinition(definition);
}
catch (Exception e)
{
@@ -165,6 +166,45 @@ namespace NzbDrone.Core.IndexerVersions
return GetHttpDefinition(fileKey);
}
private CardigannDefinition CleanIndexerDefinition(CardigannDefinition definition)
{
if (definition.Settings == null)
{
definition.Settings = new List<SettingsField>
{
new SettingsField { Name = "username", Label = "Username", Type = "text" },
new SettingsField { Name = "password", Label = "Password", Type = "password" }
};
}
if (definition.Encoding == null)
{
definition.Encoding = "UTF-8";
}
if (definition.Login != null && definition.Login.Method == null)
{
definition.Login.Method = "form";
}
if (definition.Search.Paths == null)
{
definition.Search.Paths = new List<SearchPathBlock>();
}
// convert definitions with a single search Path to a Paths entry
if (definition.Search.Path != null)
{
definition.Search.Paths.Add(new SearchPathBlock
{
Path = definition.Search.Path,
Inheritinputs = true
});
}
return definition;
}
public void Execute(IndexerDefinitionUpdateCommand message)
{
UpdateLocalDefinitions();

View File

@@ -0,0 +1,59 @@
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.UNIT3D;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions
{
public class Aither : Unit3dBase
{
public override string Name => "Aither";
public override string BaseUrl => "https://aither.cc/";
public override string Description => "Aither is a Private Torrent Tracker for HD MOVIES / TV";
public override string Language => "en-us";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public Aither(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
protected override IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies, "Movie");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TV, "TV");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio, "Music");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.PCGames, "Games");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.XXX, "XXX");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.TVSport, "Sport");
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.PC, "Software/Apps");
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.BooksEBook, "Ebooks/Magazines");
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.AudioAudiobook, "AudioBooks");
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.Other, "Education");
return caps;
}
}
}

View File

@@ -23,7 +23,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
public class AnimeBytes : HttpIndexerBase<AnimeBytesSettings>
public class AnimeBytes : TorrentIndexerBase<AnimeBytesSettings>
{
public override string Name => "AnimeBytes";
public override string BaseUrl => "https://animebytes.tv/";
@@ -449,7 +449,7 @@ namespace NzbDrone.Core.Indexers.Definitions
Guid = guid.AbsoluteUri,
DownloadUrl = linkUri.AbsoluteUri,
PublishDate = publishDate,
Category = category,
Categories = category,
Description = description,
Size = size,
Seeders = seeders,
@@ -494,10 +494,10 @@ namespace NzbDrone.Core.Indexers.Definitions
Username = "";
}
[FieldDefinition(1, Label = "Passkey", HelpText = "Site Passkey")]
[FieldDefinition(1, Label = "Passkey", Privacy = PrivacyLevel.Password, Type = FieldType.Password, HelpText = "Site Passkey")]
public string Passkey { get; set; }
[FieldDefinition(1, Label = "Username", HelpText = "Site username")]
[FieldDefinition(1, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -21,7 +21,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
public class AnimeTorrents : HttpIndexerBase<AnimeTorrentsSettings>
public class AnimeTorrents : TorrentIndexerBase<AnimeTorrentsSettings>
{
public override string Name => "AnimeTorrents";
@@ -294,7 +294,7 @@ namespace NzbDrone.Core.Indexers.Definitions
rCat = rCat.Substring(rCatIdx + 4);
}
release.Category = _categories.MapTrackerCatToNewznab(rCat);
release.Categories = _categories.MapTrackerCatToNewznab(rCat);
if (row.QuerySelector("img[alt=\"Gold Torrent\"]") != null)
{
@@ -350,10 +350,10 @@ namespace NzbDrone.Core.Indexers.Definitions
Password = "";
}
[FieldDefinition(1, Label = "Username", HelpText = "Site username")]
[FieldDefinition(1, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(2, Label = "Password", Type = FieldType.Password, HelpText = "Site password", Privacy = PrivacyLevel.Password)]
[FieldDefinition(2, Label = "Password", HelpText = "Site Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
public NzbDroneValidationResult Validate()

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