Compare commits

...

179 Commits

Author SHA1 Message Date
Qstick
3ca6f83a4d fixup! New: (Cardigann) Paging Support 2023-02-26 17:06:47 -06:00
Qstick
fb5b325271 fixup! New: (Cardigann) Add AllowEmptyInputs
Co-Authored-By: Bogdan <mynameisbogdan@users.noreply.github.com>
2023-02-26 16:48:53 -06:00
Qstick
ec8025c3dc New: (Cardigann) Add AllowEmptyInputs
Co-Authored-By: Bogdan <mynameisbogdan@users.noreply.github.com>
2023-02-26 16:41:02 -06:00
Qstick
b42bf2cf20 New: (Cardigann) Paging Support 2023-02-25 20:18:21 -06:00
Qstick
712d95e6ce Cleanup request paging code 2023-02-25 20:17:46 -06:00
Bogdan
24f6c937da Fixed: (Cardigann) Prevent fetching the first page multiple times 2023-02-26 01:56:56 +02:00
Bogdan
e94aa7c499 Fixed: (DateTimeUtil) Move check for Rfc1123ZPattern
Co-authored-by: Sergey M <msergein@users.noreply.github.com>
2023-02-25 23:55:42 +02:00
Bogdan
201bc1944b Fixed: (DateTimeUtil) Check first for Standard Format in ParseDateTimeGoLang 2023-02-25 23:16:57 +02:00
Bogdan
09e40e0060 Fixed: (Rarbg) Set rate limit to 31s for RSS sync 2023-02-25 19:56:06 +02:00
Qstick
348d90a37e Fixed: (Cardigann) Invariant date string parsing for "reltime", "timeago", "fuzzytime"
Fixes #835
2023-02-25 11:52:24 -06:00
Qstick
726dc34424 Improve GetLongFromString and ParseFields
2700X faster
2023-02-25 11:52:24 -06:00
Qstick
2e9f6cd94b More Improvement to unix timestamp performance 2023-02-25 11:52:24 -06:00
Bogdan
495f61f412 Improve unix timestamp performance 2023-02-25 11:52:24 -06:00
Qstick
0f11f414b6 Benchmark Framework 2023-02-25 11:52:24 -06:00
Bogdan
d397cdf5fb Fixed: (Cardigann) Implement validate as field filter 2023-02-25 17:45:34 +02:00
Bogdan
888b514dd8 Fixed: (Cardigann) Switch to DateTime standard 2023-02-24 15:26:44 +02:00
Bogdan
caab337379 Fixed: (Cardigann) Parse text templates only if necessary 2023-02-23 08:02:37 +02:00
Bogdan
26bea14137 Fixed: (GreatPosterWall) Use cookies for 2FA
Co-authored-by: ilike2burnthing <59480337+ilike2burnthing@users.noreply.github.com>
2023-02-23 07:01:36 +02:00
Qstick
5f26287234 Bump version to 1.3.1 2023-02-22 21:40:18 -06:00
Bogdan
6ec761c217 Fixed: (Cardigann) Change UseBeforeResponse to Usebeforeresponse 2023-02-23 02:13:41 +02:00
Bogdan
b85679de56 Fixed: Filter releases with null description 2023-02-23 01:29:56 +02:00
Bogdan
71775b97a3 Fixed: (Rarbg) Check for rate limits before parsing token errors 2023-02-22 09:30:22 +02:00
bakerboy448
5bb3dbfbf5 Fixed: (Rarbg) Change app_id per site request 2023-02-21 23:39:30 +02:00
Bogdan
b608a7a904 Fixed: (FunFile) Change download url 2023-02-21 23:32:50 +02:00
Bogdan
4ad992f76a Fixed: (UI) Replace api. only if it's a subdomain 2023-02-21 22:59:08 +02:00
Bogdan
95497480a2 Fixed: (GreatPosterWall) Remove cookies only if redirected to login.php 2023-02-21 02:42:26 +02:00
Qstick
cc57866ab0 New: Filter releases by search criteria
Co-Authored-By: Bogdan <mynameisbogdan@users.noreply.github.com>
2023-02-20 18:41:39 -06:00
Qstick
dbc4989a95 Fixed: (IndexerSearch) Update isRss logic for new properties 2023-02-20 18:41:39 -06:00
Bogdan
af4961e3e6 Fixed: (Rarbg) update cats 2023-02-21 02:38:29 +02:00
Bogdan
0ec54906c6 Fixed: (Caridgann) Custom headers in login and download blocks 2023-02-21 02:37:49 +02:00
Qstick
35f85fc986 More update tests 2023-02-19 23:40:53 -06:00
Qstick
0aedafb278 Fix update tests 2023-02-19 23:09:35 -06:00
Qstick
54dce448a8 Added react-hooks lint rules
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2023-02-19 23:09:35 -06:00
Servarr
3c915002c6 Automated API Docs update 2023-02-19 19:41:55 -06:00
Qstick
e32f8f4330 Remove unused tinytwitter library 2023-02-19 19:26:03 -06:00
Qstick
5abb5ada49 New: Ping Endpoint 2023-02-19 19:23:26 -06:00
Qstick
6579385110 Cleanup multi-platform code 2023-02-19 19:23:05 -06:00
Mark McDowall
1c6e5543df New: Return static response to requests while app is starting 2023-02-19 19:06:13 -06:00
Qstick
85737aacbe Bump version to 1.3.0 2023-02-19 18:14:12 -06:00
Servarr
30c3aedeb1 Automated API Docs update 2023-02-19 17:23:52 -06:00
Qstick
1640980e2b New: OnGrab Notifications 2023-02-19 17:16:05 -06:00
Bogdan
99bc56efb6 Fixed: (Indexers) Rate limit for download and auth 2023-02-19 18:54:17 +02:00
bakerboy448
04276eb587 Fixed: (Rarbg) Updated app_id per site request (#1447) 2023-02-19 18:23:18 +02:00
Bogdan
34c560fd3a Fixed: (CardigannBase) Remedy for casting strings to booleans 2023-02-19 17:07:25 +02:00
Bogdan
caa8bb05a7 Fixed: (Newznab API) Response with StatusCode 429 when limits are reached 2023-02-19 10:43:26 +02:00
Qstick
773e8ff1f4 Bump version to 1.2.2 2023-02-19 00:15:54 -06:00
Qstick
0984976378 Bump DryIoc, YamlDotNet, AngleSharp 2023-02-18 21:12:08 -06:00
Qstick
fcb3c96455 Call async methods when in an async method 2023-02-18 15:03:35 -06:00
Qstick
acf7a425b5 Add global analyzer config 2023-02-18 15:03:35 -06:00
Qstick
da898fe958 Remove Non-Failing Rules 2023-02-18 15:03:35 -06:00
Qstick
5bb3ea0806 Remove unnecessary assignments to default type value
The .NET runtime initializes all fields of reference types to their default values before running the constructor. In most cases, explicitly initializing a field to its default value in a constructor is redundant, adding maintenance costs and potentially degrading performance
2023-02-18 15:03:35 -06:00
Qstick
b41cb80e33 Use const where appropriate
The value of a const field is computed at compile time and stored in the metadata, which improves run-time performance when it is compared to a static readonly field.
2023-02-18 15:03:35 -06:00
Qstick
a39341be4b Enable all analyzers to default back to our rules 2023-02-18 15:03:35 -06:00
Weblate
27b3d8618a Translated using Weblate (Ukrainian)
Currently translated at 73.3% (349 of 476 strings)

Translated using Weblate (Slovak)

Currently translated at 22.6% (108 of 476 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 23.5% (112 of 476 strings)

Translated using Weblate (Catalan)

Currently translated at 74.5% (355 of 476 strings)

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

Currently translated at 99.5% (474 of 476 strings)

Translated using Weblate (Arabic)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (476 of 476 strings)

Translated using Weblate (Vietnamese)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Turkish)

Currently translated at 71.4% (340 of 476 strings)

Translated using Weblate (Thai)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Swedish)

Currently translated at 86.3% (411 of 476 strings)

Translated using Weblate (Russian)

Currently translated at 76.4% (364 of 476 strings)

Translated using Weblate (Romanian)

Currently translated at 72.0% (343 of 476 strings)

Translated using Weblate (Portuguese)

Currently translated at 79.2% (377 of 476 strings)

Translated using Weblate (Polish)

Currently translated at 74.1% (353 of 476 strings)

Translated using Weblate (Dutch)

Currently translated at 88.4% (421 of 476 strings)

Translated using Weblate (Korean)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Japanese)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Italian)

Currently translated at 99.3% (473 of 476 strings)

Translated using Weblate (Icelandic)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.5% (474 of 476 strings)

Translated using Weblate (Hindi)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Hebrew)

Currently translated at 81.3% (387 of 476 strings)

Translated using Weblate (French)

Currently translated at 98.5% (469 of 476 strings)

Translated using Weblate (Finnish)

Currently translated at 99.1% (472 of 476 strings)

Translated using Weblate (Spanish)

Currently translated at 77.9% (371 of 476 strings)

Translated using Weblate (Greek)

Currently translated at 99.5% (474 of 476 strings)

Translated using Weblate (German)

Currently translated at 98.1% (467 of 476 strings)

Translated using Weblate (Danish)

Currently translated at 74.5% (355 of 476 strings)

Translated using Weblate (Czech)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Spanish)

Currently translated at 77.9% (371 of 476 strings)

Translated using Weblate (Arabic)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Bulgarian)

Currently translated at 67.0% (319 of 476 strings)

Translated using Weblate (Spanish)

Currently translated at 77.9% (371 of 476 strings)

Translated using Weblate (Croatian)

Currently translated at 18.2% (87 of 476 strings)

Translated using Weblate (Croatian)

Currently translated at 18.2% (87 of 476 strings)

Translated using Weblate (Ukrainian)

Currently translated at 73.3% (349 of 476 strings)

Translated using Weblate (Slovak)

Currently translated at 22.6% (108 of 476 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 23.5% (112 of 476 strings)

Translated using Weblate (Catalan)

Currently translated at 74.5% (355 of 476 strings)

Translated using Weblate (Catalan)

Currently translated at 74.5% (355 of 476 strings)

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

Currently translated at 99.5% (474 of 476 strings)

Translated using Weblate (Arabic)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (476 of 476 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (476 of 476 strings)

Translated using Weblate (Vietnamese)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Turkish)

Currently translated at 71.4% (340 of 476 strings)

Translated using Weblate (Thai)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Swedish)

Currently translated at 86.3% (411 of 476 strings)

Translated using Weblate (Russian)

Currently translated at 76.4% (364 of 476 strings)

Translated using Weblate (Romanian)

Currently translated at 72.0% (343 of 476 strings)

Translated using Weblate (Portuguese)

Currently translated at 79.2% (377 of 476 strings)

Translated using Weblate (Polish)

Currently translated at 74.1% (353 of 476 strings)

Translated using Weblate (Dutch)

Currently translated at 88.4% (421 of 476 strings)

Translated using Weblate (Korean)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Japanese)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Italian)

Currently translated at 99.3% (473 of 476 strings)

Translated using Weblate (Icelandic)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.5% (474 of 476 strings)

Translated using Weblate (Hindi)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Hebrew)

Currently translated at 81.3% (387 of 476 strings)

Translated using Weblate (French)

Currently translated at 98.5% (469 of 476 strings)

Translated using Weblate (Finnish)

Currently translated at 99.1% (472 of 476 strings)

Translated using Weblate (Spanish)

Currently translated at 77.7% (370 of 476 strings)

Translated using Weblate (Greek)

Currently translated at 99.5% (474 of 476 strings)

Translated using Weblate (German)

Currently translated at 98.1% (467 of 476 strings)

Translated using Weblate (Danish)

Currently translated at 74.5% (355 of 476 strings)

Translated using Weblate (Czech)

Currently translated at 71.6% (341 of 476 strings)

Translated using Weblate (Bulgarian)

Currently translated at 67.0% (319 of 476 strings)

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

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

Added translation using Weblate (Estonian)

Added translation using Weblate (Serbian)

Added translation using Weblate (Croatian)

Added translation using Weblate (Bosnian)

Added translation using Weblate (Spanish (Mexico))

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

Translated using Weblate (Ukrainian)

Currently translated at 73.4% (346 of 471 strings)

Translated using Weblate (Russian)

Currently translated at 76.6% (361 of 471 strings)

Translated using Weblate (Dutch)

Currently translated at 88.7% (418 of 471 strings)

Translated using Weblate (Hebrew)

Currently translated at 81.5% (384 of 471 strings)

Translated using Weblate (French)

Currently translated at 98.9% (466 of 471 strings)

Translated using Weblate (Greek)

Currently translated at 75.5% (356 of 471 strings)

Translated using Weblate (Danish)

Currently translated at 74.7% (352 of 471 strings)

Translated using Weblate (French)

Currently translated at 97.0% (457 of 471 strings)

Translated using Weblate (French)

Currently translated at 97.0% (457 of 471 strings)

Translated using Weblate (French)

Currently translated at 97.0% (457 of 471 strings)

Translated using Weblate (French)

Currently translated at 96.1% (453 of 471 strings)

Translated using Weblate (French)

Currently translated at 96.1% (453 of 471 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (471 of 471 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: KevoM <lilmarsu@gmail.com>
Co-authored-by: Vasilis Ieropoulos <kirav96@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: louismaxx <lmdupouy@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translation: Servarr/Prowlarr
2023-02-11 11:28:14 -06:00
Qstick
2ae2a0b184 Delete azuresync.yml 2023-02-11 10:35:07 -06:00
Bogdan
b5e43e7a1a Fixed: (Cardigann) Show redirect url when the response has errors 2023-02-11 08:28:38 +02:00
Bogdan
3a52048dc2 Fixed: (UI) Check for non-array indexerUrls 2023-02-11 08:27:16 +02:00
Bogdan
8b898733ab Fixed: (RuTracker/Toloka) Clean title 2023-02-10 06:46:02 +02:00
Bogdan
f99a2e1164 Fixed: Standardize dashes/single quotes in search term, ignore artist if "VA" 2023-02-10 06:44:39 +02:00
Bogdan
306209fcc2 Fixed: Simplify DateTime alteration 2023-02-09 13:13:16 +02:00
Bogdan
5d09c2b5fa Fixed: (Shazbat) Simplify conditions for CheckIfLoginNeeded 2023-02-09 12:48:39 +02:00
Bogdan
41a9d2d732 New: Add Shazbat 2023-02-09 07:05:29 +02:00
Bogdan
49b120ba55 Revert "Fixed: (Redacted/Orpheus/Libble/SecretCinema) Add SupportsRawSearch"
This reverts commit c46b7c5e4b.
2023-02-08 06:28:05 +02:00
Qstick
a88fc34a78 Fixed: Settings fail to save for some auth setups 2023-02-06 23:12:20 -06:00
Bogdan
c46b7c5e4b Fixed: (Redacted/Orpheus/Libble/SecretCinema) Add SupportsRawSearch 2023-02-07 07:00:27 +02:00
Bogdan
94c45541ae Fixed: (Anidub/Animedia) Use rate limit in sub-requests 2023-02-07 06:59:48 +02:00
Bogdan
f8082047a5 Fixed: (HttpIndexerBase) Catch HttpRequestException/TaskCanceledException 2023-02-05 20:00:10 +02:00
Qstick
011fd57f7d Fixed: Handle null IEnumerable field values in SchemaBuilder 2023-02-05 10:37:35 -06:00
Bogdan
6c35c3fc6f Fixed: (ImmortalSeed/XSpeeds) Sitewide Freeleech 2023-02-05 01:34:36 +02:00
Qstick
5da02c49eb Bump version to 1.2.0 2023-02-04 15:07:01 -06:00
Bogdan
1a339b9ab2 Fixed: (ImmortalSeed) Add sorting to skip the sticky results 2023-02-04 07:39:17 +02:00
Bogdan
94edd7538e Fixed: (GreatPosterWall) Remove JsonProperty 2023-02-04 07:08:58 +02:00
Bogdan
9b2274805e Fixed: (GreatPosterWall) Remove special characters from titles 2023-02-04 07:08:58 +02:00
Bogdan
dbf86efb0a Fixed: (ExecuteAuth) Request timeout of 15s by default, if not set otherwise 2023-02-04 05:52:33 +02:00
Weblate
529fbfd9bd Translated using Weblate (Hebrew)
Currently translated at 80.8% (381 of 471 strings)

Translated using Weblate (Greek)

Currently translated at 73.8% (348 of 471 strings)

Translated using Weblate (Danish)

Currently translated at 74.0% (349 of 471 strings)

Co-authored-by: Nir Israel Hen <nirisraelh@gmail.com>
Co-authored-by: Vasilis Ieropoulos <kirav96@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: hhjuhl <hans@kopula.dk>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/he/
Translation: Servarr/Prowlarr
2023-02-03 21:49:54 -06:00
Bogdan
0ed5bfe0d0 Fixed: (AroLol) Make login possible without 2FA 2023-02-04 05:46:58 +02:00
Bogdan
6a43eb0031 Fixed: (HDBits) Change TVDB search for daily shows, append slash to IndexerUrl 2023-02-04 05:45:38 +02:00
Bogdan
a12001a5ef Fixed: (XSpeeds) Category filtering if single, add sorting to skip sticky 2023-02-04 05:42:11 +02:00
Qstick
b57014762d Fixed: (RuTracker) Update categories 2023-02-01 22:36:37 -06:00
Bogdan
a51a8bf921 Fixed: (GreatPosterWall) Parse categories based on resolution 2023-02-02 06:18:51 +02:00
Martin Häger
e8dc5b3206 Serve plain text files (e.g. logs) as UTF-8. 2023-02-01 22:17:30 -06:00
Bogdan
d4f22f3596 Fixed: (assorted) Use GetArgumentFromQueryString and other minor fixes 2023-02-02 06:09:13 +02:00
Bogdan
b6018a4cd7 Fixed: (norbits) Refactor parsing 2023-02-02 06:06:20 +02:00
Bogdan
ec389987df Fixed: (pornolab) Improvements generator/parsing 2023-02-02 06:04:02 +02:00
Bogdan
6b62504916 Fixed: (PreToMe) Improved parsing, login and settings to extend UserPassTorrentBaseSettings 2023-02-02 06:02:31 +02:00
Bogdan
626d777d3c Fixed: (HttpIndexerBase) Add IndexerAuthException to logs 2023-02-02 06:02:17 +02:00
Bogdan
234707b291 Fixed: (SpeedCD) Fix wildcard when using air date 2023-02-02 06:02:01 +02:00
Bogdan
15734ca0da Fixed: (Libble) Minor improvements 2023-02-02 05:28:01 +02:00
Bogdan
19913e5b01 Fixed: (CloudFlareDetection) Check for DDoS-Guard case-insensitive 2023-02-02 05:20:29 +02:00
Qstick
156f6505be Bump version to 1.1.3 2023-01-30 21:21:33 -06:00
Bogdan
e383287972 New: Add FunFile 2023-01-31 03:05:00 +02:00
Bogdan
0c0cbdac2f Fixed: (FileList) Add alternative URL and return only FL results when fl-only is set 2023-01-31 02:06:50 +02:00
Bogdan
0685c2eb04 Fixed: (PirateTheNet) Changed the login path 2023-01-30 04:45:25 +02:00
Bogdan
e8c132e908 New: Add PirateTheNet 2023-01-29 04:27:58 +02:00
Bogdan
bea9bd39ff Fixed: (LazyLibrarian) Sync priority 2023-01-27 03:07:43 +02:00
Bogdan
077e4727f2 New: Add aro.lol 2023-01-27 03:07:05 +02:00
Bogdan
5f7bc82eb5 Fixed: (Anidex) Update indexer capabilities 2023-01-26 04:42:21 +02:00
Bogdan
0dd5c56175 New: Add XSpeeds 2023-01-26 04:11:24 +02:00
Bogdan
409a218379 Fixed: (ImmortalSeed) Parse pre-release dates correctly 2023-01-26 04:11:24 +02:00
Bogdan
07cc1e03c8 Fixed: (Nebulance) Use single wildcard in search 2023-01-25 19:13:43 -06:00
Qstick
560cda8ba0 Bump dotnet to 6.0.13 2023-01-25 19:07:47 -06:00
Weblate
934f566359 Translated using Weblate (Greek)
Currently translated at 73.2% (345 of 471 strings)

Translated using Weblate (French)

Currently translated at 95.1% (448 of 471 strings)

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

Currently translated at 100.0% (471 of 471 strings)

Co-authored-by: Vasilis Ieropoulos <kirav96@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: aenron <1414004038@qq.com>
Co-authored-by: josehggr <jose.hogger@hotmail.fr>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-01-23 23:43:36 -06:00
Bogdan
89ae5ceaa6 Fixed: Remove NotWhatCD 2023-01-24 05:50:19 +02:00
Bogdan
c7d5889e59 Fixed: (Newznab) Use NameValueCollection.Set 2023-01-24 04:07:24 +02:00
Bogdan
bea3c051b9 Fixed: (Newznab) Remove extra ampersand 2023-01-24 04:07:24 +02:00
Bogdan
c0b1675627 Fixed: (AlphaRatio/GreatPosterWall) Add freeleech only and exclude scene settings 2023-01-23 19:56:30 -06:00
Qstick
906d09e162 Bump version to 1.1.2 2023-01-22 16:07:45 -06:00
Bogdan
8cd9ad01c2 Fixed: (Indexers) Use the defined names for C# indexers 2023-01-22 20:08:30 +02:00
Bogdan
ce2f322478 New: Add Anidex 2023-01-22 19:50:41 +02:00
Bogdan
0487309ee8 New: Add Toloka.to 2023-01-22 19:49:51 +02:00
Qstick
9862584611 Fixed: Catch InvalidDataException during initial config to prevent boot loop 2023-01-21 17:19:06 -06:00
Qstick
6a00e0db90 Filter useless PG Errors from coming to Sentry 2023-01-21 17:16:54 -06:00
Qstick
c93831dd8b Fixed: (TorrentBytes) Avoid null exception in DoLogin error handling 2023-01-21 16:51:19 -06:00
Bogdan
6546ba773c New: (Notification) Apprise 2023-01-21 20:24:28 +02:00
Qstick
4c3484a898 New: (Notifications) Add Ntfy 2023-01-21 12:14:21 -06:00
Qstick
8561b862f9 New: (Notifications) Add Simplepush 2023-01-21 12:13:50 -06:00
Bogdan
e1032fb0f5 New: Add optional app minimum seeders per indexer 2023-01-21 11:26:09 -06:00
Bogdan
4063219430 Fixed: (Orpheus) Title improvements to include ReleaseType and fix categories 2023-01-21 11:25:35 -06:00
Bogdan
e008be8581 Fixed: (Redacted) Search requests, title improvements 2023-01-21 11:25:35 -06:00
Bogdan
d6b379df64 Fixed: Validation inheritance 2023-01-19 21:04:08 -06:00
Bogdan
27094ccf62 Fixed: (ImmortalSeed) Improve tv search with season+ep and parsing, add MR/MST 2023-01-18 18:46:27 -06:00
Bogdan
edf9473e9a Fixed: (TorrentDay) Add freeleech only setting 2023-01-18 18:44:22 -06:00
Qstick
a0d11e7e33 Bump version to 1.1.1 2023-01-16 21:06:48 -06:00
Bogdan
7729eb398a Fixed: (Nebulance) CS cleanup 2023-01-16 19:48:04 -06:00
Bogdan
989564dbce Fixed: (IPTorrents) Improve clean title 2023-01-16 19:48:04 -06:00
Bogdan
c1f917f1ac Fixed: (SpeedCD) Improve clean title 2023-01-16 19:48:04 -06:00
Bogdan
4b7e47c397 Fixed: (RetroFlix) Update description and improve clean title 2023-01-16 19:48:04 -06:00
Bogdan
1529527af9 Fixed: (Cardigann) Bump to v8 2023-01-15 18:20:31 -06:00
Bogdan
a11bd1c3c7 Fixed: (GreatPosterWall) Revert category to default to Movies 2023-01-14 22:20:06 -06:00
Bogdan
915b320a4a Fixed: (Shizaproject) Obsolete: Site unavailable 2023-01-14 18:43:32 -06:00
Bogdan
155f72cc45 Fixed: (AvistaZ/CinemaZ) Remove Music category mapping 2023-01-14 18:43:15 -06:00
Bogdan
3f73fec5c3 Fixed: (Rarbg) Add slash to IndexerUrl, increase RateLimit to 5s 2023-01-14 18:06:44 -06:00
Bogdan
8515623ceb Fixed: (SpeedApp) Fix cleanse token from response when it's the only field 2023-01-14 11:53:47 -06:00
Bogdan
963cddb582 Fixed: (SpeedCD) Add wildcard to season in tvsearch, add freeleech toggle, improve query selectors 2023-01-14 10:52:55 -06:00
Bogdan
ede323b8ed Fixed: (IPTorrents/SceneTime) Remove advanced from freeleech only setting 2023-01-13 22:32:37 -06:00
Bogdan
07d7fc98b0 Fixed: (Orpheus) Add remaster title and year to release title 2023-01-12 21:41:45 -06:00
Bogdan
1b78fd38db Fixed: (FileList) RequestGenerator refactoring, append slash to IndexerUrl 2023-01-12 21:41:13 -06:00
Bogdan
5a9d4d6280 Fixed: (UI) Transpile ES6 libs to fix issues on some browsers 2023-01-11 21:23:10 -06:00
Qstick
70685de5d2 Fixed: Correctly handle relative redirects with dot segments 2023-01-11 21:20:30 -06:00
Bogdan
9860183433 Fixed: (AvistaZ/Anthelion) Cleanse pid, api_key and token 2023-01-11 21:04:50 -06:00
Qstick
50331c61ae Fixed: Use selected BaseUrl for external link
Fixes #1310
2023-01-10 22:19:15 -06:00
Bogdan
bd3408f170 Fixed: (HD-Torrents) Add more alt domains, add Internal flag and fix Blu-Ray categories 2023-01-10 22:01:44 -06:00
Bogdan
c043bf8da9 Fixed: (HD-Space) Use torrent name as release title 2023-01-10 22:00:40 -06:00
Bakerboy448
ea3fa6f28d Fixed: (BeyondHD) Cleanse RSSKey on Grabs 2023-01-10 21:59:35 -06:00
Bogdan
8917347c0b Fixed: (IPTorrents) Fix pagination when limit is zero 2023-01-10 13:30:21 -06:00
Bogdan
2cebdf4a06 Fixed: (AvistaZ) Use different timezone offset than the rest 2023-01-09 20:21:56 -06:00
Bogdan
985110cfb9 Fixed: (ImmortalSeed) Updated categories, improved searchUrl build and fixed auth 2023-01-08 15:44:51 -06:00
Bogdan
de876247a3 Fixed: (MyAnonamouse) Added search type options, search in description/series/filenames as settings 2023-01-08 15:39:25 -06:00
Qstick
bad6c301f8 More CF cases from FlareSolverrSharp 2023-01-08 13:59:17 -06:00
Weblate
fc3b23394a Translated using Weblate (Ukrainian)
Currently translated at 67.5% (318 of 471 strings)

Translated using Weblate (Russian)

Currently translated at 76.4% (360 of 471 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (471 of 471 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (471 of 471 strings)

Translated using Weblate (Dutch)

Currently translated at 88.5% (417 of 471 strings)

Translated using Weblate (Finnish)

Currently translated at 99.5% (469 of 471 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (471 of 471 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (471 of 471 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (471 of 471 strings)

Co-authored-by: AlexR-sf <omg.portal.supp@gmail.com>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Davide Palma <github@davidepalma.it>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Iagocds <cdsiago@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: andrey4korop <andrey999@i.ua>
Co-authored-by: verhese <sean.verheyen1@telenet.be>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translation: Servarr/Prowlarr
2023-01-07 13:34:30 -06:00
Qstick
92c3656bad New: (HDSpace) Parse Genre and Description 2023-01-07 13:26:49 -06:00
Qstick
1acbee2a57 New: (Notification) Mailgun
Fixes #1297
2023-01-07 13:14:00 -06:00
Qstick
c28f9b6bcd Fix Units in Flaresolverr Timeout 2023-01-06 07:21:56 -06:00
Bogdan
aa8048968c Fixed: (Cardigann) Apply RateLimit by using RequestDelay from definitions 2023-01-05 22:39:16 -06:00
Qstick
6646734510 Fixed: (Flaresolverr) Ensure Prowlarr Timeout is sufficient for FS setting 2023-01-05 22:29:15 -06:00
Bogdan
71dd8b6d04 Fixed: Use HashSet to prevent duplicated indexer flags 2023-01-05 22:12:24 -06:00
afpak
6d87bd9f8c Fixed: (IPTorrents) use offset to set page field 2023-01-05 21:50:39 -06:00
Weblate
551d969680 Translated using Weblate (Hungarian)
Currently translated at 100.0% (471 of 471 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (471 of 471 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (471 of 471 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Davide Palma <github@davidepalma.it>
Co-authored-by: Iagocds <cdsiago@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-01-05 21:49:03 -06:00
Bogdan
57dac6afdd Fixed: (GreatPosterWall) Use UTC for PublishDate, order releases and map categories accordingly 2023-01-05 21:40:46 -06:00
351 changed files with 12024 additions and 7260 deletions

View File

@@ -117,7 +117,6 @@ dotnet_diagnostic.CA1003.severity = suggestion
dotnet_diagnostic.CA1008.severity = suggestion
dotnet_diagnostic.CA1010.severity = suggestion
dotnet_diagnostic.CA1012.severity = suggestion
dotnet_diagnostic.CA1014.severity = suggestion
dotnet_diagnostic.CA1016.severity = suggestion
dotnet_diagnostic.CA1017.severity = suggestion
dotnet_diagnostic.CA1018.severity = suggestion
@@ -163,6 +162,7 @@ dotnet_diagnostic.CA1309.severity = suggestion
dotnet_diagnostic.CA1310.severity = suggestion
dotnet_diagnostic.CA1401.severity = suggestion
dotnet_diagnostic.CA1416.severity = suggestion
dotnet_diagnostic.CA1419.severity = suggestion
dotnet_diagnostic.CA1507.severity = suggestion
dotnet_diagnostic.CA1508.severity = suggestion
dotnet_diagnostic.CA1707.severity = suggestion
@@ -178,9 +178,6 @@ dotnet_diagnostic.CA1720.severity = suggestion
dotnet_diagnostic.CA1721.severity = suggestion
dotnet_diagnostic.CA1724.severity = suggestion
dotnet_diagnostic.CA1725.severity = suggestion
dotnet_diagnostic.CA1801.severity = suggestion
dotnet_diagnostic.CA1802.severity = suggestion
dotnet_diagnostic.CA1805.severity = suggestion
dotnet_diagnostic.CA1806.severity = suggestion
dotnet_diagnostic.CA1810.severity = suggestion
dotnet_diagnostic.CA1812.severity = suggestion
@@ -192,13 +189,14 @@ dotnet_diagnostic.CA1819.severity = suggestion
dotnet_diagnostic.CA1822.severity = suggestion
dotnet_diagnostic.CA1823.severity = suggestion
dotnet_diagnostic.CA1824.severity = suggestion
dotnet_diagnostic.CA1835.severity = suggestion
dotnet_diagnostic.CA1845.severity = suggestion
dotnet_diagnostic.CA1848.severity = suggestion
dotnet_diagnostic.CA1849.severity = suggestion
dotnet_diagnostic.CA2000.severity = suggestion
dotnet_diagnostic.CA2002.severity = suggestion
dotnet_diagnostic.CA2007.severity = suggestion
dotnet_diagnostic.CA2008.severity = suggestion
dotnet_diagnostic.CA2009.severity = suggestion
dotnet_diagnostic.CA2010.severity = suggestion
dotnet_diagnostic.CA2011.severity = suggestion
dotnet_diagnostic.CA2012.severity = suggestion
dotnet_diagnostic.CA2013.severity = suggestion
dotnet_diagnostic.CA2100.severity = suggestion
@@ -229,6 +227,7 @@ dotnet_diagnostic.CA2243.severity = suggestion
dotnet_diagnostic.CA2244.severity = suggestion
dotnet_diagnostic.CA2245.severity = suggestion
dotnet_diagnostic.CA2246.severity = suggestion
dotnet_diagnostic.CA2254.severity = suggestion
dotnet_diagnostic.CA3061.severity = suggestion
dotnet_diagnostic.CA3075.severity = suggestion
dotnet_diagnostic.CA3076.severity = suggestion
@@ -255,6 +254,7 @@ dotnet_diagnostic.CA5385.severity = suggestion
dotnet_diagnostic.CA5392.severity = suggestion
dotnet_diagnostic.CA5394.severity = suggestion
dotnet_diagnostic.CA5397.severity = suggestion
dotnet_diagnostic.CA5401.severity = suggestion
dotnet_diagnostic.SYSLIB0014.severity = none

View File

@@ -1,41 +0,0 @@
name: Sync issue to Azure DevOps work item
on:
issues:
types:
[opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
concurrency: azuresync-${{ github.event.issue.number }}
jobs:
alert:
runs-on: ubuntu-latest
steps:
- uses: danhellem/github-actions-issue-to-work-item@master
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == true }}"
env:
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
github_token: "${{ github.token }}"
ado_organization: "Servarr"
ado_project: "Servarr"
ado_area_path: "Servarr\\Prowlarr"
ado_wit: "Bug"
ado_new_state: "New"
ado_active_state: "Active"
ado_close_state: "Closed"
ado_bypassrules: true
log_level: 100
- uses: danhellem/github-actions-issue-to-work-item@master
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == false }}"
env:
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
github_token: "${{ github.token }}"
ado_organization: "Servarr"
ado_project: "Servarr"
ado_area_path: "Servarr\\Prowlarr"
ado_wit: "User Story"
ado_new_state: "New"
ado_active_state: "Active"
ado_close_state: "Closed"
ado_bypassrules: true
log_level: 100

View File

@@ -9,13 +9,13 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '1.1.0'
majorVersion: '1.3.1'
minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.404'
dotnetVersion: '6.0.405'
innoVersion: '6.2.0'
nodeVersion: '16.x'
windowsImage: 'windows-2022'

View File

@@ -39,6 +39,7 @@ module.exports = {
plugins: [
'filenames',
'react',
'react-hooks',
'simple-import-sort',
'import'
],
@@ -308,7 +309,9 @@ module.exports = {
'react/react-in-jsx-scope': 2,
'react/self-closing-comp': 2,
'react/sort-comp': 2,
'react/jsx-wrap-multilines': 2
'react/jsx-wrap-multilines': 2,
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error'
},
overrides: [
{

View File

@@ -142,8 +142,8 @@ module.exports = (env) => {
module: {
rules: [
{
test: /\.js?$/,
exclude: /(node_modules|JsLibraries)/,
test: /\.jsx?$/,
exclude: /[\\/]node_modules[\\/](?!(@sentry\/browser|@sentry\/integrations|chart.js|filesize|normalize.css)[\\/])/,
use: [
{
loader: 'babel-loader',

View File

@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';
import React, { Fragment, useEffect } from 'react';
import React, { Fragment, useCallback, useEffect } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import themes from 'Styles/Themes';
@@ -19,7 +19,8 @@ function createMapStateToProps() {
function ApplyTheme({ theme, children }) {
// Update the CSS Variables
function updateCSSVariables() {
const updateCSSVariables = useCallback(() => {
const arrayOfVariableKeys = Object.keys(themes[theme]);
const arrayOfVariableValues = Object.values(themes[theme]);
@@ -31,12 +32,12 @@ function ApplyTheme({ theme, children }) {
arrayOfVariableValues[index]
);
});
}
}, [theme]);
// On Component Mount and Component Update
useEffect(() => {
updateCSSVariables(theme);
}, [theme]);
}, [updateCSSVariables, theme]);
return <Fragment>{children}</Fragment>;
}

View File

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

View File

@@ -30,10 +30,10 @@ function ConfirmModal(props) {
useEffect(() => {
if (isOpen) {
bindShortcut('enter', onConfirm);
} else {
unbindShortcut('enter', onConfirm);
return () => unbindShortcut('enter', onConfirm);
}
}, [onConfirm]);
}, [bindShortcut, unbindShortcut, isOpen, onConfirm]);
return (
<Modal

View File

@@ -61,15 +61,15 @@ class TagsModalContent extends Component {
} = this.state;
const applyTagsOptions = [
{ key: 'add', value: 'Add' },
{ key: 'remove', value: 'Remove' },
{ key: 'replace', value: 'Replace' }
{ key: 'add', value: translate('Add') },
{ key: 'remove', value: translate('Remove') },
{ key: 'replace', value: translate('Replace') }
];
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Tags
{translate('Tags')}
</ModalHeader>
<ModalBody>

View File

@@ -26,7 +26,7 @@ function IndexerIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Status
{translate('Status')}
</SortMenuItem>
<SortMenuItem
@@ -62,7 +62,7 @@ function IndexerIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
{'Priority'}
{translate('Priority')}
</SortMenuItem>
<SortMenuItem
@@ -71,7 +71,7 @@ function IndexerIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
{'Protocol'}
{translate('Protocol')}
</SortMenuItem>
<SortMenuItem
@@ -80,7 +80,7 @@ function IndexerIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
{'Privacy'}
{translate('Privacy')}
</SortMenuItem>
</MenuContent>
</SortMenu>

View File

@@ -79,6 +79,7 @@ class IndexerIndexRow extends Component {
privacy,
priority,
status,
fields,
appProfile,
added,
capabilities,
@@ -96,6 +97,8 @@ class IndexerIndexRow extends Component {
isIndexerInfoModalOpen
} = this.state;
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? (Array.isArray(indexerUrls) ? indexerUrls[0] : undefined);
return (
<>
{
@@ -245,12 +248,12 @@ class IndexerIndexRow extends Component {
/>
{
indexerUrls ?
baseUrl ?
<IconButton
className={styles.externalLink}
name={icons.EXTERNAL_LINK}
title={translate('Website')}
to={indexerUrls[0].replace('api.', '')}
to={baseUrl.replace(/(:\/\/)api\./, '$1')}
/> : null
}
@@ -299,6 +302,7 @@ IndexerIndexRow.propTypes = {
name: PropTypes.string.isRequired,
enable: PropTypes.bool.isRequired,
redirect: PropTypes.bool.isRequired,
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
appProfile: PropTypes.object.isRequired,
status: PropTypes.object,
capabilities: PropTypes.object,

View File

@@ -20,11 +20,14 @@ function IndexerInfoModalContent(props) {
encoding,
language,
indexerUrls,
fields,
protocol,
capabilities,
onModalClose
} = props;
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? indexerUrls[0];
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
@@ -57,7 +60,7 @@ function IndexerInfoModalContent(props) {
/>
<DescriptionListItemTitle>{translate('IndexerSite')}</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to={indexerUrls[0]}>{indexerUrls[0]}</Link>
<Link to={baseUrl}>{baseUrl}</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
<DescriptionListItemDescription>
@@ -114,6 +117,7 @@ IndexerInfoModalContent.propTypes = {
encoding: PropTypes.string.isRequired,
language: PropTypes.string.isRequired,
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
protocol: PropTypes.string.isRequired,
capabilities: PropTypes.object.isRequired,
onModalClose: PropTypes.func.isRequired

View File

@@ -71,6 +71,19 @@ class SearchIndexRow extends Component {
});
};
onSavePress = () => {
const {
downloadUrl,
fileName,
onSavePress
} = this.props;
onSavePress({
downloadUrl,
fileName
});
};
//
// Render
@@ -85,7 +98,6 @@ class SearchIndexRow extends Component {
publishDate,
title,
infoUrl,
downloadUrl,
indexer,
size,
files,
@@ -300,7 +312,7 @@ class SearchIndexRow extends Component {
className={styles.downloadLink}
name={icons.SAVE}
title={translate('Save')}
to={downloadUrl}
onPress={this.onSavePress}
/>
</VirtualTableRowCell>
);
@@ -323,6 +335,7 @@ SearchIndexRow.propTypes = {
ageMinutes: PropTypes.number.isRequired,
publishDate: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
fileName: PropTypes.string.isRequired,
infoUrl: PropTypes.string.isRequired,
downloadUrl: PropTypes.string.isRequired,
indexerId: PropTypes.number.isRequired,
@@ -335,6 +348,7 @@ SearchIndexRow.propTypes = {
indexerFlags: PropTypes.arrayOf(PropTypes.string).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onGrabPress: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
isGrabbing: PropTypes.bool.isRequired,
isGrabbed: PropTypes.bool.isRequired,
grabError: PropTypes.string,

View File

@@ -51,7 +51,8 @@ class SearchIndexTable extends Component {
timeFormat,
selectedState,
onSelectedChange,
onGrabPress
onGrabPress,
onSavePress
} = this.props;
const release = items[rowIndex];
@@ -71,6 +72,7 @@ class SearchIndexTable extends Component {
longDateFormat={longDateFormat}
timeFormat={timeFormat}
onGrabPress={onGrabPress}
onSavePress={onSavePress}
/>
</VirtualTableRow>
);
@@ -134,6 +136,7 @@ SearchIndexTable.propTypes = {
timeFormat: PropTypes.string.isRequired,
onSortPress: PropTypes.func.isRequired,
onGrabPress: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
allSelected: PropTypes.bool.isRequired,
allUnselected: PropTypes.bool.isRequired,
selectedState: PropTypes.object.isRequired,

View File

@@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { grabRelease, setReleasesSort } from 'Store/Actions/releaseActions';
import { grabRelease, saveRelease, setReleasesSort } from 'Store/Actions/releaseActions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import SearchIndexTable from './SearchIndexTable';
@@ -25,6 +25,9 @@ function createMapDispatchToProps(dispatch, props) {
},
onGrabPress(payload) {
dispatch(grabRelease(payload));
},
onSavePress(payload) {
dispatch(saveRelease(payload));
}
};
}

View File

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

View File

@@ -71,14 +71,14 @@ class Application extends Component {
{
syncLevel === 'addOnly' &&
<Label kind={kinds.WARNING}>
Add and Remove Only
{translate('AddRemoveOnly')}
</Label>
}
{
syncLevel === 'fullSync' &&
<Label kind={kinds.SUCCESS}>
Full Sync
{translate('FullSync')}
</Label>
}
@@ -88,7 +88,7 @@ class Application extends Component {
kind={kinds.DISABLED}
outline={true}
>
Disabled
{translate('Disabled')}
</Label>
}

View File

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

View File

@@ -56,17 +56,9 @@ class Notification extends Component {
id,
name,
onGrab,
onDownload,
onUpgrade,
onRename,
onDelete,
onHealthIssue,
onApplicationUpdate,
supportsOnGrab,
supportsOnDownload,
supportsOnUpgrade,
supportsOnRename,
supportsOnDelete,
supportsOnHealthIssue,
supportsOnApplicationUpdate
} = this.props;
@@ -88,34 +80,6 @@ class Notification extends Component {
</Label>
}
{
supportsOnDelete && onDelete &&
<Label kind={kinds.SUCCESS}>
{translate('OnDelete')}
</Label>
}
{
supportsOnDownload && onDownload &&
<Label kind={kinds.SUCCESS}>
{translate('OnImport')}
</Label>
}
{
supportsOnUpgrade && onDownload && onUpgrade &&
<Label kind={kinds.SUCCESS}>
{translate('OnUpgrade')}
</Label>
}
{
supportsOnRename && onRename &&
<Label kind={kinds.SUCCESS}>
{translate('OnRename')}
</Label>
}
{
supportsOnHealthIssue && onHealthIssue &&
<Label kind={kinds.SUCCESS}>
@@ -132,7 +96,7 @@ class Notification extends Component {
}
{
!onGrab && !onDownload && !onRename && !onHealthIssue && !onDelete && !onApplicationUpdate ?
!onGrab && !onHealthIssue && !onApplicationUpdate ?
<Label
kind={kinds.DISABLED}
outline={true}
@@ -167,17 +131,9 @@ Notification.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
onGrab: PropTypes.bool.isRequired,
onDownload: PropTypes.bool.isRequired,
onUpgrade: PropTypes.bool.isRequired,
onRename: PropTypes.bool.isRequired,
onDelete: PropTypes.bool.isRequired,
onHealthIssue: PropTypes.bool.isRequired,
onApplicationUpdate: PropTypes.bool.isRequired,
supportsOnGrab: PropTypes.bool.isRequired,
supportsOnDownload: PropTypes.bool.isRequired,
supportsOnDelete: PropTypes.bool.isRequired,
supportsOnUpgrade: PropTypes.bool.isRequired,
supportsOnRename: PropTypes.bool.isRequired,
supportsOnHealthIssue: PropTypes.bool.isRequired,
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
onConfirmDeleteNotification: PropTypes.func.isRequired

View File

@@ -15,8 +15,11 @@ function NotificationEventItems(props) {
} = props;
const {
onGrab,
onHealthIssue,
onApplicationUpdate,
supportsOnGrab,
includeManualGrabs,
supportsOnHealthIssue,
includeHealthWarnings,
supportsOnApplicationUpdate
@@ -31,6 +34,31 @@ function NotificationEventItems(props) {
link="https://wiki.servarr.com/prowlarr/settings#connections"
/>
<div className={styles.events}>
<div>
<FormInputGroup
type={inputTypes.CHECK}
name="onGrab"
helpText={translate('OnGrabHelpText')}
isDisabled={!supportsOnGrab.value}
{...onGrab}
onChange={onInputChange}
/>
</div>
{
onGrab.value &&
<div>
<FormInputGroup
type={inputTypes.CHECK}
name="includeManualGrabs"
helpText={translate('IncludeManualGrabsHelpText')}
isDisabled={!supportsOnGrab.value}
{...includeManualGrabs}
onChange={onInputChange}
/>
</div>
}
<div>
<FormInputGroup
type={inputTypes.CHECK}

View File

@@ -20,7 +20,7 @@ function PendingChangesModal(props) {
useEffect(() => {
bindShortcut('enter', onConfirm);
}, [onConfirm]);
}, [bindShortcut, onConfirm]);
return (
<Modal

View File

@@ -103,9 +103,6 @@ export default {
[SELECT_NOTIFICATION_SCHEMA]: (state, { payload }) => {
return selectProviderSchema(state, section, payload, (selectedSchema) => {
selectedSchema.onGrab = selectedSchema.supportsOnGrab;
selectedSchema.onDownload = selectedSchema.supportsOnDownload;
selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade;
selectedSchema.onRename = selectedSchema.supportsOnRename;
selectedSchema.onApplicationUpdate = selectedSchema.supportsOnApplicationUpdate;
return selectedSchema;

View File

@@ -54,7 +54,7 @@ export const defaultState = {
},
{
name: 'grabTitle',
label: translate('Grab Title'),
label: translate('GrabTitle'),
isSortable: false,
isVisible: false
},
@@ -78,7 +78,7 @@ export const defaultState = {
},
{
name: 'elapsedTime',
label: translate('Elapsed Time'),
label: translate('ElapsedTime'),
isSortable: false,
isVisible: true
},

View File

@@ -1,3 +1,4 @@
import $ from 'jquery';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
@@ -229,6 +230,7 @@ export const CANCEL_FETCH_RELEASES = 'releases/cancelFetchReleases';
export const SET_RELEASES_SORT = 'releases/setReleasesSort';
export const CLEAR_RELEASES = 'releases/clearReleases';
export const GRAB_RELEASE = 'releases/grabRelease';
export const SAVE_RELEASE = 'releases/saveRelease';
export const BULK_GRAB_RELEASES = 'release/bulkGrabReleases';
export const UPDATE_RELEASE = 'releases/updateRelease';
export const SET_RELEASES_FILTER = 'releases/setReleasesFilter';
@@ -243,6 +245,7 @@ export const cancelFetchReleases = createThunk(CANCEL_FETCH_RELEASES);
export const setReleasesSort = createAction(SET_RELEASES_SORT);
export const clearReleases = createAction(CLEAR_RELEASES);
export const grabRelease = createThunk(GRAB_RELEASE);
export const saveRelease = createThunk(SAVE_RELEASE);
export const bulkGrabReleases = createThunk(BULK_GRAB_RELEASES);
export const updateRelease = createAction(UPDATE_RELEASE);
export const setReleasesFilter = createAction(SET_RELEASES_FILTER);
@@ -304,6 +307,32 @@ export const actionHandlers = handleThunks({
});
},
[SAVE_RELEASE]: function(getState, payload, dispatch) {
const link = payload.downloadUrl;
const file = payload.fileName;
$.ajax({
url: link,
method: 'GET',
headers: {
'X-Prowlarr-Client': true
},
xhrFields: {
responseType: 'blob'
},
success: function(data) {
const a = document.createElement('a');
const url = window.URL.createObjectURL(data);
a.href = url;
a.download = file;
document.body.append(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
}
});
},
[BULK_GRAB_RELEASES]: function(getState, payload, dispatch) {
dispatch(set({
section,

View File

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

View File

@@ -113,7 +113,7 @@ class Updates extends Component {
/>
<div className={styles.message}>
The latest version of Prowlarr is already installed
{translate('TheLatestVersionIsAlreadyInstalled', ['Prowlarr'])}
</div>
{

View File

@@ -16,6 +16,11 @@ function addApiKey(ajaxOptions) {
ajaxOptions.headers['X-Api-Key'] = window.Prowlarr.apiKey;
}
function addUIHeader(ajaxOptions) {
ajaxOptions.headers = ajaxOptions.headers || {};
ajaxOptions.headers['X-Prowlarr-Client'] = true;
}
function addContentType(ajaxOptions) {
if (
ajaxOptions.contentType == null &&
@@ -42,6 +47,7 @@ export default function createAjaxRequest(originalAjaxOptions) {
if (isRelative(ajaxOptions)) {
addRootUrl(ajaxOptions);
addApiKey(ajaxOptions);
addUIHeader(ajaxOptions);
addContentType(ajaxOptions);
}

View File

@@ -11,7 +11,8 @@
"lint": "esprint check",
"lint-fix": "esprint check --fix",
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc"
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc",
"check-modules": "are-you-es5 check . -r"
},
"repository": "https://github.com/Prowlarr/Prowlarr",
"author": "Team Prowlarr",
@@ -30,7 +31,7 @@
"@fortawesome/free-regular-svg-icons": "6.2.1",
"@fortawesome/free-solid-svg-icons": "6.2.1",
"@fortawesome/react-fontawesome": "0.2.0",
"@microsoft/signalr": "6.0.11",
"@microsoft/signalr": "6.0.13",
"@sentry/browser": "7.28.0",
"@sentry/integrations": "7.28.0",
"chart.js": "4.1.1",
@@ -93,6 +94,7 @@
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.20.2",
"@babel/preset-react": "7.18.6",
"are-you-es5": "2.1.2",
"autoprefixer": "10.4.13",
"babel-loader": "9.1.0",
"babel-plugin-inline-classnames": "2.0.1",
@@ -103,6 +105,7 @@
"eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-react": "7.31.11",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-simple-import-sort": "8.0.0",
"esprint": "3.6.0",
"file-loader": "6.2.0",

3
src/.globalconfig Normal file
View File

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

View File

@@ -1,6 +1,7 @@
<Project>
<!-- Common to all Prowlarr Projects -->
<PropertyGroup>
<AnalysisLevel>6.0-all</AnalysisLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
<PlatformTarget>AnyCPU</PlatformTarget>

View File

@@ -53,6 +53,26 @@ namespace NzbDrone.Common.Test.Http
newUri.FullUri.Should().Be(expected);
}
[TestCase("", "./relative", "relative")]
[TestCase("/", "./relative", "/relative")]
[TestCase("/base", "./relative", "/relative")]
[TestCase("/base/sub", "./relative", "/base/relative")]
[TestCase("/base/sub/", "./relative", "/base/sub/relative")]
[TestCase("base/sub", "./relative", "base/relative")]
[TestCase("base/sub/", "./relative", "base/sub/relative")]
[TestCase("", "../relative", "relative")]
[TestCase("/", "../relative", "/relative")]
[TestCase("/base", "../relative", "/relative")]
[TestCase("/base/sub", "../relative", "/base/relative")]
[TestCase("/base/sub/", "../relative", "/base/sub/relative")]
[TestCase("base/sub", "../relative", "base/relative")]
[TestCase("base/sub/", "../relative", "base/sub/relative")]
public void should_combine_uri_with_dot_segment(string basePath, string relativePath, string expected)
{
var newUri = new HttpUri(basePath) + new HttpUri(relativePath);
newUri.FullUri.Should().Be(expected);
}
[TestCase("", "", "")]
[TestCase("/", "", "/")]
[TestCase("base", "", "base")]

View File

@@ -24,12 +24,16 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"https://beyond-hd.me/api/torrents/2b51db35e1912ffc138825a12b9933d2")]
[TestCase(@"Req: [POST] https://www3.yggtorrent.nz/user/login: id=mySecret&pass=mySecret&ci_csrf_token=2b51db35e1912ffc138825a12b9933d2")]
[TestCase(@"https://torrentseeds.org/api/torrents/filter?api_token=2b51db35e1912ffc138825a12b9933d2&name=&sortField=created_at&sortDirection=desc&perPage=100&page=1")]
[TestCase(@"https://beyond-hd.me/torrent/download/the-next-365-days-2022-2160p-nf-web-dl-dual-ddp-51-dovi-hdr-hevc-apex.225146.2b51db35e1912ffc138825a12b9933d2")]
[TestCase(@"https://anthelion.me/api.php?api_key=2b51db35e1910123321025a12b9933d2&o=json&t=movie&q=&tmdb=&imdb=&cat=&limit=100&offset=0")]
[TestCase(@"https://avistaz.to/api/v1/jackett/auth: username=mySecret&password=mySecret&pid=mySecret")]
// Indexer and Download Client Responses
// avistaz response
[TestCase(@"""download"":""https://avistaz.to/rss/download/2b51db35e1910123321025a12b9933d2/tb51db35e1910123321025a12b9933d2.torrent"",")]
[TestCase(@",""info_hash"":""2b51db35e1910123321025a12b9933d2"",")]
[TestCase(@"""token"":""2b51db35e1910123321025a12b9933d2""")]
// animebytes response
[TestCase(@"""Link"":""https://animebytes.tv/torrent/994064/download/tb51db35e1910123321025a12b9933d2"",")]

View File

@@ -278,7 +278,7 @@ namespace NzbDrone.Common.Test
[Test]
public void GetUpdateClientExePath()
{
GetIAppDirectoryInfo().GetUpdateClientExePath(PlatformType.DotNet).Should().BeEquivalentTo(@"C:\Temp\prowlarr_update\Prowlarr.Update.exe".AsOsAgnostic());
GetIAppDirectoryInfo().GetUpdateClientExePath().Should().BeEquivalentTo(@"C:\Temp\prowlarr_update\Prowlarr.Update".AsOsAgnostic().ProcessNameToExe());
}
[Test]

View File

@@ -9,6 +9,7 @@ namespace NzbDrone.Common.EnvironmentInfo
bool IsAdmin { get; }
bool IsWindowsService { get; }
bool IsWindowsTray { get; }
bool IsStarting { get; set; }
bool IsExiting { get; set; }
bool IsTray { get; }
RuntimeMode Mode { get; }

View File

@@ -2,13 +2,6 @@ using System;
namespace NzbDrone.Common.EnvironmentInfo
{
public enum PlatformType
{
DotNet = 0,
Mono = 1,
NetCore = 2
}
public interface IPlatformInfo
{
Version Version { get; }
@@ -16,36 +9,18 @@ namespace NzbDrone.Common.EnvironmentInfo
public class PlatformInfo : IPlatformInfo
{
private static PlatformType _platform;
private static Version _version;
static PlatformInfo()
{
_platform = PlatformType.NetCore;
_version = Environment.Version;
}
public static PlatformType Platform => _platform;
public static bool IsMono => Platform == PlatformType.Mono;
public static bool IsDotNet => Platform == PlatformType.DotNet;
public static bool IsNetCore => Platform == PlatformType.NetCore;
public static string PlatformName
{
get
{
if (IsDotNet)
{
return ".NET";
}
else if (IsMono)
{
return "Mono";
}
else
{
return ".NET Core";
}
return ".NET";
}
}

View File

@@ -19,6 +19,7 @@ namespace NzbDrone.Common.EnvironmentInfo
_logger = logger;
IsWindowsService = hostLifetime is WindowsServiceLifetime;
IsStarting = true;
// net6.0 will return Radarr.dll for entry assembly, we need the actual
// executable name (Radarr on linux). On mono this will return the location of
@@ -82,6 +83,7 @@ namespace NzbDrone.Common.EnvironmentInfo
public bool IsWindowsService { get; private set; }
public bool IsStarting { get; set; }
public bool IsExiting { get; set; }
public bool IsTray
{

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Common.Extensions
var info = new FileInfo(path.Trim());
//UNC
// UNC
if (OsInfo.IsWindows && info.FullName.StartsWith(@"\\"))
{
return info.FullName.TrimEnd('/', '\\', ' ');
@@ -166,7 +166,7 @@ namespace NzbDrone.Common.Extensions
var parentDirInfo = dirInfo.Parent;
if (parentDirInfo == null)
{
//Drive letter
// Drive letter
return dirInfo.Name.ToUpper();
}
@@ -238,9 +238,9 @@ namespace NzbDrone.Common.Extensions
return null;
}
public static string ProcessNameToExe(this string processName, PlatformType runtime)
public static string ProcessNameToExe(this string processName)
{
if (OsInfo.IsWindows || runtime != PlatformType.NetCore)
if (OsInfo.IsWindows)
{
processName += ".exe";
}
@@ -248,11 +248,6 @@ namespace NzbDrone.Common.Extensions
return processName;
}
public static string ProcessNameToExe(this string processName)
{
return processName.ProcessNameToExe(PlatformInfo.Platform);
}
public static string GetAppDataPath(this IAppFolderInfo appFolderInfo)
{
return appFolderInfo.AppDataFolder;
@@ -318,9 +313,9 @@ namespace NzbDrone.Common.Extensions
return Path.Combine(GetUpdatePackageFolder(appFolderInfo), UPDATE_CLIENT_FOLDER_NAME);
}
public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo, PlatformType runtime)
public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo)
{
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe(runtime);
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe();
}
public static string GetDatabase(this IAppFolderInfo appFolderInfo)

View File

@@ -131,7 +131,7 @@ namespace NzbDrone.Common.Extensions
public static string WrapInQuotes(this string text)
{
if (!text.Contains(" "))
if (!text.Contains(' '))
{
return text;
}
@@ -255,7 +255,20 @@ namespace NzbDrone.Common.Extensions
public static string ToUrlHost(this string input)
{
return input.Contains(":") ? $"[{input}]" : input;
return input.Contains(':') ? $"[{input}]" : input;
}
public static bool IsAllDigits(this string input)
{
foreach (var c in input)
{
if (c < '0' || c > '9')
{
return false;
}
}
return true;
}
}
}

View File

@@ -113,7 +113,7 @@ namespace NzbDrone.Common.Http.Dispatchers
{
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
{
responseMessage.Content.CopyTo(request.ResponseStream, null, cts.Token);
await responseMessage.Content.CopyToAsync(request.ResponseStream, null, cts.Token);
}
else
{

View File

@@ -76,6 +76,7 @@ namespace NzbDrone.Common.Http
get
{
var newUrl = Headers["Location"];
if (newUrl == null)
{
newUrl = Headers["Refresh"];

View File

@@ -166,6 +166,37 @@ namespace NzbDrone.Common.Http
return relativePath;
}
if (relativePath.StartsWith("./"))
{
relativePath = relativePath.TrimStart('.').TrimStart('/');
var lastIndex = basePath.LastIndexOf("/");
if (lastIndex > 0)
{
basePath = basePath.Substring(0, lastIndex) + "/";
}
}
if (relativePath.StartsWith("../"))
{
relativePath = relativePath.TrimStart('.').TrimStart('/');
var lastIndex = basePath.LastIndexOf("/");
if (lastIndex > 0)
{
basePath = basePath.Substring(0, lastIndex) + "/";
}
var secondLastIndex = basePath.LastIndexOf("/");
if (lastIndex > 0)
{
basePath = basePath.Substring(0, secondLastIndex) + "/";
}
}
var baseSlashIndex = basePath.LastIndexOf('/');
if (baseSlashIndex >= 0)

View File

@@ -11,15 +11,13 @@ namespace NzbDrone.Common.Http
{
if (response.Headers.ContainsKey("Retry-After"))
{
var retryAfter = response.Headers["Retry-After"].ToString();
int seconds;
DateTime date;
var retryAfter = response.Headers["Retry-After"];
if (int.TryParse(retryAfter, out seconds))
if (int.TryParse(retryAfter, out var seconds))
{
RetryAfter = TimeSpan.FromSeconds(seconds);
}
else if (DateTime.TryParse(retryAfter, out date))
else if (DateTime.TryParse(retryAfter, out var date))
{
RetryAfter = date.ToUniversalTime() - DateTime.UtcNow;
}

View File

@@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace NzbDrone.Common.Http
{

View File

@@ -1,4 +1,3 @@
using System;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
@@ -8,10 +7,10 @@ namespace NzbDrone.Common.Instrumentation
{
public class CleanseLogMessage
{
private static readonly Regex[] CleansingRules = new[]
{
private static readonly Regex[] CleansingRules =
{
// Url
new Regex(@"(?<=[?&: ;])(apikey|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=[?&: ;])(apikey|api_key|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pid|pwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"rss\.torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"rss\.torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
@@ -21,6 +20,7 @@ namespace NzbDrone.Common.Instrumentation
new Regex(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=authkey = "")(?<secret>[^&=]+?)(?="")", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=beyond-hd\.[a-z]+/api/torrents/)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=beyond-hd\.[a-z]+/torrent/download/[\w\d-]+[.]\d+[.])(?<secret>[a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// UNIT3D
new Regex(@"(?<=[a-z0-9-]+\.[a-z]+/torrent/download/\d+\.)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
@@ -58,9 +58,10 @@ namespace NzbDrone.Common.Instrumentation
new Regex(@"(?:avistaz|exoticaz|cinemaz|privatehd)\.[a-z]{2,3}/rss/download/(?<secret>[^&=]+?)/(?<secret>[^&=]+?)\.torrent", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?:animebytes)\.[a-z]{2,3}/torrent/[0-9]+/download/(?<secret>[^&=]+?)[""]", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@",""info_hash"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"""token"":""(?<secret>[^&=]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@",""pass[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@",""rss[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
};
};
private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);

View File

@@ -8,6 +8,7 @@ using System.Threading;
using NLog;
using NLog.Common;
using NLog.Targets;
using Npgsql;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using Sentry;
@@ -34,6 +35,14 @@ namespace NzbDrone.Common.Instrumentation.Sentry
SQLiteErrorCode.Auth
};
private static readonly HashSet<string> FilteredPostgresErrorCodes = new HashSet<string>
{
PostgresErrorCodes.OutOfMemory,
PostgresErrorCodes.TooManyConnections,
PostgresErrorCodes.DiskFull,
PostgresErrorCodes.ProgramLimitExceeded
};
// use string and not Type so we don't need a reference to the project
// where these are defined
private static readonly HashSet<string> FilteredExceptionTypeNames = new HashSet<string>
@@ -239,6 +248,19 @@ namespace NzbDrone.Common.Instrumentation.Sentry
return false;
}
var pgEx = logEvent.Exception as PostgresException;
if (pgEx != null && FilteredPostgresErrorCodes.Contains(pgEx.SqlState))
{
return false;
}
// We don't care about transient network and timeout errors
var npgEx = logEvent.Exception as NpgsqlException;
if (npgEx != null && npgEx.IsTransient)
{
return false;
}
if (FilteredExceptionTypeNames.Contains(logEvent.Exception.GetType().Name))
{
return false;

View File

@@ -226,7 +226,7 @@ namespace NzbDrone.Common.OAuth
#if WINRT
return CultureInfo.InvariantCulture.CompareInfo.Compare(left, right, CompareOptions.IgnoreCase) == 0;
#else
return string.Compare(left, right, StringComparison.InvariantCultureIgnoreCase) == 0;
return string.Equals(left, right, StringComparison.InvariantCultureIgnoreCase);
#endif
}

View File

@@ -4,12 +4,13 @@
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DryIoc.dll" Version="5.3.1" />
<PackageReference Include="DryIoc.dll" Version="5.3.3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="NLog" Version="5.1.0" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.0" />
<PackageReference Include="Npgsql" Version="5.0.11" />
<PackageReference Include="Sentry" Version="3.24.1" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />

View File

@@ -64,7 +64,7 @@ namespace NzbDrone.Common
var args = $"create {serviceName} " +
$"DisplayName= \"{serviceName}\" " +
$"binpath= \"{Process.GetCurrentProcess().MainModule.FileName}\" " +
$"binpath= \"{Environment.ProcessPath}\" " +
"start= auto " +
"depend= EventLog/Tcpip/http " +
"obj= \"NT AUTHORITY\\LocalService\"";

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -19,7 +19,7 @@ namespace NzbDrone.Common.TPL
private readonly int _maxDegreeOfParallelism;
/// <summary>Whether the scheduler is currently processing work items.</summary>
private int _delegatesQueuedOrRunning = 0;
private int _delegatesQueuedOrRunning;
/// <summary>
/// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the

View File

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

View File

@@ -8,7 +8,7 @@ using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.FileList;
using NzbDrone.Core.Indexers.Definitions.FileList;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
@@ -21,10 +21,15 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
Subject.Definition = new IndexerDefinition
{
Name = "FileList",
Settings = new FileListSettings() { Username = "someuser", Passkey = "somepass" }
Settings = new FileListSettings
{
BaseUrl = "https://filelist.io/",
Username = "someuser",
Passkey = "somepass"
}
};
}
@@ -35,9 +40,9 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
Mocker.GetMock<IIndexerHttpClient>()
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), recentFeed)));
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } })).Releases;
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new[] { 2000 } })).Releases;
releases.Should().HaveCount(4);
releases.First().Should().BeOfType<TorrentInfo>();
@@ -50,12 +55,14 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
torrentInfo.InfoUrl.Should().Be("https://filelist.io/details.php?id=665873");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 20:20:19").ToUniversalTime());
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 20:20:19"));
torrentInfo.Size.Should().Be(8300512414);
torrentInfo.InfoHash.Should().Be(null);
torrentInfo.MagnetUrl.Should().Be(null);
torrentInfo.Peers.Should().Be(2 + 12);
torrentInfo.Seeders.Should().Be(12);
releases.Any(t => t.IndexerFlags.Contains(IndexerFlag.Internal)).Should().Be(true);
}
}
}

View File

@@ -3,7 +3,7 @@ using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.FileList;
using NzbDrone.Core.Indexers.Definitions.FileList;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;
@@ -16,34 +16,35 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
[SetUp]
public void Setup()
{
Subject.Settings = new FileListSettings()
Subject.Settings = new FileListSettings
{
BaseUrl = "https://filelist.io/",
Passkey = "abcd",
Username = "somename",
BaseUrl = "https://filelist.io"
Username = "somename"
};
Subject.Capabilities = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
{
TvSearchParam.Q, TvSearchParam.ImdbId, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
},
{
MusicSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
},
{
BookSearchParam.Q
},
Flags = new List<IndexerFlag>
{
IndexerFlag.FreeLeech
IndexerFlag.FreeLeech,
IndexerFlag.Internal,
}
};
@@ -53,7 +54,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
_movieSearchCriteria = new MovieSearchCriteria
{
SearchTerm = "Star Wars",
Categories = new int[] { 2000 }
Categories = new[] { 2000 }
};
}
@@ -65,13 +66,13 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
[Test]
public void should_use_categories_for_feed()
{
var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new int[] { NewznabStandardCategory.MoviesSD.Id, NewznabStandardCategory.MoviesDVD.Id } });
var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new[] { NewznabStandardCategory.MoviesSD.Id, NewznabStandardCategory.MoviesDVD.Id } });
results.GetAllTiers().Should().HaveCount(1);
results.Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var page = results.First();
page.Url.Query.Should().Contain("&category=1,2&");
page.Url.Query.Should().Contain("&category=1%2C2");
}
[Test]
@@ -80,9 +81,9 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
_movieSearchCriteria.ImdbId = "0076759";
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.GetAllTiers().Should().HaveCount(1);
results.Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var page = results.First();
page.Url.Query.Should().Contain("type=imdb");
page.Url.Query.Should().Contain("query=tt0076759");
@@ -95,12 +96,12 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.GetAllTiers().Should().HaveCount(1);
results.Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var page = results.First();
page.Url.Query.Should().Contain("type=name");
page.Url.Query.Should().Contain("query=Star Wars");
page.Url.Query.Should().Contain("query=Star+Wars");
}
}
}

View File

@@ -10,7 +10,7 @@ using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.HDBits;
using NzbDrone.Core.Indexers.Definitions.HDBits;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;

View File

@@ -5,7 +5,7 @@ using Newtonsoft.Json;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.HDBits;
using NzbDrone.Core.Indexers.Definitions.HDBits;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;
@@ -14,11 +14,13 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
public class HDBitsRequestGeneratorFixture : CoreTest<HDBitsRequestGenerator>
{
private MovieSearchCriteria _movieSearchCriteria;
private TvSearchCriteria _tvSearchSeasonEpisodeCriteria;
private TvSearchCriteria _tvSearchDailyEpisodeCriteria;
[SetUp]
public void Setup()
{
Subject.Settings = new HDBitsSettings()
Subject.Settings = new HDBitsSettings
{
ApiKey = "abcd",
Username = "somename"
@@ -47,9 +49,25 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
_movieSearchCriteria = new MovieSearchCriteria
{
Categories = new int[] { 2000, 2010 },
Categories = new[] { 2000, 2010 },
ImdbId = "0076759"
};
_tvSearchSeasonEpisodeCriteria = new TvSearchCriteria
{
Categories = new[] { 5000, 5010 },
TvdbId = 392256,
Season = 1,
Episode = "3"
};
_tvSearchDailyEpisodeCriteria = new TvSearchCriteria
{
Categories = new[] { 5000, 5010 },
TvdbId = 289574,
Season = 2023,
Episode = "01/03"
};
}
[Test]
@@ -58,9 +76,9 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
var results = Subject.GetSearchRequests(_movieSearchCriteria);
var imdbQuery = int.Parse(_movieSearchCriteria.ImdbId);
results.GetAllTiers().Should().HaveCount(1);
results.Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var page = results.First();
var encoding = HttpHeader.GetEncodingFromContentType(page.HttpRequest.Headers.ContentType);
@@ -70,5 +88,49 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
query.Category.Should().HaveCount(1);
query.ImdbInfo.Id.Should().Be(imdbQuery);
}
[Test]
public void should_search_by_tvdbid_season_episode_if_supported()
{
var results = Subject.GetSearchRequests(_tvSearchSeasonEpisodeCriteria);
var tvdbQuery = _tvSearchSeasonEpisodeCriteria.TvdbId;
results.Should().HaveCount(1);
var page = results.First();
var encoding = HttpHeader.GetEncodingFromContentType(page.HttpRequest.Headers.ContentType);
var body = encoding.GetString(page.HttpRequest.ContentData);
var query = JsonConvert.DeserializeObject<TorrentQuery>(body);
query.Category.Should().HaveCount(3);
query.TvdbInfo.Id.Should().Be(tvdbQuery);
query.Search.Should().BeNullOrWhiteSpace();
query.TvdbInfo.Season.Should().Be(1);
query.TvdbInfo.Episode.Should().Be("3");
}
[Test]
public void should_search_by_tvdbid_daily_episode_if_supported()
{
var results = Subject.GetSearchRequests(_tvSearchDailyEpisodeCriteria);
var tvdbQuery = _tvSearchDailyEpisodeCriteria.TvdbId;
results.Should().HaveCount(1);
var page = results.First();
var encoding = HttpHeader.GetEncodingFromContentType(page.HttpRequest.Headers.ContentType);
var body = encoding.GetString(page.HttpRequest.ContentData);
var query = JsonConvert.DeserializeObject<TorrentQuery>(body);
query.Category.Should().HaveCount(3);
query.TvdbInfo.Id.Should().Be(tvdbQuery);
query.Search.Should().Be("2023-01-03");
query.TvdbInfo.Season.Should().BeNull();
query.TvdbInfo.Episode.Should().BeNull();
}
}
}

View File

@@ -51,9 +51,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_movieSearchCriteria.Offset = 0;
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.GetAllTiers().Should().HaveCount(1);
results.Should().HaveCount(1);
var pages = results.GetAllTiers().First().Take(3).ToList();
var pages = results.Take(3).ToList();
pages[0].Url.FullUri.Should().Contain("&offset=0");
}
@@ -63,9 +63,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
{
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.GetAllTiers().Should().HaveCount(1);
results.Should().HaveCount(1);
var pages = results.GetAllTiers().First().Take(500).ToList();
var pages = results.Take(500).ToList();
pages.Count.Should().BeLessThan(500);
}
@@ -77,9 +77,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.GetAllTiers().Should().HaveCount(1);
results.Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var page = results.First();
page.Url.Query.Should().NotContain("imdbid=0076759");
page.Url.Query.Should().Contain("q=Star");
@@ -92,9 +92,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_capabilities.MovieSearchParams = new List<MovieSearchParam> { MovieSearchParam.Q, MovieSearchParam.ImdbId };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.GetTier(0).Should().HaveCount(1);
results.Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var page = results.First();
page.Url.Query.Should().Contain("imdbid=0076759");
}
@@ -106,9 +106,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_capabilities.MovieSearchParams = new List<MovieSearchParam> { MovieSearchParam.Q, MovieSearchParam.TmdbId };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.GetTier(0).Should().HaveCount(1);
results.Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var page = results.First();
page.Url.Query.Should().Contain("tmdbid=11");
}
@@ -120,9 +120,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_capabilities.MovieSearchParams = new List<MovieSearchParam> { MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.GetTier(0).Should().HaveCount(1);
results.Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var page = results.First();
page.Url.Query.Should().Contain("tmdbid=11");
page.Url.Query.Should().NotContain("imdbid=0076759");
@@ -136,9 +136,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_capabilities.MovieSearchParams = new List<MovieSearchParam> { MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.GetTier(0).Should().HaveCount(1);
results.Should().HaveCount(1);
var page = results.GetTier(0).First().First();
var page = results.First();
page.Url.Query.Should().Contain("tmdbid=11");
page.Url.Query.Should().Contain("imdbid=0076759");
@@ -150,10 +150,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_capabilities.MovieSearchParams = new List<MovieSearchParam> { MovieSearchParam.Q };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.Tiers.Should().Be(1);
results.GetTier(0).Should().HaveCount(1);
results.Should().HaveCount(1);
var page = results.GetTier(0).First().First();
var page = results.First();
page.Url.Query.Should().Contain("q=");
}
@@ -167,7 +166,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
var results = Subject.GetSearchRequests(_movieSearchCriteria);
var page = results.GetTier(0).First().First();
var page = results.First();
page.Url.Query.Should().Contain("q=");
}
@@ -178,9 +177,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_capabilities.MovieSearchParams = new List<MovieSearchParam> { MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.Tiers.Should().Be(1);
results.Should().HaveCount(1);
var pageTier2 = results.GetTier(0).First().First();
var pageTier2 = results.First();
pageTier2.Url.Query.Should().NotContain("tmdbid=11");
pageTier2.Url.Query.Should().NotContain("imdbid=0076759");
@@ -193,9 +192,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_capabilities.TvSearchParams = new List<TvSearchParam> { TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep };
var results = Subject.GetSearchRequests(_tvSearchCriteria);
results.Tiers.Should().Be(1);
results.Should().HaveCount(1);
var pageTier = results.GetTier(0).First().First();
var pageTier = results.First();
pageTier.Url.Query.Should().Contain("season=00");
}

View File

@@ -9,7 +9,7 @@ using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions;
using NzbDrone.Core.Indexers.Gazelle;
using NzbDrone.Core.Indexers.Definitions.Gazelle;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Test.IndexerTests.OrpheusTests
Subject.Definition = new IndexerDefinition()
{
Name = "Orpheus",
Settings = new OrpheusSettings() { Apikey = "somekey" }
Settings = new OrpheusSettings { Apikey = "somekey" }
};
}
@@ -37,14 +37,14 @@ namespace NzbDrone.Core.Test.IndexerTests.OrpheusTests
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new int[] { 2000 } })).Releases;
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new[] { 2000 } })).Releases;
releases.Should().HaveCount(65);
releases.First().Should().BeOfType<GazelleInfo>();
var torrentInfo = releases.First() as GazelleInfo;
torrentInfo.Title.Should().Be("The Beatles - Abbey Road (1969) [MP3 V2 (VBR)] [BD]");
torrentInfo.Title.Should().Be("The Beatles - Abbey Road [1969] [Album] [2.0 Mix 2019] [MP3 V2 (VBR)] [BD]");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("https://orpheus.network/ajax.php?action=download&id=1902448");
torrentInfo.InfoUrl.Should().Be("https://orpheus.network/torrents.php?id=466&torrentid=1902448");

View File

@@ -9,7 +9,7 @@ using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Rarbg;
using NzbDrone.Core.Indexers.Definitions.Rarbg;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
@@ -23,14 +23,14 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
Subject.Definition = new IndexerDefinition
{
Name = "Rarbg",
Settings = new RarbgSettings()
};
Mocker.GetMock<IRarbgTokenProvider>()
.Setup(v => v.GetToken(It.IsAny<RarbgSettings>()))
.Setup(v => v.GetToken(It.IsAny<RarbgSettings>(), Subject.RateLimit))
.Returns("validtoken");
}
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
torrentInfo.Title.Should().Be("Sense8.S01E01.WEBRip.x264-FGT");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("magnet:?xt=urn:btih:d8bde635f573acb390c7d7e7efc1556965fdc802&dn=Sense8.S01E01.WEBRip.x264-FGT&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce");
torrentInfo.InfoUrl.Should().Be($"https://torrentapi.org/redirect_to_info.php?token=i5cx7b9agd&p=8_6_4_4_5_6__d8bde635f5&app_id={BuildInfo.AppName}");
torrentInfo.InfoUrl.Should().Be($"https://torrentapi.org/redirect_to_info.php?token=i5cx7b9agd&p=8_6_4_4_5_6__d8bde635f5&app_id=rralworP_{BuildInfo.Version}");
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2015-06-05 16:58:11 +0000").ToUniversalTime());
torrentInfo.Size.Should().Be(564198371);

View File

@@ -9,7 +9,7 @@ using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions;
using NzbDrone.Core.Indexers.Gazelle;
using NzbDrone.Core.Indexers.Definitions.Gazelle;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;

View File

@@ -162,7 +162,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
releaseInfo.InfoHash.Should().Be("(removed)");
releaseInfo.Seeders.Should().Be(3);
releaseInfo.Peers.Should().Be(3);
releaseInfo.Categories.Count().Should().Be(4);
releaseInfo.Categories.Count.Should().Be(4);
}
[Test]

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using FluentAssertions;
using FluentValidation.Results;
using NUnit.Framework;
@@ -56,6 +55,11 @@ namespace NzbDrone.Core.Test.NotificationTests
{
TestLogger.Info("OnApplicationUpdate was called");
}
public override void OnGrab(GrabMessage message)
{
TestLogger.Info("OnGrab was called");
}
}
private class TestNotificationWithNoEvents : NotificationBase<TestSetting>
@@ -76,6 +80,7 @@ namespace NzbDrone.Core.Test.NotificationTests
notification.SupportsOnHealthIssue.Should().BeTrue();
notification.SupportsOnApplicationUpdate.Should().BeTrue();
notification.SupportsOnGrab.Should().BeTrue();
}
[Test]
@@ -85,6 +90,7 @@ namespace NzbDrone.Core.Test.NotificationTests
notification.SupportsOnHealthIssue.Should().BeFalse();
notification.SupportsOnApplicationUpdate.Should().BeFalse();
notification.SupportsOnGrab.Should().BeFalse();
}
}
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Globalization;
using System.Threading;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.ParserTests
{
[TestFixture]
public class DateTimeUtilFixture : CoreTest
{
[TestCase("pt-BR")]
[TestCase("en-US")]
public void should_format_date_invariant(string culture)
{
Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
var dateNow = DateTime.Now;
DateTimeUtil.FromUnknown(dateNow.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture))
.ToString(DateTimeUtil.Rfc1123ZPattern, CultureInfo.InvariantCulture)
.Should().Be(dateNow.ToString("ddd, dd MMM yyyy HH':'mm':'ss z", CultureInfo.InvariantCulture));
}
[TestCase("2022-08-08 02:07:39 -02:00", "2006-01-02 15:04:05 -07:00", "yyyy-MM-dd HH:mm:ss zzz", "2022-08-08 04:07:39 +00:00")]
[TestCase("2022-08-08 02:07:39 -02:00", "yyyy-MM-dd HH:mm:ss zzz", "yyyy-MM-dd HH:mm:ss zzz", "2022-08-08 04:07:39 +00:00")]
[TestCase("2022-08-08 -02:00", "2006-01-02 -07:00", "yyyy-MM-dd zzz", "2022-08-08 +00:00")]
[TestCase("2022-08-08 -02:00", "yyyy-MM-dd zzz", "yyyy-MM-dd zzz", "2022-08-08 +00:00")]
[TestCase("02:07:39 -02:00", "15:04:05 -07:00", "HH:mm:ss zzz", "04:07:39 +00:00")]
[TestCase("02:07:39 -02:00", "HH:mm:ss zzz", "HH:mm:ss zzz", "04:07:39 +00:00")]
[TestCase("-02:00", "zzz", "zzz", "+00:00")]
[TestCase("-02:00", "-07:00", "zzz", "+00:00")]
public void parse_datetime_golang(string dateInput, string format, string standardFormat, string expectedDate)
{
DateTimeUtil.ParseDateTimeGoLang(dateInput, format)
.ToUniversalTime()
.ToString(standardFormat, CultureInfo.InvariantCulture)
.Should().Be(expectedDate);
}
}
}

View File

@@ -52,5 +52,16 @@ namespace NzbDrone.Core.Test.ParserTests
{
ParseUtil.CoerceDouble(original).Should().Be(parsedInt);
}
[TestCase(null, null)]
[TestCase("", null)]
[TestCase("1", 1)]
[TestCase("1000 grabs", 1000)]
[TestCase("asdf123asdf", 123)]
[TestCase("asdf123asdf456asdf", 123)]
public void should_parse_long_from_string(string original, long? parsedInt)
{
ParseUtil.GetLongFromString(original).Should().Be(parsedInt);
}
}
}

View File

@@ -6,7 +6,7 @@
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="NBuilder" Version="6.1.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="YamlDotNet" Version="12.3.1" />
<PackageReference Include="YamlDotNet" Version="13.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />

View File

@@ -40,12 +40,11 @@ namespace NzbDrone.Core.Test.UpdateTests
}
[Test]
[Ignore("TODO No Updates On Server")]
public void should_get_recent_updates()
{
const string branch = "develop";
UseRealHttp();
var recent = Subject.GetRecentUpdates(branch, new Version(2, 0), null);
var recent = Subject.GetRecentUpdates(branch, new Version(1, 0), null);
var recentWithChanges = recent.Where(c => c.Changes != null);
recent.Should().NotBeEmpty();

View File

@@ -69,7 +69,7 @@ namespace NzbDrone.Core.Test.UpdateTests
.Returns(true);
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Prowlarr.Update.exe"))))
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Prowlarr.Update".ProcessNameToExe()))))
.Returns(true);
_sandboxFolder = Mocker.GetMock<IAppFolderInfo>().Object.GetUpdateSandboxFolder();
@@ -165,7 +165,7 @@ namespace NzbDrone.Core.Test.UpdateTests
public void should_return_with_warning_if_updater_doesnt_exists()
{
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Prowlarr.Update.exe"))))
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Prowlarr.Update".ProcessNameToExe()))))
.Returns(false);
Subject.Execute(new ApplicationUpdateCommand());

View File

@@ -147,6 +147,7 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
Categories = string.Join(",", indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())),
Enabled = indexer.Enable,
Type = schema,
Priority = indexer.Priority
};
return lazyLibrarianIndexer;

View File

@@ -30,6 +30,7 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
public bool Enabled { get; set; }
public string Altername { get; set; }
public LazyLibrarianProviderType Type { get; set; }
public int Priority { get; set; }
public bool Equals(LazyLibrarianIndexer other)
{
@@ -43,7 +44,8 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
other.Name == Name &&
other.Categories == Categories &&
other.Enabled == Enabled &&
other.Altername == Altername;
other.Altername == Altername &&
other.Priority == Priority;
}
}
}

View File

@@ -21,6 +21,8 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
public class LazyLibrarianV1Proxy : ILazyLibrarianV1Proxy
{
private const int ProwlarrHighestPriority = 50;
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
@@ -90,7 +92,8 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
{ "host", indexer.Host },
{ "prov_apikey", indexer.Apikey },
{ "enabled", indexer.Enabled.ToString().ToLower() },
{ "categories", indexer.Categories }
{ "categories", indexer.Categories },
{ "dlpriority", CalculatePriority(indexer.Priority).ToString() }
};
var request = BuildRequest(settings, "/api", "addProvider", HttpMethod.Get, parameters);
@@ -108,7 +111,8 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
{ "prov_apikey", indexer.Apikey },
{ "enabled", indexer.Enabled.ToString().ToLower() },
{ "categories", indexer.Categories },
{ "altername", indexer.Altername }
{ "altername", indexer.Altername },
{ "dlpriority", CalculatePriority(indexer.Priority).ToString() }
};
var request = BuildRequest(settings, "/api", "changeProvider", HttpMethod.Get, parameters);
@@ -191,5 +195,7 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
return results;
}
private int CalculatePriority(int indexerPriority) => ProwlarrHighestPriority - indexerPriority + 1;
}
}

View File

@@ -196,7 +196,7 @@ namespace NzbDrone.Core.Applications.Lidarr
if (indexer.Protocol == DownloadProtocol.Torrent)
{
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders;
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders;
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;

View File

@@ -196,7 +196,7 @@ namespace NzbDrone.Core.Applications.Radarr
if (indexer.Protocol == DownloadProtocol.Torrent)
{
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders;
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders;
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
}

View File

@@ -192,7 +192,7 @@ namespace NzbDrone.Core.Applications.Readarr
if (indexer.Protocol == DownloadProtocol.Torrent)
{
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders;
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders;
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;

View File

@@ -198,7 +198,7 @@ namespace NzbDrone.Core.Applications.Sonarr
if (indexer.Protocol == DownloadProtocol.Torrent)
{
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.PackSeedTime ?? ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;

View File

@@ -192,7 +192,7 @@ namespace NzbDrone.Core.Applications.Whisparr
if (indexer.Protocol == DownloadProtocol.Torrent)
{
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders;
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders;
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
}

View File

@@ -19,14 +19,14 @@ namespace NzbDrone.Core.Authentication
public class UserService : IUserService
{
private const int ITERATIONS = 10000;
private const int SALT_SIZE = 128 / 8;
private const int NUMBER_OF_BYTES = 256 / 8;
private readonly IUserRepository _repo;
private readonly IAppFolderInfo _appFolderInfo;
private readonly IDiskProvider _diskProvider;
private static readonly int ITERATIONS = 10000;
private static readonly int SALT_SIZE = 128 / 8;
private static readonly int NUMBER_OF_BYTES = 256 / 8;
public UserService(IUserRepository repo, IAppFolderInfo appFolderInfo, IDiskProvider diskProvider)
{
_repo = repo;

View File

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

View File

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

View File

@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration;
[Migration(027)]
public class alpharatio_greatposterwall_config_contract : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Update.Table("Indexers").Set(new { ConfigContract = "AlphaRatioSettings" }).Where(new { Implementation = "AlphaRatio" });
Update.Table("Indexers").Set(new { ConfigContract = "GreatPosterWallSettings" }).Where(new { Implementation = "GreatPosterWall" });
}
}

View File

@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration;
[Migration(028)]
public class remove_notwhatcd : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
// Remove, site dead
Delete.FromTable("Indexers").Row(new { Implementation = "NotWhatCD" });
}
}

View File

@@ -0,0 +1,15 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(029)]
public class add_on_grab_to_notifications : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Notifications").AddColumn("OnGrab").AsBoolean().WithDefaultValue(false);
Alter.Table("Notifications").AddColumn("IncludeManualGrabs").AsBoolean().WithDefaultValue(false).NotNullable();
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
@@ -250,7 +250,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
}
Index = end + 1;
identifier.Append(Buffer.Substring(start, end - start));
identifier.Append(Buffer.AsSpan(start, end - start));
if (Buffer[Index] != escape)
{

View File

@@ -66,6 +66,7 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<NotificationDefinition>("Notifications").RegisterModel()
.Ignore(x => x.ImplementationName)
.Ignore(i => i.SupportsOnGrab)
.Ignore(i => i.SupportsOnHealthIssue)
.Ignore(i => i.SupportsOnApplicationUpdate);

View File

@@ -15,9 +15,9 @@ namespace NzbDrone.Core.Datastore
private const DbType EnumerableMultiParameter = (DbType)(-1);
private readonly string _paramNamePrefix;
private readonly bool _requireConcreteValue = false;
private int _paramCount = 0;
private bool _gotConcreteValue = false;
private readonly bool _requireConcreteValue;
private int _paramCount;
private bool _gotConcreteValue;
public WhereBuilderPostgres(Expression filter, bool requireConcreteValue, int seq)
{

View File

@@ -15,9 +15,9 @@ namespace NzbDrone.Core.Datastore
private const DbType EnumerableMultiParameter = (DbType)(-1);
private readonly string _paramNamePrefix;
private readonly bool _requireConcreteValue = false;
private int _paramCount = 0;
private bool _gotConcreteValue = false;
private readonly bool _requireConcreteValue;
private int _paramCount;
private bool _gotConcreteValue;
public WhereBuilderSqlite(Expression filter, bool requireConcreteValue, int seq)
{

View File

@@ -1,7 +1,6 @@
using System;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Instrumentation.Extensions;
@@ -61,16 +60,17 @@ namespace NzbDrone.Core.Download
// Get the seed configuration for this release.
// remoteMovie.SeedConfiguration = _seedConfigProvider.GetSeedConfiguration(remoteMovie);
// Limit grabs to 2 per second.
if (release.DownloadUrl.IsNotNullOrWhiteSpace() && !release.DownloadUrl.StartsWith("magnet:"))
{
var url = new HttpUri(release.DownloadUrl);
_rateLimitService.WaitAndPulse(url.Host, TimeSpan.FromSeconds(2));
}
var indexer = _indexerFactory.GetInstance(_indexerFactory.Get(release.IndexerId));
var grabEvent = new IndexerDownloadEvent(release, true, source, host, release.Title, release.DownloadUrl)
{
DownloadClient = downloadClient.Name,
DownloadClientId = downloadClient.Definition.Id,
DownloadClientName = downloadClient.Definition.Name,
Redirect = redirect,
GrabTrigger = source == "Prowlarr" ? GrabTrigger.Manual : GrabTrigger.Api
};
string downloadClientId;
try
{
@@ -81,19 +81,20 @@ namespace NzbDrone.Core.Download
catch (ReleaseUnavailableException)
{
_logger.Trace("Release {0} no longer available on indexer.", release);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(release.IndexerId, false, source, host, release.Title, release.DownloadUrl, redirect));
grabEvent.Successful = false;
_eventAggregator.PublishEvent(grabEvent);
throw;
}
catch (DownloadClientRejectedReleaseException)
{
_logger.Trace("Release {0} rejected by download client, possible duplicate.", release);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(release.IndexerId, false, source, host, release.Title, release.DownloadUrl, redirect));
grabEvent.Successful = false;
_eventAggregator.PublishEvent(grabEvent);
throw;
}
catch (ReleaseDownloadException ex)
{
var http429 = ex.InnerException as TooManyRequestsException;
if (http429 != null)
if (ex.InnerException is TooManyRequestsException http429)
{
_indexerStatusService.RecordFailure(release.IndexerId, http429.RetryAfter);
}
@@ -102,14 +103,21 @@ namespace NzbDrone.Core.Download
_indexerStatusService.RecordFailure(release.IndexerId);
}
_eventAggregator.PublishEvent(new IndexerDownloadEvent(release.IndexerId, false, source, host, release.Title, release.DownloadUrl, redirect));
grabEvent.Successful = false;
_eventAggregator.PublishEvent(grabEvent);
throw;
}
_logger.ProgressInfo("Report sent to {0}. {1}", downloadClient.Definition.Name, downloadTitle);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(release.IndexerId, true, source, host, release.Title, release.DownloadUrl, redirect));
if (!string.IsNullOrWhiteSpace(downloadClientId))
{
grabEvent.DownloadId = downloadClientId;
}
_eventAggregator.PublishEvent(grabEvent);
}
public async Task<byte[]> DownloadReport(string link, int indexerId, string source, string host, string title)
@@ -127,22 +135,35 @@ namespace NzbDrone.Core.Download
var success = false;
var downloadedBytes = Array.Empty<byte>();
var release = new ReleaseInfo
{
Title = title,
DownloadUrl = link,
IndexerId = indexerId,
Indexer = indexer.Definition.Name,
DownloadProtocol = indexer.Protocol
};
var grabEvent = new IndexerDownloadEvent(release, success, source, host, release.Title, release.DownloadUrl)
{
GrabTrigger = source == "Prowlarr" ? GrabTrigger.Manual : GrabTrigger.Api
};
try
{
downloadedBytes = await indexer.Download(url);
_indexerStatusService.RecordSuccess(indexerId);
success = true;
grabEvent.Successful = true;
}
catch (ReleaseUnavailableException)
{
_logger.Trace("Release {0} no longer available on indexer.", link);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, host, title, url.AbsoluteUri));
_eventAggregator.PublishEvent(grabEvent);
throw;
}
catch (ReleaseDownloadException ex)
{
var http429 = ex.InnerException as TooManyRequestsException;
if (http429 != null)
if (ex.InnerException is TooManyRequestsException http429)
{
_indexerStatusService.RecordFailure(indexerId, http429.RetryAfter);
}
@@ -151,17 +172,36 @@ namespace NzbDrone.Core.Download
_indexerStatusService.RecordFailure(indexerId);
}
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, host, title, url.AbsoluteUri));
_eventAggregator.PublishEvent(grabEvent);
throw;
}
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, host, title, url.AbsoluteUri));
_logger.Trace("Downloaded {0} bytes from {1}", downloadedBytes.Length, link);
_eventAggregator.PublishEvent(grabEvent);
return downloadedBytes;
}
public void RecordRedirect(string link, int indexerId, string source, string host, string title)
{
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, true, source, host, title, link, true));
var indexer = _indexerFactory.GetInstance(_indexerFactory.Get(indexerId));
var release = new ReleaseInfo
{
Title = title,
DownloadUrl = link,
IndexerId = indexerId,
Indexer = indexer.Definition.Name,
DownloadProtocol = indexer.Protocol
};
var grabEvent = new IndexerDownloadEvent(release, true, source, host, release.Title, release.DownloadUrl)
{
Redirect = true,
GrabTrigger = source == "Prowlarr" ? GrabTrigger.Manual : GrabTrigger.Api
};
_eventAggregator.PublishEvent(grabEvent);
}
}
}

View File

@@ -34,8 +34,8 @@ namespace NzbDrone.Core.HealthCheck
private readonly ICached<HealthCheck> _healthCheckResults;
private bool _hasRunHealthChecksAfterGracePeriod = false;
private bool _isRunningHealthChecksAfterGracePeriod = false;
private bool _hasRunHealthChecksAfterGracePeriod;
private bool _isRunningHealthChecksAfterGracePeriod;
public HealthCheckService(IEnumerable<IProvideHealthCheck> healthChecks,
IServerSideNotificationService serverSideNotificationService,
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.HealthCheck
_startupHealthChecks = _healthChecks.Where(v => v.CheckOnStartup).ToArray();
_scheduledHealthChecks = _healthChecks.Where(v => v.CheckOnSchedule).ToArray();
_eventDrivenHealthChecks = GetEventDrivenHealthChecks();
_startupGracePeriodEndTime = runtimeInfo.StartTime + TimeSpan.FromMinutes(15);
_startupGracePeriodEndTime = runtimeInfo.StartTime.AddMinutes(15);
}
public List<HealthCheck> Results()

View File

@@ -52,7 +52,7 @@ namespace NzbDrone.Core.HealthCheck
.AddQueryParam("version", BuildInfo.Version)
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
.AddQueryParam("arch", RuntimeInformation.OSArchitecture)
.AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant())
.AddQueryParam("runtime", "netcore")
.AddQueryParam("branch", _configFileProvider.Branch)
.Build();
try

View File

@@ -19,6 +19,7 @@ namespace NzbDrone.Core.History
List<History> Since(DateTime date, HistoryEventType? eventType);
void Cleanup(int days);
int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes);
History FindFirstForIndexerSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes, int limit);
}
public class HistoryRepository : BasicRepository<History>, IHistoryRepository
@@ -115,5 +116,24 @@ namespace NzbDrone.Core.History
return conn.ExecuteScalar<int>(sql.RawSql, sql.Parameters);
}
}
public History FindFirstForIndexerSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes, int limit)
{
var intEvents = eventTypes.Select(t => (int)t).ToList();
var builder = Builder()
.Where<History>(x => x.IndexerId == indexerId)
.Where<History>(x => x.Date >= date)
.Where<History>(x => intEvents.Contains((int)x.EventType));
var query = Query(builder);
if (limit > 0)
{
query = query.OrderByDescending(h => h.Date).Take(limit).ToList();
}
return query.MinBy(h => h.Date);
}
}
}

View File

@@ -27,6 +27,7 @@ namespace NzbDrone.Core.History
List<History> Between(DateTime start, DateTime end);
List<History> Since(DateTime date, HistoryEventType? eventType);
int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes);
History FindFirstForIndexerSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes, int limit);
}
public class HistoryService : IHistoryService,
@@ -121,7 +122,7 @@ namespace NzbDrone.Core.History
{
Date = DateTime.UtcNow,
IndexerId = message.IndexerId,
EventType = message.Query.RssSearch ? HistoryEventType.IndexerRss : HistoryEventType.IndexerQuery,
EventType = message.Query.IsRssSearch ? HistoryEventType.IndexerRss : HistoryEventType.IndexerQuery,
Successful = message.QueryResult.Response?.StatusCode == HttpStatusCode.OK
};
@@ -173,7 +174,7 @@ namespace NzbDrone.Core.History
history.Data.Add("Categories", string.Join(",", message.Query.Categories) ?? string.Empty);
history.Data.Add("Source", message.Query.Source ?? string.Empty);
history.Data.Add("Host", message.Query.Host ?? string.Empty);
history.Data.Add("QueryResults", message.QueryResult.Releases?.Count().ToString() ?? string.Empty);
history.Data.Add("QueryResults", message.QueryResult.Releases?.Count.ToString() ?? string.Empty);
history.Data.Add("Url", message.QueryResult.Response?.Request.Url.FullUri ?? string.Empty);
_historyRepository.Insert(history);
@@ -184,7 +185,7 @@ namespace NzbDrone.Core.History
var history = new History
{
Date = DateTime.UtcNow,
IndexerId = message.IndexerId,
IndexerId = message.Release.IndexerId,
EventType = HistoryEventType.ReleaseGrabbed,
Successful = message.Successful
};
@@ -232,5 +233,10 @@ namespace NzbDrone.Core.History
{
return _historyRepository.CountSince(indexerId, date, eventTypes);
}
public History FindFirstForIndexerSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes, int limit)
{
return _historyRepository.FindFirstForIndexerSince(indexerId, date, eventTypes, limit);
}
}
}

View File

@@ -1,15 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
namespace NzbDrone.Core.Http.CloudFlare
{
public class CloudFlareDetectionService
{
private static readonly HashSet<string> CloudflareServerNames = new HashSet<string> { "cloudflare", "cloudflare-nginx", "ddos-guard" };
private static readonly HashSet<string> CloudflareServerNames = new () { "cloudflare", "cloudflare-nginx", "ddos-guard" };
private readonly Logger _logger;
public CloudFlareDetectionService(Logger logger)
@@ -29,7 +30,11 @@ namespace NzbDrone.Core.Http.CloudFlare
response.StatusCode.Equals(HttpStatusCode.Forbidden))
{
var responseHtml = response.Content;
if (responseHtml.Contains("<title>Just a moment...") || responseHtml.Contains("<title>DDOS-GUARD"))
if (responseHtml.Contains("<title>Just a moment...</title>") ||
responseHtml.Contains("<title>Access denied</title>") ||
responseHtml.Contains("<title>Attention Required! | Cloudflare</title>") ||
responseHtml.Trim().Equals("error code: 1020") ||
responseHtml.Contains("<title>DDOS-GUARD</title>", StringComparison.OrdinalIgnoreCase))
{
return true;
}
@@ -37,7 +42,7 @@ namespace NzbDrone.Core.Http.CloudFlare
// detect Custom CloudFlare for EbookParadijs, Film-Paleis, MuziekFabriek and Puur-Hollands
if (response.Headers.Vary == "Accept-Encoding,User-Agent" &&
response.Headers.ContentEncoding == "" &&
response.Headers.ContentEncoding.IsNullOrWhiteSpace() &&
response.Content.ToLower().Contains("ddos"))
{
return true;

View File

@@ -170,6 +170,7 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
newRequest.Headers.ContentType = "application/json";
newRequest.Method = HttpMethod.Post;
newRequest.LogResponseContent = true;
newRequest.RequestTimeout = TimeSpan.FromSeconds(Settings.RequestTimeout + 5);
newRequest.SetContent(req.ToJson());
_logger.Debug("Cloudflare Detected, Applying FlareSolverr Proxy {0} to request {1}", Name, request.Url);

View File

@@ -10,17 +10,15 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public int? Year { get; set; }
public string Genre { get; set; }
public override bool RssSearch
{
get
{
if (SearchTerm.IsNullOrWhiteSpace() && Author.IsNullOrWhiteSpace() && Title.IsNullOrWhiteSpace())
{
return true;
}
public override bool IsRssSearch =>
SearchTerm.IsNullOrWhiteSpace() &&
!IsIdSearch;
return false;
}
}
public override bool IsIdSearch =>
Author.IsNotNullOrWhiteSpace() ||
Title.IsNotNullOrWhiteSpace() ||
Publisher.IsNotNullOrWhiteSpace() ||
Genre.IsNotNullOrWhiteSpace() ||
Year.HasValue;
}
}

View File

@@ -13,18 +13,17 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public int? Year { get; set; }
public string Genre { get; set; }
public override bool RssSearch
{
get
{
if (SearchTerm.IsNullOrWhiteSpace() && ImdbId.IsNullOrWhiteSpace() && !TmdbId.HasValue && !TraktId.HasValue)
{
return true;
}
public override bool IsRssSearch =>
SearchTerm.IsNullOrWhiteSpace() &&
!IsIdSearch;
return false;
}
}
public override bool IsIdSearch =>
ImdbId.IsNotNullOrWhiteSpace() ||
Genre.IsNotNullOrWhiteSpace() ||
TmdbId.HasValue ||
TraktId.HasValue ||
DoubanId.HasValue ||
Year.HasValue;
public string FullImdbId => ParseUtil.GetFullImdbId(ImdbId);

View File

@@ -11,17 +11,16 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public string Track { get; set; }
public int? Year { get; set; }
public override bool RssSearch
{
get
{
if (SearchTerm.IsNullOrWhiteSpace() && Album.IsNullOrWhiteSpace() && Artist.IsNullOrWhiteSpace() && Label.IsNullOrWhiteSpace())
{
return true;
}
public override bool IsRssSearch =>
SearchTerm.IsNullOrWhiteSpace() &&
!IsIdSearch;
return false;
}
}
public override bool IsIdSearch =>
Album.IsNotNullOrWhiteSpace() ||
Artist.IsNotNullOrWhiteSpace() ||
Label.IsNotNullOrWhiteSpace() ||
Genre.IsNotNullOrWhiteSpace() ||
Track.IsNotNullOrWhiteSpace() ||
Year.HasValue;
}
}

View File

@@ -7,72 +7,39 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
{
public abstract class SearchCriteriaBase
{
private static readonly Regex SpecialCharacter = new Regex(@"[`'.]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex NonWord = new Regex(@"[\W]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex BeginningThe = new Regex(@"^the\s", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex StandardizeDashesRegex = new (@"\p{Pd}+", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex StandardizeSingleQuotesRegex = new (@"[\u0060\u00B4\u2018\u2019]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public virtual bool InteractiveSearch { get; set; }
public List<int> IndexerIds { get; set; }
public string SearchTerm { get; set; }
public int[] Categories { get; set; }
public string SearchType { get; set; }
public int? Limit { get; set; }
public int? Offset { get; set; }
public int Limit { get; set; }
public int Offset { get; set; }
public string Source { get; set; }
public string Host { get; set; }
public virtual string SearchQuery
public override string ToString() => $"{SearchQuery}, Offset: {Offset}, Limit: {Limit}, Categories: [{string.Join(", ", Categories)}]";
public virtual string SearchQuery => $"Term: [{SearchTerm}]";
public virtual bool IsRssSearch => SearchTerm.IsNullOrWhiteSpace();
public virtual bool IsIdSearch => false;
public string SanitizedSearchTerm => GetSanitizedTerm(SearchTerm);
private static string GetSanitizedTerm(string term)
{
get
{
return $"Term: [{SearchTerm}]";
}
}
term ??= "";
public override string ToString()
{
return $"{SearchQuery}, Offset: {Offset ?? 0}, Limit: {Limit ?? 0}, Categories: [{string.Join(", ", Categories)}]";
}
term = StandardizeDashesRegex.Replace(term, "-");
term = StandardizeSingleQuotesRegex.Replace(term, "'");
public virtual bool RssSearch
{
get
{
if (SearchTerm.IsNullOrWhiteSpace())
{
return true;
}
var safeTitle = term.Where(c => char.IsLetterOrDigit(c) || char.IsWhiteSpace(c) || c is '-' or '.' or '_' or '(' or ')' or '@' or '/' or '\'' or '[' or ']' or '+' or '%');
return false;
}
}
public string SanitizedSearchTerm
{
get
{
var term = SearchTerm;
if (SearchTerm == null)
{
term = "";
}
var safeTitle = term.Where(c => (char.IsLetterOrDigit(c)
|| char.IsWhiteSpace(c)
|| c == '-'
|| c == '.'
|| c == '_'
|| c == '('
|| c == ')'
|| c == '@'
|| c == '/'
|| c == '\''
|| c == '['
|| c == ']'
|| c == '+'
|| c == '%'));
return string.Concat(safeTitle);
}
return string.Concat(safeTitle);
}
}
}

View File

@@ -21,23 +21,25 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public int? Year { get; set; }
public string Genre { get; set; }
public string SanitizedTvSearchString => (SanitizedSearchTerm + " " + EpisodeSearchString).Trim();
public string SanitizedTvSearchString => $"{SanitizedSearchTerm} {EpisodeSearchString}".Trim();
public string EpisodeSearchString => GetEpisodeSearchString();
public string FullImdbId => ParseUtil.GetFullImdbId(ImdbId);
public override bool RssSearch
{
get
{
if (SearchTerm.IsNullOrWhiteSpace() && ImdbId.IsNullOrWhiteSpace() && !TvdbId.HasValue && !RId.HasValue && !TraktId.HasValue && !TvMazeId.HasValue)
{
return true;
}
public override bool IsRssSearch =>
SearchTerm.IsNullOrWhiteSpace() &&
!IsIdSearch;
return false;
}
}
public override bool IsIdSearch =>
Episode.IsNotNullOrWhiteSpace() ||
ImdbId.IsNotNullOrWhiteSpace() ||
Season.HasValue ||
TvdbId.HasValue ||
RId.HasValue ||
TraktId.HasValue ||
TvMazeId.HasValue ||
TmdbId.HasValue ||
DoubanId.HasValue;
public override string SearchQuery
{

View File

@@ -138,8 +138,8 @@ namespace NzbDrone.Core.IndexerSearch
spec.SearchTerm = query.q;
spec.SearchType = query.t;
spec.Limit = query.limit;
spec.Offset = query.offset;
spec.Limit = query.limit ?? 100;
spec.Offset = query.offset ?? 0;
spec.Source = query.source;
spec.Host = query.host;

View File

@@ -59,7 +59,7 @@ namespace NzbDrone.Core.IndexerStats
var elapsedTimeEvents = sortedEvents.Where(h => int.TryParse(h.Data.GetValueOrDefault("elapsedTime"), out temp))
.Select(h => temp);
indexerStats.AverageResponseTime = elapsedTimeEvents.Count() > 0 ? (int)elapsedTimeEvents.Average() : 0;
indexerStats.AverageResponseTime = elapsedTimeEvents.Any() ? (int)elapsedTimeEvents.Average() : 0;
foreach (var historyEvent in sortedEvents)
{

View File

@@ -29,13 +29,14 @@ namespace NzbDrone.Core.IndexerVersions
/* Update Service will fall back if version # does not exist for an indexer per Ta */
private const string DEFINITION_BRANCH = "master";
private const int DEFINITION_VERSION = 7;
private const int DEFINITION_VERSION = 9;
//Used when moving yml to C#
private readonly List<string> _defintionBlocklist = new List<string>()
// Used when moving yml to C#
private readonly List<string> _definitionBlocklist = new ()
{
"aither",
"animeworld",
"audiobookbay",
"beyond-hd-oneurl",
"beyond-hd",
"blutopia",
@@ -89,7 +90,7 @@ namespace NzbDrone.Core.IndexerVersions
{
var request = new HttpRequest($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}");
var response = _httpClient.Get<List<CardigannMetaDefinition>>(request);
indexerList = response.Resource.Where(i => !_defintionBlocklist.Contains(i.File)).ToList();
indexerList = response.Resource.Where(i => !_definitionBlocklist.Contains(i.File)).ToList();
}
catch
{
@@ -125,7 +126,7 @@ namespace NzbDrone.Core.IndexerVersions
public List<string> GetBlocklist()
{
return _defintionBlocklist;
return _definitionBlocklist;
}
private List<CardigannMetaDefinition> ReadDefinitionsFromDisk(List<CardigannMetaDefinition> defs, string path, SearchOption options = SearchOption.TopDirectoryOnly)
@@ -227,10 +228,10 @@ namespace NzbDrone.Core.IndexerVersions
if (definition.Settings == null)
{
definition.Settings = new List<SettingsField>
{
new SettingsField { Name = "username", Label = "Username", Type = "text" },
new SettingsField { Name = "password", Label = "Password", Type = "password" }
};
{
new () { Name = "username", Label = "Username", Type = "text" },
new () { Name = "password", Label = "Password", Type = "password" }
};
}
if (definition.Encoding == null)

View File

@@ -1,85 +1,120 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.Gazelle;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions
namespace NzbDrone.Core.Indexers.Definitions;
public class AlphaRatio : GazelleBase<AlphaRatioSettings>
{
public class AlphaRatio : Gazelle.Gazelle
public override string Name => "AlphaRatio";
public override string[] IndexerUrls => new[] { "https://alpharatio.cc/" };
public override string Description => "AlphaRatio(AR) is a Private Torrent Tracker for 0DAY / GENERAL";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public AlphaRatio(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
public override string Name => "AlphaRatio";
public override string[] IndexerUrls => new string[] { "https://alpharatio.cc/" };
public override string Description => "AlphaRatio(AR) is a Private Torrent Tracker for 0DAY / GENERAL";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public AlphaRatio(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AlphaRatioRequestGenerator()
{
Settings = Settings,
HttpClient = _httpClient,
Logger = _logger,
Capabilities = Capabilities
};
}
protected override IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVSD, "TvSD");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVHD, "TvHD");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVUHD, "TvUHD");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVSD, "TvDVDRip");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVSD, "TvPackSD");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TVHD, "TvPackHD");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.TVUHD, "TvPackUHD");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.MoviesSD, "MovieSD");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.MoviesHD, "MovieHD");
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.MoviesUHD, "MovieUHD");
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.MoviesSD, "MoviePackSD");
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.MoviesHD, "MoviePackHD");
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.MoviesUHD, "MoviePackUHD");
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.XXX, "MovieXXX");
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.MoviesBluRay, "Bluray");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.TVAnime, "AnimeSD");
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.TVAnime, "AnimeHD");
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.PCGames, "GamesPC");
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.ConsoleXBox, "GamesxBox");
caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.ConsolePS4, "GamesPS");
caps.Categories.AddCategoryMapping(21, NewznabStandardCategory.ConsoleWii, "GamesNin");
caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.PC0day, "AppsWindows");
caps.Categories.AddCategoryMapping(23, NewznabStandardCategory.PCMac, "AppsMAC");
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.PC0day, "AppsLinux");
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.PCMobileOther, "AppsMobile");
caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.XXX, "0dayXXX");
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.Books, "eBook");
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.AudioAudiobook, "AudioBook");
caps.Categories.AddCategoryMapping(29, NewznabStandardCategory.AudioOther, "Music");
caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.Other, "Misc");
return caps;
}
}
public class AlphaRatioRequestGenerator : Gazelle.GazelleRequestGenerator
public override IIndexerRequestGenerator GetRequestGenerator()
{
protected override bool ImdbInTags => true;
return new AlphaRatioRequestGenerator(Settings, Capabilities, _httpClient, _logger);
}
protected override IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVSD, "TvSD");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVHD, "TvHD");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVUHD, "TvUHD");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVSD, "TvDVDRip");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVSD, "TvPackSD");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TVHD, "TvPackHD");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.TVUHD, "TvPackUHD");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.MoviesSD, "MovieSD");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.MoviesHD, "MovieHD");
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.MoviesUHD, "MovieUHD");
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.MoviesSD, "MoviePackSD");
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.MoviesHD, "MoviePackHD");
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.MoviesUHD, "MoviePackUHD");
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.XXX, "MovieXXX");
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.MoviesBluRay, "Bluray");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.TVAnime, "AnimeSD");
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.TVAnime, "AnimeHD");
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.PCGames, "GamesPC");
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.ConsoleXBox, "GamesxBox");
caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.ConsolePS4, "GamesPS");
caps.Categories.AddCategoryMapping(21, NewznabStandardCategory.ConsoleWii, "GamesNin");
caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.PC0day, "AppsWindows");
caps.Categories.AddCategoryMapping(23, NewznabStandardCategory.PCMac, "AppsMAC");
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.PC0day, "AppsLinux");
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.PCMobileOther, "AppsMobile");
caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.XXX, "0dayXXX");
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.Books, "eBook");
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.AudioAudiobook, "AudioBook");
caps.Categories.AddCategoryMapping(29, NewznabStandardCategory.AudioOther, "Music");
caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.Other, "Misc");
return caps;
}
}
public class AlphaRatioRequestGenerator : GazelleRequestGenerator
{
protected override bool ImdbInTags => true;
private readonly AlphaRatioSettings _settings;
public AlphaRatioRequestGenerator(AlphaRatioSettings settings,
IndexerCapabilities capabilities,
IIndexerHttpClient httpClient,
Logger logger)
: base(settings, capabilities, httpClient, logger)
{
_settings = settings;
}
protected override NameValueCollection GetBasicSearchParameters(string term, int[] categories)
{
var parameters = base.GetBasicSearchParameters(term, categories);
if (_settings.FreeleechOnly)
{
parameters.Set("freetorrent", "1");
}
if (_settings.ExcludeScene)
{
parameters.Set("scene", "0");
}
return parameters;
}
}
public class AlphaRatioSettings : GazelleSettings
{
[FieldDefinition(6, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Search freeleech torrents only")]
public bool FreeleechOnly { get; set; }
[FieldDefinition(7, Label = "Exclude Scene", Type = FieldType.Checkbox, HelpText = "Exclude Scene torrents from results")]
public bool ExcludeScene { get; set; }
}

View File

@@ -0,0 +1,349 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using AngleSharp.Html.Parser;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Indexers.Settings;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Definitions
{
public class Anidex : TorrentIndexerBase<AnidexSettings>
{
public override string Name => "Anidex";
public override string[] IndexerUrls => new[] { "https://anidex.info/" };
public override string Description => "Anidex is a Public torrent tracker and indexer, primarily for English fansub groups of anime";
public override string Language => "en-US";
public override Encoding Encoding => Encoding.UTF8;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
public override IndexerCapabilities Capabilities => SetCapabilities();
public Anidex(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AnidexRequestGenerator(Settings, Capabilities);
}
public override IParseIndexerResponse GetParser()
{
return new AnidexParser(Settings, Capabilities.Categories);
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q,
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q,
}
};
caps.Categories.AddCategoryMapping("1", NewznabStandardCategory.TVAnime, "Anime - Sub");
caps.Categories.AddCategoryMapping("2", NewznabStandardCategory.TVAnime, "Anime - Raw");
caps.Categories.AddCategoryMapping("3", NewznabStandardCategory.TVAnime, "Anime - Dub");
caps.Categories.AddCategoryMapping("4", NewznabStandardCategory.TVAnime, "LA - Sub");
caps.Categories.AddCategoryMapping("5", NewznabStandardCategory.TVAnime, "LA - Raw");
caps.Categories.AddCategoryMapping("6", NewznabStandardCategory.BooksEBook, "Light Novel");
caps.Categories.AddCategoryMapping("7", NewznabStandardCategory.BooksComics, "Manga - TLed");
caps.Categories.AddCategoryMapping("8", NewznabStandardCategory.BooksComics, "Manga - Raw");
caps.Categories.AddCategoryMapping("9", NewznabStandardCategory.AudioMP3, "♫ - Lossy");
caps.Categories.AddCategoryMapping("10", NewznabStandardCategory.AudioLossless, "♫ - Lossless");
caps.Categories.AddCategoryMapping("11", NewznabStandardCategory.AudioVideo, "♫ - Video");
caps.Categories.AddCategoryMapping("12", NewznabStandardCategory.PCGames, "Games");
caps.Categories.AddCategoryMapping("13", NewznabStandardCategory.PC0day, "Applications");
caps.Categories.AddCategoryMapping("14", NewznabStandardCategory.XXXImageSet, "Pictures");
caps.Categories.AddCategoryMapping("15", NewznabStandardCategory.XXX, "Adult Video");
caps.Categories.AddCategoryMapping("16", NewznabStandardCategory.Other, "Other");
return caps;
}
}
public class AnidexRequestGenerator : IIndexerRequestGenerator
{
private readonly AnidexSettings _settings;
private readonly IndexerCapabilities _capabilities;
public AnidexRequestGenerator(AnidexSettings settings, IndexerCapabilities capabilities)
{
_settings = settings;
_capabilities = capabilities;
}
public IEnumerable<IndexerRequest> GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return new List<IndexerRequest>();
}
public IEnumerable<IndexerRequest> GetSearchRequests(MusicSearchCriteria searchCriteria)
{
return GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories);
}
public IEnumerable<IndexerRequest> GetSearchRequests(TvSearchCriteria searchCriteria)
{
return GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories);
}
public IEnumerable<IndexerRequest> GetSearchRequests(BookSearchCriteria searchCriteria)
{
return GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories);
}
public IEnumerable<IndexerRequest> GetSearchRequests(BasicSearchCriteria searchCriteria)
{
return GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories);
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{
var parameters = new NameValueCollection
{
{ "page", "search" },
{ "s", "upload_timestamp" },
{ "o", "desc" },
{ "group_id", "0" }, // All groups
{ "q", term ?? string.Empty }
};
if (_settings.AuthorisedOnly)
{
parameters.Add("a", "1");
}
var searchUrl = $"{_settings.BaseUrl}?{parameters.GetQueryString()}";
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(categories);
if (queryCats.Any())
{
searchUrl += "&id=" + string.Join(",", queryCats);
}
if (_settings.LanguagesOnly.Any())
{
searchUrl += "&lang_id=" + string.Join(",", _settings.LanguagesOnly);
}
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
yield return request;
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class AnidexParser : IParseIndexerResponse
{
private readonly AnidexSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public AnidexParser(AnidexSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse, $"Anidex search returned unexpected result. Expected 200 OK but got {indexerResponse.HttpResponse.StatusCode}.");
}
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll("div#content table > tbody > tr");
foreach (var row in rows)
{
var downloadUrl = _settings.BaseUrl + row.QuerySelector("a[href^=\"/dl/\"]")?.GetAttribute("href");
var infoUrl = _settings.BaseUrl + row.QuerySelector("td:nth-child(3) a")?.GetAttribute("href");
var title = row.QuerySelector("td:nth-child(3) span")?.GetAttribute("title")?.Trim();
var language = row.QuerySelector("td:nth-child(1) img")?.GetAttribute("title")?.Trim();
if (language.IsNotNullOrWhiteSpace())
{
title += $" [{language}]";
}
var categoryLink = row.QuerySelector("td:nth-child(1) a").GetAttribute("href");
var cat = ParseUtil.GetArgumentFromQueryString(categoryLink, "id");
var categories = _categories.MapTrackerCatToNewznab(cat);
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(9)")?.TextContent);
var peers = seeders + ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(10)")?.TextContent.Trim());
var added = row.QuerySelector("td:nth-child(8)").GetAttribute("title").Trim();
var release = new TorrentInfo
{
Guid = infoUrl,
InfoUrl = infoUrl,
DownloadUrl = downloadUrl,
MagnetUrl = row.QuerySelector("a[href^=\"magnet:?\"]")?.GetAttribute("href"),
Title = title,
Categories = categories,
Seeders = seeders,
Peers = peers,
Size = ParseUtil.GetBytes(row.QuerySelector("td:nth-child(7)")?.TextContent.Trim()),
Grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(11)")?.TextContent),
PublishDate = DateTime.ParseExact(added, "yyyy-MM-dd HH:mm:ss UTC", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal),
DownloadVolumeFactor = 0,
UploadVolumeFactor = 1
};
releaseInfos.Add(release);
}
return releaseInfos.ToArray();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class AnidexSettings : NoAuthTorrentBaseSettings
{
public AnidexSettings()
{
AuthorisedOnly = false;
LanguagesOnly = Array.Empty<int>();
}
[FieldDefinition(2, Label = "Authorised Only", Type = FieldType.Checkbox, HelpText = "Search authorised torrents only")]
public bool AuthorisedOnly { get; set; }
[FieldDefinition(3, Label = "Languages Only", Type = FieldType.Select, SelectOptions = typeof(AnidexLanguage), HelpText = "Search selected languages only. None ticked = ALL.")]
public IEnumerable<int> LanguagesOnly { get; set; }
}
public enum AnidexLanguage
{
[FieldOption(Hint = "English")]
GB = 1,
[FieldOption(Hint = "Japanese")]
JP = 2,
[FieldOption(Hint = "Polish")]
PL = 3,
[FieldOption(Hint = "Serbo-Croatian")]
RS = 4,
[FieldOption(Hint = "Dutch")]
NL = 5,
[FieldOption(Hint = "Italian")]
IT = 6,
[FieldOption(Hint = "Russian")]
RU = 7,
[FieldOption(Hint = "German")]
DE = 8,
[FieldOption(Hint = "Hungarian")]
HU = 9,
[FieldOption(Hint = "French")]
FR = 10,
[FieldOption(Hint = "Finnish")]
FI = 11,
[FieldOption(Hint = "Vietnamese")]
VN = 12,
[FieldOption(Hint = "Greek")]
GR = 13,
[FieldOption(Hint = "Bulgarian")]
BG = 14,
[FieldOption(Hint = "Spanish (Spain)")]
ES = 15,
[FieldOption(Hint = "Portuguese (Brazil)")]
BR = 16,
[FieldOption(Hint = "Portuguese (Portugal)")]
PT = 17,
[FieldOption(Hint = "Swedish")]
SE = 18,
[FieldOption(Hint = "Arabic")]
SA = 19,
[FieldOption(Hint = "Danish")]
DK = 20,
[FieldOption(Hint = "Chinese (Simplified)")]
CN = 21,
[FieldOption(Hint = "Bengali")]
BD = 22,
[FieldOption(Hint = "Romanian")]
RO = 23,
[FieldOption(Hint = "Czech")]
CZ = 24,
[FieldOption(Hint = "Mongolian")]
MN = 25,
[FieldOption(Hint = "Turkish")]
TR = 26,
[FieldOption(Hint = "Indonesian")]
ID = 27,
[FieldOption(Hint = "Korean")]
KR = 28,
[FieldOption(Hint = "Spanish (LATAM)")]
MX = 29,
[FieldOption(Hint = "Persian")]
IR = 30,
[FieldOption(Hint = "Malaysian")]
MY = 31,
}
}

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