Compare commits

..

210 Commits

Author SHA1 Message Date
Bogdan
b3ddf2f9cd Improve logging when no releases were found 2023-05-28 02:04:39 +03:00
Bogdan
d9ce9eb0b2 Add defaults definitions for indexer proxies 2023-05-28 01:52:38 +03:00
bakerboy448
29ab1801db Fixed a really important spelling mistake
(cherry picked from commit b510201b43f6bc5e6774119ebbd7b8a0d89ee487)
2023-05-27 13:08:16 +03:00
Mark McDowall
19ff73dad0 Fixed: Don't log handled exceptions in API
(cherry picked from commit 59f2e5b65dd7352aad92b33adefa6cf5ca79a0de)
2023-05-27 13:03:36 +03:00
Bogdan
c455f1a113 New: (BakaBT) Add freeleech only option 2023-05-26 20:45:23 +03:00
Qstick
b8793d8783 Remove mono process detection
(cherry picked from commit 5a046026725084bc880a7b63d7105dcf4d882128)
2023-05-26 16:51:33 +03:00
Bogdan
ce34940287 Ensuring backward compatibility with older versions on first sync 2023-05-26 09:54:51 +03:00
Bogdan
dcb19a66b0 New: Add minimum version checks for applications 2023-05-26 09:54:51 +03:00
Weblate
b3bc92e60e Translated using Weblate (Indonesian)
Currently translated at 3.5% (18 of 514 strings)

Co-authored-by: liimee <git.taaa@fedora.email>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/id/
Translation: Servarr/Prowlarr
2023-05-25 13:38:21 +03:00
bakerboy448
1b17d38564 Fixed: (Animedia) Description Language 2023-05-24 23:17:34 -05:00
Bogdan
d8c7361205 Convert typeof to nameof 2023-05-24 19:25:08 +03:00
Bogdan
7a0dd0bc0d Fixed: (AnimeTorrents) Replace non-word chars with wildcard in search term 2023-05-24 00:15:13 +03:00
Mark McDowall
c02bfb5930 Fixed: Don't rollback file move if destination already exists
(cherry picked from commit f05405fe1ce4c78a8c75e27920c863c5b83686bd)
(cherry picked from commit 8ab040f612ee04dac4813a08cdeaddd446a64dc9)
2023-05-23 20:16:53 +03:00
Weblate
d0fbb1f49a Translated using Weblate (French)
Currently translated at 98.6% (507 of 514 strings)

Co-authored-by: foXaCe <foxace66@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translation: Servarr/Prowlarr
2023-05-23 10:36:59 +03:00
Bogdan
aafdefe2f0 Fixed: (RuTracker) Improve the error message for failed logins 2023-05-22 14:12:41 +03:00
Bogdan
96234c0fe1 Fixed: (SceneTime) Update categories 2023-05-21 22:13:11 +03:00
Bogdan
8b5648d7bd Fix spelling "Use languages from Torznab/Newznab attributes if given"
(cherry picked from commit de3bfb7c5ab03e527dca1be3ef4a664dce266db6)
2023-05-21 21:15:20 +03:00
Qstick
1fc79f9e9b New: Use languages from Torznab/Newznab attributes if given
(cherry picked from commit 9c5a07f62a6e32832c10c80813cd3b98c5859989)
2023-05-21 21:13:48 +03:00
S0me6uy
ec40761757 New: Signal Notifications
(cherry picked from commit 59dd3b11271a63ea16f0e32a596dba8e9b9d1096)
2023-05-21 21:01:46 +03:00
Bogdan
0a8e4eb092 New: Improve validation messages
(cherry picked from commit a117001de673e80abd90d54a34a7c86292b3a649)
2023-05-21 20:59:31 +03:00
Bogdan
ade961fad5 Minor CS improvements in NzbDroneValidation
(cherry picked from commit 6118afa339621509aad55caf27b05e89bd0b8c74)
2023-05-21 20:58:25 +03:00
Bogdan
81b1c0e445 Add tests and ignore 0 in GetFullImdbId 2023-05-21 11:53:06 +03:00
Bogdan
0fe54ed36a Fix tests in IndexerServiceFixture 2023-05-21 11:52:54 +03:00
Bogdan
337828ff9c Bump version to 1.5.1 2023-05-21 10:15:52 +03:00
Mark McDowall
fb34294d2e Fixed: Exception when request to SABnzbd times out
(cherry picked from commit f946d78153b85ad726a06a1140143c8beac8766d)
2023-05-21 10:10:17 +03:00
Mark McDowall
931e3cf42d Cleanup TorrentDownloadStation
Fixed: Don't move seeding torrents in Synology Download Station

(cherry picked from commit 3cd33d3f44097b4cb4fb291bca70a0aa53c4b844)
2023-05-21 10:09:21 +03:00
Bogdan
051930455e Add tests for normalizing IMDb IDs in ReleaseSearchService 2023-05-20 19:46:38 +03:00
Bogdan
eba5413250 Format ImdbId to 7 digits in ReleaseSearchService
Fixes #1679
2023-05-20 14:47:28 +03:00
Bogdan
cc2f50544b New: Show tags in Applications and Notifications index 2023-05-20 03:04:50 +03:00
Bogdan
450c6d7af5 Fixed: (Cardigann) Ignore disabled or unchecked inputs in login 2023-05-20 02:02:36 +03:00
Bogdan
bdc0178e44 Limit ESLint configuration to this project 2023-05-20 01:56:41 +03:00
Bogdan
aa9705846e Fixed: (XSpeeds) Update categories 2023-05-19 05:35:08 +03:00
Servarr
7559a87bc8 Automated API Docs update 2023-05-19 05:11:39 +03:00
Bogdan
6a7fe30171 Fixed: Use indexer errors from response if Content-Type is XML before processing response
(cherry picked from commit 9bdc6183663a3510e53433a30ad701065e7ee9d9)
2023-05-19 04:21:10 +03:00
bakerboy448
2b0f4e18e7 Add forceSave to the OpenAPI docs (#1670) 2023-05-19 03:28:49 +03:00
Bogdan
4a5a986220 Replace UC preset with VC
(cherry picked from commit eca3776ddd4b12020833967ad9d98daa0117caff)
2023-05-19 01:56:51 +03:00
Bogdan
38ae17a99f Fixed: (GreatPosterWall) Fetch latest 50 releases when using grouped torrents 2023-05-18 04:32:37 +03:00
Bogdan
9a72da2803 Fixed: Log name of mount point failure
(cherry picked from commit b5050d02d6adbaaaa0f8ae9f8426551e5606fff1)
2023-05-18 04:32:37 +03:00
bakerboy448
3bba76caab Simplify new expression for Newznab categories (#1669)
IDE0090 - simplify newznab categories
2023-05-18 02:51:01 +03:00
Bogdan
47ceabc834 Replace API request with indexer request 2023-05-18 02:48:38 +03:00
Bogdan
48bb3196dd Fixed: (Cardigann) Check redirect for /login.php 2023-05-18 02:47:11 +03:00
Bogdan
4c4ebdf17c Fixed: (Gazelle) Don't use usetoken=0 when UseFreeleechToken is not enabled
Fixes #1668
2023-05-18 02:23:52 +03:00
Bogdan
b5706a0d55 Remove not in use ContentType header for auth requests for AvistaZ 2023-05-17 20:53:17 +03:00
Qstick
d946ef4a9e Convert method to static that doesn't use instance data
(cherry picked from commit a42f97229acb713719c616851db572100f319ad7)
2023-05-17 20:24:15 +03:00
Weblate
48ec5bbaa1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (514 of 514 strings)

Translated using Weblate (French)

Currently translated at 98.6% (507 of 514 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: foXaCe <foxace66@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-05-17 17:45:30 +03:00
Bogdan
2bcdae44c7 Fixed: (HDTorrents) Use Accept Html for indexer requests 2023-05-17 17:39:50 +03:00
Bogdan
541b8b4f7f Increase Request Timeout in Download File
Closes #1655
2023-05-16 15:02:25 +03:00
Weblate
8dd79c38d5 Translated using Weblate (Indonesian)
Currently translated at 2.3% (12 of 514 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (514 of 514 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: liimee <git.taaa@fedora.email>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-05-15 16:55:48 +03:00
Weblate
615b85fffe Translated using Weblate (French)
Currently translated at 98.8% (508 of 514 strings)

Co-authored-by: Antoine <coderademii@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translation: Servarr/Prowlarr
2023-05-13 19:06:55 +03:00
Qstick
ceab19caf9 Fixed: Provider health checks persist after add until next scheduled check
(cherry picked from commit 202449c40c82c6dfd2d15844c578436bbe3c8872)
2023-05-13 18:10:59 +03:00
Bogdan
3d61719a2c Log removal of invalid definitions as warnings 2023-05-13 16:35:32 +03:00
Bogdan
befb354913 Add minimum length as const in ApiKeyValidationCheck 2023-05-13 15:43:19 +03:00
Bogdan
10bbaee55d Update UI dependencies 2023-05-12 22:33:45 +03:00
Benjamin Staneck
131550b92d Add inset to stylelintrc
(cherry picked from commit 6a49f3989a17898c957df8777f0cbb19af647804)
2023-05-12 22:28:54 +03:00
Benjamin Staneck
5f83da9725 Remove unused babel plugins and fix build with profiling
(cherry picked from commit d79f42351fd3d61d180a224d4b8fb51184eb347e)
2023-05-12 22:27:08 +03:00
Benjamin Staneck
1ca8ff5012 Update all relevant dev tool deps
Delete esformatter

Address lint failures

Delete unknown component property

remove deprecated stylelint rules

Address stylelint violation

Update rimraf

(cherry picked from commit 4aba540b894729c730640f03b2f96c451af2dba0)
2023-05-12 22:22:45 +03:00
Benjamin Staneck
061a0c0da8 Add VSCode extension recommendations
To make it easier for new contributors, suggest extensions for the tools we use

(cherry picked from commit 9ebd2f96adb19db7c7357336a37f7b989d21797d)
2023-05-12 22:01:48 +03:00
Benjamin Staneck
4cc2706ee5 Move vscode settings to the frontend folder
Since it applies to all of frontend, I think it makes more sense to have it here instead of src

(cherry picked from commit e12c679cd8961ec9d2ef744761303831b81e64fb)
2023-05-12 22:01:29 +03:00
Benjamin Staneck
32691832a5 Delete various old config files
Delete `jsconfig.json`

This file actually did nothing since we have a `tsconfig.json`. Behavior does not change since `checkJs` is `false` in both.

Delete `.jsbeautifyrc`

Was not used from what I could tell and we have a ESFormatter config file as well and that is basically the successor.

Delete `.csscomb.json`

Was not used from what I could tell, also the project seems dead, last publish 4 years ago. Also we have stylelint in place that covers CSS.

(cherry picked from commit 0da89478cc7a5eec7a35bff47e34b824487661a1)
2023-05-12 22:01:08 +03:00
Bogdan
48977de3b8 Add Pull Request Labeler
Closes #1658
2023-05-12 21:57:04 +03:00
Bogdan
34fbb3e135 Use await using in async methods 2023-05-12 16:32:21 +03:00
Bogdan
d38f2614d3 Remove unused imports 2023-05-11 18:48:51 +03:00
Bogdan
ecc5439464 Enforce code style on build 2023-05-11 18:46:42 +03:00
Bogdan
795274e7e1 Remove empty constructors 2023-05-11 18:44:40 +03:00
Bogdan
eb96fbe956 Fixed: (AnimeTorrents) Add current time of day if date added is today 2023-05-11 17:48:54 +03:00
Weblate
2f1fb396a5 Translated using Weblate (Chinese (Simplified) (zh_CN))
Currently translated at 97.4% (501 of 514 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (514 of 514 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Lithaway <478279934@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-05-11 15:31:22 +03:00
Bogdan
20c085a979 Fix migration running in PostgreSQL 2023-05-08 23:58:48 +03:00
Bogdan
4990e537eb Fixed: AudioBookBay removed 2023-05-08 15:54:47 +03:00
Servarr
f8111ac7ba Automated API Docs update 2023-05-08 15:19:31 +03:00
Bogdan
cb1fd39cb3 API key improvements
Fixed: Special characters in API key
New: Add heathcheck for API Key

(cherry picked from commit 9325140b90f8ac625ae5b26075748c22f6f06158)
2023-05-08 15:13:01 +03:00
Bogdan
5e9094b54c Fixed: custom script error when importing some downloads
Co-authored-by: Qstick <376117+Qstick@users.noreply.github.com>

(cherry picked from commit 8f482c534f15c14a9b3097313a4f5e9273549d88)
2023-05-08 15:04:22 +03:00
Mark McDowall
746d84cf83 Why rename many files when few file do trick
(cherry picked from commit eaa4a358e8eb93e15203001d16e868e22aded5c3)
2023-05-08 15:02:39 +03:00
Mark McDowall
bbe3241b83 GracePeriod not Graceperiod
(cherry picked from commit 993c69530ed34460800f40ecf8a0b7bc9a2f7d48)
2023-05-08 15:01:47 +03:00
The Dark
a86aa4c5d3 New: On Health Restored notification
(cherry picked from commit 5fdc8514da7c7ad98192f2ecb2415b3a7b5d0d05)
2023-05-08 15:01:03 +03:00
Devin Buhl
a753f721d1 New: Send additional information with Webhook and Custom Scripts
(cherry picked from commit e5d6e569cf05cbe431e7ffa98569017d5243d848)
2023-05-08 14:30:32 +03:00
Devin Buhl
202836110e New: Add application URL to host configuration settings
(cherry picked from commit 762042ba97c2ae689cee32d8e66a458f6d7a8adc)
2023-05-08 14:24:50 +03:00
Weblate
1a5e41d831 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (509 of 509 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-05-08 12:49:16 +03:00
Bogdan
e1d0e2c799 Log invalid config file exceptions
Fixes #1648
2023-05-07 16:51:00 +03:00
Bogdan
92e7a38bd0 Fixed: (Rarbg) Move check response by status code to parser 2023-05-07 15:54:31 +03:00
Mark McDowall
008f238dda New: Only add version header for API requests
(cherry picked from commit 453891e620459ff38f7bc43b207004b240fc5fb8)
2023-05-07 14:07:21 +03:00
Bogdan
40125046fa New: Add token authentication for ntfy.sh notifications
Co-authored-by: KucharczykL <lukas@kucharczyk.xyz>

(cherry picked from commit 5bb03a9ddf4d2d33976dfdc39fc70bcf56bf1b49)
2023-05-07 14:06:28 +03:00
Bogdan
1fd188fe7a Add wiki fragment and translation to UpdateCheck 2023-05-06 19:06:43 +03:00
Lars
5e5699fbbe New: Option to use Telegram topics for notifications
(cherry picked from commit c8933d812490ba375c6f7e07cd4355921dc513ac)

Closes #1640
2023-05-06 16:33:49 +03:00
Mark McDowall
d61275e6db New: Improve path validation when handling paths from different OSes
(cherry picked from commit 0321368cc392d7a0a488409bf6bd586ba45497af)
2023-05-06 16:30:20 +03:00
Benjamin Staneck
dca3e939f0 Update webpack and webpack-cli
(cherry picked from commit 8bcaa17e25161aeedd9ca8488a4bb6f4c423de2b)
2023-05-06 11:00:01 +03:00
Benjamin Staneck
26ac66c0e1 Fixed some aria violations
(cherry picked from commit 7aa846343815105e3576e6aa20eac64fcb0edf8d)
2023-05-06 10:50:05 +03:00
Benjamin Staneck
649b301444 New: Updated button and calendar outline colors for dark theme
(cherry picked from commit 5d873fafec07c9d67ad8d2c16eecea195a78eecf)
2023-05-06 10:47:58 +03:00
Benjamin Staneck
78ed2a1af0 Update core-js and use defaults for browserlist
(cherry picked from commit de4cfefde4d00aba829356541b02e8f9a7729977)
2023-05-06 10:44:51 +03:00
Benjamin Staneck
a4854b7b5f Use minified jquery
(cherry picked from commit bb77bb640c0529ca3e6386ec657e64ebafad02f4)
2023-05-06 10:41:55 +03:00
Weblate
97edf495bd Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (508 of 508 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-05-05 15:26:39 +03:00
Mark McDowall
d10bdf4676 New style scrollbar in Firefox
(cherry picked from commit 9bd783d49c91600d6575fc86e7bdd56858c213f1)
2023-05-05 04:21:09 +03:00
Bogdan
03647143e3 Remove unused ReactDOM import 2023-05-05 04:14:51 +03:00
Mark McDowall
8090dc9983 Fixed: File browser
(cherry picked from commit f7ce5c7b115ea0d12ab63f19960c473e09e30f3d)
2023-05-05 04:04:58 +03:00
Qstick
5bc1f345c0 Auto-reply for Log Label
(cherry picked from commit d851ecdf2f826f25b9d2d67d3b7e9e3642bc5299)
2023-05-04 20:24:26 +03:00
Weblate
4ef01f5640 Translated using Weblate (Chinese (Simplified) (zh_CN))
Currently translated at 96.6% (489 of 506 strings)

Translated using Weblate (French)

Currently translated at 100.0% (506 of 506 strings)

Co-authored-by: Remy <remy@mrbk.fr>
Co-authored-by: hellojuly <pangcheinug@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-05-03 22:47:30 +03:00
Bogdan
f13d5c5a14 Fix typo in ShowSearchHelpText 2023-05-03 21:24:22 +03:00
Bogdan
dc8773cf79 Update and sort translations 2023-05-03 20:25:04 +03:00
Weblate
cad774e250 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (506 of 506 strings)

Translated using Weblate (Dutch)

Currently translated at 83.0% (420 of 506 strings)

Translated using Weblate (French)

Currently translated at 100.0% (506 of 506 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: R00T99 <ddaa5e30-3d20-41f9-8ed8-bba67e8acf80@anonaddy.me>
Co-authored-by: foXaCe <foxace66@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-05-02 13:05:17 +03:00
Bogdan
b28eee578a Log apps sync not having intersecting tags as debug 2023-05-02 10:23:13 +03:00
Bogdan
5b8c7d0b79 New: (Rarbg) Add caching results 2023-05-02 08:08:51 +03:00
Bogdan
8bdc7a6db7 Check response status code in classes that extend RssParser 2023-05-02 08:06:21 +03:00
Tebowy Seba
cb189b8f61 Fixed: (BrokenStones) Restored, site moved domains 2023-05-02 01:47:17 +03:00
Bogdan
24468db376 Fixed: (AudioBookBay) New indexer url 2023-05-01 00:52:12 +03:00
Bakerboy448
9b10cea556 Fixed: Improve Rarbg Rate Limit Messaging 2023-04-30 20:03:56 +03:00
Weblate
d8fb71d501 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/
Translation: Servarr/Prowlarr
2023-04-29 23:31:37 +03:00
Bogdan
fc39a11ece (Apprise) Change BaseUrl to ServerUrl 2023-04-29 23:30:49 +03:00
Bakerboy448
40dc4de47d Fixed: Missing Translates 2023-04-29 15:29:46 -05:00
Bogdan
a0e2f3324c Fixed: (AnimeBytes) Cache result releases 2023-04-29 19:29:38 +03:00
Qstick
1bcc3b426e Bump version to 1.5.0 2023-04-29 11:27:02 -05:00
Bogdan
66f5fd2a26 Fixed: (BroadcastheNet) Add tests & prefer TvdbId 2023-04-29 19:00:26 +03:00
Bogdan
b5e5701791 Trim search term 2023-04-29 19:00:26 +03:00
Bakerboy448
1a9b202afe Fixed: Clarify App Sync-Indexer Logging for tags 2023-04-29 09:12:31 -05:00
Qstick
309f42bac5 Fixed: Normalize ImdbId for incoming requests
Fixes #1631
2023-04-28 22:38:19 -05:00
Weblate
ed330ea657 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (484 of 484 strings)

Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-04-26 07:11:28 +03:00
Bogdan
fc6a31ea78 Fixed: (AnimeBytes) Parsing season improvements 2023-04-25 15:20:55 +03:00
Bogdan
25ba9195cf Fixed: (XSpeeds) Update category mappings 2023-04-25 15:18:35 +03:00
Bogdan
681f06e321 Fixed: Disable checking for updates when in debug runtime
Same as a8e2b1520a

Closes #1626
2023-04-24 17:08:02 +03:00
Bogdan
af7fb442d2 New: Add PixelHD 2023-04-24 14:38:34 +03:00
Bogdan
2061b9142f Fixed: (XSpeeds) Update category mappings 2023-04-24 06:10:15 +03:00
Bogdan
b97f6f8ddf Fixed: (UI) Add New Indexer button to open the Add Indexer modal
Fixes #1625
Closes #1389
2023-04-24 04:31:11 +03:00
Qstick
f31c0bb1de Bump version to 1.4.1 2023-04-23 13:18:30 -05:00
Qstick
65e6aa05c3 Bump version to 1.4.0 2023-04-23 12:15:23 -05:00
Bogdan
fb20b3e61b Fixed: (AnimeBytes) Add tests for season parsing 2023-04-23 14:55:50 +03:00
Bogdan
b8a77830aa Fixed: (AnimeBytes) Parse response with STJson 2023-04-23 07:44:46 +03:00
Bogdan
d2ba52cdce Fixed: (Indexers) Hide errors with SuppressHttpErrorStatusCodes 2023-04-23 07:44:16 +03:00
Bogdan
43f881c442 Fixed: (Nebulance) Don't parse invalid response as JSON 2023-04-23 06:40:26 +03:00
Bogdan
4a5e923999 Fixed: (AnimeBytes) Parse season only for category Anime 2023-04-22 20:37:09 +03:00
Bogdan
57e1b6b4a0 Fixed: (AnimeBytes) Improve season/episode detection 2023-04-22 20:19:44 +03:00
Bogdan
9cc60760c3 Fixed: (AnimeBytes) Exclude RAW only for category Anime 2023-04-22 20:17:36 +03:00
Weblate
2811feb14e Translated using Weblate (Chinese (Simplified) (zh_CN))
Currently translated at 99.7% (483 of 484 strings)

Translated using Weblate (Portuguese)

Currently translated at 78.3% (379 of 484 strings)

Co-authored-by: PedroBuffon <henriquebuffon@gmail.com>
Co-authored-by: SHUAI.W <x@ousui.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-04-22 14:21:43 +03:00
Bogdan
46af9223bc Fixed: (BakaBT) Update check if logged in
Resolves #1617
2023-04-22 07:47:14 +03:00
Bogdan
025156978b Fixed: (AnimeBytes) Ignore useless extensions to improve single file names 2023-04-22 03:13:56 +03:00
Bogdan
d3ca861aea Fixed: (AnimeBytes) Remove The Movie from search term 2023-04-21 07:36:52 +03:00
Bogdan
c9249ed583 Fixed: (UI) Typo in hover border colors 2023-04-20 11:51:44 +03:00
Bogdan
94cc56d0f6 Fixed: (AnimeBytes) Improve season/episode detection 2023-04-20 10:39:15 +03:00
Bogdan
c8addc0d62 Rearrange params in Apprise 2023-04-20 08:10:05 +03:00
Bogdan
2015156061 New: (Apprise) Add notification type
Closes #1619
2023-04-20 07:45:51 +03:00
Bogdan
742c680014 Fixed: (AnimeBytes) Add Remux to release titles when possible 2023-04-20 03:08:13 +03:00
Bogdan
b1add3f649 Fixed: (AnimeBytes) Parse M2TS property as BR-DISK 2023-04-20 01:54:18 +03:00
bakerboy448
65d6d518d7 New: Improve applications error reporting for requests (#1618)
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2023-04-19 03:44:22 +03:00
Cedric Lewe
bc8ba5ca02 Fixed: (BakaBT) Update login check (#1617)
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2023-04-18 22:43:17 +03:00
Bogdan
6aebc4ee01 Revert properties order in SonarrSettings 2023-04-18 04:47:09 +03:00
Bogdan
9bbe51253b New: Add default urls to applications 2023-04-18 04:42:08 +03:00
Mark McDowall
88fbc30be2 New: Improved messaging when qBittorrent fails due to host header rejection
(cherry picked from commit 48b4cc5f3ffa0cb8eea6748db9091267216cef4f)
2023-04-18 03:42:48 +03:00
bakerboy448
5fdc6ee25d Fixed: (BroadcasTheNet) Improve season/episode/daily episode searches 2023-04-17 23:44:37 +03:00
Bogdan
4eb5a2d613 Fixed: (Cardigann) Simplify creating CardigannRequest 2023-04-17 21:42:05 +03:00
Bogdan
122883053a Fixed: (Cardigann) Respect Followredirect for login forms
Fixes #526
2023-04-17 19:38:37 +03:00
Bogdan
28d09cd384 Fixed: (Rarbg) Simplify retry fetching for expired tokens 2023-04-17 06:21:07 +03:00
Bogdan
17be8bb68a Add SuppressHttpErrorStatusCodes to HttpRequestBuilder 2023-04-17 06:21:07 +03:00
Qstick
c5baded3d6 Simplify DatabaseType logic 2023-04-16 20:09:04 -05:00
Bogdan
349cfacdca Rename CC to Cc 2023-04-17 04:06:29 +03:00
Bogdan
788fa6d96a Fixed: (Database) Improve Version detection 2023-04-17 04:04:01 +03:00
Bogdan
fbea5bbc06 Fixed: (CookieUtil) Add tests 2023-04-16 05:13:30 +03:00
Bogdan
d667c7d853 Fixed: Use Array.Empty and fix a few multiple enumerations
(cherry picked from commit 11d91faaada0e70910c832ce405ddeed52a24172)
2023-04-16 05:09:12 +03:00
Qstick
a9e1204a9b Fixed: Validate if equals or child for startup folder
(cherry picked from commit 0991cfe27efd6ddb533227b25754661e18d7e9ad)
2023-04-16 05:07:50 +03:00
Bogdan
88e3f86262 Fixed: Migrate to FluentValidation 9 2023-04-16 05:07:50 +03:00
Bogdan
1c173fc984 Fixed: (Cardigann) Update namespace and use nameof() 2023-04-15 07:04:43 +03:00
Bogdan
6e8f3d814a Fixed: (Cardigann) Log requests as debug 2023-04-15 06:54:44 +03:00
Bogdan
14e105e37e Fixed: (Redacted) Add tests 2023-04-14 23:50:33 +03:00
Bogdan
9e0deb8f74 Fixed: (TorrentInfo) Cleanup redundant Freeleech property 2023-04-14 23:13:21 +03:00
Bogdan
245e573089 Fixed: (TorrentPotato) Update namespace 2023-04-14 23:11:03 +03:00
Bogdan
5e8bfa2ffb Fixed: (RuTracker) Add new indexer url
Fixes #1610
2023-04-14 09:06:41 +03:00
Bogdan
555c924e50 New: Add version and timestamp to backup archive
(cherry picked from commit ed3d880974ae6a1430866eebaf72533f35258f6f)

Fixes #662
Closes #1600
2023-04-14 06:06:15 +03:00
Bogdan
8404b85624 Fixed: (AnimeBytes) API responds now with size as integer 2023-04-14 05:58:23 +03:00
Bogdan
dc5e6d29e1 Bump dotnet to 6.0.16 2023-04-14 03:21:01 +03:00
Bogdan
8c42b7a69b Update DryIoc, Newtonsoft.Json, Sentry, SharpZipLib, MailKit 2023-04-14 01:56:21 +03:00
Weblate
3a6ebdef8a Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (484 of 484 strings)

Translated using Weblate (Spanish)

Currently translated at 77.0% (373 of 484 strings)

Translated using Weblate (German)

Currently translated at 95.8% (464 of 484 strings)

Co-authored-by: Deflector8249 <lh2jwko5@gomail.me>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: xuko <jorge.xuko@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-04-13 01:29:23 +03:00
Bogdan
5f57957462 Fixed: (AnimeBytes) Improve release group detection 2023-04-13 01:13:03 +03:00
Bogdan
12526c1bb3 Fixed: (AnimeBytes) Improve season detection 2023-04-12 23:01:24 +03:00
Bogdan
29f049f766 Fixed: (AnimeBytes) Fix tests for release title structure change 2023-04-12 04:19:37 +03:00
Bogdan
40f4e1b82a Fixed: (AnimeBytes) Change release title structure for movies 2023-04-12 03:35:28 +03:00
Bogdan
065fbb30bf Fixed: Support the old broken functionality in GetValueEnum
(cherry picked from commit 2c4e1be12ad5e3b8362f83b8185c143f8e66062b)

Fixes #1602
2023-04-11 21:42:20 +03:00
Bogdan
ea24a81ef7 Fixed: (API) Log errors in Newznab response 2023-04-11 18:32:45 +03:00
Bogdan
451f60319f Fixed: (Cardigann) Add check for request.inputs, since are null when pathselector is used
Fixes #1158
2023-04-11 18:31:11 +03:00
Bogdan
c6ed5d65e0 Fixed: (Core) Ensure default config file on starting app
Fixes #674
Fixes #1588
2023-04-11 17:22:48 +03:00
bakerboy448
4e5cd05bbd Fixed: Improve Indexer Tags Help Text 2023-04-10 22:47:27 +03:00
Bogdan
6b2b953686 Fixed: (Cardigann) Catch errors when search.rows.count is not present 2023-04-10 07:49:22 +03:00
Mark McDowall
31c05be9de Fixed: Prevent getting disk space from returning no information when it partially fails
(cherry picked from commit 2c65e4fa41418157d0d27b34c3bab80158cff219)
2023-04-09 22:16:51 -05:00
Mark McDowall
bc852c0b55 Fixed: USB drives mounted to folders are treated as different mounts
(cherry picked from commit 75378f7bde90b9d3d9b72404c25c017da2cd147c)
2023-04-09 22:15:51 -05:00
Qstick
18651d8be1 Cleanup old Radarr parsing library conditional 2023-04-09 22:14:53 -05:00
Qstick
1608095345 Fixed: Cleanup TaskManager, add BackupInterval limits 2023-04-09 22:11:46 -05:00
Bogdan
7700014ceb Fixed: (PassThePopcorn) Disable grouping, add pagination and use STJson 2023-04-10 05:32:33 +03:00
Bogdan
3fbc2912f0 Fixed: (AnimeBytes) Add limit and refactor parser 2023-04-10 05:03:43 +03:00
Mark McDowall
3192990874 Fixed: Number input changing while scrolling
(cherry picked from commit cc46ed56b4b70fe1f1443c0a927383f19c989c47)
2023-04-09 20:19:02 -05:00
Bogdan
fb908e8e19 Fixed: Use project name as relative path 2023-04-10 04:15:02 +03:00
Bogdan
8e60c707b2 Fixed: (Cardigann) Skip rows parsing on zero rows 2023-04-10 02:55:32 +03:00
Bogdan
a184bb0784 Fixed: (Core) Use MinBy and MaxBy 2023-04-09 21:14:40 +03:00
bakerboy448
e5ccbaaf24 Update bug_report.yml - trace logs checkbox 2023-04-09 20:25:11 +03:00
Weblate
362e0acad1 Translated using Weblate (Czech)
Currently translated at 70.0% (339 of 484 strings)

Co-authored-by: tomas15420 <tomas15420@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/cs/
Translation: Servarr/Prowlarr
2023-04-09 20:23:39 +03:00
Bogdan
54d06460d0 Fixed: (Avistaz/SecretCinema) Fix tests 2023-04-09 16:49:56 +03:00
Bogdan
c11bcf4c41 Fixed: (SecretCinema) Fix PublishDate timezone 2023-04-09 16:18:05 +03:00
Bogdan
2e58583263 Fixed: (Avistaz) Fix PublishDate timezone 2023-04-09 16:14:09 +03:00
Bogdan
bf7f769f13 Fixed: (AvistaZ) Don't log http errors like 404 for imdb/tmdb/tvdb searches 2023-04-08 22:55:05 +03:00
Bogdan
7820a83a5d Fixed: (Indexers) Include exception message in ValidationFailure 2023-04-08 22:49:53 +03:00
Bogdan
d937bdac69 Fixed: (NZBIndex) Request generator cleanup 2023-04-08 22:17:29 +03:00
Qstick
ebca32af46 Fixed: (NzbIndex) Paging starts at 0
Fixes #1586
2023-04-08 09:52:17 -05:00
Bogdan
21bda07510 Fixed: (Toloka) Add authors.gif to check for FL
Co-authored-by: odayny <odayny@users.noreply.github.com>
2023-04-08 01:11:37 +03:00
Bogdan
f638cf34d1 Fixed: (UI) Fix search sorting by empty categories 2023-04-08 00:52:30 +03:00
Bogdan
b7fcdb5356 Fixed: (AnimeBytes) Add search by year 2023-04-06 17:23:54 +03:00
bakerboy448
2e4fa9d06d Fixed: (AnimeBytes) Change RateLimit to 4s (#1580) 2023-04-06 04:32:58 +03:00
bakerboy448
9b50fc40ca Fix: (MaM) Improve No results logic (#1578) 2023-04-05 22:21:25 +03:00
bakerboy448
3c60159df0 Fixed: (AnimeBytes) RateLimit 1req per 10s
Fixes #1572
2023-04-05 20:53:04 +03:00
Bogdan
e075003c8b Fixed: (FileList) Change TZ to account DST 2023-04-05 06:28:07 +03:00
Servarr
b19202d9f5 Automated API Docs update 2023-04-05 05:45:34 +03:00
Bogdan
2784ee8ce6 Fixed: (UI) Update frontend packages 2023-04-05 05:14:08 +03:00
Bogdan
5aa4a5faaa Fixed: (Tags) Show applications in tag details 2023-04-05 05:12:56 +03:00
Weblate
1d00b40f90 Translated using Weblate (Portuguese)
Currently translated at 77.6% (376 of 484 strings)

Translated using Weblate (Hebrew)

Currently translated at 80.3% (389 of 484 strings)

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

Currently translated at 100.0% (484 of 484 strings)

Co-authored-by: Cassio Rizzi <clrizzi@gmail.com>
Co-authored-by: Nir Israel Hen <nirisraelh@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: 雨 <625250353@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-04-03 19:54:21 -05:00
Qstick
93dd378ade Bump version to 1.3.3 2023-04-02 15:14:06 -05:00
Bogdan
534ca73bf8 Fixed: (Toloka) Add FreeleechOnly setting 2023-04-01 21:12:23 +03:00
Bogdan
bceebc34c1 New: (Cardigann) Bump to v9 (#1551)
* New: (Cardigann) Add MissingAttributeEqualsNoResults support

(cherry picked from commit 4e8bb37a5c)

* New: (Cardigann) Add AllowEmptyInputs

* New: (Cardigann) Bump to v9

* New: (Cardigann) Add default value for fields
2023-03-30 14:57:04 +03:00
414 changed files with 8280 additions and 3964 deletions

View File

@@ -40,6 +40,9 @@ csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
# Using directive is unnecessary.
dotnet_diagnostic.IDE0005.severity = error
# Stylecop Rules
dotnet_diagnostic.SA0001.severity = none
dotnet_diagnostic.SA1005.severity = none

View File

@@ -71,3 +71,10 @@ body:
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
validations:
required: true
- type: checkboxes
attributes:
label: Trace Logs have been provided as applicable. Reports may be closed if the required logs are not provided.
description: Trace logs are generally required for all bug reports
options:
- label: I have followed the steps in the wiki link above and provided the required trace logs that are relevant and show this issue.
required: true

View File

@@ -13,4 +13,11 @@
:wave: @{issue-author}, we use the issue tracker exclusively
for bug reports and feature requests. However, this issue appears
to be a indexer request. Please use our Indexer request [site](https://requests.prowlarr.com/)
close: true
close: true
'Status: Logs Needed':
comment: >
:wave: @{issue-author}, in order to help you further we'll need to see logs.
You'll need to enable trace logging and replicate the problem that you encountered.
Guidance on how to enable trace logging can be found in
our [troubleshooting guide](https://wiki.servarr.com/prowlarr/troubleshooting#logging-and-log-files).

19
.github/labeler.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
'Area: API':
- src/Prowlarr.Api.V1/**/*
'Area: Db-migration':
- src/NzbDrone.Core/Datastore/Migration/*
'Area: Download Clients':
- src/NzbDrone.Core/Download/Clients/**/*
'Area: Indexer':
- src/NzbDrone.Core/Indexers/**/*
'Area: Notifications':
- src/NzbDrone.Core/Notifications/**/*
'Area: UI':
- frontend/**/*
- package.json
- yarn.lock

12
.github/workflows/labeler.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: "Pull Request Labeler"
on:
- pull_request_target
jobs:
triage:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4

View File

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

View File

@@ -1,25 +0,0 @@
{
"remove-empty-rulesets": true,
"always-semicolon": true,
"color-case": "lower",
"block-indent": " ",
"color-shorthand": false,
"element-case": "lower",
"eof-newline": true,
"leading-zero": true,
"quotes": "double",
"sort-order-fallback": "abc",
"space-before-colon": "",
"space-after-colon": " ",
"space-before-combinator": " ",
"space-after-combinator": " ",
"space-between-declarations": "\n",
"space-before-opening-brace": " ",
"space-after-opening-brace": "\n",
"space-after-selector-delimiter": " ",
"space-before-selector-delimiter": "",
"space-before-closing-brace": "\n",
"strip-spaces": true,
"tab-size": true,
"unitless-zero": false
}

View File

@@ -1,335 +0,0 @@
{
"indent": {
"value": " ",
"FunctionExpression": 1,
"ArrayExpression": 1,
"ObjectExpression": 1
},
"lineBreak": {
"value": "\n",
"before": {
"ArrayPatternClosing": 0,
"ArrayPatternComma": 0,
"ArrayPatternOpening": 0,
"ArrowFunctionExpressionArrow": 0,
"ArrowFunctionExpressionClosingBrace": ">=1",
"ArrowFunctionExpressionOpeningBrace": 0,
"AssignmentExpression": ">=1",
"AssignmentOperator": 0,
"BlockStatement": 0,
"BreakKeyword": ">=1",
"CallExpression": -1,
"CallExpressionClosingParentheses": -1,
"CallExpressionOpeningParentheses": 0,
"CatchClosingBrace": ">=1",
"CatchKeyword": 0,
"CatchOpeningBrace": 0,
"ClassDeclaration": ">=1",
"ClassDeclarationClosingBrace": ">=1",
"ClassDeclarationOpeningBrace": 0,
"ConditionalExpression": ">=1",
"DeleteOperator": ">=1",
"DoWhileStatement": ">=1",
"DoWhileStatementClosingBrace": ">=1",
"DoWhileStatementOpeningBrace": 0,
"ElseIfStatement": 0,
"ElseIfStatementClosingBrace": ">=1",
"ElseIfStatementOpeningBrace": 0,
"ElseStatement": 0,
"ElseStatementClosingBrace": ">=1",
"ElseStatementOpeningBrace": 0,
"EmptyStatement": -1,
"EndOfFile": -1,
"FinallyClosingBrace": ">=1",
"FinallyKeyword": -1,
"FinallyOpeningBrace": 0,
"ForInStatement": ">=1",
"ForInStatementClosingBrace": ">=1",
"ForInStatementExpressionClosing": 0,
"ForInStatementExpressionOpening": 0,
"ForInStatementOpeningBrace": 0,
"ForStatement": ">=1",
"ForStatementClosingBrace": ">=1",
"ForStatementExpressionClosing": "<2",
"ForStatementExpressionOpening": 0,
"ForStatementOpeningBrace": 0,
"FunctionDeclaration": ">=1",
"FunctionDeclarationClosingBrace": ">=1",
"FunctionDeclarationOpeningBrace": 0,
"FunctionExpression": 0,
"FunctionExpressionClosingBrace": 1,
"FunctionExpressionOpeningBrace":0,
"IIFEClosingParentheses": 0,
"IfStatement": ">=1",
"IfStatementClosingBrace": ">=1",
"IfStatementOpeningBrace": 0,
"LogicalExpression": -1,
"MemberExpressionClosing": 0,
"MemberExpressionOpening": 0,
"MemberExpressionPeriod": -1,
"MethodDefinition": ">=1",
"ObjectExpressionClosingBrace": "<=1",
"ObjectPatternClosingBrace": 0,
"ObjectPatternComma": 0,
"ObjectPatternOpeningBrace": 0,
"ParameterDefault": 0,
"Property": "<=2",
"PropertyValue": 0,
"ReturnStatement": -1,
"SwitchClosingBrace": ">=1",
"SwitchOpeningBrace": 0,
"ThisExpression": -1,
"ThrowStatement": ">=1",
"TryClosingBrace": ">=1",
"TryKeyword": -1,
"TryOpeningBrace": 0,
"VariableDeclaration": ">=1",
"VariableDeclarationSemiColon": 0,
"VariableDeclarationWithoutInit": ">=1",
"VariableName": ">=1",
"VariableValue": 0,
"WhileStatement": ">=1",
"WhileStatementClosingBrace": ">=1",
"WhileStatementOpeningBrace": 0
},
"after": {
"ArrayPatternClosing": 0,
"ArrayPatternComma": 0,
"ArrayPatternOpening": 0,
"ArrowFunctionExpressionArrow": 0,
"ArrowFunctionExpressionClosingBrace": -1,
"ArrowFunctionExpressionOpeningBrace": ">=1",
"AssignmentExpression": ">=1",
"AssignmentOperator": 0,
"BlockStatement": 0,
"BreakKeyword": -1,
"CallExpression": -1,
"CallExpressionClosingParentheses": -1,
"CallExpressionOpeningParentheses": -1,
"CatchClosingBrace": ">=0",
"CatchKeyword": 0,
"CatchOpeningBrace": ">=1",
"ClassDeclaration": ">=1",
"ClassDeclarationClosingBrace": ">=1",
"ClassDeclarationOpeningBrace": ">=1",
"ConditionalExpression": ">=1",
"DeleteOperator": ">=1",
"DoWhileStatement": ">=1",
"DoWhileStatementClosingBrace": 0,
"DoWhileStatementOpeningBrace": ">=1",
"ElseIfStatement": ">=1",
"ElseIfStatementClosingBrace": ">=1",
"ElseIfStatementOpeningBrace": ">=1",
"ElseStatement": ">=1",
"ElseStatementClosingBrace": ">=1",
"ElseStatementOpeningBrace": ">=1",
"EmptyStatement": -1,
"FinallyClosingBrace": ">=1",
"FinallyKeyword": -1,
"FinallyOpeningBrace": ">=1",
"ForInStatement": ">=1",
"ForInStatementClosingBrace": ">=1",
"ForInStatementExpressionClosing": -1,
"ForInStatementExpressionOpening": "<2",
"ForInStatementOpeningBrace": ">=1",
"ForStatement": ">=1",
"ForStatementClosingBrace": ">=1",
"ForStatementExpressionClosing": -1,
"ForStatementExpressionOpening": "<2",
"ForStatementOpeningBrace": ">=1",
"FunctionDeclaration": ">=1",
"FunctionDeclarationClosingBrace": ">=1",
"FunctionDeclarationOpeningBrace": ">=1",
"FunctionExpression": 0,
"FunctionExpressionClosingBrace": -1,
"FunctionExpressionOpeningBrace": 1,
"IIFEOpeningParentheses": 0,
"IfStatement": ">=1",
"IfStatementClosingBrace": ">=1",
"IfStatementOpeningBrace": ">=1",
"LogicalExpression": -1,
"MemberExpressionClosing": 0,
"MemberExpressionOpening": 0,
"MemberExpressionPeriod": 0,
"MethodDefinition": ">=1",
"ObjectExpressionOpeningBrace": "<=1",
"ObjectPatternClosingBrace": 0,
"ObjectPatternComma": 0,
"ObjectPatternOpeningBrace": 0,
"ParameterDefault": 0,
"Property": -1,
"PropertyName": 0,
"ReturnStatement": -1,
"SwitchCaseColon": ">=1",
"SwitchClosingBrace": ">=1",
"SwitchOpeningBrace": ">=1",
"ThisExpression": 0,
"ThrowStatement": ">=1",
"TryClosingBrace": 0,
"TryKeyword": -1,
"TryOpeningBrace": ">=1",
"VariableDeclaration": ">=1",
"VariableDeclarationSemiColon": ">=1",
"VariableValue": -1,
"WhileStatement": ">=1",
"WhileStatementClosingBrace": ">=1",
"WhileStatementOpeningBrace": ">=1"
}
},
"whiteSpace": {
"value": " ",
"removeTrailing": 1,
"before": {
"ArgumentComma": 0,
"ArgumentList": 0,
"ArgumentListArrayExpression": 0,
"ArgumentListFunctionExpression": 1,
"ArgumentListObjectExpression": 0,
"ArrayExpressionClosing": 0,
"ArrayExpressionComma": 0,
"ArrayExpressionOpening": 1,
"AssignmentOperator": 1,
"BinaryExpression": 0,
"BinaryExpressionOperator": 1,
"BlockComment": 1,
"CallExpression": 1,
"CatchClosingBrace": 1,
"CatchKeyword": 1,
"CatchOpeningBrace": 1,
"CatchParameterList": 0,
"CommaOperator": 0,
"ConditionalExpressionAlternate": 1,
"ConditionalExpressionConsequent": 1,
"DoWhileStatementClosingBrace": 1,
"DoWhileStatementConditional": 1,
"DoWhileStatementOpeningBrace": 1,
"ElseIfStatementClosingBrace": 1,
"ElseIfStatementOpeningBrace": 1,
"ElseStatementClosingBrace": 1,
"ElseStatementOpeningBrace": 1,
"EmptyStatement": 0,
"ExpressionClosingParentheses": 0,
"FinallyClosingBrace": 1,
"FinallyKeyword": -1,
"FinallyOpeningBrace": 1,
"ForInStatement": 1,
"ForInStatementClosingBrace": 1,
"ForInStatementExpressionClosing": 0,
"ForInStatementExpressionOpening": 1,
"ForInStatementOpeningBrace": 1,
"ForStatement": 1,
"ForStatementClosingBrace": 1,
"ForStatementExpressionClosing": 0,
"ForStatementExpressionOpening": 1,
"ForStatementOpeningBrace": 1,
"ForStatementSemicolon": 0,
"FunctionDeclarationClosingBrace": 1,
"FunctionDeclarationOpeningBrace": 1,
"FunctionExpressionClosingBrace": 1,
"FunctionExpressionOpeningBrace": 1,
"IfStatementClosingBrace": 1,
"IfStatementConditionalClosing": 0,
"IfStatementConditionalOpening": 1,
"IfStatementOpeningBrace": 1,
"LineComment": 1,
"LogicalExpressionOperator": 1,
"MemberExpressionClosing": 0,
"ObjectExpressionClosingBrace": 1,
"ParameterComma": 0,
"ParameterList": 0,
"Property": 1,
"PropertyName": 1,
"PropertyValue": 1,
"SwitchDiscriminantClosing": 0,
"SwitchDiscriminantOpening": 1,
"ThrowKeyword": 1,
"TryClosingBrace": 1,
"TryKeyword": -1,
"TryOpeningBrace": 1,
"UnaryExpressionOperator": 0,
"VariableName": 1,
"VariableValue": 1,
"WhileStatementClosingBrace": 1,
"WhileStatementConditionalClosing": 0,
"WhileStatementConditionalOpening": 1,
"WhileStatementOpeningBrace": 1
},
"after": {
"ArgumentComma": 1,
"ArgumentList": 0,
"ArgumentListArrayExpression": 1,
"ArgumentListFunctionExpression": 1,
"ArgumentListObjectExpression": 0,
"ArrayExpressionClosing": 0,
"ArrayExpressionComma": 1,
"ArrayExpressionOpening": 0,
"AssignmentOperator": 1,
"BinaryExpression": 0,
"BinaryExpressionOperator": 1,
"BlockComment": 1,
"CallExpression": 0,
"CatchClosingBrace": 1,
"CatchKeyword": 1,
"CatchOpeningBrace": 1,
"CatchParameterList": 0,
"CommaOperator": 1,
"ConditionalExpressionConsequent": 1,
"ConditionalExpressionTest": 1,
"DoWhileStatementBody": 1,
"DoWhileStatementClosingBrace": 1,
"DoWhileStatementOpeningBrace": 1,
"ElseIfStatementClosingBrace": 1,
"ElseIfStatementOpeningBrace": 1,
"ElseStatementClosingBrace": 1,
"ElseStatementOpeningBrace": 1,
"EmptyStatement": 0,
"ExpressionOpeningParentheses": 0,
"FinallyClosingBrace": 1,
"FinallyKeyword": -1,
"FinallyOpeningBrace": 1,
"ForInStatement": 1,
"ForInStatementClosingBrace": 1,
"ForInStatementExpressionClosing": 1,
"ForInStatementExpressionOpening": 0,
"ForInStatementOpeningBrace": 1,
"ForStatement": 1,
"ForStatementClosingBrace": 1,
"ForStatementExpressionClosing": 1,
"ForStatementExpressionOpening": 0,
"ForStatementOpeningBrace": 1,
"ForStatementSemicolon": 1,
"FunctionDeclarationClosingBrace": 0,
"FunctionDeclarationOpeningBrace": 0,
"FunctionExpressionClosingBrace": 0,
"FunctionExpressionOpeningBrace": 0,
"FunctionName": 0,
"FunctionReservedWord": 0,
"IfStatementClosingBrace": 1,
"IfStatementConditionalClosing": 0,
"IfStatementConditionalOpening": 0,
"IfStatementOpeningBrace": 1,
"LogicalExpressionOperator": 1,
"MemberExpressionOpening": 0,
"ObjectExpressionClosingBrace": 0,
"ObjectExpressionOpeningBrace": 1,
"ParameterComma": 1,
"ParameterList": 0,
"PropertyName": 0,
"PropertyValue": 0,
"SwitchDiscriminantClosing": 1,
"SwitchDiscriminantOpening": 0,
"ThrowKeyword": 1,
"TryClosingBrace": 1,
"TryKeyword": -1,
"TryOpeningBrace": 1,
"UnaryExpressionOperator": 0,
"VariableName": 1,
"WhileStatementClosingBrace": 1,
"WhileStatementConditionalClosing": 1,
"WhileStatementConditionalOpening": 0,
"WhileStatementOpeningBrace": 1
}
}
}

View File

@@ -12,6 +12,8 @@ const dirs = fs
.join('|');
module.exports = {
root: true,
parser: '@babel/eslint-parser',
env: {

View File

@@ -1,12 +0,0 @@
{
"js": {
"indent_size": 2,
"indent_char": " ",
"indent_level": 2,
"indent_with_tabs": false,
"preserve_newlines": true,
"brace_style": "collapse",
"max_preserve_newlines": 2,
"jslint_happy": true
}
}

View File

@@ -1,12 +1,12 @@
{
"plugins": [
"stylelint-order"
],
"ignoreFiles": [
"frontend/src/Styles/scaffolding.css",
"**/*.js"
],
"rules": {
"plugins": [
"stylelint-order"
],
"ignoreFiles": [
"frontend/src/Styles/scaffolding.css",
"**/*.js"
],
"rules": {
"at-rule-empty-line-before": [
"always",
{
@@ -15,9 +15,6 @@
]
}
],
"at-rule-name-case": "lower",
"at-rule-name-newline-after": "always-multi-line",
"at-rule-name-space-after": "always",
"at-rule-no-unknown": [
true,
{
@@ -28,83 +25,36 @@
}
],
"at-rule-no-vendor-prefix": true,
"at-rule-semicolon-newline-after": "always",
"at-rule-semicolon-space-before": "never",
"block-closing-brace-empty-line-before": "never",
"block-closing-brace-newline-after": "always",
"block-closing-brace-newline-before": "always",
"block-closing-brace-space-after": "always-single-line",
"block-closing-brace-space-before": "always-single-line",
"block-no-empty": true,
"block-opening-brace-newline-after": "always",
"block-opening-brace-newline-before": "never-single-line",
"block-opening-brace-space-after": "always-single-line",
"block-opening-brace-space-before": "always",
"color-hex-case": "lower",
"color-hex-length": "short",
"color-named": "never",
"color-no-invalid-hex": true,
"comment-whitespace-inside": "always",
"declaration-bang-space-after": "never",
"declaration-bang-space-before": "always",
"declaration-block-no-duplicate-properties": [
true,
{
"ignoreProperties": [
"composes"
"composes"
]
}
],
"declaration-block-no-redundant-longhand-properties": true,
"declaration-block-no-shorthand-property-overrides": true,
"declaration-block-semicolon-newline-after": "always",
"declaration-block-semicolon-newline-before": "never-multi-line",
"declaration-block-semicolon-space-before": "never",
"declaration-block-single-line-max-declarations": 1,
"declaration-block-trailing-semicolon": "always",
"declaration-colon-space-after": "always",
"declaration-colon-space-before": "never",
"font-family-name-quotes": "always-unless-keyword",
"function-calc-no-unspaced-operator": true,
"function-comma-newline-after": "never-multi-line",
"function-comma-newline-before": "never-multi-line",
"function-comma-space-after": "always",
"function-comma-space-before": "never",
"function-linear-gradient-no-nonstandard-direction": true,
"function-name-case": "lower",
"function-parentheses-newline-inside": "never-multi-line",
"function-parentheses-space-inside": "never",
"function-url-quotes": "always",
"function-url-scheme-disallowed-list": [
"data"
],
"function-whitespace-after": "always",
"indentation": 2,
"keyframe-declaration-no-important": true,
"length-zero-no-unit": true,
"max-empty-lines": 1,
"max-line-length": [
100,
{
"ignore": [
"non-comments"
]
}
],
"max-nesting-depth": 2,
"media-feature-colon-space-after": "always",
"media-feature-colon-space-before": "never",
"media-feature-name-case": "lower",
"media-feature-name-no-vendor-prefix": true,
"media-feature-range-operator-space-after": "always",
"media-feature-range-operator-space-before": "always",
"no-empty-source": true,
"no-eol-whitespace": true,
"no-extra-semicolons": true,
"no-invalid-double-slash-comments": true,
"no-missing-end-of-source-newline": true,
"number-leading-zero": "always",
"number-no-trailing-zeros": true,
"order/order": [
"custom-properties",
"dollar-variables",
@@ -132,6 +82,7 @@
"right",
"bottom",
"left",
"inset",
"z-index",
"display",
"visibility",
@@ -343,54 +294,33 @@
]
}
],
"property-case": "lower",
"property-no-vendor-prefix": true,
"rule-empty-line-before": [
"always",
{
"except": [
"first-nested"
"first-nested"
],
"ignore": [
"after-comment"
"after-comment"
]
}
],
"selector-attribute-brackets-space-inside": "never",
"selector-attribute-operator-space-after": "never",
"selector-attribute-operator-space-before": "never",
"selector-attribute-quotes": "never",
"selector-class-pattern": "^[A-Za-z0-9]+$",
"selector-combinator-space-after": "always",
"selector-combinator-space-before": "always",
"selector-descendant-combinator-no-non-space": true,
"selector-list-comma-newline-after": "always",
"selector-list-comma-newline-before": "never-multi-line",
"selector-list-comma-space-before": "never",
"selector-max-attribute": 0,
"selector-max-class": 3,
"selector-max-compound-selectors": 3,
"selector-max-empty-lines": 0,
"selector-max-id": 0,
"selector-max-universal": 0,
"selector-pseudo-class-case": "lower",
"selector-pseudo-class-parentheses-space-inside": "never",
"selector-pseudo-element-case": "lower",
"selector-pseudo-element-colon-notation": "double",
"selector-pseudo-element-no-unknown": true,
"selector-type-case": "lower",
"selector-type-no-unknown": true,
"shorthand-property-no-redundant-values": true,
"string-no-newline": true,
"string-quotes": "single",
"time-min-milliseconds": 100,
"unit-case": "lower",
"unit-no-unknown": true,
"value-list-comma-newline-after": "never-multi-line",
"value-list-comma-newline-before": "never-multi-line",
"value-list-comma-space-after": "always",
"value-list-comma-space-before": "never",
"value-list-max-empty-lines": 0,
"value-no-vendor-prefix": true
}
}
}

7
frontend/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"recommendations": [
"stylelint.vscode-stylelint",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

View File

@@ -50,7 +50,7 @@ module.exports = (env) => {
'node_modules'
],
alias: {
jquery: 'jquery/src/jquery'
jquery: 'jquery/dist/jquery.min'
},
fallback: {
buffer: false,
@@ -251,18 +251,19 @@ module.exports = (env) => {
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
config.optimization.minimizer = [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
mangle: false,
keep_classnames: true,
keep_fnames: true
}
})
];
config.optimization = {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
sourceMap: true, // Must be set to true if using source-maps in production
mangle: false,
keep_classnames: true,
keep_fnames: true
}
})
]
};
}
return config;

View File

@@ -17,7 +17,7 @@ class DescriptionListItem extends Component {
} = this.props;
return (
<span>
<div>
<DescriptionListItemTitle
className={titleClassName}
>
@@ -29,7 +29,7 @@ class DescriptionListItem extends Component {
>
{data}
</DescriptionListItemDescription>
</span>
</div>
);
}
}

View File

@@ -1,6 +1,5 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Alert from 'Components/Alert';
import PathInput from 'Components/Form/PathInput';
import Button from 'Components/Link/Button';
@@ -39,7 +38,7 @@ class FileBrowserModalContent extends Component {
constructor(props, context) {
super(props, context);
this._scrollerNode = null;
this._scrollerRef = React.createRef();
this.state = {
isFileBrowserModalOpen: false,
@@ -57,21 +56,10 @@ class FileBrowserModalContent extends Component {
currentPath !== prevState.currentPath
) {
this.setState({ currentPath });
this._scrollerNode.scrollTop = 0;
this._scrollerRef.current.scrollTop = 0;
}
}
//
// Control
setScrollerRef = (ref) => {
if (ref) {
this._scrollerNode = ReactDOM.findDOMNode(ref);
} else {
this._scrollerNode = null;
}
};
//
// Listeners
@@ -145,7 +133,7 @@ class FileBrowserModalContent extends Component {
/>
<Scroller
ref={this.setScrollerRef}
ref={this._scrollerRef}
className={styles.scroller}
scrollDirection={scrollDirections.BOTH}
>

View File

@@ -1,8 +1,5 @@
.inputContainer {
top: -1px;
right: -1px;
bottom: -1px;
left: -1px;
inset: -1px;
display: flex;
align-items: start;
flex-wrap: wrap;

View File

@@ -112,6 +112,12 @@ class TextInput extends Component {
this._isMouseTarget = false;
};
onWheel = () => {
if (this.props.type === 'number') {
this._input.blur();
}
};
//
// Render
@@ -161,6 +167,7 @@ class TextInput extends Component {
onKeyUp={this.onKeyUp}
onMouseDown={this.onMouseDown}
onMouseUp={this.onMouseUp}
onWheel={this.onWheel}
/>
);
}

View File

@@ -23,6 +23,7 @@ function IconButton(props) {
className,
isDisabled && styles.isDisabled
)}
aria-label="Table Options Button"
isDisabled={isDisabled}
{...otherProps}
>

View File

@@ -13,7 +13,7 @@ const messages = [
'Loading humorous message... Please Wait',
'I could\'ve been faster in Python',
'Don\'t forget to rewind your tracks',
'Congratulations! you are the 1000th visitor.',
'Congratulations! You are the 1000th visitor.',
'HELP! I\'m being held hostage and forced to write these stupid lines!',
'RE-calibrating the internet...',
'I\'ll be here all week',

View File

@@ -56,6 +56,7 @@ class PageHeader extends Component {
<img
className={styles.logo}
src={`${window.Prowlarr.urlBase}/Content/Images/logo.png`}
alt="Prowlarr Logo"
/>
</Link>
</div>
@@ -74,6 +75,7 @@ class PageHeader extends Component {
<IconButton
className={styles.donate}
name={icons.HEART}
aria-label="Donate"
to="https://prowlarr.com/donate"
size={14}
/>

View File

@@ -21,7 +21,7 @@ function PageHeaderActionsMenu(props) {
return (
<div>
<Menu alignMenu={align.RIGHT}>
<MenuButton className={styles.menuButton}>
<MenuButton className={styles.menuButton} aria-label="Menu Button">
<Icon
name={icons.INTERACTIVE}
/>

View File

@@ -56,7 +56,9 @@ function ProgressBar(props) {
styles[kind],
enableColorImpairedMode && 'colorImpaired'
)}
aria-valuenow={progress}
role="meter"
aria-label={`Progress Bar at ${progress.toFixed(0)}%`}
aria-valuenow={progress.toFixed(0)}
aria-valuemin="0"
aria-valuemax="100"
style={{ width: progressPercent }}

View File

@@ -54,7 +54,7 @@ function Logger(minimumLogLevel) {
}
Logger.prototype.cleanse = function(message) {
const apikey = new RegExp(`access_token=${window.Prowlarr.apiKey}`, 'g');
const apikey = new RegExp(`access_token=${encodeURIComponent(window.Prowlarr.apiKey)}`, 'g');
return message.replace(apikey, 'access_token=(removed)');
};
@@ -98,7 +98,7 @@ class SignalRConnector extends Component {
this.connection = new signalR.HubConnectionBuilder()
.configureLogging(new Logger(signalR.LogLevel.Information))
.withUrl(`${url}?access_token=${window.Prowlarr.apiKey}`)
.withUrl(`${url}?access_token=${encodeURIComponent(window.Prowlarr.apiKey)}`)
.withAutomaticReconnect({
nextRetryDelayInMilliseconds: (retryContext) => {
if (retryContext.elapsedMilliseconds > 180000) {

View File

@@ -6,6 +6,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import { icons } from 'Helpers/Props';
import CapabilitiesLabel from 'Indexer/Index/Table/CapabilitiesLabel';
import translate from 'Utilities/String/translate';
import HistoryDetailsModal from './Details/HistoryDetailsModal';
import HistoryEventTypeCell from './HistoryEventTypeCell';
import HistoryRowParameter from './HistoryRowParameter';
@@ -193,7 +194,7 @@ class HistoryRow extends Component {
{
data.season ?
<HistoryRowParameter
title='Season'
title={translate('Season')}
value={data.season}
/> :
null
@@ -202,7 +203,7 @@ class HistoryRow extends Component {
{
data.episode ?
<HistoryRowParameter
title='Episode'
title={translate('Episode')}
value={data.episode}
/> :
null
@@ -211,7 +212,7 @@ class HistoryRow extends Component {
{
data.artist ?
<HistoryRowParameter
title='Artist'
title={translate('Artist')}
value={data.artist}
/> :
null
@@ -220,7 +221,7 @@ class HistoryRow extends Component {
{
data.album ?
<HistoryRowParameter
title='Album'
title={translate('Album')}
value={data.album}
/> :
null
@@ -229,7 +230,7 @@ class HistoryRow extends Component {
{
data.label ?
<HistoryRowParameter
title='Label'
title={translate('Label')}
value={data.label}
/> :
null
@@ -238,7 +239,7 @@ class HistoryRow extends Component {
{
data.track ?
<HistoryRowParameter
title='Track'
title={translate('Track')}
value={data.track}
/> :
null
@@ -247,7 +248,7 @@ class HistoryRow extends Component {
{
data.year ?
<HistoryRowParameter
title='Year'
title={translate('Year')}
value={data.year}
/> :
null
@@ -256,7 +257,7 @@ class HistoryRow extends Component {
{
data.genre ?
<HistoryRowParameter
title='Genre'
title={translate('Genre')}
value={data.genre}
/> :
null
@@ -265,7 +266,7 @@ class HistoryRow extends Component {
{
data.author ?
<HistoryRowParameter
title='Author'
title={translate('Author')}
value={data.author}
/> :
null
@@ -274,7 +275,7 @@ class HistoryRow extends Component {
{
data.bookTitle ?
<HistoryRowParameter
title='Book'
title={translate('Book')}
value={data.bookTitle}
/> :
null
@@ -283,7 +284,7 @@ class HistoryRow extends Component {
{
data.publisher ?
<HistoryRowParameter
title='Publisher'
title={translate('Publisher')}
value={data.publisher}
/> :
null
@@ -351,6 +352,11 @@ class HistoryRow extends Component {
`${data.elapsedTime}ms` :
null
}
{
data.cached === '1' ?
' (cached)' :
null
}
</TableRowCell>
);
}
@@ -376,14 +382,14 @@ class HistoryRow extends Component {
<IconButton
name={icons.SEARCH}
onPress={this.onSearchPress}
title='Repeat Search'
title={translate('RepeatSearch')}
/> :
null
}
<IconButton
name={icons.INFO}
onPress={this.onDetailsPress}
title='History Details'
title={translate('HistoryDetails')}
/>
</TableRowCell>
);

View File

@@ -222,7 +222,11 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
<PageToolbarSeparator />
<IndexerIndexSelectModeButton
label={isSelectMode ? 'Stop Selecting' : 'Select Indexer'}
label={
isSelectMode
? translate('StopSelecting')
: translate('SelectIndexer')
}
iconName={isSelectMode ? icons.SERIES_ENDED : icons.CHECK}
isSelectMode={isSelectMode}
overflowComponent={IndexerIndexSelectModeMenuItem}
@@ -230,7 +234,7 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
/>
<IndexerIndexSelectAllButton
label="SelectAll"
label={translate('SelectAll')}
isSelectMode={isSelectMode}
overflowComponent={IndexerIndexSelectAllMenuItem}
/>
@@ -245,7 +249,10 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
optionsComponent={IndexerIndexTableOptions}
onTableOptionChange={onTableOptionChange}
>
<PageToolbarButton label="Options" iconName={icons.TABLE} />
<PageToolbarButton
label={translate('Options')}
iconName={icons.TABLE}
/>
</TableOptionsModalWrapper>
<PageToolbarSeparator />
@@ -276,7 +283,9 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
>
{isFetching && !isPopulated ? <LoadingIndicator /> : null}
{!isFetching && !!error ? <div>Unable to load indexers</div> : null}
{!isFetching && !!error ? (
<div>{translate('UnableToLoadIndexers')}</div>
) : null}
{isLoaded ? (
<div className={styles.contentBodyContainer}>
@@ -295,7 +304,10 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
) : null}
{!error && isPopulated && !items.length ? (
<NoIndexer totalItems={totalItems} />
<NoIndexer
totalItems={totalItems}
onAddIndexerPress={onAddIndexerPress}
/>
) : null}
</PageContentBody>
{isLoaded && !!jumpBarItems.order.length ? (

View File

@@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
import { SelectActionType, useSelect } from 'App/SelectContext';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
interface IndexerIndexSelectAllButtonProps {
label: string;
@@ -32,7 +33,7 @@ function IndexerIndexSelectAllButton(props: IndexerIndexSelectAllButtonProps) {
return isSelectMode ? (
<PageToolbarButton
label={allSelected ? 'Unselect All' : 'Select All'}
label={allSelected ? translate('UnselectAll') : translate('SelectAll')}
iconName={icon}
onPress={onPress}
/>

View File

@@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
import { SelectActionType, useSelect } from 'App/SelectContext';
import PageToolbarOverflowMenuItem from 'Components/Page/Toolbar/PageToolbarOverflowMenuItem';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
interface IndexerIndexSelectAllMenuItemProps {
label: string;
@@ -33,7 +34,7 @@ function IndexerIndexSelectAllMenuItem(
return isSelectMode ? (
<PageToolbarOverflowMenuItem
label={allSelected ? 'Unselect All' : 'Select All'}
label={allSelected ? translate('UnselectAll') : translate('SelectAll')}
iconName={iconName}
onPress={onPressWrapper}
/>

View File

@@ -14,6 +14,7 @@ import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import translate from 'Utilities/String/translate';
import styles from './TagsModalContent.css';
interface TagsModalContentProps {
@@ -70,7 +71,7 @@ function TagsModalContent(props: TagsModalContentProps) {
<ModalBody>
<Form>
<FormGroup>
<FormLabel>Tags</FormLabel>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
@@ -81,7 +82,7 @@ function TagsModalContent(props: TagsModalContentProps) {
</FormGroup>
<FormGroup>
<FormLabel>Apply Tags</FormLabel>
<FormLabel>{translate('ApplyTags')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
@@ -89,17 +90,17 @@ function TagsModalContent(props: TagsModalContentProps) {
value={applyTags}
values={applyTagsOptions}
helpTexts={[
'How to apply tags to the selected series',
'Add: Add the tags the existing list of tags',
'Remove: Remove the entered tags',
'Replace: Replace the tags with the entered tags (enter no tags to clear all tags)',
translate('ApplyTagsHelpTexts1'),
translate('ApplyTagsHelpTexts2'),
translate('ApplyTagsHelpTexts3'),
translate('ApplyTagsHelpTexts4'),
]}
onChange={onApplyTagsChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Result</FormLabel>
<FormLabel>{translate('Result')}</FormLabel>
<div className={styles.result}>
{indexerTags.map((id) => {
@@ -116,7 +117,11 @@ function TagsModalContent(props: TagsModalContentProps) {
return (
<Label
key={tag.id}
title={removeTag ? 'Removing tag' : 'Existing tag'}
title={
removeTag
? translate('RemoveTagRemovingTag')
: translate('RemoveTagExistingTag')
}
kind={removeTag ? kinds.INVERSE : kinds.INFO}
size={sizes.LARGE}
>
@@ -140,7 +145,7 @@ function TagsModalContent(props: TagsModalContentProps) {
return (
<Label
key={tag.id}
title={'Adding tag'}
title={translate('AddingTag')}
kind={kinds.SUCCESS}
size={sizes.LARGE}
>

View File

@@ -55,7 +55,11 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
const vipExpiration =
fields.find((field) => field.name === 'vipExpiration')?.value ?? '';
const rssUrl = `${window.location.origin}${window.Prowlarr.urlBase}/${id}/api?t=search&extended=1&apikey=${window.Prowlarr.apiKey}`;
const rssUrl = `${window.location.origin}${
window.Prowlarr.urlBase
}/${id}/api?apikey=${encodeURIComponent(
window.Prowlarr.apiKey
)}&extended=1&t=search`;
const [isEditIndexerModalOpen, setIsEditIndexerModalOpen] = useState(false);
const [isDeleteIndexerModalOpen, setIsDeleteIndexerModalOpen] =

View File

@@ -4,6 +4,7 @@ import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import selectTableOptions from './selectTableOptions';
interface IndexerIndexTableOptionsProps {
@@ -32,13 +33,13 @@ function IndexerIndexTableOptions(props: IndexerIndexTableOptionsProps) {
return (
<Fragment>
<FormGroup>
<FormLabel>Show Search</FormLabel>
<FormLabel>{translate('ShowSearch')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showSearchAction"
value={showSearchAction}
helpText="Show search button on hover"
helpText={translate('ShowSearchHelpText')}
onChange={onTableOptionChangeWrapper}
/>
</FormGroup>

View File

@@ -33,8 +33,8 @@ function IndexerStatusCell(props: IndexerStatusCellProps) {
const enableKind = redirect ? kinds.INFO : kinds.SUCCESS;
const enableIcon = redirect ? icons.REDIRECT : icons.CHECK;
const enableTitle = redirect
? 'Indexer is Enabled, Redirect is Enabled'
: 'Indexer is Enabled';
? translate('EnabledRedirected')
: translate('Enabled');
return (
<Component className={className} {...otherProps}>
@@ -43,7 +43,7 @@ function IndexerStatusCell(props: IndexerStatusCellProps) {
className={styles.statusIcon}
kind={enabled ? enableKind : kinds.DEFAULT}
name={enabled ? enableIcon : icons.BLOCKLIST}
title={enabled ? enableTitle : 'Indexer is Disabled'}
title={enabled ? enableTitle : translate('EnabledIndexerIsDisabled')}
/>
}
{status ? (

View File

@@ -10,6 +10,7 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import { align, kinds } from 'Helpers/Props';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import StatsFilterMenu from './StatsFilterMenu';
import styles from './Stats.css';
@@ -188,53 +189,53 @@ function Stats(props) {
<div className={styles.fullWidthChart}>
<BarChart
data={getAverageResponseTimeData(item.indexers)}
title='Average Response Times (ms)'
title={translate('AverageResponseTimesMs')}
/>
</div>
<div className={styles.fullWidthChart}>
<BarChart
data={getFailureRateData(item.indexers)}
title='Indexer Failure Rate'
title={translate('IndexerFailureRate')}
kind={kinds.WARNING}
/>
</div>
<div className={styles.halfWidthChart}>
<StackedBarChart
data={getTotalRequestsData(item.indexers)}
title='Total Indexer Queries'
title={translate('TotalIndexerQueries')}
/>
</div>
<div className={styles.halfWidthChart}>
<BarChart
data={getNumberGrabsData(item.indexers)}
title='Total Indexer Successful Grabs'
title={translate('TotalIndexerSuccessfulGrabs')}
/>
</div>
<div className={styles.halfWidthChart}>
<BarChart
data={getUserAgentQueryData(item.userAgents)}
title='Total User Agent Queries'
title={translate('TotalUserAgentQueries')}
horizontal={true}
/>
</div>
<div className={styles.halfWidthChart}>
<BarChart
data={getUserAgentGrabsData(item.userAgents)}
title='Total User Agent Grabs'
title={translate('TotalUserAgentGrabs')}
horizontal={true}
/>
</div>
<div className={styles.halfWidthChart}>
<DoughnutChart
data={getHostQueryData(item.hosts)}
title='Total Host Queries'
title={translate('TotalHostQueries')}
horizontal={true}
/>
</div>
<div className={styles.halfWidthChart}>
<DoughnutChart
data={getHostGrabsData(item.hosts)}
title='Total Host Grabs'
title={translate('TotalHostGrabs')}
horizontal={true}
/>
</div>

View File

@@ -3,6 +3,7 @@ import React, { Component } from 'react';
import Card from 'Components/Card';
import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TagList from 'Components/TagList';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditApplicationModalConnector from './EditApplicationModalConnector';
@@ -55,7 +56,9 @@ class Application extends Component {
const {
id,
name,
syncLevel
syncLevel,
tags,
tagList
} = this.props;
return (
@@ -92,6 +95,11 @@ class Application extends Component {
</Label>
}
<TagList
tags={tags}
tagList={tagList}
/>
<EditApplicationModalConnector
id={id}
isOpen={this.state.isEditApplicationModalOpen}
@@ -117,6 +125,8 @@ Application.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
syncLevel: PropTypes.string.isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteApplication: PropTypes.func
};

View File

@@ -49,6 +49,7 @@ class Applications extends Component {
render() {
const {
items,
tagList,
onConfirmDeleteApplication,
...otherProps
} = this.props;
@@ -71,6 +72,7 @@ class Applications extends Component {
<Application
key={item.id}
{...item}
tagList={tagList}
onConfirmDeleteApplication={onConfirmDeleteApplication}
/>
);
@@ -109,6 +111,7 @@ Applications.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteApplication: PropTypes.func.isRequired
};

View File

@@ -4,13 +4,20 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { deleteApplication, fetchApplications } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import sortByName from 'Utilities/Array/sortByName';
import Applications from './Applications';
function createMapStateToProps() {
return createSelector(
createSortedSectionSelector('settings.applications', sortByName),
(applications) => applications
createTagsSelector(),
(applications, tagList) => {
return {
...applications,
tagList
};
}
);
}

View File

@@ -21,6 +21,7 @@ function HostSettings(props) {
port,
urlBase,
instanceName,
applicationUrl,
enableSsl,
sslPort,
sslCertPath,
@@ -89,6 +90,21 @@ function HostSettings(props) {
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('ApplicationURL')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="applicationUrl"
helpText={translate('ApplicationUrlHelpText')}
onChange={onInputChange}
{...applicationUrl}
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}

View File

@@ -3,6 +3,7 @@ import React, { Component } from 'react';
import Card from 'Components/Card';
import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TagList from 'Components/TagList';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditNotificationModalConnector from './EditNotificationModalConnector';
@@ -57,10 +58,14 @@ class Notification extends Component {
name,
onGrab,
onHealthIssue,
onHealthRestored,
onApplicationUpdate,
supportsOnGrab,
supportsOnHealthIssue,
supportsOnApplicationUpdate
supportsOnHealthRestored,
supportsOnApplicationUpdate,
tags,
tagList
} = this.props;
return (
@@ -74,17 +79,27 @@ class Notification extends Component {
</div>
{
supportsOnGrab && onGrab &&
supportsOnGrab && onGrab ?
<Label kind={kinds.SUCCESS}>
{translate('OnGrab')}
</Label>
</Label> :
null
}
{
supportsOnHealthIssue && onHealthIssue &&
supportsOnHealthIssue && onHealthIssue ?
<Label kind={kinds.SUCCESS}>
{translate('OnHealthIssue')}
</Label>
</Label> :
null
}
{
supportsOnHealthRestored && onHealthRestored ?
<Label kind={kinds.SUCCESS}>
{translate('OnHealthRestored')}
</Label> :
null
}
{
@@ -96,7 +111,7 @@ class Notification extends Component {
}
{
!onGrab && !onHealthIssue && !onApplicationUpdate ?
!onGrab && !onHealthIssue && !onHealthRestored && !onApplicationUpdate ?
<Label
kind={kinds.DISABLED}
outline={true}
@@ -106,6 +121,11 @@ class Notification extends Component {
null
}
<TagList
tags={tags}
tagList={tagList}
/>
<EditNotificationModalConnector
id={id}
isOpen={this.state.isEditNotificationModalOpen}
@@ -132,10 +152,14 @@ Notification.propTypes = {
name: PropTypes.string.isRequired,
onGrab: PropTypes.bool.isRequired,
onHealthIssue: PropTypes.bool.isRequired,
onHealthRestored: PropTypes.bool.isRequired,
onApplicationUpdate: PropTypes.bool.isRequired,
supportsOnGrab: PropTypes.bool.isRequired,
supportsOnHealthIssue: PropTypes.bool.isRequired,
supportsOnHealthRestored: PropTypes.bool.isRequired,
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteNotification: PropTypes.func.isRequired
};

View File

@@ -17,10 +17,12 @@ function NotificationEventItems(props) {
const {
onGrab,
onHealthIssue,
onHealthRestored,
onApplicationUpdate,
supportsOnGrab,
includeManualGrabs,
supportsOnHealthIssue,
supportsOnHealthRestored,
includeHealthWarnings,
supportsOnApplicationUpdate
} = item;
@@ -70,8 +72,19 @@ function NotificationEventItems(props) {
/>
</div>
<div>
<FormInputGroup
type={inputTypes.CHECK}
name="onHealthRestored"
helpText={translate('OnHealthRestoredHelpText')}
isDisabled={!supportsOnHealthRestored.value}
{...onHealthRestored}
onChange={onInputChange}
/>
</div>
{
onHealthIssue.value &&
(onHealthIssue.value || onHealthRestored.value) &&
<div>
<FormInputGroup
type={inputTypes.CHECK}

View File

@@ -49,6 +49,7 @@ class Notifications extends Component {
render() {
const {
items,
tagList,
onConfirmDeleteNotification,
...otherProps
} = this.props;
@@ -71,6 +72,7 @@ class Notifications extends Component {
<Notification
key={item.id}
{...item}
tagList={tagList}
onConfirmDeleteNotification={onConfirmDeleteNotification}
/>
);
@@ -109,6 +111,7 @@ Notifications.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteNotification: PropTypes.func.isRequired
};

View File

@@ -4,13 +4,20 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { deleteNotification, fetchNotifications } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import sortByName from 'Utilities/Array/sortByName';
import Notifications from './Notifications';
function createMapStateToProps() {
return createSelector(
createSortedSectionSelector('settings.notifications', sortByName),
(notifications) => notifications
createTagsSelector(),
(notifications, tagList) => {
return {
...notifications,
tagList
};
}
);
}

View File

@@ -17,6 +17,7 @@ function TagDetailsModalContent(props) {
indexers,
notifications,
indexerProxies,
applications,
onModalClose,
onDeleteTagPress
} = props;
@@ -79,6 +80,21 @@ function TagDetailsModalContent(props) {
}
</FieldSet>
}
{
!!applications.length &&
<FieldSet legend={translate('Applications')}>
{
applications.map((item) => {
return (
<div key={item.id}>
{item.name}
</div>
);
})
}
</FieldSet>
}
</ModalBody>
<ModalFooter>
@@ -110,6 +126,7 @@ TagDetailsModalContent.propTypes = {
indexers: PropTypes.arrayOf(PropTypes.object).isRequired,
notifications: PropTypes.arrayOf(PropTypes.object).isRequired,
indexerProxies: PropTypes.arrayOf(PropTypes.object).isRequired,
applications: PropTypes.arrayOf(PropTypes.object).isRequired,
onModalClose: PropTypes.func.isRequired,
onDeleteTagPress: PropTypes.func.isRequired
};

View File

@@ -18,16 +18,24 @@ function createMatchingIndexersSelector() {
function createMatchingIndexerProxiesSelector() {
return createSelector(
(state, { notificationIds }) => notificationIds,
(state) => state.settings.notifications.items,
(state, { indexerProxyIds }) => indexerProxyIds,
(state) => state.settings.indexerProxies.items,
findMatchingItems
);
}
function createMatchingNotificationsSelector() {
return createSelector(
(state, { indexerProxyIds }) => indexerProxyIds,
(state) => state.settings.indexerProxies.items,
(state, { notificationIds }) => notificationIds,
(state) => state.settings.notifications.items,
findMatchingItems
);
}
function createMatchingApplicationsSelector() {
return createSelector(
(state, { applicationIds }) => applicationIds,
(state) => state.settings.applications.items,
findMatchingItems
);
}
@@ -37,11 +45,13 @@ function createMapStateToProps() {
createMatchingIndexersSelector(),
createMatchingIndexerProxiesSelector(),
createMatchingNotificationsSelector(),
(indexers, indexerProxies, notifications) => {
createMatchingApplicationsSelector(),
(indexers, indexerProxies, notifications, applications) => {
return {
indexers,
indexerProxies,
notifications
notifications,
applications
};
}
);

View File

@@ -55,7 +55,8 @@ class Tag extends Component {
label,
notificationIds,
indexerIds,
indexerProxyIds
indexerProxyIds,
applicationIds
} = this.props;
const {
@@ -66,7 +67,8 @@ class Tag extends Component {
const isTagUsed = !!(
indexerIds.length ||
notificationIds.length ||
indexerProxyIds.length
indexerProxyIds.length ||
applicationIds.length
);
return (
@@ -102,6 +104,13 @@ class Tag extends Component {
{indexerProxyIds.length} {indexerProxyIds.length > 1 ? translate('IndexerProxies') : translate('IndexerProxy')}
</div>
}
{
!!applicationIds.length &&
<div>
{applicationIds.length} {applicationIds.length > 1 ? translate('Applications') : translate('Application')}
</div>
}
</div>
}
@@ -118,6 +127,7 @@ class Tag extends Component {
indexerIds={indexerIds}
notificationIds={notificationIds}
indexerProxyIds={indexerProxyIds}
applicationIds={applicationIds}
isOpen={isDetailsModalOpen}
onModalClose={this.onDetailsModalClose}
onDeleteTagPress={this.onDeleteTagPress}
@@ -143,13 +153,15 @@ Tag.propTypes = {
notificationIds: PropTypes.arrayOf(PropTypes.number).isRequired,
indexerIds: PropTypes.arrayOf(PropTypes.number).isRequired,
indexerProxyIds: PropTypes.arrayOf(PropTypes.number).isRequired,
applicationIds: PropTypes.arrayOf(PropTypes.number).isRequired,
onConfirmDeleteTag: PropTypes.func.isRequired
};
Tag.defaultProps = {
indexerIds: [],
notificationIds: [],
indexerProxyIds: []
indexerProxyIds: [],
applicationIds: []
};
export default Tag;

View File

@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchIndexerProxies, fetchNotifications } from 'Store/Actions/settingsActions';
import { fetchApplications, fetchIndexerProxies, fetchNotifications } from 'Store/Actions/settingsActions';
import { fetchTagDetails } from 'Store/Actions/tagActions';
import Tags from './Tags';
@@ -27,7 +27,8 @@ function createMapStateToProps() {
const mapDispatchToProps = {
dispatchFetchTagDetails: fetchTagDetails,
dispatchFetchNotifications: fetchNotifications,
dispatchFetchIndexerProxies: fetchIndexerProxies
dispatchFetchIndexerProxies: fetchIndexerProxies,
dispatchFetchApplications: fetchApplications
};
class MetadatasConnector extends Component {
@@ -39,12 +40,14 @@ class MetadatasConnector extends Component {
const {
dispatchFetchTagDetails,
dispatchFetchNotifications,
dispatchFetchIndexerProxies
dispatchFetchIndexerProxies,
dispatchFetchApplications
} = this.props;
dispatchFetchTagDetails();
dispatchFetchNotifications();
dispatchFetchIndexerProxies();
dispatchFetchApplications();
}
//
@@ -62,7 +65,8 @@ class MetadatasConnector extends Component {
MetadatasConnector.propTypes = {
dispatchFetchTagDetails: PropTypes.func.isRequired,
dispatchFetchNotifications: PropTypes.func.isRequired,
dispatchFetchIndexerProxies: PropTypes.func.isRequired
dispatchFetchIndexerProxies: PropTypes.func.isRequired,
dispatchFetchApplications: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(MetadatasConnector);

View File

@@ -144,7 +144,7 @@ export const defaultState = {
},
category: function(item) {
if (item.categories.length > 0) {
if (item.categories !== undefined && item.categories.length > 0) {
const sortedCats = item.categories.filter((cat) => cat.name !== undefined).sort((c) => c.id);
const firstCat = sortedCats[0];

View File

@@ -1,4 +1,7 @@
@define-mixin scrollbar {
scrollbar-color: var(--scrollbarBackgroundColor) transparent;
scrollbar-width: thin;
&::-webkit-scrollbar {
width: 10px;
height: 10px;

View File

@@ -74,29 +74,29 @@ module.exports = {
defaultButtonTextColor: '#eee',
defaultButtonBackgroundColor: '#333',
defaultBorderColor: '#eaeaea',
defaultBorderColor: '#393f45',
defaultHoverBackgroundColor: '#444',
defaultHoverBorderColor: '#d6d6d6;',
defaultHoverBorderColor: '#5a6265',
primaryBackgroundColor: '#5d9cec',
primaryBorderColor: '#5899eb',
primaryHoverBackgroundColor: '#4b91ea',
primaryHoverBorderColor: '#3483e7;',
primaryHoverBorderColor: '#3483e7',
successBackgroundColor: '#27c24c',
successBorderColor: '#26be4a',
successHoverBackgroundColor: '#24b145',
successHoverBorderColor: '#1f9c3d;',
successHoverBorderColor: '#1f9c3d',
warningBackgroundColor: '#ff902b',
warningBorderColor: '#ff8d26',
warningHoverBackgroundColor: '#ff8517',
warningHoverBorderColor: '#fc7800;',
warningHoverBorderColor: '#fc7800',
dangerBackgroundColor: '#f05050',
dangerBorderColor: '#f04b4b',
dangerHoverBackgroundColor: '#ee3d3d',
dangerHoverBorderColor: '#ec2626;',
dangerHoverBorderColor: '#ec2626',
iconButtonDisabledColor: '#7a7a7a',
iconButtonHoverColor: '#666',

View File

@@ -76,27 +76,27 @@ module.exports = {
defaultButtonBackgroundColor: '#fff',
defaultBorderColor: '#eaeaea',
defaultHoverBackgroundColor: '#f5f5f5',
defaultHoverBorderColor: '#d6d6d6;',
defaultHoverBorderColor: '#d6d6d6',
primaryBackgroundColor: '#5d9cec',
primaryBorderColor: '#5899eb',
primaryHoverBackgroundColor: '#4b91ea',
primaryHoverBorderColor: '#3483e7;',
primaryHoverBorderColor: '#3483e7',
successBackgroundColor: '#27c24c',
successBorderColor: '#26be4a',
successHoverBackgroundColor: '#24b145',
successHoverBorderColor: '#1f9c3d;',
successHoverBorderColor: '#1f9c3d',
warningBackgroundColor: '#ff902b',
warningBorderColor: '#ff8d26',
warningHoverBackgroundColor: '#ff8517',
warningHoverBorderColor: '#fc7800;',
warningHoverBorderColor: '#fc7800',
dangerBackgroundColor: '#f05050',
dangerBorderColor: '#f04b4b',
dangerHoverBackgroundColor: '#ee3d3d',
dangerHoverBorderColor: '#ec2626;',
dangerHoverBorderColor: '#ec2626',
iconButtonDisabledColor: '#7a7a7a',
iconButtonHoverColor: '#666',

View File

@@ -5,7 +5,7 @@
"scripts": {
"build": "webpack --config ./frontend/build/webpack.config.js",
"prebuild": "yarn clean",
"clean": "rimraf ./_output/UI && rimraf \"**/*.js.map\"",
"clean": "rimraf ./_output/UI && rimraf --glob \"**/*.js.map\"",
"start": "webpack --watch --config ./frontend/build/webpack.config.js",
"watch": "webpack --watch --config ./frontend/build/webpack.config.js",
"lint": "eslint --config frontend/.eslintrc.js --ignore-path frontend/.eslintignore frontend/",
@@ -20,42 +20,38 @@
"readmeFilename": "readme.md",
"main": "index.js",
"browserslist": [
">0.25%",
"not ie 11",
"not op_mini all",
"not chrome < 60"
"defaults"
],
"dependencies": {
"@fortawesome/fontawesome-free": "6.2.1",
"@fortawesome/fontawesome-svg-core": "6.2.1",
"@fortawesome/free-regular-svg-icons": "6.2.1",
"@fortawesome/free-solid-svg-icons": "6.2.1",
"@fortawesome/fontawesome-free": "6.4.0",
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-regular-svg-icons": "6.4.0",
"@fortawesome/free-solid-svg-icons": "6.4.0",
"@fortawesome/react-fontawesome": "0.2.0",
"@juggle/resize-observer": "3.4.0",
"@microsoft/signalr": "6.0.13",
"@sentry/browser": "7.28.0",
"@sentry/integrations": "7.28.0",
"@types/jest": "29.2.5",
"@types/node": "18.11.18",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
"chart.js": "4.1.1",
"@microsoft/signalr": "6.0.16",
"@sentry/browser": "7.51.2",
"@sentry/integrations": "7.51.2",
"@types/node": "18.15.11",
"@types/react": "18.2.6",
"@types/react-dom": "18.2.4",
"chart.js": "4.3.0",
"classnames": "2.3.2",
"clipboard": "2.0.11",
"connected-react-router": "6.9.3",
"element-class": "0.2.2",
"filesize": "10.0.6",
"filesize": "10.0.7",
"history": "4.10.1",
"https-browserify": "1.0.0",
"jdu": "1.0.0",
"jquery": "3.6.2",
"jquery": "3.7.0",
"lodash": "4.17.21",
"mobile-detect": "1.4.5",
"moment": "2.29.4",
"mousetrap": "1.6.5",
"normalize.css": "8.0.1",
"prop-types": "15.8.1",
"qs": "6.11.0",
"qs": "6.11.1",
"react": "17.0.2",
"react-addons-shallow-compare": "15.6.3",
"react-async-script": "1.2.0",
@@ -67,7 +63,7 @@
"react-dnd-touch-backend": "14.1.1",
"react-document-title": "2.0.3",
"react-dom": "17.0.2",
"react-focus-lock": "2.9.2",
"react-focus-lock": "2.9.4",
"react-google-recaptcha": "2.1.0",
"react-lazyload": "3.2.0",
"react-measure": "1.4.7",
@@ -77,79 +73,76 @@
"react-router-dom": "5.2.0",
"react-text-truncate": "0.19.0",
"react-use-measure": "2.1.1",
"react-virtualized": "9.21.1",
"react-virtualized": "9.22.3",
"react-window": "1.8.8",
"redux": "4.2.0",
"redux": "4.2.1",
"redux-actions": "2.6.5",
"redux-batched-actions": "0.5.0",
"redux-localstorage": "0.4.1",
"redux-thunk": "2.4.2",
"reselect": "4.1.7",
"stacktrace-js": "2.0.2",
"typescript": "4.9.4"
"typescript": "5.0.4"
},
"devDependencies": {
"@babel/core": "7.20.5",
"@babel/eslint-parser": "7.19.1",
"@babel/core": "7.21.8",
"@babel/eslint-parser": "7.21.8",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-decorators": "7.20.5",
"@babel/plugin-proposal-export-default-from": "7.18.10",
"@babel/plugin-proposal-export-namespace-from": "7.18.9",
"@babel/plugin-proposal-function-sent": "7.18.6",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
"@babel/plugin-proposal-numeric-separator": "7.18.6",
"@babel/plugin-proposal-optional-chaining": "7.18.9",
"@babel/plugin-proposal-throw-expressions": "7.18.6",
"@babel/plugin-proposal-optional-chaining": "7.21.0",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.20.2",
"@babel/preset-env": "7.21.5",
"@babel/preset-react": "7.18.6",
"@babel/preset-typescript": "7.18.6",
"@babel/preset-typescript": "7.21.5",
"@types/react-window": "1.8.5",
"@typescript-eslint/eslint-plugin": "5.48.1",
"@typescript-eslint/parser": "5.48.0",
"@types/webpack-livereload-plugin": "2.3.3",
"@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.5",
"are-you-es5": "2.1.2",
"autoprefixer": "10.4.13",
"babel-eslint": "10.1.0",
"babel-loader": "9.1.0",
"autoprefixer": "10.4.14",
"babel-loader": "9.1.2",
"babel-plugin-inline-classnames": "2.0.1",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
"core-js": "3.26.1",
"core-js": "3.30.2",
"css-loader": "6.7.3",
"css-modules-typescript-loader": "4.0.1",
"eslint": "8.30.0",
"eslint-config-prettier": "8.6.0",
"eslint": "8.40.0",
"eslint-config-prettier": "8.8.0",
"eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.31.11",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-simple-import-sort": "8.0.0",
"eslint-plugin-simple-import-sort": "10.0.0",
"file-loader": "6.2.0",
"filemanager-webpack-plugin": "8.0.0",
"fork-ts-checker-webpack-plugin": "7.2.14",
"html-webpack-plugin": "5.5.0",
"fork-ts-checker-webpack-plugin": "8.0.0",
"html-webpack-plugin": "5.5.1",
"loader-utils": "^3.2.1",
"mini-css-extract-plugin": "2.7.2",
"postcss": "8.4.20",
"mini-css-extract-plugin": "2.7.5",
"postcss": "8.4.23",
"postcss-color-function": "4.1.0",
"postcss-loader": "7.0.2",
"postcss-loader": "7.3.0",
"postcss-mixins": "9.0.4",
"postcss-nested": "6.0.0",
"postcss-nested": "6.0.1",
"postcss-simple-vars": "7.0.1",
"postcss-url": "10.1.3",
"prettier": "2.8.2",
"prettier": "2.8.8",
"require-nocache": "1.0.0",
"rimraf": "3.0.2",
"rimraf": "4.4.1",
"run-sequence": "2.2.1",
"streamqueue": "1.1.2",
"style-loader": "3.3.1",
"stylelint": "14.16.0",
"stylelint-order": "5.0.0",
"style-loader": "3.3.2",
"stylelint": "15.6.1",
"stylelint-order": "6.0.3",
"terser-webpack-plugin": "5.3.8",
"ts-loader": "9.4.2",
"typescript-plugin-css-modules": "4.1.1",
"typescript-plugin-css-modules": "5.0.1",
"url-loader": "4.1.1",
"webpack": "5.75.0",
"webpack-cli": "5.0.1",
"webpack": "5.82.1",
"webpack-cli": "5.1.1",
"webpack-livereload-plugin": "3.0.2"
}
}

View File

@@ -3,7 +3,9 @@
<PropertyGroup>
<AnalysisLevel>6.0-all</AnalysisLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
<PlatformTarget>AnyCPU</PlatformTarget>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<RuntimeIdentifiers>win-x64;win-x86;osx-x64;osx-arm64;linux-x64;linux-musl-x64;linux-musl-arm;linux-arm;linux-arm64;linux-musl-arm64</RuntimeIdentifiers>
@@ -24,7 +26,7 @@
<ProwlarrProject Condition="$(MSBuildProjectName.StartsWith('Prowlarr'))">true</ProwlarrProject>
<ProwlarrProject Condition="$(MSBuildProjectName.StartsWith('ServiceInstall'))">true</ProwlarrProject>
<ProwlarrProject Condition="$(MSBuildProjectName.StartsWith('ServiceUninstall'))">true</ProwlarrProject>
<!-- A test project gets the test sdk packages automatically added -->
<TestProject>false</TestProject>
<TestProject Condition="$(MSBuildProjectName.EndsWith('.Test'))">true</TestProject>
@@ -50,7 +52,7 @@
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<!-- Test projects need bindingRedirects -->
<PropertyGroup Condition="'$(ProwlarrOutputType)'=='Test'">
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
@@ -63,7 +65,7 @@
<Product>Prowlarr</Product>
<Company>prowlarr.com</Company>
<Copyright>Copyright 2014-$([System.DateTime]::Now.ToString('yyyy')) prowlarr.com (GNU General Public v3)</Copyright>
<!-- Should be replaced by CI -->
<AssemblyVersion>10.0.0.*</AssemblyVersion>
<AssemblyConfiguration>$(Configuration)-dev</AssemblyConfiguration>
@@ -72,8 +74,10 @@
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<Deterministic Condition="$(AssemblyVersion.EndsWith('*'))">False</Deterministic>
<PathMap>$(MSBuildProjectDirectory)=./$(MSBuildProjectName)/</PathMap>
</PropertyGroup>
<!-- Set the AssemblyConfiguration attribute for projects -->
@@ -92,7 +96,7 @@
<!-- FXCop Built into Net5 SDK now as NETAnalyzers, Enabled by default on net5 projects -->
<EnableNETAnalyzers>false</EnableNETAnalyzers>
</PropertyGroup>
<!-- Standard testing packages -->
<ItemGroup Condition="'$(TestProject)'=='true'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />

View File

@@ -11,7 +11,6 @@ using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Test.Common;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Remote;
namespace NzbDrone.Automation.Test
{

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -352,6 +351,26 @@ namespace NzbDrone.Common.Test.DiskTests
.Verify(v => v.DeleteFile(_targetPath), Times.Once());
}
[Test]
public void should_not_rollback_move_on_partial_if_destination_already_exists()
{
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.MoveFile(_sourcePath, _targetPath, false))
.Callback(() =>
{
WithExistingFile(_targetPath, true, 900);
});
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.MoveFile(_sourcePath, _targetPath, false))
.Throws(new FileAlreadyExistsException("File already exists", _targetPath));
Assert.Throws<FileAlreadyExistsException>(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move));
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFile(_targetPath), Times.Never());
}
[Test]
public void should_log_error_if_rollback_partialmove_fails()
{

View File

@@ -1,4 +1,5 @@
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Test.Common;
@@ -12,14 +13,14 @@ namespace NzbDrone.Common.Test.EnsureTest
public void EnsureWindowsPath(string path)
{
WindowsOnly();
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
}
[TestCase(@"/var/user/file with, comma.mkv")]
public void EnsureLinuxPath(string path)
{
PosixOnly();
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
}
}
}

View File

@@ -1,4 +1,3 @@
using System.Globalization;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;

View File

@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using NUnit.Framework;
using NzbDrone.Common.Http;
namespace NzbDrone.Common.Test.Http
{
[TestFixture]
public class CookieUtilFixture
{
[Test]
public void CookieHeaderToDictionaryGood()
{
// valid cookies with non-alpha characters in the value
var cookieHeader = "__cfduid=d6237f041586694295; __cf_bm=TlOng/xyqckk-TMen38z+0RFYA7YA=";
var expectedCookieDictionary = new Dictionary<string, string>
{
{ "__cfduid", "d6237f041586694295" },
{ "__cf_bm", "TlOng/xyqckk-TMen38z+0RFYA7YA=" }
};
CollectionAssert.AreEqual(expectedCookieDictionary, CookieUtil.CookieHeaderToDictionary(cookieHeader));
}
[Test]
public void CookieHeaderToDictionaryDuplicateKeys()
{
// cookie with duplicate keys and whitespace separator instead of ;
var cookieHeader = "__cfduid=d6237f041586694295; __cf_bm=TlOng/xyqckk-TMen38z+0RFYA7YA= __cf_bm=test";
var expectedCookieDictionary = new Dictionary<string, string>
{
{ "__cfduid", "d6237f041586694295" },
{ "__cf_bm", "test" } // we always assume the latest value is the most recent
};
CollectionAssert.AreEqual(expectedCookieDictionary, CookieUtil.CookieHeaderToDictionary(cookieHeader));
}
[Test]
public void CookieHeaderToDictionaryMalformed()
{
// malformed cookies
var cookieHeader = "__cfduidd6237f041586694295; __cf_;bm TlOng; good_cookie=value";
var expectedCookieDictionary = new Dictionary<string, string> { { "good_cookie", "value" }, };
CollectionAssert.AreEqual(expectedCookieDictionary, CookieUtil.CookieHeaderToDictionary(cookieHeader));
}
[Test]
[SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")]
public void CookieHeaderToDictionaryNull()
{
// null cookie header
var expectedCookieDictionary = new Dictionary<string, string>();
CollectionAssert.AreEqual(expectedCookieDictionary, CookieUtil.CookieHeaderToDictionary(null));
}
[Test]
public void CookieDictionaryToHeaderGood()
{
// valid cookies with non-alpha characters in the value
var cookieDictionary = new Dictionary<string, string>
{
{ "__cfduid", "d6237f041586694295" },
{ "__cf_bm", "TlOng/xyqckk-TMen38z+0RFYA7YA=" }
};
var expectedCookieHeader = "__cfduid=d6237f041586694295; __cf_bm=TlOng/xyqckk-TMen38z+0RFYA7YA=";
CollectionAssert.AreEqual(expectedCookieHeader, CookieUtil.CookieDictionaryToHeader(cookieDictionary));
}
[Test]
public void CookieDictionaryToHeaderMalformed1()
{
// malformed key
var cookieDictionary = new Dictionary<string, string>
{
{ "__cf_=bm", "34234234" }
};
var ex = Assert.Throws<FormatException>(() => CookieUtil.CookieDictionaryToHeader(cookieDictionary));
Assert.AreEqual("The cookie '__cf_=bm=34234234' is malformed.", ex.Message);
}
[Test]
public void CookieDictionaryToHeaderMalformed2()
{
// malformed value
var cookieDictionary = new Dictionary<string, string>
{
{ "__cf_bm", "34234 234" }
};
var ex = Assert.Throws<FormatException>(() => CookieUtil.CookieDictionaryToHeader(cookieDictionary));
Assert.AreEqual("The cookie '__cf_bm=34234 234' is malformed.", ex.Message);
}
[Test]
public void CookieDictionaryToHeaderNull()
{
// null cookie dictionary
var expectedCookieHeader = "";
CollectionAssert.AreEqual(expectedCookieHeader, CookieUtil.CookieDictionaryToHeader(null));
}
}
}

View File

@@ -35,6 +35,7 @@ namespace NzbDrone.Common.Test
[TestCase(@"\\Testserver\Test\file.ext", @"\\Testserver\Test\file.ext")]
[TestCase(@"\\Testserver\Test\file.ext\\", @"\\Testserver\Test\file.ext")]
[TestCase(@"\\Testserver\Test\file.ext \\", @"\\Testserver\Test\file.ext")]
[TestCase(@"//CAPITAL//lower// ", @"\\CAPITAL\lower")]
public void Clean_Path_Windows(string dirty, string clean)
{
WindowsOnly();

View File

@@ -3,7 +3,6 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Cache
{

View File

@@ -5,8 +5,6 @@ using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Text;
using System.Threading.Tasks;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Common.Composition
@@ -19,16 +17,17 @@ namespace NzbDrone.Common.Composition
RegisterSQLiteResolver();
}
public static IEnumerable<Assembly> Load(IEnumerable<string> assemblies)
public static IList<Assembly> Load(IList<string> assemblyNames)
{
var toLoad = assemblies.ToList();
var toLoad = assemblyNames.ToList();
toLoad.Add("Prowlarr.Common");
toLoad.Add(OsInfo.IsWindows ? "Prowlarr.Windows" : "Prowlarr.Mono");
var startupPath = AppDomain.CurrentDomain.BaseDirectory;
return toLoad.Select(x =>
AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(startupPath, $"{x}.dll")));
return toLoad
.Select(x => AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(startupPath, $"{x}.dll")))
.ToList();
}
private static Assembly ContainerResolveEventHandler(object sender, ResolveEventArgs args)

View File

@@ -65,7 +65,7 @@ namespace NzbDrone.Common.Disk
private void CheckFolderExists(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
if (!FolderExists(path))
{
@@ -75,7 +75,7 @@ namespace NzbDrone.Common.Disk
private void CheckFileExists(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
if (!FileExists(path))
{
@@ -93,19 +93,19 @@ namespace NzbDrone.Common.Disk
public bool FolderExists(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return Directory.Exists(path);
}
public bool FileExists(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return FileExists(path, PathStringComparison);
}
public bool FileExists(string path, StringComparison stringComparison)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
switch (stringComparison)
{
@@ -125,7 +125,7 @@ namespace NzbDrone.Common.Disk
public bool FolderWritable(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
try
{
@@ -144,35 +144,35 @@ namespace NzbDrone.Common.Disk
public bool FolderEmpty(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return Directory.EnumerateFileSystemEntries(path).Empty();
}
public string[] GetDirectories(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return Directory.GetDirectories(path);
}
public string[] GetFiles(string path, SearchOption searchOption)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return Directory.GetFiles(path, "*.*", searchOption);
}
public long GetFolderSize(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return GetFiles(path, SearchOption.AllDirectories).Sum(e => new FileInfo(e).Length);
}
public long GetFileSize(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
if (!FileExists(path))
{
@@ -185,13 +185,13 @@ namespace NzbDrone.Common.Disk
public void CreateFolder(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Directory.CreateDirectory(path);
}
public void DeleteFile(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Logger.Trace("Deleting file: {0}", path);
RemoveReadOnly(path);
@@ -201,8 +201,8 @@ namespace NzbDrone.Common.Disk
public void CloneFile(string source, string destination, bool overwrite = false)
{
Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath();
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
if (source.PathEquals(destination))
{
@@ -219,8 +219,8 @@ namespace NzbDrone.Common.Disk
public void CopyFile(string source, string destination, bool overwrite = false)
{
Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath();
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
if (source.PathEquals(destination))
{
@@ -237,8 +237,8 @@ namespace NzbDrone.Common.Disk
public void MoveFile(string source, string destination, bool overwrite = false)
{
Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath();
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
if (source.PathEquals(destination))
{
@@ -256,14 +256,19 @@ namespace NzbDrone.Common.Disk
public void MoveFolder(string source, string destination, bool overwrite = false)
{
Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath();
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
Directory.Move(source, destination);
}
protected virtual void MoveFileInternal(string source, string destination)
{
if (File.Exists(destination))
{
throw new FileAlreadyExistsException("File already exists", destination);
}
File.Move(source, destination);
}
@@ -281,7 +286,7 @@ namespace NzbDrone.Common.Disk
public void DeleteFolder(string path, bool recursive)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var files = Directory.GetFiles(path, "*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
Array.ForEach(files, RemoveReadOnly);
@@ -291,14 +296,14 @@ namespace NzbDrone.Common.Disk
public string ReadAllText(string filePath)
{
Ensure.That(filePath, () => filePath).IsValidPath();
Ensure.That(filePath, () => filePath).IsValidPath(PathValidationType.CurrentOs);
return File.ReadAllText(filePath);
}
public void WriteAllText(string filename, string contents)
{
Ensure.That(filename, () => filename).IsValidPath();
Ensure.That(filename, () => filename).IsValidPath(PathValidationType.CurrentOs);
RemoveReadOnly(filename);
// File.WriteAllText is broken on net core when writing to some CIFS mounts
@@ -314,7 +319,7 @@ namespace NzbDrone.Common.Disk
public void FolderSetLastWriteTime(string path, DateTime dateTime)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
if (dateTime.Before(DateTimeExtensions.Epoch))
{
@@ -326,7 +331,7 @@ namespace NzbDrone.Common.Disk
public void FileSetLastWriteTime(string path, DateTime dateTime)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
if (dateTime.Before(DateTimeExtensions.Epoch))
{
@@ -351,16 +356,16 @@ namespace NzbDrone.Common.Disk
}
}
public string GetPathRoot(string path)
public virtual string GetPathRoot(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return Path.GetPathRoot(path);
}
public string GetParentFolder(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var parent = Directory.GetParent(path.TrimEnd(Path.DirectorySeparatorChar));
@@ -407,7 +412,7 @@ namespace NzbDrone.Common.Disk
public void EmptyFolder(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
foreach (var file in GetFiles(path, SearchOption.TopDirectoryOnly))
{
@@ -478,8 +483,7 @@ namespace NzbDrone.Common.Disk
return mounts.Where(drive => drive.RootDirectory.PathEquals(path) ||
drive.RootDirectory.IsParentPath(path))
.OrderByDescending(drive => drive.RootDirectory.Length)
.FirstOrDefault();
.MaxBy(drive => drive.RootDirectory.Length);
}
catch (Exception ex)
{
@@ -497,7 +501,7 @@ namespace NzbDrone.Common.Disk
public List<DirectoryInfo> GetDirectoryInfos(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var di = new DirectoryInfo(path);
@@ -506,14 +510,14 @@ namespace NzbDrone.Common.Disk
public FileInfo GetFileInfo(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return new FileInfo(path);
}
public List<FileInfo> GetFileInfos(string path, SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var di = new DirectoryInfo(path);

View File

@@ -43,8 +43,8 @@ namespace NzbDrone.Common.Disk
public TransferMode TransferFolder(string sourcePath, string targetPath, TransferMode mode)
{
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
sourcePath = ResolveRealParentPath(sourcePath);
targetPath = ResolveRealParentPath(targetPath);
@@ -140,8 +140,8 @@ namespace NzbDrone.Common.Disk
{
var filesCopied = 0;
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
sourcePath = ResolveRealParentPath(sourcePath);
targetPath = ResolveRealParentPath(targetPath);
@@ -255,8 +255,8 @@ namespace NzbDrone.Common.Disk
public TransferMode TransferFile(string sourcePath, string targetPath, TransferMode mode, bool overwrite = false)
{
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
sourcePath = ResolveRealParentPath(sourcePath);
targetPath = ResolveRealParentPath(targetPath);
@@ -500,9 +500,13 @@ namespace NzbDrone.Common.Disk
throw new IOException(string.Format("File move incomplete, data loss may have occurred. [{0}] was {1} bytes long instead of the expected {2}.", targetPath, targetSize, originalSize));
}
}
catch
catch (Exception ex)
{
RollbackPartialMove(sourcePath, targetPath);
if (ex is not FileAlreadyExistsException)
{
RollbackPartialMove(sourcePath, targetPath);
}
throw;
}
}

View File

@@ -0,0 +1,15 @@
using System;
namespace NzbDrone.Common.Disk
{
public class FileAlreadyExistsException : Exception
{
public string Filename { get; set; }
public FileAlreadyExistsException(string message, string filename)
: base(message)
{
Filename = filename;
}
}
}

View File

@@ -71,7 +71,7 @@ namespace NzbDrone.Common.Disk
if (
allowFoldersWithoutTrailingSlashes &&
query.IsPathValid() &&
query.IsPathValid(PathValidationType.CurrentOs) &&
_diskProvider.FolderExists(query))
{
return GetResult(query, includeFiles);

View File

@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;
namespace NzbDrone.Common.Disk
{

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Disk
@@ -162,7 +161,7 @@ namespace NzbDrone.Common.Disk
}
}
public bool IsValid => _path.IsPathValid();
public bool IsValid => _path.IsPathValid(PathValidationType.CurrentOs);
private int GetFileNameIndex()
{

View File

@@ -0,0 +1,8 @@
namespace NzbDrone.Common.Disk
{
public enum PathValidationType
{
CurrentOs,
AnyOs
}
}

View File

@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.Text.RegularExpressions;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnsureThat.Resources;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@@ -111,14 +112,14 @@ namespace NzbDrone.Common.EnsureThat
}
[DebuggerStepThrough]
public static Param<string> IsValidPath(this Param<string> param)
public static Param<string> IsValidPath(this Param<string> param, PathValidationType validationType)
{
if (string.IsNullOrWhiteSpace(param.Value))
{
throw ExceptionFactory.CreateForParamValidation(param.Name, ExceptionMessages.EnsureExtensions_IsNotNullOrWhiteSpace);
}
if (param.Value.IsPathValid())
if (param.Value.IsPathValid(validationType))
{
return param;
}

View File

@@ -1,8 +1,6 @@
using System;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Exceptions;

View File

@@ -29,12 +29,12 @@ namespace NzbDrone.Common.Extensions
public static string CleanFilePath(this string path)
{
Ensure.That(path, () => path).IsNotNullOrWhiteSpace();
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.AnyOs);
var info = new FileInfo(path.Trim());
// UNC
if (OsInfo.IsWindows && info.FullName.StartsWith(@"\\"))
if (!info.FullName.Contains('/') && info.FullName.StartsWith(@"\\"))
{
return info.FullName.TrimEnd('/', '\\', ' ');
}
@@ -136,24 +136,24 @@ namespace NzbDrone.Common.Extensions
private static readonly Regex WindowsPathWithDriveRegex = new Regex(@"^[a-zA-Z]:\\", RegexOptions.Compiled);
public static bool IsPathValid(this string path)
public static bool IsPathValid(this string path, PathValidationType validationType)
{
if (path.ContainsInvalidPathChars() || string.IsNullOrWhiteSpace(path))
{
return false;
}
if (validationType == PathValidationType.AnyOs)
{
return IsPathValidForWindows(path) || IsPathValidForNonWindows(path);
}
if (OsInfo.IsNotWindows)
{
return path.StartsWith(Path.DirectorySeparatorChar.ToString());
return IsPathValidForNonWindows(path);
}
if (path.StartsWith("\\") || WindowsPathWithDriveRegex.IsMatch(path))
{
return true;
}
return false;
return IsPathValidForWindows(path);
}
public static bool ContainsInvalidPathChars(this string text)
@@ -337,5 +337,15 @@ namespace NzbDrone.Common.Extensions
{
return Path.Combine(appFolderInfo.StartUpFolder, NLOG_CONFIG_FILE);
}
private static bool IsPathValidForWindows(string path)
{
return path.StartsWith("\\") || WindowsPathWithDriveRegex.IsMatch(path);
}
private static bool IsPathValidForNonWindows(string path)
{
return path.StartsWith("/");
}
}
}

View File

@@ -16,18 +16,7 @@ namespace NzbDrone.Common.Extensions
return false;
}
Uri uri;
if (!Uri.TryCreate(path, UriKind.Absolute, out uri))
{
return false;
}
if (!uri.IsWellFormedOriginalString())
{
return false;
}
return true;
return Uri.TryCreate(path, UriKind.Absolute, out var uri) && uri.IsWellFormedOriginalString();
}
}
}

View File

@@ -29,6 +29,14 @@ namespace NzbDrone.Common
return $"{mCrc:x8}";
}
public static string ComputeSha256Hash(string rawData)
{
using var sha256Hash = SHA256.Create();
var hashBytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(rawData));
return Convert.ToHexString(hashBytes);
}
public static string CalculateMd5(string s)
{
// Use input string to calculate MD5 hash

View File

@@ -9,7 +9,7 @@ namespace NzbDrone.Common.Http
{
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
// NOTE: we are not checking non-ascii characters and we should
private static readonly Regex _CookieRegex = new Regex(@"([^\(\)<>@,;:\\""/\[\]\?=\{\}\s]+)=([^,;\\""\s]+)");
private static readonly Regex CookieRegex = new (@"([^\(\)<>@,;:\\""/\[\]\?=\{\}\s]+)=([^,;\\""\s]+)");
private static readonly string[] FilterProps = { "COMMENT", "COMMENTURL", "DISCORD", "DOMAIN", "EXPIRES", "MAX-AGE", "PATH", "PORT", "SECURE", "VERSION", "HTTPONLY", "SAMESITE" };
private static readonly char[] InvalidKeyChars = { '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t', '\n' };
private static readonly char[] InvalidValueChars = { '"', ',', ';', '\\', ' ', '\t', '\n' };
@@ -22,7 +22,7 @@ namespace NzbDrone.Common.Http
return cookieDictionary;
}
var matches = _CookieRegex.Match(cookieHeader);
var matches = CookieRegex.Match(cookieHeader);
while (matches.Success)
{
if (matches.Groups.Count > 2 && !FilterProps.Contains(matches.Groups[1].Value.ToUpperInvariant()))

View File

@@ -1,4 +1,3 @@
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

View File

@@ -1,7 +1,6 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Security;
@@ -9,7 +8,6 @@ using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http.Proxy;
@@ -247,7 +245,7 @@ namespace NzbDrone.Common.Http.Dispatchers
}
}
private void AddContentHeader(HttpRequestMessage request, string header, string value)
private static void AddContentHeader(HttpRequestMessage request, string header, string value)
{
var headers = request.Content?.Headers;
if (headers == null)

View File

@@ -322,11 +322,12 @@ namespace NzbDrone.Common.Http
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
var stopWatch = Stopwatch.StartNew();
using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite))
await using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite))
{
var request = new HttpRequest(url);
request.AllowAutoRedirect = true;
request.ResponseStream = fileStream;
request.RequestTimeout = TimeSpan.FromSeconds(300);
var response = await GetAsync(request);
if (response.Headers.ContentType != null && response.Headers.ContentType.Contains("text/html"))

View File

@@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using NzbDrone.Common.Extensions;

View File

@@ -21,6 +21,7 @@ namespace NzbDrone.Common.Http
public Dictionary<string, string> Segments { get; private set; }
public HttpHeader Headers { get; private set; }
public bool SuppressHttpError { get; set; }
public IEnumerable<HttpStatusCode> SuppressHttpErrorStatusCodes { get; set; }
public bool LogHttpError { get; set; }
public bool UseSimplifiedUserAgent { get; set; }
public bool AllowAutoRedirect { get; set; }
@@ -108,6 +109,7 @@ namespace NzbDrone.Common.Http
request.Method = Method;
request.Encoding = Encoding;
request.SuppressHttpError = SuppressHttpError;
request.SuppressHttpErrorStatusCodes = SuppressHttpErrorStatusCodes;
request.LogHttpError = LogHttpError;
request.UseSimplifiedUserAgent = UseSimplifiedUserAgent;
request.AllowAutoRedirect = AllowAutoRedirect;

View File

@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NLog.Fluent;
namespace NzbDrone.Common.Instrumentation.Extensions
{

View File

@@ -1,7 +1,6 @@
using System;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Common.Instrumentation
{

View File

@@ -1,4 +1,3 @@
using System;
using System.Text;
using NLog;
using NLog.Targets;

View File

@@ -1,7 +1,6 @@
using System;
using System.Linq;
using Sentry;
using Sentry.Protocol;
namespace NzbDrone.Common.Instrumentation.Sentry
{

View File

@@ -12,7 +12,6 @@ using Npgsql;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using Sentry;
using Sentry.Protocol;
namespace NzbDrone.Common.Instrumentation.Sentry
{

View File

@@ -14,14 +14,14 @@ namespace NzbDrone.Common.OAuth
{
get
{
var parameters = this.Where(p => p.Name.Equals(name));
var parameters = this.Where(p => p.Name.Equals(name)).ToArray();
if (!parameters.Any())
{
return null;
}
if (parameters.Count() == 1)
if (parameters.Length == 1)
{
return parameters.Single();
}

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;

View File

@@ -131,14 +131,7 @@ namespace NzbDrone.Common.Processes
var key = environmentVariable.Key.ToString();
var value = environmentVariable.Value?.ToString();
if (startInfo.EnvironmentVariables.ContainsKey(key))
{
startInfo.EnvironmentVariables[key] = value;
}
else
{
startInfo.EnvironmentVariables.Add(key, value);
}
startInfo.EnvironmentVariables[key] = value;
}
catch (Exception e)
{
@@ -320,7 +313,7 @@ namespace NzbDrone.Common.Processes
processInfo = new ProcessInfo();
processInfo.Id = process.Id;
processInfo.Name = process.ProcessName;
processInfo.StartPath = GetExeFileName(process);
processInfo.StartPath = process.MainModule.FileName;
if (process.Id != GetCurrentProcessId() && process.HasExited)
{
@@ -335,16 +328,6 @@ namespace NzbDrone.Common.Processes
return processInfo;
}
private static string GetExeFileName(Process process)
{
if (process.MainModule.FileName != "mono.exe")
{
return process.MainModule.FileName;
}
return process.Modules.Cast<ProcessModule>().FirstOrDefault(module => module.ModuleName.ToLower().EndsWith(".exe")).FileName;
}
private List<Process> GetProcessesByName(string name)
{
//TODO: move this to an OS specific class

View File

@@ -4,16 +4,16 @@
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DryIoc.dll" Version="5.3.3" />
<PackageReference Include="DryIoc.dll" Version="5.3.4" />
<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="Newtonsoft.Json" Version="13.0.3" />
<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="Sentry" Version="3.29.1" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" />

View File

@@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using NLog;

View File

@@ -0,0 +1,826 @@
{
"Results": 9999,
"Pagination": {
"Current": 1,
"Max": 99,
"Limit": {
"Min": 15,
"Coerced": 15,
"Max": 50
}
},
"Matches": 2,
"Groups": [
{
"ID": 575,
"CategoryName": "Anime",
"FullName": "Cowboy Bebop: Tengoku no Tobira - Movie&nbsp;&nbsp;[2001]",
"GroupName": "Movie",
"SeriesID": "141",
"SeriesName": "Cowboy Bebop: Tengoku no Tobira",
"Artists": null,
"Year": "2001",
"Image": "https://mei.animebytes.tv/2bac1a04148be77ce41251d3cb44bbd5.jpg",
"Synonymns": [
"カウボーイビバップ天国の扉",
"Cowboy Bebop: Knockin' on Heaven's Door"
],
"SynonymnsV2": {
"Japanese": "カウボーイビバップ天国の扉",
"Romaji": "",
"Alternative": "Cowboy Bebop: Knockin' on Heaven's Door"
},
"Snatched": 4900,
"Comments": 11,
"Links": {
"AniDB": "https://anidb.net/anime/219",
"ANN": "https://www.animenewsnetwork.com/encyclopedia/anime.php?id=353",
"Wikipedia": "https://en.wikipedia.org/wiki/Cowboy_Bebop:_The_Movie",
"MAL": "https://myanimelist.net/anime/5"
},
"Votes": 572,
"AvgVote": 8.3,
"Associations": null,
"Description": "Mars is under siege! Just before Halloween 2071, a terrorist bomb destroys a tanker truck on Highway One, close to the densely-populated crater city. There are casualties up to half a mile from the blast — 500 killed or injured by what appears to be a biochemical weapon. The reward for the bomber's capture is a massive 300,000,000 woolongs... and there are four humans and a dog who really need the money. Down on their luck as usual, the crew of the Bebop get on the case.\r\n\r\n[i]Note: The movie takes place between (in the time period of) the Cowboy Bebop episodes 22 and 23.[/i]",
"DescriptionHTML": "Mars is under siege! Just before Halloween 2071, a terrorist bomb destroys a tanker truck on Highway One, close to the densely-populated crater city. There are casualties up to half a mile from the blast — 500 killed or injured by what appears to be a biochemical weapon. The reward for the bomber&#039;s capture is a massive 300,000,000 woolongs... and there are four humans and a dog who really need the money. Down on their luck as usual, the crew of the Bebop get on the case.<br />\r\n<br />\r\n<em>Note: The movie takes place between (in the time period of) the Cowboy Bebop episodes 22 and 23.</em>",
"EpCount": 0,
"StudioList": "Sunrise///28|BONES///35",
"PastWeek": 2,
"Incomplete": false,
"Ongoing": false,
"Tags": [
"adventure",
"comedy",
"drama",
"scifi",
"seinen",
"action"
],
"Torrents": [
{
"ID": 959397,
"EditionData": {
"EditionTitle": ""
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/959397/download/somepass",
"Property": "Blu-ray | MKV | h265 10-bit | 1080p | Opus 5.1 | Softsubs (Polarwindz) | Freeleech",
"Snatched": 16,
"Seeders": 5,
"Leechers": 1,
"Status": 0,
"Size": 13090646841,
"FileCount": 1,
"FileList": [
{
"filename": "[Polarwindz] Cowboy Bebop The Movie - Knockin' on Heaven's Door [BD 1080p x265 10bit Opus 5.1].mkv",
"size": 13090646841
}
],
"UploadTime": "2023-04-02 05:00:43"
},
{
"ID": 909565,
"EditionData": {
"EditionTitle": ""
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/909565/download/somepass",
"Property": "Blu-ray | MKV | h265 10-bit | 1080p | Opus 5.1 | Dual Audio | Softsubs (Yūrei) | Freeleech",
"Snatched": 29,
"Seeders": 6,
"Leechers": 0,
"Status": 0,
"Size": 15717521349,
"FileCount": 1,
"FileList": [
{
"filename": "Cowboy Bebop - Tengoku no Tobira.mkv",
"size": 15717521349
}
],
"UploadTime": "2020-09-03 18:04:38"
}
]
},
{
"ID": 2709,
"CategoryName": "Anime",
"FullName": "BLEACH - TV Series&nbsp;&nbsp;[2004]",
"GroupName": "TV Series",
"SeriesID": "191",
"SeriesName": "BLEACH",
"Artists": null,
"Year": "2004",
"Image": "https://mei.animebytes.tv/997c8ec3ca0e70254b182b0a176f0161.jpg",
"Synonymns": [
"ブリーチ"
],
"SynonymnsV2": {
"Japanese": "ブリーチ",
"Romaji": "",
"Alternative": ""
},
"Snatched": 22653,
"Comments": 51,
"Links": {
"AniDB": "https://anidb.net/anime/2369",
"ANN": "https://www.animenewsnetwork.com/encyclopedia/anime.php?id=4240",
"Wikipedia": "https://en.wikipedia.org/wiki/Bleach_(anime)",
"MAL": "https://myanimelist.net/anime/269"
},
"Votes": 530,
"AvgVote": 7.3,
"Associations": null,
"Description": "Kurosaki Ichigo is a teenager gifted with the ability to see spirits. His life is drastically changed by the sudden appearance of a Shinigami (literally, Death god) - one who governs the flow of souls between the human world and the afterlife - named Kuchiki Rukia, who arrives in search of a Hollow, a dangerous lost soul. When Rukia is severely wounded while trying to defeat the Hollow, she attempts to transfer half of her Reiatsu (literally, Spiritual pressure) energy to Ichigo so that he can defeat the Hollow. However, Ichigo takes almost all of her energy, transforming into a Shinigami and allowing him to defeat the Hollow with ease. With her powers diminished, Rukia is left stranded in the human world until she can recover her strength. In the meantime, Ichigo must take over Rukia's role as a Shinigami, battling Hollows and guiding souls to the afterlife realm known as the Soul Society.",
"DescriptionHTML": "Kurosaki Ichigo is a teenager gifted with the ability to see spirits. His life is drastically changed by the sudden appearance of a Shinigami (literally, Death god) - one who governs the flow of souls between the human world and the afterlife - named Kuchiki Rukia, who arrives in search of a Hollow, a dangerous lost soul. When Rukia is severely wounded while trying to defeat the Hollow, she attempts to transfer half of her Reiatsu (literally, Spiritual pressure) energy to Ichigo so that he can defeat the Hollow. However, Ichigo takes almost all of her energy, transforming into a Shinigami and allowing him to defeat the Hollow with ease. With her powers diminished, Rukia is left stranded in the human world until she can recover her strength. In the meantime, Ichigo must take over Rukia&#039;s role as a Shinigami, battling Hollows and guiding souls to the afterlife realm known as the Soul Society.",
"EpCount": 366,
"StudioList": "Studio Pierrot///45",
"PastWeek": 26,
"Incomplete": false,
"Ongoing": false,
"Tags": [
"adventure",
"comedy",
"fantasy",
"martial.arts",
"school.life",
"shounen",
"super.power",
"contemporary.fantasy",
"swordplay",
"action",
"supernatural"
],
"Torrents": [
{
"ID": 1031199,
"EditionData": {
"EditionTitle": "Season 02: The Entry (021-041)"
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/1031199/download/somepass",
"Property": "Blu-ray | MKV | h265 10-bit | 1080p | AAC 2.0 | Dual Audio | Softsubs (GHOST) | Freeleech",
"Snatched": 20,
"Seeders": 24,
"Leechers": 0,
"Status": 0,
"Size": 19584943785,
"FileCount": 21,
"FileList": [
{
"filename": "[GHOST][1080p] Bleach - 021 [BD HEVC 10bit Dual Audio AC3][035452C3].mkv",
"size": 880693454
},
{
"filename": "[GHOST][1080p] Bleach - 022 [BD HEVC 10bit Dual Audio AC3][0E923AAD].mkv",
"size": 851531918
},
{
"filename": "[GHOST][1080p] Bleach - 023 [BD HEVC 10bit Dual Audio AC3][604A0EC6].mkv",
"size": 882518038
},
{
"filename": "[GHOST][1080p] Bleach - 024 [BD HEVC 10bit Dual Audio AC3][ABABE9B3].mkv",
"size": 837335522
},
{
"filename": "[GHOST][1080p] Bleach - 025 [BD HEVC 10bit Dual Audio AC3][17AEB0C1].mkv",
"size": 933034706
},
{
"filename": "[GHOST][1080p] Bleach - 026 [BD HEVC 10bit Dual Audio AC3][E6F0017C].mkv",
"size": 891916996
},
{
"filename": "[GHOST][1080p] Bleach - 027 [BD HEVC 10bit Dual Audio AC3][317791C7].mkv",
"size": 896856044
},
{
"filename": "[GHOST][1080p] Bleach - 028 [BD HEVC 10bit Dual Audio AC3][5CA4DF06].mkv",
"size": 1010510386
},
{
"filename": "[GHOST][1080p] Bleach - 029 [BD HEVC 10bit Dual Audio AC3][549CF25A].mkv",
"size": 995648398
},
{
"filename": "[GHOST][1080p] Bleach - 030 [BD HEVC 10bit Dual Audio AC3][CED479F9].mkv",
"size": 991633973
},
{
"filename": "[GHOST][1080p] Bleach - 031 [BD HEVC 10bit Dual Audio AC3][1EEFAB0E].mkv",
"size": 950195341
},
{
"filename": "[GHOST][1080p] Bleach - 032 [BD HEVC 10bit Dual Audio AC3][C6F3C39A].mkv",
"size": 859218784
},
{
"filename": "[GHOST][1080p] Bleach - 033 [BD HEVC 10bit Dual Audio AC3][A8424897].mkv",
"size": 933230823
},
{
"filename": "[GHOST][1080p] Bleach - 034 [BD HEVC 10bit Dual Audio AC3][96C94DB8].mkv",
"size": 818179634
},
{
"filename": "[GHOST][1080p] Bleach - 035 [BD HEVC 10bit Dual Audio AC3][A07831AE].mkv",
"size": 882457138
},
{
"filename": "[GHOST][1080p] Bleach - 036 [BD HEVC 10bit Dual Audio AC3][D984C169].mkv",
"size": 895683290
},
{
"filename": "[GHOST][1080p] Bleach - 037 [BD HEVC 10bit Dual Audio AC3][32C05A7E].mkv",
"size": 970033716
},
{
"filename": "[GHOST][1080p] Bleach - 038 [BD HEVC 10bit Dual Audio AC3][824B3EEC].mkv",
"size": 956573899
},
{
"filename": "[GHOST][1080p] Bleach - 039 [BD HEVC 10bit Dual Audio AC3][A45233DF].mkv",
"size": 1238240707
},
{
"filename": "[GHOST][1080p] Bleach - 040 [BD HEVC 10bit Dual Audio AC3][5334A7F1].mkv",
"size": 894491562
},
{
"filename": "[GHOST][1080p] Bleach - 041 [BD HEVC 10bit Dual Audio AC3][A4F17363].mkv",
"size": 1014959456
}
],
"UploadTime": "2023-03-03 03:03:17"
},
{
"ID": 1031203,
"EditionData": {
"EditionTitle": "Season 03: The Rescue (042-063)"
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/1031203/download/somepass",
"Property": "Blu-ray | MKV | h265 10-bit | 1080p | AC3 2.0 | Dual Audio | Softsubs (GHOST) | Freeleech",
"Snatched": 6,
"Seeders": 12,
"Leechers": 2,
"Status": 0,
"Size": 24498538059,
"FileCount": 22,
"FileList": [
{
"filename": "[GHOST][1080p] Bleach - 042 [BD HEVC 10bit Dual Audio AC3][55763BF6].mkv",
"size": 899825828
},
{
"filename": "[GHOST][1080p] Bleach - 043 [BD HEVC 10bit Dual Audio AC3][70B71ECC].mkv",
"size": 902256094
},
{
"filename": "[GHOST][1080p] Bleach - 044 [BD HEVC 10bit Dual Audio AC3][35F5526B].mkv",
"size": 885323344
},
{
"filename": "[GHOST][1080p] Bleach - 045 [BD HEVC 10bit Dual Audio AC3][9C1DAE4E].mkv",
"size": 1082465042
},
{
"filename": "[GHOST][1080p] Bleach - 046 [BD HEVC 10bit Dual Audio AC3][869EF5B6].mkv",
"size": 957433930
},
{
"filename": "[GHOST][1080p] Bleach - 047 [BD HEVC 10bit Dual Audio AC3][890DA7CE].mkv",
"size": 997124540
},
{
"filename": "[GHOST][1080p] Bleach - 048 [BD HEVC 10bit Dual Audio AC3][39064E08].mkv",
"size": 1026751383
},
{
"filename": "[GHOST][1080p] Bleach - 049 [BD HEVC 10bit Dual Audio AC3][C536D3DB].mkv",
"size": 994918391
},
{
"filename": "[GHOST][1080p] Bleach - 050 [BD HEVC 10bit Dual Audio AC3][A7A0CB24].mkv",
"size": 1141146920
},
{
"filename": "[GHOST][1080p] Bleach - 051 [BD HEVC 10bit Dual Audio AC3][25C06D9D].mkv",
"size": 951751791
},
{
"filename": "[GHOST][1080p] Bleach - 052 [BD HEVC 10bit Dual Audio AC3][FB506194].mkv",
"size": 1131756065
},
{
"filename": "[GHOST][1080p] Bleach - 053 [BD HEVC 10bit Dual Audio AC3][4A76C66D].mkv",
"size": 1076952986
},
{
"filename": "[GHOST][1080p] Bleach - 054 [BD HEVC 10bit Dual Audio AC3][51D8E5F8].mkv",
"size": 1369454462
},
{
"filename": "[GHOST][1080p] Bleach - 055 [BD HEVC 10bit Dual Audio AC3][DCF20007].mkv",
"size": 1428073116
},
{
"filename": "[GHOST][1080p] Bleach - 056 [BD HEVC 10bit Dual Audio AC3][34A28687].mkv",
"size": 1304804717
},
{
"filename": "[GHOST][1080p] Bleach - 057 [BD HEVC 10bit Dual Audio AC3][D1D5FE29].mkv",
"size": 1056220730
},
{
"filename": "[GHOST][1080p] Bleach - 058 [BD HEVC 10bit Dual Audio AC3][C6EAC278].mkv",
"size": 1551455953
},
{
"filename": "[GHOST][1080p] Bleach - 059 [BD HEVC 10bit Dual Audio AC3][E7B25869].mkv",
"size": 1026910923
},
{
"filename": "[GHOST][1080p] Bleach - 060 [BD HEVC 10bit Dual Audio AC3][C9D257D4].mkv",
"size": 1059631177
},
{
"filename": "[GHOST][1080p] Bleach - 061 [BD HEVC 10bit Dual Audio AC3][0521C8D3].mkv",
"size": 1457893287
},
{
"filename": "[GHOST][1080p] Bleach - 062 [BD HEVC 10bit Dual Audio AC3][65CBA616].mkv",
"size": 1262863946
},
{
"filename": "[GHOST][1080p] Bleach - 063 [BD HEVC 10bit Dual Audio AC3][CF63E244].mkv",
"size": 933523434
}
],
"UploadTime": "2023-04-03 03:14:35"
}
]
},
{
"ID": 81926,
"CategoryName": "Anime",
"FullName": "Dr. STONE: NEW WORLD - TV Series&nbsp;&nbsp;[2023]",
"GroupName": "TV Series",
"SeriesID": "79217",
"SeriesName": "Dr. STONE: NEW WORLD",
"Artists": null,
"Year": "2023",
"Image": "https://mei.animebytes.tv/Tu0p0k56514.jpg",
"Synonymns": {
"0": "ドクターストーン NEW WORLD",
"2": "Dr. STONE S3, Dr. STONE Season 3, Dr.STONE 3rd Season"
},
"SynonymnsV2": {
"Japanese": "ドクターストーン NEW WORLD",
"Romaji": "",
"Alternative": "Dr. STONE S3, Dr. STONE Season 3, Dr.STONE 3rd Season"
},
"Snatched": 1870,
"Comments": 0,
"Links": {
"AniDB": "https://anidb.net/anime/17053",
"ANN": "https://www.animenewsnetwork.com/encyclopedia/anime.php?id=25068",
"Wikipedia": "https://en.wikipedia.org/wiki/Dr._Stone",
"MAL": "https://myanimelist.net/anime/48549"
},
"Votes": 0,
"AvgVote": 0,
"Associations": null,
"Description": "Third season of [i]Dr. STONE[/i].\r\n\r\nWith the Stone Wars over, the former members of Tsukasa's Empire of Might join forces with the Kingdom of Science to build a ship capable of sailing across the open ocean to seek answers to the mystery of global petrification. However, before they can begin their voyage Senku and his friends need to find some key resources and push some new scientific advancements to build the type of vessel they need.\r\n\r\n* Based on an adventure sci-fi shounen manga series written by Inagaki Riichirou and illustrated by Boichi.\r\n\r\n[i]Note: The first episode received an early screening at a special event on March 12, 2023 at Iino Hall in Tokyo. The regular TV broadcast started on April 6, 2023.[/i]",
"DescriptionHTML": "Third season of <em>Dr. STONE</em>.<br />\r\n<br />\r\nWith the Stone Wars over, the former members of Tsukasa&#039;s Empire of Might join forces with the Kingdom of Science to build a ship capable of sailing across the open ocean to seek answers to the mystery of global petrification. However, before they can begin their voyage Senku and his friends need to find some key resources and push some new scientific advancements to build the type of vessel they need.<br />\r\n<br />\r\n* Based on an adventure sci-fi shounen manga series written by Inagaki Riichirou and illustrated by Boichi.<br />\r\n<br />\r\n<em>Note: The first episode received an early screening at a special event on March 12, 2023 at Iino Hall in Tokyo. The regular TV broadcast started on April 6, 2023.</em>",
"EpCount": 0,
"StudioList": "TMS Entertainment///11",
"PastWeek": 6,
"Incomplete": false,
"Ongoing": false,
"Tags": [
"adventure",
"scifi",
"shounen",
"post.apocalyptic"
],
"Torrents": [
{
"ID": 1041495,
"EditionData": {
"EditionTitle": "Episode 3"
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/1041495/download/somepass",
"Property": "Web | MKV | h264 | 720p | AAC 2.0 | Softsubs (SubsPlease) | Episode 3 | Freeleech",
"Snatched": 165,
"Seeders": 137,
"Leechers": 3,
"Status": 0,
"Size": 748209543,
"FileCount": 1,
"FileList": [
{
"filename": "[SubsPlease] Dr. Stone S3 - 03 (720p) [DAC92E18].mkv",
"size": 748209543
}
],
"UploadTime": "2023-04-20 14:32:29"
},
{
"ID": 1037731,
"EditionData": {
"EditionTitle": "Episode 2"
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/1037731/download/somepass",
"Property": "Web | MKV | h264 | 720p | AAC 2.0 | Softsubs (SubsPlease) | Episode 2 | Freeleech",
"Snatched": 174,
"Seeders": 122,
"Leechers": 1,
"Status": 0,
"Size": 748808730,
"FileCount": 1,
"FileList": [
{
"filename": "[SubsPlease] Dr. Stone S3 - 02 (720p) [AE2DA9AB].mkv",
"size": 748808730
}
],
"UploadTime": "2023-04-13 14:34:16"
}
]
},
{
"ID": 69267,
"CategoryName": "Anime",
"FullName": "Dr. STONE: STONE WARS - TV Series&nbsp;&nbsp;[2021]",
"GroupName": "TV Series",
"SeriesID": "67161",
"SeriesName": "Dr. STONE: STONE WARS",
"Artists": null,
"Year": "2021",
"Image": "https://mei.animebytes.tv/6pqXEK82OfD.jpg",
"Synonymns": {
"0": "ドクターストーン STONE WARS",
"2": "Dr. STONE S2, Dr. STONE Season 2, Dr.STONE 2nd Season"
},
"SynonymnsV2": {
"Japanese": "ドクターストーン STONE WARS",
"Romaji": "",
"Alternative": "Dr. STONE S2, Dr. STONE Season 2, Dr.STONE 2nd Season"
},
"Snatched": 1181,
"Comments": 4,
"Links": {
"AniDB": "https://anidb.net/anime/15305",
"ANN": "https://www.animenewsnetwork.com/encyclopedia/anime.php?id=22942",
"Wikipedia": "https://en.wikipedia.org/wiki/Dr._Stone",
"MAL": "https://myanimelist.net/anime/40852/Dr_Stone__Stone_Wars"
},
"Votes": 23,
"AvgVote": 7.8,
"Associations": null,
"Description": "* Based on an adventure sci-fi shounen manga series written by Inagaki Riichirou and illustrated by Boichi.\r\n\r\nSenkuu, Chrome and the other villagers are in a battle of wits and brawn against the Tsukasa Empire after the revelation that Senkuu's father left behind a lasting message.\r\n\r\nWith a plan to build a phone that could be used to take down the bad guys from within, Senkuu will need to finally enlist the help of his friends who are a part of the Tsukasa Empire.",
"DescriptionHTML": "* Based on an adventure sci-fi shounen manga series written by Inagaki Riichirou and illustrated by Boichi.<br />\r\n<br />\r\nSenkuu, Chrome and the other villagers are in a battle of wits and brawn against the Tsukasa Empire after the revelation that Senkuu&#039;s father left behind a lasting message.<br />\r\n<br />\r\nWith a plan to build a phone that could be used to take down the bad guys from within, Senkuu will need to finally enlist the help of his friends who are a part of the Tsukasa Empire.",
"EpCount": 11,
"StudioList": "TMS Entertainment///11",
"PastWeek": 0,
"Incomplete": false,
"Ongoing": false,
"Tags": [
"adventure",
"scifi",
"shounen",
"post.apocalyptic"
],
"Torrents": [
{
"ID": 944509,
"EditionData": {
"EditionTitle": ""
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/944509/download/somepass",
"Property": "Web | MKV | h264 | 1080p | AAC 2.0 | Dual Audio | Softsubs (-ZR-) | Freeleech",
"Snatched": 188,
"Seeders": 31,
"Leechers": 1,
"Status": 0,
"Size": 16611719364,
"FileCount": 11,
"FileList": [
{
"filename": "Dr. Stone S02E01v2 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1512195256
},
{
"filename": "Dr. Stone S02E02 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1507917714
},
{
"filename": "Dr. Stone S02E03 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1510054199
},
{
"filename": "Dr. Stone S02E04 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1507100461
},
{
"filename": "Dr. Stone S02E05 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1507258273
},
{
"filename": "Dr. Stone S02E06 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1511039711
},
{
"filename": "Dr. Stone S02E07 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1507219047
},
{
"filename": "Dr. Stone S02E08 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1510996213
},
{
"filename": "Dr. Stone S02E09 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1512785600
},
{
"filename": "Dr. Stone S02E10 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1511889715
},
{
"filename": "Dr. Stone S02E11 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1513263175
}
],
"UploadTime": "2021-06-03 20:30:00"
}
]
},
{
"ID": 60598,
"CategoryName": "Anime",
"FullName": "Dr. STONE - TV Series&nbsp;&nbsp;[2019]",
"GroupName": "TV Series",
"SeriesID": "56617",
"SeriesName": "Dr. STONE",
"Artists": null,
"Year": "2019",
"Image": "https://mei.animebytes.tv/SaFez5XG8T3.jpg",
"Synonymns": [
"Dr.STONE [ドクターストーン]"
],
"SynonymnsV2": {
"Japanese": "Dr.STONE [ドクターストーン]",
"Romaji": "",
"Alternative": ""
},
"Snatched": 2174,
"Comments": 8,
"Links": {
"AniDB": "https://anidb.net/anime/14491",
"ANN": "https://www.animenewsnetwork.com/encyclopedia/anime.php?id=21703",
"Wikipedia": "https://en.wikipedia.org/wiki/Dr._Stone",
"MAL": "https://myanimelist.net/anime/38691"
},
"Votes": 68,
"AvgVote": 7.9,
"Associations": null,
"Description": "Several thousand years after a mysterious phenomenon that turns all of humanity to stone, the extraordinarily intelligent, science-driven boy, Senku Ishigami, awakens. Facing a world of stone and the total collapse of civilization, Senku makes up his mind to use science to rebuild the world. Starting with his super strong childhood friend Taiju Oki, who awakened at the same time, they will begin to rebuild civilization from nothing... Depicting two million years of scientific history from the Stone Age to present day, the unprecedented crafting adventure story is about to begin!\r\n\r\n* Based on an adventure sci-fi shounen manga series written by Inagaki Riichirou and illustrated by Boichi.",
"DescriptionHTML": "Several thousand years after a mysterious phenomenon that turns all of humanity to stone, the extraordinarily intelligent, science-driven boy, Senku Ishigami, awakens. Facing a world of stone and the total collapse of civilization, Senku makes up his mind to use science to rebuild the world. Starting with his super strong childhood friend Taiju Oki, who awakened at the same time, they will begin to rebuild civilization from nothing... Depicting two million years of scientific history from the Stone Age to present day, the unprecedented crafting adventure story is about to begin!<br />\r\n<br />\r\n* Based on an adventure sci-fi shounen manga series written by Inagaki Riichirou and illustrated by Boichi.",
"EpCount": 24,
"StudioList": "TMS Entertainment///11|8PAN///6344",
"PastWeek": 0,
"Incomplete": false,
"Ongoing": false,
"Tags": [
"adventure",
"comedy",
"scifi",
"shounen"
],
"Torrents": [
{
"ID": 430074,
"EditionData": {
"EditionTitle": ""
},
"RawDownMultiplier": 1,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/430074/download/somepass",
"Property": "Web | MKV | h264 | 720p | AAC 2.0 | Softsubs (HorribleSubs)",
"Snatched": 108,
"Seeders": 33,
"Leechers": 1,
"Status": 0,
"Size": 16366224176,
"FileCount": 24,
"FileList": [
{
"filename": "[HorribleSubs] Dr. Stone - 01 [720p].mkv",
"size": 477027555
},
{
"filename": "[HorribleSubs] Dr. Stone - 02 [720p].mkv",
"size": 489436551
},
{
"filename": "[HorribleSubs] Dr. Stone - 03 [720p].mkv",
"size": 503786828
},
{
"filename": "[HorribleSubs] Dr. Stone - 04 [720p].mkv",
"size": 442977598
},
{
"filename": "[HorribleSubs] Dr. Stone - 05 [720p].mkv",
"size": 523531555
},
{
"filename": "[HorribleSubs] Dr. Stone - 06 [720p].mkv",
"size": 506742468
},
{
"filename": "[HorribleSubs] Dr. Stone - 07 [720p].mkv",
"size": 746577276
},
{
"filename": "[HorribleSubs] Dr. Stone - 08 [720p].mkv",
"size": 745942485
},
{
"filename": "[HorribleSubs] Dr. Stone - 09 [720p].mkv",
"size": 746035250
},
{
"filename": "[HorribleSubs] Dr. Stone - 10 [720p].mkv",
"size": 746001386
},
{
"filename": "[HorribleSubs] Dr. Stone - 11 [720p].mkv",
"size": 746155088
},
{
"filename": "[HorribleSubs] Dr. Stone - 12 [720p].mkv",
"size": 746560710
},
{
"filename": "[HorribleSubs] Dr. Stone - 13 [720p].mkv",
"size": 745880614
},
{
"filename": "[HorribleSubs] Dr. Stone - 14 [720p].mkv",
"size": 744563919
},
{
"filename": "[HorribleSubs] Dr. Stone - 15 [720p].mkv",
"size": 745303312
},
{
"filename": "[HorribleSubs] Dr. Stone - 16 [720p].mkv",
"size": 746850910
},
{
"filename": "[HorribleSubs] Dr. Stone - 17 [720p].mkv",
"size": 744188496
},
{
"filename": "[HorribleSubs] Dr. Stone - 18 [720p].mkv",
"size": 746212236
},
{
"filename": "[HorribleSubs] Dr. Stone - 19 [720p].mkv",
"size": 744840131
},
{
"filename": "[HorribleSubs] Dr. Stone - 20 [720p].mkv",
"size": 746380081
},
{
"filename": "[HorribleSubs] Dr. Stone - 21 [720p].mkv",
"size": 744975636
},
{
"filename": "[HorribleSubs] Dr. Stone - 22 [720p].mkv",
"size": 746214757
},
{
"filename": "[HorribleSubs] Dr. Stone - 23 [720p].mkv",
"size": 744924693
},
{
"filename": "[HorribleSubs] Dr. Stone - 24 [720p].mkv",
"size": 745114641
}
],
"UploadTime": "2019-12-13 17:02:48"
}
]
},
{
"ID": 41952,
"CategoryName": "Anime",
"FullName": "One Piece - TV Series&nbsp;&nbsp;[2019]",
"GroupName": "TV Series",
"SeriesID": "114",
"SeriesName": "One Piece",
"Artists": null,
"Year": "2019",
"Image": "https://mei.animebytes.tv/cQieN6oZ6Ft.jpg",
"Synonymns": {
"0": "ワンピース",
"2": "One Piece: The Great Gold Pirate"
},
"SynonymnsV2": {
"Japanese": "ワンピース",
"Romaji": "",
"Alternative": "One Piece: The Great Gold Pirate"
},
"Snatched": 100700,
"Comments": 3,
"Links": {
"AniDB": "https://anidb.net/anime/69",
"ANN": "https://www.animenewsnetwork.com/encyclopedia/anime.php?id=836",
"Wikipedia": "https://en.wikipedia.org/wiki/One_Piece",
"MAL": "https://myanimelist.net/anime/21/One_Piece"
},
"Votes": 89,
"AvgVote": 8.8,
"Associations": null,
"Description": "The 20th season of One Piece. This represents episode 892 to current.",
"DescriptionHTML": "The 20th season of One Piece. This represents episode 892 to current.",
"EpCount": 0,
"StudioList": null,
"PastWeek": 10,
"Incomplete": false,
"Ongoing": false,
"Tags": [
"adventure",
"fantasy",
"martial.arts",
"shounen",
"super.power",
"action"
],
"Torrents": [
{
"ID": 1043925,
"EditionData": {
"EditionTitle": "Episode 1059"
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/1043925/download/somepass",
"Property": "Web | MKV | h264 | 720p | AAC 2.0 | Softsubs (SubsPlease) | Episode 1059 | Freeleech",
"Snatched": 125,
"Seeders": 114,
"Leechers": 1,
"Status": 0,
"Size": 743629489,
"FileCount": 1,
"FileList": [
{
"filename": "[SubsPlease] One Piece - 1059 (720p) [B347D9DE].mkv",
"size": 743629489
}
],
"UploadTime": "2023-04-23 02:06:08"
},
{
"ID": 1039046,
"EditionData": {
"EditionTitle": "Episode 1058"
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/1039046/download/somepass",
"Property": "Web | MKV | h264 | 1080p | AAC 2.0 | Softsubs (SubsPlease) | Episode 1058 | Freeleech",
"Snatched": 290,
"Seeders": 232,
"Leechers": 2,
"Status": 0,
"Size": 1453835224,
"FileCount": 1,
"FileList": [
{
"filename": "[SubsPlease] One Piece - 1058 (1080p) [E4094B4A].mkv",
"size": 1453835224
}
],
"UploadTime": "2023-04-16 02:07:42"
}
]
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
using System;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Cloud;
@@ -10,7 +9,6 @@ using NzbDrone.Common.Http.Proxy;
using NzbDrone.Common.TPL;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Http;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Security;
using NzbDrone.Test.Common;

View File

@@ -0,0 +1,91 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerSearchTests
{
public class ReleaseSearchServiceFixture : CoreTest<ReleaseSearchService>
{
private Mock<IIndexer> _mockIndexer;
[SetUp]
public void SetUp()
{
_mockIndexer = Mocker.GetMock<IIndexer>();
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition { Id = 1 });
_mockIndexer.SetupGet(s => s.SupportsSearch).Returns(true);
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.Enabled(It.IsAny<bool>()))
.Returns(new List<IIndexer> { _mockIndexer.Object });
}
private List<SearchCriteriaBase> WatchForSearchCriteria()
{
var result = new List<SearchCriteriaBase>();
_mockIndexer.Setup(v => v.Fetch(It.IsAny<MovieSearchCriteria>()))
.Callback<MovieSearchCriteria>(s => result.Add(s))
.Returns(Task.FromResult(new IndexerPageableQueryResult()));
_mockIndexer.Setup(v => v.Fetch(It.IsAny<TvSearchCriteria>()))
.Callback<TvSearchCriteria>(s => result.Add(s))
.Returns(Task.FromResult(new IndexerPageableQueryResult()));
return result;
}
[TestCase("tt0183790", "0183790")]
[TestCase("0183790", "0183790")]
[TestCase("183790", "0183790")]
[TestCase("tt10001870", "10001870")]
[TestCase("10001870", "10001870")]
public void should_normalize_imdbid_movie_search_criteria(string input, string expected)
{
var allCriteria = WatchForSearchCriteria();
var request = new NewznabRequest
{
t = "movie",
imdbid = input
};
Subject.Search(request, new List<int> { 1 }, false);
var criteria = allCriteria.OfType<MovieSearchCriteria>().ToList();
criteria.Count.Should().Be(1);
criteria[0].ImdbId.Should().Be(expected);
}
[TestCase("tt0183790", "0183790")]
[TestCase("0183790", "0183790")]
[TestCase("183790", "0183790")]
[TestCase("tt10001870", "10001870")]
[TestCase("10001870", "10001870")]
public void should_normalize_imdbid_tv_search_criteria(string input, string expected)
{
var allCriteria = WatchForSearchCriteria();
var request = new NewznabRequest
{
t = "tvsearch",
imdbid = input
};
Subject.Search(request, new List<int> { 1 }, false);
var criteria = allCriteria.OfType<TvSearchCriteria>().ToList();
criteria.Count.Should().Be(1);
criteria[0].ImdbId.Should().Be(expected);
}
}
}

View File

@@ -1,6 +1,3 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;

View File

@@ -0,0 +1,160 @@
using System;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests.AnimeBytesTests
{
[TestFixture]
public class AnimeBytesFixture : CoreTest<AnimeBytes>
{
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition
{
Name = "AnimeBytes",
Settings = new AnimeBytesSettings
{
BaseUrl = "https://animebytes.tv/",
Username = "someuser",
Passkey = "somepass"
}
};
}
[Test]
public async Task should_parse_recent_feed_from_animebytes()
{
var recentFeed = ReadAllText(@"Files/Indexers/AnimeBytes/recentfeed.json");
Mocker.GetMock<IIndexerHttpClient>()
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new[] { 2000, 5000 } })).Releases;
releases.Should().HaveCount(33);
releases.First().Should().BeOfType<TorrentInfo>();
var firstTorrentInfo = releases.ElementAt(2) as TorrentInfo;
firstTorrentInfo.Title.Should().Be("[SubsPlease] One Piece: The Great Gold Pirate - 1059 [Web][MKV][h264][720p][AAC 2.0][Softsubs (SubsPlease)][Episode 1059]");
firstTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
firstTorrentInfo.DownloadUrl.Should().Be("https://animebytes.tv/torrent/1043925/download/somepass");
firstTorrentInfo.InfoUrl.Should().Be("https://animebytes.tv/torrent/1043925/group");
firstTorrentInfo.Guid.Should().Be("https://animebytes.tv/torrent/1043925/group?nh=0F6BB43603CC07F4C804B9A29139F852");
firstTorrentInfo.CommentUrl.Should().BeNullOrEmpty();
firstTorrentInfo.Indexer.Should().Be(Subject.Definition.Name);
firstTorrentInfo.PublishDate.Should().Be(DateTime.Parse("2023-04-23 02:06:08", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal));
firstTorrentInfo.Size.Should().Be(743629489);
firstTorrentInfo.InfoHash.Should().Be(null);
firstTorrentInfo.MagnetUrl.Should().Be(null);
firstTorrentInfo.Peers.Should().Be(1 + 114);
firstTorrentInfo.Seeders.Should().Be(114);
firstTorrentInfo.Files.Should().Be(1);
firstTorrentInfo.MinimumSeedTime.Should().Be(259200);
var secondTorrentInfo = releases.ElementAt(16) as TorrentInfo;
secondTorrentInfo.Title.Should().Be("[GHOST] BLEACH S03 [Blu-ray][MKV][h265 10-bit][1080p][AC3 2.0][Dual Audio][Softsubs (GHOST)]");
secondTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
secondTorrentInfo.DownloadUrl.Should().Be("https://animebytes.tv/torrent/1031203/download/somepass");
secondTorrentInfo.InfoUrl.Should().Be("https://animebytes.tv/torrent/1031203/group");
secondTorrentInfo.Guid.Should().Be("https://animebytes.tv/torrent/1031203/group?nh=F7C73EF631FE269D3A7F10BD12EC99A1");
secondTorrentInfo.CommentUrl.Should().BeNullOrEmpty();
secondTorrentInfo.Indexer.Should().Be(Subject.Definition.Name);
secondTorrentInfo.PublishDate.Should().Be(DateTime.Parse("2023-04-03 03:14:35", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal));
secondTorrentInfo.Size.Should().Be(24498538059);
secondTorrentInfo.InfoHash.Should().Be(null);
secondTorrentInfo.MagnetUrl.Should().Be(null);
secondTorrentInfo.Peers.Should().Be(2 + 12);
secondTorrentInfo.Seeders.Should().Be(12);
secondTorrentInfo.Files.Should().Be(22);
secondTorrentInfo.MinimumSeedTime.Should().Be(655200);
var thirdTorrentInfo = releases.ElementAt(18) as TorrentInfo;
thirdTorrentInfo.Title.Should().Be("[Polarwindz] Cowboy Bebop: Tengoku no Tobira 2001 [Blu-ray][MKV][h265 10-bit][1080p][Opus 5.1][Softsubs (Polarwindz)]");
thirdTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
thirdTorrentInfo.DownloadUrl.Should().Be("https://animebytes.tv/torrent/959397/download/somepass");
thirdTorrentInfo.InfoUrl.Should().Be("https://animebytes.tv/torrent/959397/group");
thirdTorrentInfo.Guid.Should().Be("https://animebytes.tv/torrent/959397/group?nh=D63895DA87A25239C11F9823F46000E1");
thirdTorrentInfo.CommentUrl.Should().BeNullOrEmpty();
thirdTorrentInfo.Indexer.Should().Be(Subject.Definition.Name);
thirdTorrentInfo.PublishDate.Should().Be(DateTime.Parse("2023-04-02 05:00:43", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal));
thirdTorrentInfo.Size.Should().Be(13090646841);
thirdTorrentInfo.InfoHash.Should().Be(null);
thirdTorrentInfo.MagnetUrl.Should().Be(null);
thirdTorrentInfo.Peers.Should().Be(1 + 5);
thirdTorrentInfo.Seeders.Should().Be(5);
thirdTorrentInfo.Files.Should().Be(1);
thirdTorrentInfo.MinimumSeedTime.Should().Be(475200);
var fourthTorrentInfo = releases.ElementAt(3) as TorrentInfo;
fourthTorrentInfo.Title.Should().Be("[SubsPlease] Dr. STONE: NEW WORLD S03E03 - 03 [Web][MKV][h264][720p][AAC 2.0][Softsubs (SubsPlease)][Episode 3]");
fourthTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
fourthTorrentInfo.DownloadUrl.Should().Be("https://animebytes.tv/torrent/1041495/download/somepass");
fourthTorrentInfo.InfoUrl.Should().Be("https://animebytes.tv/torrent/1041495/group");
fourthTorrentInfo.Guid.Should().Be("https://animebytes.tv/torrent/1041495/group?nh=8B78B0DD3BCC6068BFCD927E4AC674F6");
fourthTorrentInfo.CommentUrl.Should().BeNullOrEmpty();
fourthTorrentInfo.Indexer.Should().Be(Subject.Definition.Name);
fourthTorrentInfo.PublishDate.Should().Be(DateTime.Parse("2023-04-20 14:32:29", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal));
fourthTorrentInfo.Size.Should().Be(748209543);
fourthTorrentInfo.InfoHash.Should().Be(null);
fourthTorrentInfo.MagnetUrl.Should().Be(null);
fourthTorrentInfo.Peers.Should().Be(3 + 137);
fourthTorrentInfo.Seeders.Should().Be(137);
fourthTorrentInfo.Files.Should().Be(1);
fourthTorrentInfo.MinimumSeedTime.Should().Be(259200);
var fifthTorrentInfo = releases.ElementAt(23) as TorrentInfo;
fifthTorrentInfo.Title.Should().Be("[-ZR-] Dr. STONE: STONE WARS S02 [Web][MKV][h264][1080p][AAC 2.0][Dual Audio][Softsubs (-ZR-)]");
fifthTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
fifthTorrentInfo.DownloadUrl.Should().Be("https://animebytes.tv/torrent/944509/download/somepass");
fifthTorrentInfo.InfoUrl.Should().Be("https://animebytes.tv/torrent/944509/group");
fifthTorrentInfo.Guid.Should().Be("https://animebytes.tv/torrent/944509/group?nh=FDCAA1EAB36D7C802F1E4B13DAE5EED7");
fifthTorrentInfo.CommentUrl.Should().BeNullOrEmpty();
fifthTorrentInfo.Indexer.Should().Be(Subject.Definition.Name);
fifthTorrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-06-03 20:30:00", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal));
fifthTorrentInfo.Size.Should().Be(16611719364);
fifthTorrentInfo.InfoHash.Should().Be(null);
fifthTorrentInfo.MagnetUrl.Should().Be(null);
fifthTorrentInfo.Peers.Should().Be(1 + 31);
fifthTorrentInfo.Seeders.Should().Be(31);
fifthTorrentInfo.Files.Should().Be(11);
fifthTorrentInfo.MinimumSeedTime.Should().Be(529200);
var sixthTorrentInfo = releases.ElementAt(31) as TorrentInfo;
sixthTorrentInfo.Title.Should().Be("[HorribleSubs] Dr. STONE S01 [Web][MKV][h264][720p][AAC 2.0][Softsubs (HorribleSubs)]");
sixthTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
sixthTorrentInfo.DownloadUrl.Should().Be("https://animebytes.tv/torrent/430074/download/somepass");
sixthTorrentInfo.InfoUrl.Should().Be("https://animebytes.tv/torrent/430074/group");
sixthTorrentInfo.Guid.Should().Be("https://animebytes.tv/torrent/430074/group?nh=32279E138015D8718B2B4B49AEF64574");
sixthTorrentInfo.CommentUrl.Should().BeNullOrEmpty();
sixthTorrentInfo.Indexer.Should().Be(Subject.Definition.Name);
sixthTorrentInfo.PublishDate.Should().Be(DateTime.Parse("2019-12-13 17:02:48", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal));
sixthTorrentInfo.Size.Should().Be(16366224176);
sixthTorrentInfo.InfoHash.Should().Be(null);
sixthTorrentInfo.MagnetUrl.Should().Be(null);
sixthTorrentInfo.Peers.Should().Be(1 + 33);
sixthTorrentInfo.Seeders.Should().Be(33);
sixthTorrentInfo.Files.Should().Be(24);
sixthTorrentInfo.MinimumSeedTime.Should().Be(529200);
}
}
}

View File

@@ -22,10 +22,10 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
Subject.Definition = new IndexerDefinition
{
Name = "AvistaZ",
Settings = new AvistazSettings() { Username = "someuser", Password = "somepass", Pid = "somepid" }
Settings = new AvistazSettings { Username = "someuser", Password = "somepass", Pid = "somepid" }
};
}
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
.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 MovieSearchCriteria { Categories = new int[] { 2000 } })).Releases;
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new[] { 2000 } })).Releases;
releases.Should().HaveCount(100);
releases.First().Should().BeOfType<TorrentInfo>();
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://avistaz.to/torrent/187240-japan-sinks-people-of-hope-2021-s01e05-720p-nf-web-dl-ddp20-x264-seikel");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-14 22:26:21"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-14 21:26:21"));
torrentInfo.Size.Should().Be(935127615);
torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -22,10 +22,10 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
Subject.Definition = new IndexerDefinition
{
Name = "ExoticaZ",
Settings = new AvistazSettings() { Username = "someuser", Password = "somepass", Pid = "somepid" }
Settings = new AvistazSettings { Username = "someuser", Password = "somepass", Pid = "somepid" }
};
}
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
.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 MovieSearchCriteria { Categories = new int[] { 2000 } })).Releases;
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new[] { 2000 } })).Releases;
releases.Should().HaveCount(100);
releases.First().Should().BeOfType<TorrentInfo>();
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://exoticaz.to/torrent/64040-ssis-419-my-first-experience-is-yua-mikami-from-the-day-i-lost-my-virginity-i-was-devoted-to-sex");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-06-11 16:04:50"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-06-11 15:04:50"));
torrentInfo.Size.Should().Be(7085405541);
torrentInfo.InfoHash.Should().Be("asdjfiasdf54asd7f4a2sdf544asdf");
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -22,10 +22,10 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
Subject.Definition = new IndexerDefinition
{
Name = "PrivateHD",
Settings = new AvistazSettings() { Username = "someuser", Password = "somepass", Pid = "somepid" }
Settings = new AvistazSettings { Username = "someuser", Password = "somepass", Pid = "somepid" }
};
}
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
.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 MovieSearchCriteria { Categories = new int[] { 2000 } })).Releases;
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new[] { 2000 } })).Releases;
releases.Should().HaveCount(100);
releases.First().Should().BeOfType<TorrentInfo>();
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://privatehd.to/torrent/78506-godzilla-2014-2160p-uhd-bluray-remux-hdr-hevc-atmos-triton");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-03-21 05:24:49"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-03-21 04:24:49"));
torrentInfo.Size.Should().Be(69914591044);
torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -0,0 +1,291 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.BroadcastheNet;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests.BroadcastheNetTests
{
public class BroadcastheNetRequestGeneratorFixture : CoreTest<BroadcastheNetRequestGenerator>
{
[SetUp]
public void Setup()
{
Subject.Settings = new BroadcastheNetSettings
{
BaseUrl = "https://api.broadcasthe.net/",
ApiKey = "abc"
};
Subject.Capabilities = new IndexerCapabilities
{
LimitsDefault = 100,
LimitsMax = 1000,
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.TvdbId, TvSearchParam.RId
}
};
}
[Test]
public void should_have_empty_parameters_if_rss_search()
{
var tvSearchCriteria = new TvSearchCriteria
{
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id }
};
var results = Subject.GetSearchRequests(tvSearchCriteria);
results.GetAllTiers().Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var query = ParseTorrentQueryFromRequest(page.HttpRequest);
query.Tvdb.Should().BeNull();
query.Tvrage.Should().BeNull();
query.Search.Should().BeNullOrWhiteSpace();
query.Category.Should().BeNullOrWhiteSpace();
query.Name.Should().BeNullOrWhiteSpace();
}
[Test]
public void should_search_by_tvdbid_season_if_supported()
{
var tvSearchCriteria = new TvSearchCriteria
{
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id },
TvdbId = 371980,
Season = 1
};
var results = Subject.GetSearchRequests(tvSearchCriteria);
results.Tiers.Should().Be(1);
results.GetAllTiers().Should().HaveCount(2);
var firstPage = results.GetAllTiers().First().First();
var firstQuery = ParseTorrentQueryFromRequest(firstPage.HttpRequest);
firstQuery.Tvdb.Should().Be("371980");
firstQuery.Tvrage.Should().BeNull();
firstQuery.Search.Should().BeNull();
firstQuery.Category.Should().Be("Season");
firstQuery.Name.Should().Be("Season 1%");
var secondPage = results.GetAllTiers().Skip(1).First().First();
var secondQuery = ParseTorrentQueryFromRequest(secondPage.HttpRequest);
secondQuery.Tvdb.Should().Be("371980");
secondQuery.Tvrage.Should().BeNull();
secondQuery.Search.Should().BeNull();
secondQuery.Category.Should().Be("Episode");
secondQuery.Name.Should().Be("S01E%");
}
[Test]
public void should_search_by_tvdbid_season_episode_if_supported()
{
var tvSearchCriteria = new TvSearchCriteria
{
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id },
TvdbId = 371980,
Season = 1,
Episode = "3"
};
var results = Subject.GetSearchRequests(tvSearchCriteria);
results.Tiers.Should().Be(1);
results.GetAllTiers().Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var query = ParseTorrentQueryFromRequest(page.HttpRequest);
query.Tvdb.Should().Be("371980");
query.Tvrage.Should().BeNull();
query.Search.Should().BeNull();
query.Category.Should().Be("Episode");
query.Name.Should().Be("S01E03");
}
[Test]
public void should_search_by_tvdbid_daily_episode_if_supported()
{
var tvSearchCriteria = new TvSearchCriteria
{
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id },
TvdbId = 289574,
Season = 2023,
Episode = "01/03"
};
var results = Subject.GetSearchRequests(tvSearchCriteria);
results.Tiers.Should().Be(1);
results.GetAllTiers().Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var query = ParseTorrentQueryFromRequest(page.HttpRequest);
query.Tvdb.Should().Be("289574");
query.Tvrage.Should().BeNull();
query.Search.Should().BeNull();
query.Category.Should().Be("Episode");
query.Name.Should().Be("2023.01.03");
}
[Test]
public void should_prefer_search_by_tvdbid_if_rid_supported()
{
var tvSearchCriteria = new TvSearchCriteria
{
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id },
TvdbId = 371980,
RId = 12345
};
var results = Subject.GetSearchRequests(tvSearchCriteria);
results.Tiers.Should().Be(1);
results.GetAllTiers().Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var query = ParseTorrentQueryFromRequest(page.HttpRequest);
query.Tvdb.Should().Be("371980");
query.Tvrage.Should().BeNull();
query.Search.Should().BeNull();
query.Category.Should().BeNull();
query.Name.Should().BeNull();
}
[Test]
public void should_search_by_term_supported()
{
var tvSearchCriteria = new TvSearchCriteria
{
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id },
SearchTerm = "Malcolm in the Middle"
};
var results = Subject.GetSearchRequests(tvSearchCriteria);
results.Tiers.Should().Be(1);
results.GetAllTiers().Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var query = ParseTorrentQueryFromRequest(page.HttpRequest);
query.Tvdb.Should().BeNull();
query.Tvrage.Should().BeNull();
query.Search.Should().Be("Malcolm%in%the%Middle");
query.Category.Should().BeNull();
query.Name.Should().BeNull();
}
[Test]
public void should_search_by_term_season_if_supported()
{
var tvSearchCriteria = new TvSearchCriteria
{
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id },
SearchTerm = "Malcolm in the Middle",
Season = 2
};
var results = Subject.GetSearchRequests(tvSearchCriteria);
results.Tiers.Should().Be(1);
results.GetAllTiers().Should().HaveCount(2);
var firstPage = results.GetAllTiers().First().First();
var firstQuery = ParseTorrentQueryFromRequest(firstPage.HttpRequest);
firstQuery.Tvdb.Should().BeNull();
firstQuery.Tvrage.Should().BeNull();
firstQuery.Search.Should().Be("Malcolm%in%the%Middle");
firstQuery.Category.Should().Be("Season");
firstQuery.Name.Should().Be("Season 2%");
var secondPage = results.GetAllTiers().Skip(1).First().First();
var secondQuery = ParseTorrentQueryFromRequest(secondPage.HttpRequest);
secondQuery.Tvdb.Should().BeNull();
secondQuery.Tvrage.Should().BeNull();
secondQuery.Search.Should().Be("Malcolm%in%the%Middle");
secondQuery.Category.Should().Be("Episode");
secondQuery.Name.Should().Be("S02E%");
}
[Test]
public void should_search_by_term_season_episode_if_supported()
{
var tvSearchCriteria = new TvSearchCriteria
{
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id },
SearchTerm = "Malcolm in the Middle",
Season = 2,
Episode = "3"
};
var results = Subject.GetSearchRequests(tvSearchCriteria);
results.Tiers.Should().Be(1);
results.GetAllTiers().Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var query = ParseTorrentQueryFromRequest(page.HttpRequest);
query.Tvdb.Should().BeNull();
query.Tvrage.Should().BeNull();
query.Search.Should().Be("Malcolm%in%the%Middle");
query.Category.Should().Be("Episode");
query.Name.Should().Be("S02E03");
}
[Test]
public void should_search_by_term_daily_episode_if_supported()
{
var tvSearchCriteria = new TvSearchCriteria
{
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id },
SearchTerm = "The Late Show with Stephen Colbert",
Season = 2023,
Episode = "01/03"
};
var results = Subject.GetSearchRequests(tvSearchCriteria);
results.Tiers.Should().Be(1);
results.GetAllTiers().Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var query = ParseTorrentQueryFromRequest(page.HttpRequest);
query.Tvdb.Should().BeNull();
query.Tvrage.Should().BeNull();
query.Search.Should().Be("The%Late%Show%with%Stephen%Colbert");
query.Category.Should().Be("Episode");
query.Name.Should().Be("2023.01.03");
}
private static BroadcastheNetTorrentQuery ParseTorrentQueryFromRequest(HttpRequest httpRequest)
{
var encoding = HttpHeader.GetEncodingFromContentType(httpRequest.Headers.ContentType);
var body = encoding.GetString(httpRequest.ContentData);
var rpcBody = JsonConvert.DeserializeObject<Dictionary<string, object>>(body);
return JsonConvert.DeserializeObject<BroadcastheNetTorrentQuery>(((JArray)rpcBody["params"])[1].ToJson());
}
}
}

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Indexers.Cardigann;
using NzbDrone.Core.Indexers.Definitions.Cardigann;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests.CardigannTests

View File

@@ -55,7 +55,7 @@ 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"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 19:20:19"));
torrentInfo.Size.Should().Be(8300512414);
torrentInfo.InfoHash.Should().Be(null);
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -12,7 +12,6 @@ using NzbDrone.Core.Indexers.Definitions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
{

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