Compare commits

...

107 Commits

Author SHA1 Message Date
Qstick
e47c7e6a47 New: Add coverurl to Newznab response 2021-08-07 12:07:38 -04:00
Nyuels
d937e0324f Fixed: (Internet Archive) Follow redirects on grabs (#403) 2021-08-06 20:50:06 -04:00
Yukine
ff623d4c39 Fixed: (Indexer) AnimeBytes improve sonarr compatibility and make optional (#376)
* Fixed: (Indexer) AnimeBytes improve sonarr compatibility and make optional

* fix(AnimeBytes): correct numbering
2021-08-06 20:47:52 -04:00
Weblate
0b05f5ef24 Translated using Weblate (Arabic)
Currently translated at 1.1% (5 of 426 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (426 of 426 strings)

Translated using Weblate (Italian)

Currently translated at 84.2% (359 of 426 strings)

Translated using Weblate (Danish)

Currently translated at 13.6% (58 of 426 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Laubau <migdahs@gmail.com>
Co-authored-by: Simone <simoneungaro@hotmail.it>
Co-authored-by: bison529 <abshalsh@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2021-08-06 17:51:10 -04:00
Nyuels
e6c3292485 Fixed: (Tracker) Internet Archive: Add handling of missing result fields. (#402) 2021-08-06 13:53:58 -05:00
Nyuels
ba1c1baeb5 New: (Indexer) Internet Archive 2021-08-05 21:07:09 -04:00
Qstick
5e7f4f3fc1 Fixed: Add Test All for IndexerLongTermStatusCheck
Fixes #397
2021-08-05 13:47:30 -04:00
chryzsh
3dd11213fa Fixed: Gazelle search using full IMDb ID 2021-08-04 22:04:53 -04:00
bakerboy448
50cae0719f New: (Indexer) Alternative Links for TorrentDay
Fixes #386
2021-08-02 19:04:48 -05:00
crusher
2c6680e4fa New: (Indexer) Rutracker.org (#371)
* first attempt at sc definition

* add dologin. seems to return html instead of json

* barebones working setup

* added category but errors on override

* fixed and tested

* guid fix

* fixed bdmv and dvd releases to report in radarr accepted format

* New: (Indexer) RuTracker

* minor fixes

* somewhat working rutracker org defintion with some filtering

* filter russian letters option implemented

* deal with subtitle languages

* russian filtering pretty good now

* removed sc from branch

* rutracker handles series ok now

* final touches to rutracker

* tore out the captcha

Co-authored-by: Qstick <qstick@gmail.com>
2021-08-01 09:59:45 -04:00
Qstick
96afb7f327 Fixed: (BakaBT) Map AnimeMovie to Movie
Fixes #147
2021-08-01 09:59:44 -04:00
Qstick
0d1025d60a Fixed: (Cardigann) Input pairs in HandleRequest should be form data
Should fix some download links not working for Cardigann indexers
2021-08-01 09:59:44 -04:00
Qstick
0508dd2b66 Fixed: (Cardigann) Sites that use POST search not sending form params
Fixes #367
2021-08-01 09:59:44 -04:00
Weblate
026a503d5f Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.7% (425 of 426 strings)

Co-authored-by: Will Segatto <segatto.w@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2021-08-01 09:59:44 -04:00
Qstick
b3f8e648cd New: (Clients) Torrent and Usenet Blackhole
Fixes #238
2021-08-01 09:59:44 -04:00
Qstick
13b458090d New: RARBG tvdbId support and retry on failure
Fixes #333
2021-08-01 09:59:44 -04:00
Qstick
f97c3ff9bd Fixed: Application mapping regex fails 2021-08-01 09:59:43 -04:00
Qstick
841ff7b6ee Fixed: Don't fail sync on single app failure 2021-08-01 09:59:43 -04:00
bakerboy448
377db47daf git updates [skip ci] 2021-08-01 09:59:43 -04:00
crusher
2addcab765 New: (Indexer) - Secret Cinema (#372)
* first attempt at sc definition

* add dologin. seems to return html instead of json

* barebones working setup

* added category but errors on override

* fixed and tested

* guid fix

* fixed bdmv and dvd releases to report in radarr accepted format

* minor fixes

* fixed sc

* first attempt at SC categories

* category fixes for movies and music

* toggled back IsProduction
2021-08-01 09:59:31 -04:00
crusher
ddc676c608 Iptorrents tv episode search fix (#374)
* adjust search term for individual season in ipt
2021-08-01 09:59:01 -04:00
Qstick
924db7a394 New: (Indexer) TorrentParadiseMl 2021-07-29 21:23:23 -04:00
Qstick
580113d6ce New: (Indexer) TorrentSyndikat 2021-07-28 19:37:51 -04:00
Qstick
d532a69edc New: (Indexer) TorrentsCSV 2021-07-28 19:02:20 -04:00
Qstick
01f7e11d5a New: (Indexer) SceneTime 2021-07-27 22:28:07 -04:00
Qstick
de97ec95db Update YamlDotNet to 11.2.1 2021-07-26 21:55:46 -04:00
Qstick
6e66467ab3 Update Mailkit to 2.13.0 2021-07-26 21:52:59 -04:00
Qstick
4c5131708d Update RestSharp Packages 2021-07-26 21:48:47 -04:00
Qstick
bb2e1a6037 Update Sentry Packages 2021-07-26 21:46:04 -04:00
Qstick
5949bd97fd Update Lint Packages 2021-07-26 21:41:45 -04:00
Qstick
eaff071b16 Bump .Net5 to 5.0.8 2021-07-26 21:35:25 -04:00
Dmitry Chepurovskiy
a922586aba New: (Indexer) Anidub 2021-07-26 21:22:01 -04:00
Qstick
80beea9bdb Fixed: Don't return results with categories that were not searched 2021-07-25 22:58:31 -04:00
Qstick
8c326fc5c2 Fixed: (ExoticaZ) Update Categories 2021-07-25 21:38:21 -04:00
Dmitry Chepurovskiy
e26081acff New: (Indexer) Animedia (#360) 2021-07-24 11:37:28 -04:00
Ashino
b3b0467d22 New: (Indexer) Torrent Xthor (#342)
* New: (Indexer) Torrent Xthor

* New: (Indexer) Torrent Xthor

Fix multiple lines fieldDefinition and texts

* New: (Indexer) Torrent Xthor
Remove unwanted blankline

* New: (Indexer) Torrent Xthor
Fix BaseSettings after rebase

* New: (Indexer) Torrent Xthor

* New: (Indexer) Torrent Xthor

Fix multiple lines fieldDefinition and texts

* New: (Indexer) Torrent Xthor
Remove unwanted blankline

* New: (Indexer) Torrent Xthor
Fix BaseSettings after rebase

* New: (Indexer) Torrent Xthor
- Add "EnhancedFrenchAccent" field that will allow to find VF2 releases when searching VFF or VFQ
- Fix BaseSettings
- Decrease the RateLimit to 2.1 seconds
- Remove page argument when searching page 0

* Fix punctuation

* New: (Indexer) Torrent Xthor

* New: (Indexer) Torrent Xthor

Fix multiple lines fieldDefinition and texts

* New: (Indexer) Torrent Xthor
Remove unwanted blankline

* New: (Indexer) Torrent Xthor
Fix BaseSettings after rebase

* New: (Indexer) Torrent Xthor
- Add "EnhancedFrenchAccent" field that will allow to find VF2 releases when searching VFF or VFQ
- Fix BaseSettings
- Decrease the RateLimit to 2.1 seconds
- Remove page argument when searching page 0

* Fix punctuation

* New: (Indexer) Torrent Xthor
- Fix "unknown" categories when searching with Prowlarr (category conversion was missing)
- Now every class has its own file (like all others indexers)

* New: (Indexer) Torrent Xthor

- Fix fielddefinition to be on single line

Co-authored-by: TCLE <t.clemenceau@shinken-solutions.com>
2021-07-23 21:09:57 -04:00
Servarr
e1c98d2b38 Translated using Weblate (Portuguese (Brazil)) (#354)
Currently translated at 99.7% (425 of 426 strings)

Added translation using Weblate (Chinese (Min Nan))

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

Currently translated at 65.7% (280 of 426 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (426 of 426 strings)

Translated using Weblate (French)

Currently translated at 100.0% (426 of 426 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (425 of 425 strings)

Translated using Weblate (French)

Currently translated at 100.0% (425 of 425 strings)

Translated using Weblate (German)

Currently translated at 98.1% (417 of 425 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Sean <zhangshuyan@fuji.waseda.jp>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: doob187 <amderkum@gmail.com>
Co-authored-by: foXaCe <foxace66@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
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

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Sean <zhangshuyan@fuji.waseda.jp>
Co-authored-by: doob187 <amderkum@gmail.com>
Co-authored-by: foXaCe <foxace66@gmail.com>
2021-07-21 10:49:00 -05:00
Qstick
e304461dfb New: Add PosterUrl to ReleaseInfo for parsing 2021-07-18 21:33:17 -04:00
Qstick
a127e5a30f Update and rename feature_request.md to feature_request.yml
[skip ci]
2021-07-17 11:51:03 -04:00
Qstick
e252cd4d3e Update bug_report.yml 2021-07-17 11:42:54 -04:00
Dmitry Chepurovskiy
9a1bd3db4c New: (Indexer) Shizaproject 2021-07-17 08:29:00 -04:00
Qstick
acce098e02 Update and rename bug_report.md to bug_report.yml 2021-07-17 08:14:36 -04:00
Yukine
afa87b7113 Fixed: (Anime Tosho) Only include Releases which have a NZB (#351) 2021-07-15 21:33:42 -04:00
bakerboy448
4cbd2cd8bc Fixed: Add Missing Search Translates 2021-07-15 21:23:39 -04:00
Yukine
e81d0f3e97 Fixed: (AnimeBytes) apply LinksUnionConverter to model (#353) 2021-07-15 21:22:54 -04:00
Hawks
34a6a0e0c9 Update indexer list text 2021-07-15 06:36:46 -05:00
Yukine
4254a05ea3 Fixed: (AnimeBytes) cleanup code, fix episode searching & improve Season matching (#329)
* refactor/fix(AnimeBytes): use data classes & fix season searching

* fix: only append epsisode when season was found

* feat: add Episode padding back for Sonarr compatibility

* fix: strip Epsiode number from request
2021-07-14 21:03:49 -04:00
Yukine
d32a94c14d Fixed: (SubsPlease) fix offset release time issues 2021-07-14 19:01:54 -04:00
ntldr0
6a9155bcf5 New: (Indexers) (Redacted) Add API support 2021-07-14 19:01:03 -04:00
Dmitry Chepurovskiy
de442cc659 New: (Indexer) Anilibria 2021-07-14 18:55:04 -04:00
bakerboy448
7f514c8f1e Fixed: Improved IPT's Cookie Help Text
ref https://www.reddit.com/r/prowlarr/comments/ojlamb/help_needed_with_ipt/h52nxnl/
2021-07-14 02:27:29 -05:00
bakerboy448
4c51f09acb Update bug report template [skip ci]
(cherry picked from commit 4659a8366d8a1565890d3b72442bd35c6eb8176e)
2021-07-13 20:55:43 +01:00
Qstick
a60388fcf9 Fixed: Indexer Info modal doesn't show correct Url 2021-07-12 23:26:17 -04:00
Qstick
11b656dabf Fixed: (TVVault) Advertise Indexer flags 2021-07-12 23:11:28 -04:00
Qstick
535f29bef4 Fixed: (DigitalCore) Release InfoUrls not being set 2021-07-12 23:11:28 -04:00
bakerboy448
0ddc530dd4 Fixed: Enhanced and Added Various C# Indexer Descriptions
Fixed: Consistency in C# Indexer Credential Fields
2021-07-12 21:57:27 -05:00
Servarr
b5321d33c9 Translated using Weblate (Catalan) (#336)
Currently translated at 1.1% (5 of 425 strings)

Added translation using Weblate (Catalan)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: dtalens <databio@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translation: Servarr/Prowlarr

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: dtalens <databio@gmail.com>
2021-07-12 21:57:14 -05:00
Qstick
4116c10caa New: (Indexers) Per indexer api and download limits 2021-07-12 22:53:33 -04:00
Qstick
0fe2cf5c2d New: (Indexer) TV Vault 2021-07-12 22:03:51 -04:00
PearsonFlyer
c94573e868 Fixed: Respect categories for ImmortalSeed in search 2021-07-12 21:34:02 -04:00
bakerboy448
1945af060d Fixed: Provider Wiki Links (supported-X => supported#X) 2021-07-12 21:33:27 -04:00
Qstick
cf399ffdcc Fixed: (DanishBytes) Convert to use API
Fixes #158
2021-07-11 21:28:57 -04:00
Qstick
5846188202 Fixed: (IPTorrents) Correctly map WebDL 2021-07-11 20:51:59 -04:00
Yukine
fc65a89fbc refactor: improve readability, use UTC in request instead New York 2021-07-11 18:21:16 -04:00
bakerboy448
b62ae41de8 update pr temp [skip ci] 2021-07-10 15:12:00 -05:00
bakerboy448
8107f309b4 Update PULL_REQUEST_TEMPLATE.md 2021-07-10 11:34:11 -04:00
Qstick
c2c12297bd Fixed: Persist columns for search page 2021-07-08 20:49:15 -04:00
bakerboy448
81cbdab5eb New: (Indexer) SubsPlease Alt Links 2021-07-07 23:04:14 -04:00
Servarr
9ee5a3e94b Translated using Weblate (Hungarian) (#300)
Currently translated at 100.0% (425 of 425 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (425 of 425 strings)

Translated using Weblate (Portuguese)

Currently translated at 85.4% (363 of 425 strings)

Translated using Weblate (French)

Currently translated at 100.0% (425 of 425 strings)

Translated using Weblate (German)

Currently translated at 92.4% (393 of 425 strings)

Translated using Weblate (Portuguese)

Currently translated at 84.4% (359 of 425 strings)

Translated using Weblate (Portuguese)

Currently translated at 84.4% (359 of 425 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: foXaCe <foxace66@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: foXaCe <foxace66@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
2021-07-07 00:55:40 -05:00
Qstick
a570fd2a8f New: Advanced settings toggle in indexer edit/add modal 2021-07-05 13:12:06 -04:00
Lagicrus
5cffb10e08 Fixed: If no categories are passed in, flag up a unknown error (#311)
* If no categories are passed in, flag up a unknown error

* Pass back in default props to deal with undefined issues
2021-07-05 08:42:51 -04:00
Qstick
b11bf284dc New: (IPTorrents) Add freeleech only option
Fixes #314
2021-07-05 08:41:29 -04:00
Robin Dadswell
5c4c042b2e Updated movieService reference for App Sync Profile Service dependency on Indexer Factory 2021-07-04 22:26:35 -04:00
Qstick
d1cb744efd Update azuresync.yml
[skip ci]
2021-07-01 21:59:49 -04:00
bakerboy448
79b910a80c Fixed: Cleanse BHD APIKey from logs 2021-07-01 16:22:57 -05:00
bakerboy448
01e08e0c31 fix radarr reference in contributing [skip ci] 2021-07-01 16:21:38 -05:00
Junkbite
dd3c9c268e allow empty catergories on indexer search 2021-06-30 21:57:53 -04:00
bakerboy448
725f738ee1 Fixed: Clarify redirect wording 2021-06-30 10:43:33 -05:00
Qstick
22c738f43e Prevent sync jobs from running in parallel
[skip ci]
2021-06-29 21:53:16 -04:00
Qstick
e45f88473c Fixed: (Anthelion) Null BaseUrl and better error message on auth fail
Fixes #295
2021-06-28 23:04:10 -04:00
Qstick
889591d0b1 Fixed: (HDSpace) Use query params 2021-06-28 22:43:30 -04:00
Qstick
fe8247df8a Fixed: (HDSpace) Auth failure and Name 2021-06-28 21:36:05 -04:00
bakerboy448
7a5721bcee typo fix 2021-06-28 20:26:34 -05:00
bakerboy448
eea5c3e9a4 Fixed: Cleanse Pwd from logs 2021-06-28 12:22:47 -05:00
Qstick
f55493c9a9 New: (Indexer) HD-Space 2021-06-27 21:40:43 -04:00
bakerboy448
79618adaf9 New: (Indexer) Add IPTorrents Alt Links
as of Jackett 4524c18d39251c677bec1d4d8f75f71c6c6ac010
2021-06-27 19:32:31 -04:00
Qstick
af13d6ed80 Fixed: Not using correct BaseUrl if changed between tests 2021-06-27 18:15:18 -04:00
Qstick
38c09277d9 New: Alternative Site Links 2021-06-27 01:58:37 -04:00
Qstick
1fb693d066 Update azuresync.yml
[skip ci]
2021-06-26 18:04:48 -04:00
Qstick
7414a2f690 Update azuresync.yml
[skip ci]
2021-06-26 17:59:12 -04:00
Qstick
000590bcf7 Update azuresync.yml
[skip ci]
2021-06-26 17:57:00 -04:00
Qstick
d5d6625a63 Update azuresync.yml
[skip ci]
2021-06-26 17:16:12 -04:00
Qstick
00f33cb48f Set area and sub-area on azure sync
[skip ci]
2021-06-26 17:09:23 -04:00
Qstick
fd265c5734 Update azure-pipelines.yml 2021-06-26 14:58:58 -04:00
Qstick
316543b9aa Change Azure to Agile Config
[skip ci]
2021-06-26 11:07:51 -04:00
Qstick
117ebcff2d Fix default azure board buckets
[skip ci]
2021-06-26 10:55:13 -04:00
Qstick
aab394b2c8 Update azuresync.yml
[skip ci]
2021-06-26 10:51:14 -04:00
Qstick
6ae520c061 Test Azure Boards sync 2021-06-26 10:47:23 -04:00
bakerboy448
dfb254d2dc Fixed: Default Branch is now Develop 2021-06-26 06:36:55 -05:00
Weblate
07c03b0a12 Translated using Weblate (Portuguese)
Currently translated at 83.7% (356 of 425 strings)

Translated using Weblate (German)

Currently translated at 92.7% (394 of 425 strings)

Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translation: Servarr/Prowlarr
2021-06-26 07:14:03 -04:00
bakerboy448
eb0cf2d5f6 bug update [skip ci] 2021-06-26 05:48:17 -05:00
Servarr
69c04ebe7a Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (425 of 425 strings)

Translated using Weblate (Portuguese)

Currently translated at 83.7% (356 of 425 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 0.9% (4 of 425 strings)

Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: angelsky11 <angelsky11@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_Hans/
Translation: Servarr/Prowlarr

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: angelsky11 <angelsky11@gmail.com>
2021-06-25 20:02:41 -05:00
bakerboy448
135db6d2ff Fixed: Cleanse additional AuthKey instances in logs 2021-06-24 19:27:32 -05:00
bakerboy448
c3deace9e6 Fixed: Incorrectly cleansing usetoken param 2021-06-24 18:56:15 -05:00
Qstick
9042594b14 Update FUNDING.yml to include GitHub sponsors 2021-06-24 19:52:20 -04:00
bakerboy448
44df0f5c3d Fixed: NZBGet Settings hint mentions Sabnzbd 2021-06-24 11:02:23 -05:00
bakerboy448
7b9446eb35 fix template formatting [skip ci] 2021-06-23 23:55:10 -05:00
203 changed files with 9689 additions and 1248 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1,6 +1,6 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: Prowlarr # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: prowlarr
ko_fi: # Replace with a single Ko-fi username

View File

@@ -1,37 +0,0 @@
---
name: Bug Report
about: Support Requests will be closed immediately, if you are not 100% certain this is a bug please go to our Reddit or Discord first. Exceptions do not mean you found a bug!
title: ''
labels: 'Type: Bug'
assignees: ''
---
<!-- Support Requests will be closed immediately, if you are unsure go to our Reddit or Discord first. Exceptions do not mean you found a bug! -->
<!-- Note: Text between <!- and -> will be hidden -->
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
**To Reproduce**
<!-- Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error -->
**Expected behavior**
<!-- A clear and concise description of what you expected to happen.-->
**Screenshots**
<!-- If applicable, add screenshots to help explain your problem.-->
**Platform Information (please complete the following information):**
- OS: <!-- [e.g. Windows 10 2004 / Ubuntu 20.04] -->
- Docker: <!-- [Yes/No] -->
- .NET Version (System -> Status): <!--[e.g. .NET 5.0.1] -->
- Browser and Version (Only needed for UI issues): <!--[e.g. chrome 86.0.4240.198] -->
- Prowlarr Version: <!--[e.g. 0.1.2.1854-->
- Prowlarr Branch: <!--[e.g. develop, nightly]-->
Turn on Trace logs under Settings -> General and wait for the bug to occur again.
**Upload the full log file here (or another site (e.g. pastebin) and link it). Issues will be closed, if they do not include this!**
<!-- Trace logs are named Prowlarr.trace.txt or Prowlarr.trace.#.txt and will contain "trace" in them-->

74
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,74 @@
name: Bug Report
title: "[BUG]: "
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first'
labels: ['Type: Bug', 'Status: Needs Triage']
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing issues
required: true
- type: textarea
attributes:
label: Current Behavior
description: A concise description of what you're experiencing.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Steps To Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. In this environment...
2. With this config...
3. Run '...'
4. See error...
validations:
required: false
- type: textarea
attributes:
label: Environment
description: |
examples:
- **OS**: Ubuntu 20.04
- **Prowlarr**: Prowlarr 0.1.0.650
- **Docker Install**: Yes
- **Using Reverse Proxy**: No
- **Browser**: Firefox 90 (If UI related)
value: |
- OS:
- Prowlarr:
- Docker Install:
- Using Reverse Proxy:
- Browser:
render: markdown
validations:
required: true
- type: dropdown
attributes:
label: What branch are you running?
options:
- Master
- Develop
- Nightly
- Other (This issue will be closed)
validations:
required: true
- type: textarea
attributes:
label: Anything else?
description: |
Trace Logs (https://wiki.servarr.com/prowlarr/troubleshooting#logging-and-log-files)
Links? References? Anything that will give us more context about the issue you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: true

View File

@@ -1,20 +0,0 @@
---
name: Feature Request
about: Suggest an idea for Prowlarr
title: ''
labels: 'Type: Feature Request'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->

View File

@@ -0,0 +1,39 @@
name: Feature Request
title: "[FEAT]: "
description: 'Suggest an idea for Prowlarr'
labels: ['Type: Feature Request', 'Status: Needs Triage']
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the feature you are requesting.
options:
- label: I have searched the existing issues
required: true
- type: textarea
attributes:
label: Is your feature request related to a problem? Please describe
description: A clear and concise description of what the problem is.
validations:
required: true
- type: textarea
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
attributes:
label: Anything else?
description: |
Links? References? Mockups? Anything that will give us more context about the feature you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: true

View File

@@ -1,14 +1,15 @@
#### Database Migration
YES | NO
YES - XXXX | NO
#### Description
A few sentences describing the overall goals of the pull request's commits.
#### Screenshot (if UI related)
#### Todos
- [ ] Tests
- [ ] Translation Keys
- [ ] Wiki Updates
- [ ] Translation Keys (./src/NzbDrone.Core/Localization/Core/en.json)
- [ ] [Wiki Updates](https://wiki.servarr.com)
#### Issues Fixed or Closed by this PR

41
.github/workflows/azuresync.yml vendored Normal file
View File

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

View File

@@ -2,6 +2,8 @@
We're always looking for people to help make Prowlarr even better, there are a number of ways to contribute.
This file is updated on an ad-hoc basis, for the latest details please see the [contributing wiki page](https://wiki.servarr.com/prowlarr/contributing).
## Documentation ##
Setup guides, FAQ, the more information we have on the [wiki](https://wiki.servarr.com/prowlarr) the better.
@@ -27,7 +29,7 @@ Setup guides, FAQ, the more information we have on the [wiki](https://wiki.serva
### Contributing Code ###
- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Prowlarr/Prowlarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first)
- Rebase from Radarr's develop branch, don't merge
- Rebase from Prowlarr's develop branch, don't merge
- Make meaningful commits, or squash them
- Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements
- Reach out to us on the discord if you have any questions

View File

@@ -11,7 +11,7 @@ Prowlarr is a indexer manager/proxy built on the popular arr .net/reactjs base s
## Major Features Include:
- Usenet support for 24 indexers natively, including Headphones VIP, and support for any Newznab compatible indexer via "Generic Newznab"
- Torrent support for almost 500 trackers & more coming soon
- Torrent support for over 500 trackers with more added all the time
- Torrent support for any Torznab compatible tracker via "Generic Torznab"
- Indexer Sync to Sonarr/Radarr/Readarr/Lidarr, so no manual configuration of the other applications are required
- Indexer History and Statistics
@@ -34,10 +34,6 @@ Note: Prowlarr is currently early in life, thus bugs should be expected
[Indexer Requests](https://requests.prowlarr.com)
- Request or vote on an existing request for a new tracker/indexer
## Feature Requests
[Feature Requests](https://github.com/Prowlarr/Prowlarr/issues/new?assignees=&template=feature_request.md&Type%3A%20Feature%20Request&title=)
## Contributors & Developers
This project exists thanks to all the people who contribute. [Contribute](CONTRIBUTING.md).
<a href="https://github.com/Prowlarr/Prowlarr/graphs/contributors"><img src="https://opencollective.com/Prowlarr/contributors.svg?width=890&button=false" /></a>

View File

@@ -13,7 +13,7 @@ variables:
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '5.0.203'
dotnetVersion: '5.0.302'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
trigger:
@@ -879,7 +879,7 @@ stages:
artifactName: 'WindowsAutomationScreenshots'
targetPath: $(Build.SourcesDirectory)
- checkout: none
- powershell: |
- pwsh: |
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/Servarr/AzureDiscordNotify/master/DiscordNotify.ps1'))
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)

View File

@@ -13,6 +13,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import translate from 'Utilities/String/translate';
import styles from './EditIndexerModalContent.css';
@@ -31,6 +32,7 @@ function EditIndexerModalContent(props) {
onSavePress,
onTestPress,
onDeleteIndexerPress,
onAdvancedSettingsPress,
...otherProps
} = props;
@@ -165,6 +167,12 @@ function EditIndexerModalContent(props) {
</Button>
}
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton
isSpinning={isTesting}
error={saveError}
@@ -204,6 +212,7 @@ EditIndexerModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteIndexerPress: PropTypes.func
};

View File

@@ -3,6 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveIndexer, setIndexerFieldValue, setIndexerValue, testIndexer } from 'Store/Actions/indexerActions';
import { toggleAdvancedSettings } from 'Store/Actions/settingsActions';
import createIndexerSchemaSelector from 'Store/Selectors/createIndexerSchemaSelector';
import EditIndexerModalContent from './EditIndexerModalContent';
@@ -23,7 +24,8 @@ const mapDispatchToProps = {
setIndexerValue,
setIndexerFieldValue,
saveIndexer,
testIndexer
testIndexer,
toggleAdvancedSettings
};
class EditIndexerModalContentConnector extends Component {
@@ -56,6 +58,11 @@ class EditIndexerModalContentConnector extends Component {
this.props.testIndexer({ id: this.props.id });
}
onAdvancedSettingsPress = () => {
console.log('settings');
this.props.toggleAdvancedSettings();
}
//
// Render
@@ -65,6 +72,7 @@ class EditIndexerModalContentConnector extends Component {
{...this.props}
onSavePress={this.onSavePress}
onTestPress={this.onTestPress}
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
/>
@@ -80,6 +88,7 @@ EditIndexerModalContentConnector.propTypes = {
item: PropTypes.object.isRequired,
setIndexerValue: PropTypes.func.isRequired,
setIndexerFieldValue: PropTypes.func.isRequired,
toggleAdvancedSettings: PropTypes.func.isRequired,
saveIndexer: PropTypes.func.isRequired,
testIndexer: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired

View File

@@ -71,7 +71,7 @@ class IndexerIndexRow extends Component {
const {
id,
name,
baseUrl,
indexerUrls,
enable,
redirect,
tags,
@@ -248,7 +248,7 @@ class IndexerIndexRow extends Component {
className={styles.externalLink}
name={icons.EXTERNAL_LINK}
title={'Website'}
to={baseUrl.replace('api.', '')}
to={indexerUrls[0].replace('api.', '')}
/>
<IconButton
@@ -289,7 +289,7 @@ class IndexerIndexRow extends Component {
IndexerIndexRow.propTypes = {
id: PropTypes.number.isRequired,
baseUrl: PropTypes.string.isRequired,
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
protocol: PropTypes.string.isRequired,
privacy: PropTypes.string.isRequired,
priority: PropTypes.number.isRequired,

View File

@@ -18,7 +18,7 @@ function IndexerInfoModalContent(props) {
description,
encoding,
language,
baseUrl,
indexerUrls,
protocol,
onModalClose
} = props;
@@ -54,10 +54,10 @@ function IndexerInfoModalContent(props) {
<DescriptionListItemTitle>Indexer Site</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to={baseUrl}>{baseUrl}</Link>
<Link to={indexerUrls[0]}>{indexerUrls[0]}</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>{protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url</DescriptionListItemTitle>
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
<DescriptionListItemDescription>
{`${window.location.origin}${window.Prowlarr.urlBase}/${id}/api`}
</DescriptionListItemDescription>
@@ -74,7 +74,7 @@ IndexerInfoModalContent.propTypes = {
description: PropTypes.string.isRequired,
encoding: PropTypes.string.isRequired,
language: PropTypes.string.isRequired,
baseUrl: PropTypes.string.isRequired,
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
protocol: PropTypes.string.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -10,7 +10,6 @@ function createMapStateToProps() {
(state) => state.settings.advancedSettings,
createIndexerSelector(),
(advancedSettings, indexer) => {
console.log(indexer);
return {
advancedSettings,
...indexer

View File

@@ -26,7 +26,7 @@ function SearchIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Protocol
{translate('Protocol')}
</SortMenuItem>
<SortMenuItem
@@ -35,7 +35,7 @@ function SearchIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Age
{translate('Age')}
</SortMenuItem>
<SortMenuItem
@@ -53,7 +53,7 @@ function SearchIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Indexer
{translate('Indexer')}
</SortMenuItem>
<SortMenuItem
@@ -62,7 +62,7 @@ function SearchIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Size
{translate('Size')}
</SortMenuItem>
<SortMenuItem
@@ -71,7 +71,7 @@ function SearchIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Files
{translate('Files')}
</SortMenuItem>
<SortMenuItem
@@ -80,7 +80,7 @@ function SearchIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Grabs
{translate('Grabs')}
</SortMenuItem>
<SortMenuItem
@@ -89,7 +89,7 @@ function SearchIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Peers
{translate('Peers')}
</SortMenuItem>
<SortMenuItem
@@ -98,7 +98,7 @@ function SearchIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Category
{translate('Category')}
</SortMenuItem>
</MenuContent>
</SortMenu>

View File

@@ -19,7 +19,7 @@ function NoSearchResults(props) {
return (
<div>
<div className={styles.message}>
No search results found, try performing a new search below.
{translate('NoSearchResultsFound')}
</div>
</div>
);

View File

@@ -7,6 +7,7 @@ import TextInput from 'Components/Form/TextInput';
import keyboardShortcuts from 'Components/keyboardShortcuts';
import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter';
import translate from 'Utilities/String/translate';
import SearchFooterLabel from './SearchFooterLabel';
import styles from './SearchFooter.css';
@@ -167,7 +168,7 @@ class SearchFooter extends Component {
isDisabled={isFetching || !hasIndexers}
onPress={this.onSearchPress}
>
Search
{translate('Search')}
</SpinnerButton>
</div>
</div>

View File

@@ -1,10 +1,22 @@
import PropTypes from 'prop-types';
import React from 'react';
import Label from 'Components/Label';
import { kinds, tooltipPositions } from 'Helpers/Props';
import Tooltip from '../../Components/Tooltip/Tooltip';
function CategoryLabel({ categories }) {
const sortedCategories = categories.filter((cat) => cat.name !== undefined).sort((c) => c.id);
if (categories?.length === 0) {
return (
<Tooltip
anchor={<Label kind={kinds.DANGER}>Unknown</Label>}
tooltip="Please report this issue to the GitHub as this shouldn't be happening"
position={tooltipPositions.LEFT}
/>
);
}
return (
<span>
{
@@ -20,6 +32,10 @@ function CategoryLabel({ categories }) {
);
}
CategoryLabel.defaultProps = {
categories: []
};
CategoryLabel.propTypes = {
categories: PropTypes.arrayOf(PropTypes.object).isRequired
};

View File

@@ -10,7 +10,8 @@ import styles from './AdvancedSettingsButton.css';
function AdvancedSettingsButton(props) {
const {
advancedSettings,
onAdvancedSettingsPress
onAdvancedSettingsPress,
showLabel
} = props;
return (
@@ -43,18 +44,27 @@ function AdvancedSettingsButton(props) {
/>
</span>
<div className={styles.labelContainer}>
<div className={styles.label}>
{advancedSettings ? translate('HideAdvanced') : translate('ShowAdvanced')}
</div>
</div>
{
showLabel &&
<div className={styles.labelContainer}>
<div className={styles.label}>
{advancedSettings ? translate('HideAdvanced') : translate('ShowAdvanced')}
</div>
</div>
}
</Link>
);
}
AdvancedSettingsButton.propTypes = {
advancedSettings: PropTypes.bool.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired
onAdvancedSettingsPress: PropTypes.func.isRequired,
showLabel: PropTypes.bool.isRequired
};
AdvancedSettingsButton.defaultProps = {
showLabel: true
};
export default AdvancedSettingsButton;

View File

@@ -202,7 +202,8 @@ export const defaultState = {
export const persistState = [
'releases.customFilters',
'releases.selectedFilterKey'
'releases.selectedFilterKey',
'releases.columns'
];
//

View File

@@ -43,6 +43,7 @@ function getInternalLink(source) {
function getTestLink(source, props) {
switch (source) {
case 'IndexerStatusCheck':
case 'IndexerLongTermStatusCheck':
return (
<SpinnerIconButton
name={icons.TEST}

View File

@@ -30,9 +30,9 @@
"@fortawesome/free-regular-svg-icons": "5.15.3",
"@fortawesome/free-solid-svg-icons": "5.15.3",
"@fortawesome/react-fontawesome": "0.1.14",
"@microsoft/signalr": "5.0.6",
"@sentry/browser": "6.3.1",
"@sentry/integrations": "6.3.1",
"@microsoft/signalr": "5.0.8",
"@sentry/browser": "6.10.0",
"@sentry/integrations": "6.10.0",
"chart.js": "3.2.0",
"classnames": "2.3.1",
"clipboard": "2.0.8",
@@ -98,12 +98,12 @@
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
"core-js": "3.11.0",
"css-loader": "5.2.4",
"eslint": "7.25.0",
"eslint": "7.31.0",
"eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-react": "7.23.2",
"eslint-plugin-import": "2.23.4",
"eslint-plugin-react": "7.24.0",
"eslint-plugin-simple-import-sort": "7.0.0",
"esprint": "2.0.0",
"esprint": "3.1.0",
"file-loader": "6.2.0",
"filemanager-webpack-plugin": "5.0.0",
"html-webpack-plugin": "5.3.1",
@@ -125,7 +125,7 @@
"webpack": "5.35.1",
"webpack-cli": "4.6.0",
"webpack-livereload-plugin": "3.0.1",
"stylelint": "13.13.0",
"stylelint": "13.13.1",
"stylelint-order": "4.1.0"
}
}

View File

@@ -94,7 +94,7 @@
<!-- Standard testing packages -->
<ItemGroup Condition="'$(TestProject)'=='true'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NunitXml.TestLogger" Version="3.0.97" />

View File

@@ -19,6 +19,9 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"http://nzb.su/getnzb/2b51db35e1912ffc138825a12b9933d2.nzb&i=37292&r=2b51db35e1910123321025a12b9933d2")]
[TestCase(@"https://horrorcharnel.org/takeloginhorror.php: username=mySecret&password=mySecret&use_sslvalue==&perm_ssl=1&submitme=X&use_ssl=1&returnto=%2F&captchaSelection=1230456")]
[TestCase(@"https://torrentdb.net/login: _token=2b51db35e1912ffc138825a12b9933d2&username=mySecret&password=mySecret&remember=on")]
[TestCase(@" var authkey = ""2b51db35e1910123321025a12b9933d2"";")]
[TestCase(@"https://hd-space.org/index.php?page=login: uid=mySecret&pwd=mySecret")]
[TestCase(@"https://beyond-hd.me/api/torrents/2b51db35e1912ffc138825a12b9933d2")]
// NzbGet
[TestCase(@"{ ""Name"" : ""ControlUsername"", ""Value"" : ""mySecret"" }, { ""Name"" : ""ControlPassword"", ""Value"" : ""mySecret"" }, ")]
@@ -90,5 +93,14 @@ namespace NzbDrone.Common.Test.InstrumentationTests
cleansedMessage.Should().Be(message);
}
[TestCase(@"&useToken=2b51db35e1910123321025a12b9933d2")]
[TestCase(@"&useToken=2b51db35e1910123321025a12b9933d2")]
public void should_not_clean_usetoken(string message)
{
var cleansedMessage = CleanseLogMessage.Cleanse(message);
cleansedMessage.Should().Be(message);
}
}
}

View File

@@ -53,6 +53,16 @@ namespace NzbDrone.Common.Extensions
return dateTime >= afterDateTime && dateTime <= beforeDateTime;
}
public static DateTime EndOfDay(this DateTime date)
{
return new DateTime(date.Year, date.Month, date.Day, 23, 59, 59, 999);
}
public static DateTime StartOfDay(this DateTime date)
{
return new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, 0);
}
public static DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
}
}

View File

@@ -194,5 +194,21 @@ namespace NzbDrone.Common.Extensions
var inputBytes = encoding.GetBytes(searchString);
return encoding.GetString(WebUtility.UrlDecodeToBytes(inputBytes, 0, inputBytes.Length));
}
public static string CleanFileName(this string name)
{
string result = name;
string[] badCharacters = { "\\", "/", "<", ">", "?", "*", ":", "|", "\"" };
string[] goodCharacters = { "+", "+", "", "", "!", "-", "-", "", "" };
result = result.Replace(": ", " - ");
for (int i = 0; i < badCharacters.Length; i++)
{
result = result.Replace(badCharacters[i], goodCharacters[i]);
}
return result.TrimStart(' ', '.').TrimEnd(' ');
}
}
}

View File

@@ -30,6 +30,7 @@ namespace NzbDrone.Common.Http
public HttpUri Url { get; set; }
public HttpMethod Method { get; set; }
public HttpHeader Headers { get; set; }
public Encoding Encoding { get; set; }
public byte[] ContentData { get; set; }
public string ContentSummary { get; set; }
public bool SuppressHttpError { get; set; }
@@ -75,8 +76,15 @@ namespace NzbDrone.Common.Http
public void SetContent(string data)
{
var encoding = HttpHeader.GetEncodingFromContentType(Headers.ContentType);
ContentData = encoding.GetBytes(data);
if (Encoding != null)
{
ContentData = Encoding.GetBytes(data);
}
else
{
var encoding = HttpHeader.GetEncodingFromContentType(Headers.ContentType);
ContentData = encoding.GetBytes(data);
}
}
public void AddBasicAuthentication(string username, string password)

View File

@@ -47,7 +47,14 @@ namespace NzbDrone.Common.Http
{
if (_content == null)
{
_content = Headers.GetEncodingFromContentType().GetString(ResponseData);
if (Request.Encoding != null)
{
_content = Request.Encoding.GetString(ResponseData);
}
else
{
_content = Headers.GetEncodingFromContentType().GetString(ResponseData);
}
}
return _content;

View File

@@ -11,13 +11,15 @@ namespace NzbDrone.Common.Instrumentation
private static readonly Regex[] CleansingRules = new[]
{
// Url
new Regex(@"(?<=\?|&|: |;)(apikey|token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=\?|&| )[^=]*?(_?token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=\?|&|: |;)(apikey|token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd|pwd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=\?|&| )[^=]*?(_?(?<!use)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=authkey = "")(?<secret>[^&=]+?)(?="")", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=beyond-hd\.[a-z]+/api/torrents/)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Path
new Regex(@"""C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),

View File

@@ -6,11 +6,11 @@
<ItemGroup>
<PackageReference Include="DotNet4.SocksProxy" Version="1.4.0.1" />
<PackageReference Include="DryIoc.dll" Version="4.7.6" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.2" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NLog" Version="4.7.9" />
<PackageReference Include="Sentry" Version="3.3.3" />
<PackageReference Include="Sentry" Version="3.8.3" />
<PackageReference Include="SharpZipLib" Version="1.3.1" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.113.0-0" />

View File

@@ -19,7 +19,8 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
Subject.Settings = new FileListSettings()
{
Passkey = "abcd",
Username = "somename"
Username = "somename",
BaseUrl = "https://filelist.io"
};
Subject.Capabilities = new IndexerCapabilities
@@ -54,8 +55,6 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
SearchTerm = "Star Wars",
Categories = new int[] { 2000 }
};
Subject.BaseUrl = "https://filelist.io";
}
private void MovieWithoutIMDB()

View File

@@ -10,7 +10,8 @@ namespace NzbDrone.Core.Test.IndexerTests
public class TestIndexer : UsenetIndexerBase<TestIndexerSettings>
{
public override string Name => "Test Indexer";
public override string BaseUrl => "http://testindexer.com";
public override string[] IndexerUrls => new string[] { "http://testindexer.com" };
public override string Description => "";
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;

View File

@@ -1,12 +1,10 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Test.IndexerTests
{
public class TestIndexerSettings : IProviderConfig
public class TestIndexerSettings : IIndexerSettings
{
public NzbDroneValidationResult Validate()
{
@@ -14,5 +12,6 @@ namespace NzbDrone.Core.Test.IndexerTests
}
public string BaseUrl { get; set; }
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
}
}

View File

@@ -6,7 +6,7 @@
<PackageReference Include="Dapper" Version="2.0.78" />
<PackageReference Include="NBuilder" Version="6.1.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.113.0-0" />
<PackageReference Include="YamlDotNet" Version="11.1.1" />
<PackageReference Include="YamlDotNet" Version="11.2.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />

View File

@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Applications
protected readonly IAppIndexerMapService _appIndexerMapService;
protected readonly Logger _logger;
protected static readonly Regex AppIndexerRegex = new Regex(@"(?<indexer>\d*)/api",
protected static readonly Regex AppIndexerRegex = new Regex(@"\/(?<indexer>\d.)\/",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
public abstract string Name { get; }

View File

@@ -114,7 +114,12 @@ namespace NzbDrone.Core.Applications
var prowlarrMappings = indexerMappings.ToDictionary(i => i.RemoteIndexerId, i => i.IndexerId);
//Get Dictionary of Remote Indexers point to Prowlarr and what they are mapped to
var remoteMappings = app.GetIndexerMappings();
var remoteMappings = ExecuteAction(a => a.GetIndexerMappings(), app);
if (remoteMappings == null)
{
continue;
}
//Add mappings if not already in db, these were setup manually in the app or orphaned by a table wipe
foreach (var mapping in remoteMappings)
@@ -214,5 +219,64 @@ namespace NzbDrone.Core.Applications
_logger.Error(ex, "An error occurred while talking to remote application.");
}
}
private TResult ExecuteAction<TResult>(Func<IApplication, TResult> applicationAction, IApplication application)
{
TResult result;
try
{
result = applicationAction(application);
_applicationStatusService.RecordSuccess(application.Definition.Id);
return result;
}
catch (WebException webException)
{
if (webException.Status == WebExceptionStatus.NameResolutionFailure ||
webException.Status == WebExceptionStatus.ConnectFailure)
{
_applicationStatusService.RecordConnectionFailure(application.Definition.Id);
}
else
{
_applicationStatusService.RecordFailure(application.Definition.Id);
}
if (webException.Message.Contains("502") || webException.Message.Contains("503") ||
webException.Message.Contains("timed out"))
{
_logger.Warn("{0} server is currently unavailable. {1}", this, webException.Message);
}
else
{
_logger.Warn("{0} {1}", this, webException.Message);
}
}
catch (TooManyRequestsException ex)
{
if (ex.RetryAfter != TimeSpan.Zero)
{
_applicationStatusService.RecordFailure(application.Definition.Id, ex.RetryAfter);
}
else
{
_applicationStatusService.RecordFailure(application.Definition.Id, TimeSpan.FromHours(1));
}
_logger.Warn("API Request Limit reached for {0}", this);
}
catch (HttpException ex)
{
_applicationStatusService.RecordFailure(application.Definition.Id);
_logger.Warn("{0} {1}", this, ex.Message);
}
catch (Exception ex)
{
_applicationStatusService.RecordFailure(application.Definition.Id);
_logger.Error(ex, "An error occurred while talking to remote application.");
}
return default(TResult);
}
}
}

View File

@@ -1,11 +1,7 @@
using System;
using System.Linq;
using System.Xml.Linq;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Authentication
{

View File

@@ -180,7 +180,7 @@ namespace NzbDrone.Core.Configuration
public bool AnalyticsEnabled => GetValueBoolean("AnalyticsEnabled", true, persist: false);
// TODO: Change back to "master" for the first stable release.
public string Branch => GetValue("Branch", "nightly").ToLowerInvariant();
public string Branch => GetValue("Branch", "develop").ToLowerInvariant();
public string LogLevel => GetValue("LogLevel", "info").ToLowerInvariant();
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Configuration
var releaseInfoPath = Path.Combine(bin, "release_info");
PackageUpdateMechanism = UpdateMechanism.BuiltIn;
DefaultBranch = "nightly";
DefaultBranch = "develop";
if (Path.GetFileName(bin) == "bin" && diskProvider.FileExists(packageInfoPath))
{

View File

@@ -1,6 +1,3 @@
using System;
using System.Collections.Generic;
using System.Data;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;

View File

@@ -1,5 +1,4 @@
using FluentMigrator;
using Newtonsoft.Json.Linq;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration

View File

@@ -0,0 +1,63 @@
using System.Data;
using FluentMigrator;
using Newtonsoft.Json.Linq;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(8)]
public class redacted_api : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.WithConnection(MigrateToRedactedApi);
}
private void MigrateToRedactedApi(IDbConnection conn, IDbTransaction tran)
{
using (var cmd = conn.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandText = "SELECT Id, Settings FROM Indexers WHERE Implementation = 'Redacted'";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var id = reader.GetInt32(0);
var settings = reader.GetString(1);
if (!string.IsNullOrWhiteSpace(settings))
{
var jsonObject = Json.Deserialize<JObject>(settings);
// Remove username
if (jsonObject.ContainsKey("username"))
{
jsonObject.Remove("username");
}
// Remove password
if (jsonObject.ContainsKey("password"))
{
jsonObject.Remove("password");
}
// write new json back to db, switch to new ConfigContract, and disable the indexer
settings = jsonObject.ToJson();
using (var updateCmd = conn.CreateCommand())
{
updateCmd.Transaction = tran;
updateCmd.CommandText = "UPDATE Indexers SET Settings = ?, ConfigContract = ?, Enable = 0 WHERE Id = ?";
updateCmd.AddParameter(settings);
updateCmd.AddParameter("RedactedSettings");
updateCmd.AddParameter(id);
updateCmd.ExecuteNonQuery();
}
}
}
}
}
}
}
}

View File

@@ -46,7 +46,7 @@ namespace NzbDrone.Core.Datastore
.Ignore(i => i.Description)
.Ignore(i => i.Language)
.Ignore(i => i.Encoding)
.Ignore(i => i.BaseUrl)
.Ignore(i => i.IndexerUrls)
.Ignore(i => i.Protocol)
.Ignore(i => i.Privacy)
.Ignore(i => i.SupportsRss)

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download.Clients.Blackhole
{
public class TorrentBlackhole : TorrentClientBase<TorrentBlackholeSettings>
{
public override bool PreferTorrentFile => true;
public TorrentBlackhole(ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
{
}
protected override string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink)
{
throw new NotImplementedException("Blackhole does not support redirected indexers.");
}
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
{
if (!Settings.SaveMagnetFiles)
{
throw new NotSupportedException("Blackhole does not support magnet links.");
}
var title = release.Title;
title = title.CleanFileName();
var filepath = Path.Combine(Settings.TorrentFolder, $"{title}.{Settings.MagnetFileExtension.Trim('.')}");
var fileContent = Encoding.UTF8.GetBytes(magnetLink);
using (var stream = _diskProvider.OpenWriteStream(filepath))
{
stream.Write(fileContent, 0, fileContent.Length);
}
_logger.Debug("Saving magnet link succeeded, saved to: {0}", filepath);
return null;
}
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
{
var title = release.Title;
title = title.CleanFileName();
var filepath = Path.Combine(Settings.TorrentFolder, string.Format("{0}.torrent", title));
using (var stream = _diskProvider.OpenWriteStream(filepath))
{
stream.Write(fileContent, 0, fileContent.Length);
}
_logger.Debug("Torrent Download succeeded, saved to: {0}", filepath);
return null;
}
public override string Name => "Torrent Blackhole";
protected override void Test(List<ValidationFailure> failures)
{
failures.AddIfNotNull(TestFolder(Settings.TorrentFolder, "TorrentFolder"));
}
}
}

View File

@@ -0,0 +1,46 @@
using System.ComponentModel;
using FluentValidation;
using Newtonsoft.Json;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Core.Download.Clients.Blackhole
{
public class TorrentBlackholeSettingsValidator : AbstractValidator<TorrentBlackholeSettings>
{
public TorrentBlackholeSettingsValidator()
{
//Todo: Validate that the path actually exists
RuleFor(c => c.TorrentFolder).IsValidPath();
RuleFor(c => c.MagnetFileExtension).NotEmpty();
}
}
public class TorrentBlackholeSettings : IProviderConfig
{
public TorrentBlackholeSettings()
{
MagnetFileExtension = ".magnet";
}
private static readonly TorrentBlackholeSettingsValidator Validator = new TorrentBlackholeSettingsValidator();
[FieldDefinition(0, Label = "Torrent Folder", Type = FieldType.Path, HelpText = "Folder in which Prowlarr will store the .torrent file")]
public string TorrentFolder { get; set; }
[DefaultValue(false)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
[FieldDefinition(1, Label = "Save Magnet Files", Type = FieldType.Checkbox, HelpText = "Save a .magnet file with the magnet link if no .torrent file is available (only useful if the download client supports .magnet files)")]
public bool SaveMagnetFiles { get; set; }
[FieldDefinition(2, Label = "Save Magnet Files", Type = FieldType.Textbox, HelpText = "Extension to use for magnet links, defaults to '.magnet'")]
public string MagnetFileExtension { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.IO;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download.Clients.Blackhole
{
public class UsenetBlackhole : UsenetClientBase<UsenetBlackholeSettings>
{
public UsenetBlackhole(IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
Logger logger)
: base(httpClient, configService, diskProvider, logger)
{
}
protected override string AddFromLink(ReleaseInfo release)
{
throw new NotSupportedException("Blackhole does not support redirected indexers.");
}
protected override string AddFromNzbFile(ReleaseInfo release, string filename, byte[] fileContent)
{
var title = release.Title;
title = title.CleanFileName();
var filepath = Path.Combine(Settings.NzbFolder, title + ".nzb");
using (var stream = _diskProvider.OpenWriteStream(filepath))
{
stream.Write(fileContent, 0, fileContent.Length);
}
_logger.Debug("NZB Download succeeded, saved to: {0}", filepath);
return null;
}
public override string Name => "Usenet Blackhole";
protected override void Test(List<ValidationFailure> failures)
{
failures.AddIfNotNull(TestFolder(Settings.NzbFolder, "NzbFolder"));
}
}
}

View File

@@ -0,0 +1,29 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Core.Download.Clients.Blackhole
{
public class UsenetBlackholeSettingsValidator : AbstractValidator<UsenetBlackholeSettings>
{
public UsenetBlackholeSettingsValidator()
{
RuleFor(c => c.NzbFolder).IsValidPath();
}
}
public class UsenetBlackholeSettings : IProviderConfig
{
private static readonly UsenetBlackholeSettingsValidator Validator = new UsenetBlackholeSettingsValidator();
[FieldDefinition(0, Label = "Nzb Folder", Type = FieldType.Path, HelpText = "Folder in which Prowlarr will store the .nzb file")]
public string NzbFolder { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -41,10 +41,10 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Sabnzbd")]
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to NZBGet")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the nzbget url, e.g. http://[host]:[port]/[urlBase]/jsonrpc")]
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the NZBGet url, e.g. http://[host]:[port]/[urlBase]/jsonrpc")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]

View File

@@ -6,7 +6,6 @@ using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser;

View File

@@ -1,5 +1,4 @@
using System;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using MonoTorrent;

View File

@@ -1,6 +1,5 @@
using System;
using NzbDrone.Common.Exceptions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Exceptions
{

View File

@@ -1,5 +1,4 @@
using System;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Exceptions
{

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dapper;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
@@ -16,6 +17,7 @@ namespace NzbDrone.Core.History
History MostRecentForIndexer(int indexerId);
List<History> Since(DateTime date, HistoryEventType? eventType);
void Cleanup(int days);
int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes);
}
public class HistoryRepository : BasicRepository<History>, IHistoryRepository
@@ -87,5 +89,21 @@ namespace NzbDrone.Core.History
return Query(builder).OrderBy(h => h.Date).ToList();
}
public int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes)
{
var builder = new SqlBuilder()
.SelectCount()
.Where<History>(x => x.IndexerId == indexerId)
.Where<History>(x => x.Date >= date)
.Where<History>(x => eventTypes.Contains(x.EventType));
var sql = builder.AddPageCountTemplate(typeof(History));
using (var conn = _database.OpenConnection())
{
return conn.ExecuteScalar<int>(sql.RawSql, sql.Parameters);
}
}
}
}

View File

@@ -24,6 +24,7 @@ namespace NzbDrone.Core.History
List<History> GetByIndexerId(int indexerId, HistoryEventType? eventType);
void UpdateMany(List<History> toUpdate);
List<History> Since(DateTime date, HistoryEventType? eventType);
int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes);
}
public class HistoryService : IHistoryService,
@@ -205,5 +206,10 @@ namespace NzbDrone.Core.History
{
_historyRepository.Purge(vacuum: true);
}
public int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes)
{
return _historyRepository.CountSince(indexerId, date, eventTypes);
}
}
}

View File

@@ -22,7 +22,7 @@ namespace NzbDrone.Core.IndexerSearch
@"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]",
RegexOptions.Compiled);
public List<ReleaseInfo> Releases;
public List<ReleaseInfo> Releases { get; set; }
private static string RemoveInvalidXMLChars(string text)
{
@@ -100,7 +100,8 @@ namespace NzbDrone.Core.IndexerSearch
GetNabElement("minimumratio", t.MinimumRatio, protocol),
GetNabElement("minimumseedtime", t.MinimumSeedTime, protocol),
GetNabElement("downloadvolumefactor", t.DownloadVolumeFactor, protocol),
GetNabElement("uploadvolumefactor", t.UploadVolumeFactor, protocol)))));
GetNabElement("uploadvolumefactor", t.UploadVolumeFactor, protocol),
GetNabElement("coverurl", r.PosterUrl, protocol)))));
return xdoc.Declaration + Environment.NewLine + xdoc;
}

View File

@@ -4,7 +4,6 @@ using System.Linq;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Download;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Events;
using NzbDrone.Core.IndexerSearch.Definitions;
@@ -20,19 +19,19 @@ namespace NzbDrone.Core.IndexerSearch
public class NzbSearchService : ISearchForNzb
{
private readonly IIndexerLimitService _indexerLimitService;
private readonly IEventAggregator _eventAggregator;
private readonly IIndexerFactory _indexerFactory;
private readonly IDownloadMappingService _downloadMappingService;
private readonly Logger _logger;
public NzbSearchService(IEventAggregator eventAggregator,
IIndexerFactory indexerFactory,
IDownloadMappingService downloadMappingService,
IIndexerLimitService indexerLimitService,
Logger logger)
{
_eventAggregator = eventAggregator;
_indexerFactory = indexerFactory;
_downloadMappingService = downloadMappingService;
_indexerLimitService = indexerLimitService;
_logger = logger;
}
@@ -163,16 +162,36 @@ namespace NzbDrone.Core.IndexerSearch
private async Task<IList<ReleaseInfo>> DispatchIndexer(Func<IIndexer, Task<IndexerPageableQueryResult>> searchAction, IIndexer indexer, SearchCriteriaBase criteriaBase)
{
if (_indexerLimitService.AtQueryLimit((IndexerDefinition)indexer.Definition))
{
return new List<ReleaseInfo>();
}
try
{
var indexerReports = await searchAction(indexer);
var releases = indexerReports.Releases;
//Filter results to only those in searched categories
if (criteriaBase.Categories.Length > 0)
{
var expandedQueryCats = ((IndexerDefinition)indexer.Definition).Capabilities.Categories.ExpandTorznabQueryCategories(criteriaBase.Categories);
releases = releases.Where(result => result.Categories?.Any() != true || expandedQueryCats.Intersect(result.Categories.Select(c => c.Id)).Any()).ToList();
if (releases.Count != indexerReports.Releases.Count)
{
_logger.Trace("{0} {1} Releases which didn't contain search categories [{2}] were filtered", indexerReports.Releases.Count - releases.Count, indexer.Name, string.Join(", ", expandedQueryCats));
}
}
foreach (var query in indexerReports.Queries)
{
_eventAggregator.PublishEvent(new IndexerQueryEvent(indexer.Definition.Id, criteriaBase, query.ElapsedTime, query.StatusCode == 200, query.Releases.Count()));
}
return indexerReports.Releases;
return releases;
}
catch (Exception e)
{

View File

@@ -25,7 +25,17 @@ namespace NzbDrone.Core.IndexerVersions
public class IndexerDefinitionUpdateService : IIndexerDefinitionUpdateService, IExecute<IndexerDefinitionUpdateCommand>
{
private const int DEFINITION_VERSION = 1;
private readonly List<string> _defintionBlacklist = new List<string>() { "aither", "animeworld", "blutopia", "beyond-hd", "beyond-hd-oneurl", "hdbits", "shareisland" };
private readonly List<string> _defintionBlacklist = new List<string>()
{
"aither",
"animeworld",
"blutopia",
"beyond-hd",
"beyond-hd-oneurl",
"danishbytes",
"hdbits",
"shareisland"
};
private readonly IHttpClient _httpClient;
private readonly IAppFolderInfo _appFolderInfo;

View File

@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class Aither : Unit3dBase
{
public override string Name => "Aither";
public override string BaseUrl => "https://aither.cc/";
public override string[] IndexerUrls => new string[] { "https://aither.cc/" };
public override string Description => "Aither is a Private Torrent Tracker for HD MOVIES / TV";
public override string Language => "en-us";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;

View File

@@ -9,7 +9,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class AlphaRatio : Gazelle.Gazelle
{
public override string Name => "AlphaRatio";
public override string BaseUrl => "https://alpharatio.cc/";
public override string[] IndexerUrls => new string[] { "https://alpharatio.cc/" };
public override string Description => "AlphaRatio(AR) is a Private Torrent Tracker for 0DAY / GENERAL";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
@@ -25,8 +25,7 @@ namespace NzbDrone.Core.Indexers.Definitions
Settings = Settings,
HttpClient = _httpClient,
Logger = _logger,
Capabilities = Capabilities,
BaseUrl = BaseUrl
Capabilities = Capabilities
};
}
@@ -40,7 +39,7 @@ namespace NzbDrone.Core.Indexers.Definitions
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
MovieSearchParam.Q
}
};

View File

@@ -0,0 +1,550 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AngleSharp.Html.Parser;
using FluentValidation;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
public class Anidub : TorrentIndexerBase<AnidubSettings>
{
public override string Name => "Anidub";
public override string[] IndexerUrls => new string[] { "https://tr.anidub.com/" };
public override string Description => "Anidub is russian anime voiceover group and eponymous anime tracker.";
public override string Language => "ru-ru";
public override Encoding Encoding => Encoding.UTF8;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.SemiPublic;
public override IndexerCapabilities Capabilities => SetCapabilities();
public Anidub(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AnidubRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new AnidubParser(Settings, Capabilities.Categories) { HttpClient = _httpClient, Logger = _logger };
}
protected override async Task DoLogin()
{
UpdateCookies(null, null);
var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl + "index.php")
{
LogResponseContent = true,
AllowAutoRedirect = true
};
var mainPage = await _httpClient.ExecuteAsync(new HttpRequest(Settings.BaseUrl));
requestBuilder.Method = Common.Http.HttpMethod.POST;
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
requestBuilder.SetCookies(mainPage.GetCookies());
var authLoginRequest = requestBuilder
.AddFormParameter("login_name", Settings.Username)
.AddFormParameter("login_password", Settings.Password)
.AddFormParameter("login", "submit")
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
.Build();
var response = await ExecuteAuth(authLoginRequest);
if (response.Content != null && !CheckIfLoginNeeded(response))
{
UpdateCookies(response.GetCookies(), DateTime.Now + TimeSpan.FromDays(30));
_logger.Debug("Anidub authentication succeeded");
}
else
{
const string ErrorSelector = "#content .berror .berror_c";
var parser = new HtmlParser();
var document = await parser.ParseDocumentAsync(response.Content);
var errorMessage = document.QuerySelector(ErrorSelector).TextContent.Trim();
throw new IndexerAuthException("Anidub authentication failed. Error: " + errorMessage);
}
}
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{
if (httpResponse.Content.Contains("index.php?action=logout"))
{
return false;
}
return true;
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "Аниме TV");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Movies, "Аниме Фильмы");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVAnime, "Аниме OVA");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVAnime, "Аниме OVA |- Аниме ONA");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TV, "Дорамы / Японские Сериалы и Фильмы");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.TV, "Дорамы / Корейские Сериалы и Фильмы");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.TV, "Дорамы / Китайские Сериалы и Фильмы");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.TV, "Дорамы");
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.TVAnime, "Аниме TV / Аниме Ongoing");
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.TVAnime, "Аниме TV / Многосерийный сёнэн");
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.Other, "Аниме Ongoing Анонсы");
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.XXX, "18+");
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.TVAnime, "Аниме TV / Законченные сериалы");
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.BooksComics, "Манга");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.Audio, "OST");
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.Audio, "Подкасты");
return caps;
}
}
public class AnidubRequestGenerator : IIndexerRequestGenerator
{
public AnidubSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public AnidubRequestGenerator()
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{
var requestUrl = string.Empty;
var isSearch = !string.IsNullOrWhiteSpace(term);
if (isSearch)
{
requestUrl = string.Format("{0}/index.php?do=search", Settings.BaseUrl.TrimEnd('/'));
}
else
{
requestUrl = Settings.BaseUrl;
}
var request = new IndexerRequest(requestUrl, HttpAccept.Html);
if (isSearch)
{
request.HttpRequest.Method = NzbDrone.Common.Http.HttpMethod.POST;
var postData = new NameValueCollection
{
{ "do", "search" },
{ "subaction", "search" },
{ "search_start", "1" },
{ "full_search", "1" },
{ "result_from", "1" },
// Remove season and episode info from search term cause it breaks search
{ "story", Regex.Replace(term, @"(?:[SsEe]?\d{1,4}){1,2}$", "").TrimEnd() },
{ "titleonly", "3" },
{ "searchuser", "" },
{ "replyless", "0" },
{ "replylimit", "0" },
{ "searchdate", "0" },
{ "beforeafter", "after" },
{ "sortby", "" },
{ "resorder", "desc" },
{ "showposts", "1" },
{ "catlist[]", "0" }
};
var headers = new NameValueCollection
{
{ "Content-Type", "application/x-www-form-urlencoded" }
};
request.HttpRequest.SetContent(postData.GetQueryString());
request.HttpRequest.Headers.Add(headers);
}
yield return request;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class AnidubParser : IParseIndexerResponse
{
private readonly AnidubSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public IHttpClient HttpClient { get; set; }
public Logger Logger { get; set; }
private static Dictionary<string, string> CategoriesMap => new Dictionary<string, string>
{
{ "/anime_tv/full", "14" },
{ "/anime_tv/anime_ongoing", "10" },
{ "/anime_tv/shonen", "11" },
{ "/anime_tv", "2" },
{ "/xxx", "13" },
{ "/manga", "15" },
{ "/ost", "16" },
{ "/podcast", "17" },
{ "/anime_movie", "3" },
{ "/anime_ova/anime_ona", "5" },
{ "/anime_ova", "4" },
{ "/dorama/japan_dorama", "6" },
{ "/dorama/korea_dorama", "7" },
{ "/dorama/china_dorama", "8" },
{ "/dorama", "9" },
{ "/anons_ongoing", "12" }
};
public AnidubParser(AnidubSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
private static string GetTitle(AngleSharp.Html.Dom.IHtmlDocument content, AngleSharp.Dom.IElement tabNode)
{
var domTitle = content.QuerySelector("#news-title");
var baseTitle = domTitle.TextContent.Trim();
var quality = GetQuality(tabNode.ParentElement);
if (!string.IsNullOrWhiteSpace(quality))
{
return $"{baseTitle} [{quality}]";
}
return baseTitle;
}
private static string GetQuality(AngleSharp.Dom.IElement releaseNode)
{
// For some releases there's no block with quality
if (string.IsNullOrWhiteSpace(releaseNode.Id))
{
return null;
}
var quality = releaseNode.Id.Trim();
switch (quality.ToLowerInvariant())
{
case "tv720":
return "HDTV 720p";
case "tv1080":
return "HDTV 1080p";
case "bd720":
return "BDRip 720p";
case "bd1080":
return "BDRip 1080p";
case "hwp":
return "SDTV";
default:
return quality.ToUpperInvariant();
}
}
private static int GetReleaseLeechers(AngleSharp.Dom.IElement tabNode)
{
const string LeechersSelector = ".list.down > .li_swing_m";
var leechersStr = tabNode.QuerySelector(LeechersSelector).TextContent;
int.TryParse(leechersStr, out var leechers);
return leechers;
}
private static int GetReleaseSeeders(AngleSharp.Dom.IElement tabNode)
{
const string SeedersSelector = ".list.down > .li_distribute_m";
var seedersStr = tabNode.QuerySelector(SeedersSelector).TextContent;
int.TryParse(seedersStr, out var seeders);
return seeders;
}
private static int GetReleaseGrabs(AngleSharp.Dom.IElement tabNode)
{
const string GrabsSelector = ".list.down > .li_download_m";
var grabsStr = tabNode.QuerySelector(GrabsSelector).TextContent;
int.TryParse(grabsStr, out var grabs);
return grabs;
}
private static string GetDateFromDocument(AngleSharp.Html.Dom.IHtmlDocument content)
{
const string DateSelector = ".story_inf > li:nth-child(2)";
var domDate = content.QuerySelector(DateSelector).LastChild;
if (domDate?.NodeName != "#text")
{
return string.Empty;
}
return domDate.NodeValue.Trim();
}
private DateTime GetDateFromShowPage(AngleSharp.Html.Dom.IHtmlDocument content)
{
const string dateFormat = "d-MM-yyyy";
const string dateTimeFormat = dateFormat + ", HH:mm";
// Would be better to use AssumeLocal and provide "ru-RU" culture,
// but doesn't work cross-platform
const DateTimeStyles style = DateTimeStyles.AssumeUniversal;
var culture = CultureInfo.InvariantCulture;
var dateText = GetDateFromDocument(content);
//Correct way but will not always work on cross-platform
//var localTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time");
//var nowLocal = TimeZoneInfo.ConvertTime(DateTime.UtcNow, localTimeZone);
// Russian Standard Time is +03:00, no DST
const int russianStandardTimeDiff = 3;
var nowLocal = DateTime.UtcNow.AddHours(russianStandardTimeDiff);
dateText = dateText
.Replace("Вчера", nowLocal.AddDays(-1).ToString(dateFormat))
.Replace("Сегодня", nowLocal.ToString(dateFormat));
if (DateTime.TryParseExact(dateText, dateTimeFormat, culture, style, out var date))
{
var utcDate = date.ToUniversalTime();
return utcDate.AddHours(-russianStandardTimeDiff);
}
Logger.Warn($"[AniDub] Date time couldn't be parsed on. Date text: {dateText}");
return DateTime.UtcNow;
}
private static long GetReleaseSize(AngleSharp.Dom.IElement tabNode)
{
const string SizeSelector = ".list.down > .red";
var sizeStr = tabNode.QuerySelector(SizeSelector).TextContent;
return ReleaseInfo.GetBytes(sizeStr);
}
private string GetReleaseLink(AngleSharp.Dom.IElement tabNode)
{
return $"{_settings.BaseUrl}engine/download.php?id={GetTorrentId(tabNode)}";
}
private static string GetTorrentId(AngleSharp.Dom.IElement tabNode)
{
var nodeId = tabNode.Id;
// Format is "torrent_{id}_info"
return nodeId
.Replace("torrent_", string.Empty)
.Replace("_info", string.Empty);
}
private ICollection<IndexerCategory> ParseCategories(string uriPath)
{
var categoriesMap = CategoriesMap;
return categoriesMap
.Where(categoryMap => uriPath.StartsWith(categoryMap.Key))
.Select(categoryMap => _categories.MapTrackerCatToNewznab(categoryMap.Value))
.FirstOrDefault();
}
private IList<TorrentInfo> ParseRelease(IndexerResponse indexerResponse)
{
var torrentInfos = new List<TorrentInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
foreach (var t in dom.QuerySelectorAll("#tabs .torrent_c > div"))
{
var release = new TorrentInfo
{
Title = GetTitle(dom, t),
InfoUrl = indexerResponse.Request.Url.ToString(),
DownloadVolumeFactor = 0,
UploadVolumeFactor = 1,
Guid = indexerResponse.Request.Url.ToString() + t.Id,
Seeders = GetReleaseSeeders(t),
Peers = GetReleaseSeeders(t) + GetReleaseLeechers(t),
Grabs = GetReleaseGrabs(t),
Categories = ParseCategories(indexerResponse.Request.Url.Path),
PublishDate = GetDateFromShowPage(dom),
DownloadUrl = GetReleaseLink(t),
Size = GetReleaseSize(t),
Resolution = GetQuality(t)
};
torrentInfos.Add(release);
}
return torrentInfos;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
var domQuery = string.Empty;
if (indexerResponse.Request.Url.Query.Contains("do=search"))
{
domQuery = ".searchitem > h3 > a";
}
else
{
domQuery = "#dle-content > .story > .story_h > .lcol > h2 > a";
}
var links = dom.QuerySelectorAll(domQuery);
foreach (var link in links)
{
var url = link.GetAttribute("href");
var releaseRequest = new IndexerRequest(url, HttpAccept.Html);
var releaseResponse = new IndexerResponse(releaseRequest, HttpClient.Execute(releaseRequest.HttpRequest));
// Throw common http errors here before we try to parse
if (releaseResponse.HttpResponse.HasHttpError)
{
if ((int)releaseResponse.HttpResponse.StatusCode == 429)
{
throw new TooManyRequestsException(releaseRequest.HttpRequest, releaseResponse.HttpResponse);
}
else
{
throw new IndexerException(releaseResponse, "Http error code: " + releaseResponse.HttpResponse.StatusCode);
}
}
torrentInfos.AddRange(ParseRelease(releaseResponse));
}
return torrentInfos.ToArray();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class AnidubSettingsValidator : AbstractValidator<AnidubSettings>
{
public AnidubSettingsValidator()
{
RuleFor(c => c.Username).NotEmpty();
RuleFor(c => c.Password).NotEmpty();
}
}
public class AnidubSettings : IIndexerSettings
{
private static readonly AnidubSettingsValidator Validator = new AnidubSettingsValidator();
public AnidubSettings()
{
Username = "";
Password = "";
}
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
public string Password { get; set; }
[FieldDefinition(4)]
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -0,0 +1,294 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Text.RegularExpressions;
using FluentValidation;
using Newtonsoft.Json;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
public class Anilibria : TorrentIndexerBase<AnilibriaSettings>
{
public override string Name => "Anilibria";
public override string[] IndexerUrls => new string[] { "https://anilibria.tv/" };
public override string Description => "Anilibria is russian anime voiceover group and eponymous anime tracker.";
public override string Language => "ru-ru";
public override Encoding Encoding => Encoding.UTF8;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
public override IndexerCapabilities Capabilities => SetCapabilities();
public Anilibria(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AnilibriaRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new AnilibriaParser(Settings, Capabilities.Categories);
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "ТВ");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVAnime, "ONA");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVAnime, "OVA");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Movies, "Фильм");
return caps;
}
}
public class AnilibriaRequestGenerator : IIndexerRequestGenerator
{
public AnilibriaSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public AnilibriaRequestGenerator()
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{
var apiUrl = Regex.Replace(Settings.BaseUrl, @"(https?:\/\/)(.*)", "$1api.$2v2");
var queryCollection = new NameValueCollection
{
{ "limit", "100" },
{ "filter", "names,code,torrents.list,season.year,type.string" }
};
if (string.IsNullOrWhiteSpace(term))
{
apiUrl += "/getUpdates?" + queryCollection.GetQueryString();
}
else
{
apiUrl += "/searchTitles?" + queryCollection.GetQueryString() + "&search=" + Uri.EscapeDataString(term);
}
var request = new IndexerRequest(apiUrl, HttpAccept.Json);
yield return request;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
// Anilibria doesn't support music, but this function required by interface
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
// Anilibria doesn't support books, but this function required by interface
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class AnilibriaParser : IParseIndexerResponse
{
private readonly AnilibriaSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public AnilibriaParser(AnilibriaSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
private string composeTitle(AnilibriaTitle tl, AnilibriaTorrent tr)
{
var title = tl.Names.Ru;
title += " / " + tl.Names.En;
if (tl.Names.Alternative is string)
{
title += " / " + tl.Names.Alternative;
}
title += " " + tl.Season.Year;
title += " [" + tr.Quality.String + "]";
if (!string.IsNullOrWhiteSpace(tr.Series.String))
{
title += " - E" + tr.Series.String;
}
return title;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
var queryResponseItems = JsonConvert.DeserializeObject<List<AnilibriaTitle>>(indexerResponse.Content);
foreach (var tl in queryResponseItems)
{
foreach (var tr in tl.Torrents.List)
{
var torrentInfo = new TorrentInfo
{
Title = composeTitle(tl, tr),
InfoUrl = string.Format("{0}/release/{1}.html", _settings.BaseUrl.TrimEnd('/'), tl.Code),
DownloadVolumeFactor = 0,
UploadVolumeFactor = 1,
Seeders = tr.Seeders,
Peers = tr.Leechers + tr.Seeders,
Grabs = tr.Downloads,
Categories = _categories.MapTrackerCatDescToNewznab(tl.Type.String),
// API provides timestamp in UTC+3 timezone, so we need to substract 3 hours
PublishDate = DateTimeUtil.UnixTimestampToDateTime(tr.UploadedTimestamp).AddHours(-3),
Guid = _settings.BaseUrl + tr.Url,
DownloadUrl = _settings.BaseUrl + tr.Url,
Size = tr.TotalSize,
Resolution = tr.Quality.Resolution,
Codec = tr.Quality.Encoder
};
torrentInfos.Add(torrentInfo);
}
}
return torrentInfos.ToArray();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class AnilibriaSettingsValidator : AbstractValidator<AnilibriaSettings>
{
public AnilibriaSettingsValidator()
{
}
}
public class AnilibriaSettings : IIndexerSettings
{
private static readonly AnilibriaSettingsValidator Validator = new AnilibriaSettingsValidator();
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
public string BaseUrl { get; set; }
[FieldDefinition(2)]
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
public class AnilibriaTitle
{
public AnilibriaNames Names { get; set; }
public string Code { get; set; }
public AnilibriaTorrents Torrents { get; set; }
public AnilibriaSeason Season { get; set; }
public AnilibriaTitleType Type { get; set; }
}
public class AnilibriaTitleType
{
public string String { get; set; }
}
public class AnilibriaNames
{
public string Ru { get; set; }
public string En { get; set; }
public object Alternative { get; set; }
}
public class AnilibriaSeason
{
public long Year { get; set; }
}
public class AnilibriaTorrents
{
public AnilibriaTorrent[] List { get; set; }
}
public class AnilibriaTorrent
{
public AnilibriaSeries Series { get; set; }
public AnilibriaQuality Quality { get; set; }
public int Leechers { get; set; }
public int Seeders { get; set; }
public int Downloads { get; set; }
[JsonProperty("total_size")]
public long TotalSize { get; set; }
public string Url { get; set; }
[JsonProperty("uploaded_timestamp")]
public long UploadedTimestamp { get; set; }
}
public class AnilibriaQuality
{
public string String { get; set; }
public string Type { get; set; }
public string Resolution { get; set; }
public string Encoder { get; set; }
}
public class AnilibriaSeries
{
public long First { get; set; }
public long Last { get; set; }
public string String { get; set; }
}
}

View File

@@ -8,6 +8,7 @@ using System.Text;
using System.Text.RegularExpressions;
using FluentValidation;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common.Http;
@@ -18,7 +19,6 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
@@ -26,8 +26,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public class AnimeBytes : TorrentIndexerBase<AnimeBytesSettings>
{
public override string Name => "AnimeBytes";
public override string BaseUrl => "https://animebytes.tv/";
public override string Description => "Powered by Tentacles";
public override string[] IndexerUrls => new string[] { "https://animebytes.tv/" };
public override string Description => "AnimeBytes (AB) is the largest private torrent tracker that specialises in anime and anime-related content.";
public override string Language => "en-us";
public override Encoding Encoding => Encoding.UTF8;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
@@ -41,12 +41,12 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AnimeBytesRequestGenerator() { Settings = Settings, Capabilities = Capabilities, BaseUrl = BaseUrl };
return new AnimeBytesRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new AnimeBytesParser(Settings, Capabilities.Categories, BaseUrl);
return new AnimeBytesParser(Settings, Capabilities.Categories);
}
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
@@ -101,7 +101,6 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public AnimeBytesSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public string BaseUrl { get; set; }
public AnimeBytesRequestGenerator()
{
@@ -109,14 +108,14 @@ namespace NzbDrone.Core.Indexers.Definitions
private IEnumerable<IndexerRequest> GetPagedRequests(string searchType, string term, int[] categories)
{
var searchUrl = string.Format("{0}/scrape.php", BaseUrl.TrimEnd('/'));
var searchUrl = string.Format("{0}/scrape.php", Settings.BaseUrl.TrimEnd('/'));
var queryCollection = new NameValueCollection
{
{ "username", Settings.Username },
{ "torrent_pass", Settings.Passkey },
{ "type", searchType },
{ "searchstr", term }
{ "searchstr", StripEpisodeNumber(term) }
};
var queryCats = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
@@ -183,19 +182,26 @@ namespace NzbDrone.Core.Indexers.Definitions
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
private string StripEpisodeNumber(string term)
{
// Tracer does not support searching with episode number so strip it if we have one
term = Regex.Replace(term, @"\W(\dx)?\d?\d$", string.Empty);
term = Regex.Replace(term, @"\W(S\d\d?E)?\d?\d$", string.Empty);
term = Regex.Replace(term, @"\W\d+$", string.Empty);
return term;
}
}
public class AnimeBytesParser : IParseIndexerResponse
{
private readonly AnimeBytesSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
private readonly string _baseUrl;
public AnimeBytesParser(AnimeBytesSettings settings, IndexerCapabilitiesCategories categories, string baseUrl)
public AnimeBytesParser(AnimeBytesSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
_baseUrl = baseUrl;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@@ -212,29 +218,17 @@ namespace NzbDrone.Core.Indexers.Definitions
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
}
//TODO: Create API Resource Type
var json = JsonConvert.DeserializeObject<dynamic>(indexerResponse.Content);
var response = JsonConvert.DeserializeObject<AnimeBytesResponse>(indexerResponse.Content);
if (json["error"] != null)
if (response.Matches > 0)
{
throw new Exception(json["error"].ToString());
}
var matches = (long)json["Matches"];
if (matches > 0)
{
var groups = (JArray)json.Groups;
foreach (var group in groups)
foreach (var group in response.Groups)
{
var synonyms = new List<string>();
var posterStr = (string)group["Image"];
var poster = string.IsNullOrWhiteSpace(posterStr) ? null : new Uri(posterStr);
var year = (int)group["Year"];
var groupName = (string)group["GroupName"];
var seriesName = (string)group["SeriesName"];
var mainTitle = WebUtility.HtmlDecode((string)group["FullName"]);
var year = group.Year;
var groupName = group.GroupName;
var seriesName = group.SeriesName;
var mainTitle = WebUtility.HtmlDecode(group.FullName);
if (seriesName != null)
{
mainTitle = seriesName;
@@ -242,105 +236,111 @@ namespace NzbDrone.Core.Indexers.Definitions
synonyms.Add(mainTitle);
// TODO: Do we need all these options?
//if (group["Synonymns"].HasValues)
//{
// if (group["Synonymns"] is JArray)
// {
// var allSyonyms = group["Synonymns"].ToObject<List<string>>();
// if (AddJapaneseTitle && allSyonyms.Count >= 1)
// synonyms.Add(allSyonyms[0]);
// if (AddRomajiTitle && allSyonyms.Count >= 2)
// synonyms.Add(allSyonyms[1]);
// if (AddAlternativeTitles && allSyonyms.Count >= 3)
// synonyms.AddRange(allSyonyms[2].Split(',').Select(t => t.Trim()));
// }
// else
// {
// var allSynonyms = group["Synonymns"].ToObject<Dictionary<int, string>>();
// if (AddJapaneseTitle && allSynonyms.ContainsKey(0))
// synonyms.Add(allSynonyms[0]);
// if (AddRomajiTitle && allSynonyms.ContainsKey(1))
// synonyms.Add(allSynonyms[1]);
// if (AddAlternativeTitles && allSynonyms.ContainsKey(2))
// {
// synonyms.AddRange(allSynonyms[2].Split(',').Select(t => t.Trim()));
// }
// }
//}
List<IndexerCategory> category = null;
var categoryName = (string)group["CategoryName"];
var description = (string)group["Description"];
foreach (var torrent in group["Torrents"])
if (group.Synonymns.StringArray != null)
{
var releaseInfo = "S01";
string episode = null;
synonyms.AddRange(group.Synonymns.StringArray);
}
else
{
synonyms.AddRange(group.Synonymns.StringMap.Values);
}
List<IndexerCategory> category = null;
var categoryName = group.CategoryName;
var description = group.Description;
foreach (var torrent in group.Torrents)
{
var releaseInfo = _settings.EnableSonarrCompatibility ? "S01" : "";
int? episode = null;
int? season = null;
var editionTitle = (string)torrent["EditionData"]["EditionTitle"];
var editionTitle = torrent.EditionData.EditionTitle;
if (!string.IsNullOrWhiteSpace(editionTitle))
{
releaseInfo = WebUtility.HtmlDecode(editionTitle);
if (_settings.EnableSonarrCompatibility)
{
var simpleSeasonRegEx = new Regex(@"Season (\d+)", RegexOptions.Compiled);
var simpleSeasonRegExMatch = simpleSeasonRegEx.Match(releaseInfo);
if (simpleSeasonRegExMatch.Success)
{
season = ParseUtil.CoerceInt(simpleSeasonRegExMatch.Groups[1].Value);
}
}
var episodeRegEx = new Regex(@"Episode (\d+)", RegexOptions.Compiled);
var episodeRegExMatch = episodeRegEx.Match(releaseInfo);
if (episodeRegExMatch.Success)
{
episode = ParseUtil.CoerceInt(episodeRegExMatch.Groups[1].Value);
}
}
var seasonRegEx = new Regex(@"Season (\d+)", RegexOptions.Compiled);
var seasonRegExMatch = seasonRegEx.Match(releaseInfo);
if (seasonRegExMatch.Success)
if (_settings.EnableSonarrCompatibility)
{
season = ParseUtil.CoerceInt(seasonRegExMatch.Groups[1].Value);
var advancedSeasonRegEx = new Regex(@"(\d+)(st|nd|rd|th) Season", RegexOptions.Compiled | RegexOptions.IgnoreCase);
var advancedSeasonRegExMatch = advancedSeasonRegEx.Match(mainTitle);
if (advancedSeasonRegExMatch.Success)
{
season = ParseUtil.CoerceInt(advancedSeasonRegExMatch.Groups[1].Value);
}
var seasonCharactersRegEx = new Regex(@"(I{2,})$", RegexOptions.Compiled);
var seasonCharactersRegExMatch = seasonCharactersRegEx.Match(mainTitle);
if (seasonCharactersRegExMatch.Success)
{
season = seasonCharactersRegExMatch.Groups[1].Value.Length;
}
var seasonNumberRegEx = new Regex(@"([2-9])$", RegexOptions.Compiled);
var seasonNumberRegExMatch = seasonNumberRegEx.Match(mainTitle);
if (seasonNumberRegExMatch.Success)
{
season = ParseUtil.CoerceInt(seasonNumberRegExMatch.Groups[1].Value);
}
}
var episodeRegEx = new Regex(@"Episode (\d+)", RegexOptions.Compiled);
var episodeRegExMatch = episodeRegEx.Match(releaseInfo);
if (episodeRegExMatch.Success)
if (episode != null)
{
episode = episodeRegExMatch.Groups[1].Value;
releaseInfo = episode is > 0 and < 10
? "0" + episode
: episode.ToString();
}
else
{
if (season != null && _settings.EnableSonarrCompatibility)
{
releaseInfo = $"S{season}";
}
}
releaseInfo = releaseInfo.Replace("Episode ", "");
releaseInfo = releaseInfo.Replace("Season ", "S");
releaseInfo = releaseInfo.Trim();
//if (PadEpisode && int.TryParse(releaseInfo, out _) && releaseInfo.Length == 1)
//{
// releaseInfo = "0" + releaseInfo;
//}
//if (FilterSeasonEpisode)
//{
// if (query.Season != 0 && season != null && season != query.Season) // skip if season doesn't match
// continue;
// if (query.Episode != null && episode != null && episode != query.Episode) // skip if episode doesn't match
// continue;
//}
var torrentId = (long)torrent["ID"];
var property = ((string)torrent["Property"]).Replace(" | Freeleech", "");
var link = (string)torrent["Link"];
var linkUri = new Uri(link);
var uploadTimeString = (string)torrent["UploadTime"];
var uploadTime = DateTime.ParseExact(uploadTimeString, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
var publishDate = DateTime.SpecifyKind(uploadTime, DateTimeKind.Utc).ToLocalTime();
var details = new Uri(_baseUrl + "torrent/" + torrentId + "/group");
var size = (long)torrent["Size"];
var snatched = (int)torrent["Snatched"];
var seeders = (int)torrent["Seeders"];
var leechers = (int)torrent["Leechers"];
var fileCount = (int)torrent["FileCount"];
var torrentId = torrent.Id;
var property = torrent.Property.Replace(" | Freeleech", string.Empty);
var link = torrent.Link;
var uploadTime = torrent.UploadTime;
var publishDate = DateTime.SpecifyKind(uploadTime.DateTime, DateTimeKind.Utc).ToLocalTime();
var details = new Uri(_settings.BaseUrl + "torrent/" + torrentId + "/group");
var size = torrent.Size;
var snatched = torrent.Snatched;
var seeders = torrent.Seeders;
var leechers = torrent.Leechers;
var fileCount = torrent.FileCount;
var peers = seeders + leechers;
var rawDownMultiplier = (int?)torrent["RawDownMultiplier"] ?? 0;
var rawUpMultiplier = (int?)torrent["RawUpMultiplier"] ?? 0;
var rawDownMultiplier = torrent.RawDownMultiplier;
var rawUpMultiplier = torrent.RawUpMultiplier;
// Ignore these categories as they'll cause hell with the matcher
// TV Special, ONA, DVD Special, BD Special
if (groupName == "TV Series" || groupName == "OVA")
{
category = new List<IndexerCategory> { NewznabStandardCategory.TVAnime };
}
// Ignore these categories as they'll cause hell with the matcher
// TV Special, OVA, ONA, DVD Special, BD Special
if (groupName == "Movie" || groupName == "Live Action Movie")
{
category = new List<IndexerCategory> { NewznabStandardCategory.Movies };
@@ -426,7 +426,7 @@ namespace NzbDrone.Core.Indexers.Definitions
//{
// continue;
//}
var infoString = releaseTags.Aggregate("", (prev, cur) => prev + "[" + cur + "]");
var infoString = releaseTags.Aggregate(string.Empty, (prev, cur) => prev + "[" + cur + "]");
var minimumSeedTime = 259200;
// Additional 5 hours per GB
@@ -447,7 +447,7 @@ namespace NzbDrone.Core.Indexers.Definitions
Title = releaseTitle,
InfoUrl = details.AbsoluteUri,
Guid = guid.AbsoluteUri,
DownloadUrl = linkUri.AbsoluteUri,
DownloadUrl = link.AbsoluteUri,
PublishDate = publishDate,
Categories = category,
Description = description,
@@ -457,7 +457,7 @@ namespace NzbDrone.Core.Indexers.Definitions
Grabs = snatched,
Files = fileCount,
DownloadVolumeFactor = rawDownMultiplier,
UploadVolumeFactor = rawUpMultiplier
UploadVolumeFactor = rawUpMultiplier,
};
torrentInfos.Add(release);
@@ -484,7 +484,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
public class AnimeBytesSettings : IProviderConfig
public class AnimeBytesSettings : IIndexerSettings
{
private static readonly AnimeBytesSettingsValidator Validator = new AnimeBytesSettingsValidator();
@@ -492,17 +492,321 @@ namespace NzbDrone.Core.Indexers.Definitions
{
Passkey = "";
Username = "";
EnableSonarrCompatibility = true;
}
[FieldDefinition(1, Label = "Passkey", Privacy = PrivacyLevel.Password, Type = FieldType.Password, HelpText = "Site Passkey")]
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "Passkey", HelpText = "Site Passkey", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
public string Passkey { get; set; }
[FieldDefinition(1, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
[FieldDefinition(3, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(4, Label = "Enable Sonarr Compatibility", Type = FieldType.Checkbox, HelpText = "Makes Prowlarr try to add Season information into Release names, without this Sonarr can't match any Seasons, but it has a lot of false positives as well")]
public bool EnableSonarrCompatibility { get; set; }
[FieldDefinition(5)]
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
public class AnimeBytesResponse
{
[JsonProperty("Matches")]
public long Matches { get; set; }
[JsonProperty("Limit")]
public long Limit { get; set; }
[JsonProperty("Results")]
[JsonConverter(typeof(ParseStringConverter))]
public long Results { get; set; }
[JsonProperty("Groups")]
public Group[] Groups { get; set; }
}
public class Group
{
[JsonProperty("ID")]
public long Id { get; set; }
[JsonProperty("CategoryName")]
public string CategoryName { get; set; }
[JsonProperty("FullName")]
public string FullName { get; set; }
[JsonProperty("GroupName")]
public string GroupName { get; set; }
[JsonProperty("SeriesID")]
[JsonConverter(typeof(ParseStringConverter))]
public long SeriesId { get; set; }
[JsonProperty("SeriesName")]
public string SeriesName { get; set; }
[JsonProperty("Artists")]
public object Artists { get; set; }
[JsonProperty("Year")]
[JsonConverter(typeof(ParseStringConverter))]
public long Year { get; set; }
[JsonProperty("Image")]
public Uri Image { get; set; }
[JsonProperty("Synonymns")]
[JsonConverter(typeof(SynonymnsConverter))]
public Synonymns Synonymns { get; set; }
[JsonProperty("Snatched")]
public long Snatched { get; set; }
[JsonProperty("Comments")]
public long Comments { get; set; }
[JsonProperty("Links")]
[JsonConverter(typeof(LinksUnionConverter))]
public LinksUnion Links { get; set; }
[JsonProperty("Votes")]
public long Votes { get; set; }
[JsonProperty("AvgVote")]
public double AvgVote { get; set; }
[JsonProperty("Associations")]
public object Associations { get; set; }
[JsonProperty("Description")]
public string Description { get; set; }
[JsonProperty("DescriptionHTML")]
public string DescriptionHtml { get; set; }
[JsonProperty("EpCount")]
public long EpCount { get; set; }
[JsonProperty("StudioList")]
public string StudioList { get; set; }
[JsonProperty("PastWeek")]
public long PastWeek { get; set; }
[JsonProperty("Incomplete")]
public bool Incomplete { get; set; }
[JsonProperty("Ongoing")]
public bool Ongoing { get; set; }
[JsonProperty("Tags")]
public List<string> Tags { get; set; }
[JsonProperty("Torrents")]
public List<Torrent> Torrents { get; set; }
}
public class LinksClass
{
[JsonProperty("ANN", NullValueHandling = NullValueHandling.Ignore)]
public Uri Ann { get; set; }
[JsonProperty("Manga-Updates", NullValueHandling = NullValueHandling.Ignore)]
public Uri MangaUpdates { get; set; }
[JsonProperty("Wikipedia", NullValueHandling = NullValueHandling.Ignore)]
public Uri Wikipedia { get; set; }
[JsonProperty("MAL", NullValueHandling = NullValueHandling.Ignore)]
public Uri Mal { get; set; }
[JsonProperty("AniDB", NullValueHandling = NullValueHandling.Ignore)]
public Uri AniDb { get; set; }
}
public class Torrent
{
[JsonProperty("ID")]
public long Id { get; set; }
[JsonProperty("EditionData")]
public EditionData EditionData { get; set; }
[JsonProperty("RawDownMultiplier")]
public double? RawDownMultiplier { get; set; }
[JsonProperty("RawUpMultiplier")]
public double? RawUpMultiplier { get; set; }
[JsonProperty("Link")]
public Uri Link { get; set; }
[JsonProperty("Property")]
public string Property { get; set; }
[JsonProperty("Snatched")]
public int Snatched { get; set; }
[JsonProperty("Seeders")]
public int Seeders { get; set; }
[JsonProperty("Leechers")]
public int Leechers { get; set; }
[JsonProperty("Size")]
public long Size { get; set; }
[JsonProperty("FileCount")]
public int FileCount { get; set; }
[JsonProperty("UploadTime")]
public DateTimeOffset UploadTime { get; set; }
}
public class EditionData
{
[JsonProperty("EditionTitle")]
public string EditionTitle { get; set; }
}
public struct LinksUnion
{
public List<object> AnythingArray;
public LinksClass LinksClass;
public static implicit operator LinksUnion(List<object> anythingArray) => new LinksUnion { AnythingArray = anythingArray };
public static implicit operator LinksUnion(LinksClass linksClass) => new LinksUnion { LinksClass = linksClass };
}
public struct Synonymns
{
public List<string> StringArray;
public Dictionary<string, string> StringMap;
public static implicit operator Synonymns(List<string> stringArray) => new Synonymns { StringArray = stringArray };
public static implicit operator Synonymns(Dictionary<string, string> stringMap) => new Synonymns { StringMap = stringMap };
}
internal class LinksUnionConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(LinksUnion) || t == typeof(LinksUnion?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.StartObject:
var objectValue = serializer.Deserialize<LinksClass>(reader);
return new LinksUnion { LinksClass = objectValue };
case JsonToken.StartArray:
var arrayValue = serializer.Deserialize<List<object>>(reader);
return new LinksUnion { AnythingArray = arrayValue };
}
throw new Exception("Cannot unmarshal type LinksUnion");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
var value = (LinksUnion)untypedValue;
if (value.AnythingArray != null)
{
serializer.Serialize(writer, value.AnythingArray);
return;
}
if (value.LinksClass == null)
{
throw new Exception("Cannot marshal type LinksUnion");
}
serializer.Serialize(writer, value.LinksClass);
}
public static readonly LinksUnionConverter Singleton = new LinksUnionConverter();
}
internal class ParseStringConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(long) || t == typeof(long?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
var value = serializer.Deserialize<string>(reader);
if (long.TryParse(value, out var l))
{
return l;
}
throw new Exception("Cannot unmarshal type long");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
if (untypedValue == null)
{
serializer.Serialize(writer, null);
return;
}
var value = (long)untypedValue;
serializer.Serialize(writer, value.ToString());
}
public static readonly ParseStringConverter Singleton = new ParseStringConverter();
}
internal class SynonymnsConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(Synonymns) || t == typeof(Synonymns?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.StartObject:
var objectValue = serializer.Deserialize<Dictionary<string, string>>(reader);
return new Synonymns { StringMap = objectValue };
case JsonToken.StartArray:
var arrayValue = serializer.Deserialize<List<string>>(reader);
return new Synonymns { StringArray = arrayValue };
}
throw new Exception("Cannot unmarshal type Synonymns");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
var value = (Synonymns)untypedValue;
if (value.StringArray != null)
{
serializer.Serialize(writer, value.StringArray);
return;
}
if (value.StringMap == null)
{
throw new Exception("Cannot marshal type Synonymns");
}
serializer.Serialize(writer, value.StringMap);
}
public static readonly SynonymnsConverter Singleton = new SynonymnsConverter();
}
}

View File

@@ -16,7 +16,6 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
@@ -25,8 +24,9 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public override string Name => "AnimeTorrents";
public override string BaseUrl => "https://animetorrents.me/";
private string LoginUrl => BaseUrl + "login.php";
public override string[] IndexerUrls => new string[] { "https://animetorrents.me/" };
public override string Description => "Definitive source for anime and manga";
private string LoginUrl => Settings.BaseUrl + "login.php";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
@@ -38,12 +38,12 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AnimeTorrentsRequestGenerator() { Settings = Settings, Capabilities = Capabilities, BaseUrl = BaseUrl };
return new AnimeTorrentsRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new AnimeTorrentsParser(Settings, Capabilities.Categories, BaseUrl);
return new AnimeTorrentsParser(Settings, Capabilities.Categories);
}
protected override async Task DoLogin()
@@ -135,7 +135,6 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public AnimeTorrentsSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public string BaseUrl { get; set; }
public AnimeTorrentsRequestGenerator()
{
@@ -148,8 +147,8 @@ namespace NzbDrone.Core.Indexers.Definitions
// replace any space, special char, etc. with % (wildcard)
var replaceRegex = new Regex("[^a-zA-Z0-9]+");
searchString = replaceRegex.Replace(searchString, "%");
var searchUrl = BaseUrl + "ajax/torrents_data.php";
var searchUrlReferer = BaseUrl + "torrents.php?cat=0&searchin=filename&search=";
var searchUrl = Settings.BaseUrl + "ajax/torrents_data.php";
var searchUrlReferer = Settings.BaseUrl + "torrents.php?cat=0&searchin=filename&search=";
var trackerCats = Capabilities.Categories.MapTorznabCapsToTrackers(categories) ?? new List<string>();
@@ -229,13 +228,11 @@ namespace NzbDrone.Core.Indexers.Definitions
{
private readonly AnimeTorrentsSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
private readonly string _baseUrl;
public AnimeTorrentsParser(AnimeTorrentsSettings settings, IndexerCapabilitiesCategories categories, string baseUrl)
public AnimeTorrentsParser(AnimeTorrentsSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
_baseUrl = baseUrl;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@@ -340,7 +337,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
public class AnimeTorrentsSettings : IProviderConfig
public class AnimeTorrentsSettings : IIndexerSettings
{
private static readonly AnimeTorrentsSettingsValidator Validator = new AnimeTorrentsSettingsValidator();
@@ -350,12 +347,18 @@ namespace NzbDrone.Core.Indexers.Definitions
Password = "";
}
[FieldDefinition(1, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(2, Label = "Password", HelpText = "Site Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
public string Password { get; set; }
[FieldDefinition(4)]
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class AnimeWorld : Unit3dBase
{
public override string Name => "AnimeWorld";
public override string BaseUrl => "https://animeworld.cx/";
public override string[] IndexerUrls => new string[] { "https://animeworld.cx/" };
public override string Description => "AnimeWorld (AW) is a GERMAN Private site for ANIME / MANGA / HENTAI";
public override string Language => "de-de";

View File

@@ -0,0 +1,350 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Text.RegularExpressions;
using AngleSharp.Html.Parser;
using FluentValidation;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
public class Animedia : TorrentIndexerBase<AnimediaSettings>
{
public override string Name => "Animedia";
public override string[] IndexerUrls => new string[] { "https://tt.animedia.tv/" };
public override string Description => "Animedia is russian anime voiceover group and eponymous anime tracker.";
public override string Language => "ru-ru";
public override Encoding Encoding => Encoding.UTF8;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
public override IndexerCapabilities Capabilities => SetCapabilities();
public Animedia(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AnimediaRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new AnimediaParser(Settings, Capabilities.Categories) { HttpClient = _httpClient, Logger = _logger };
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "TV Anime");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "OVA/ONA/Special");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TV, "Dorama");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.Movies, "Movies");
return caps;
}
}
public class AnimediaRequestGenerator : IIndexerRequestGenerator
{
public AnimediaSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public AnimediaRequestGenerator()
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{
var requestUrl = string.Empty;
if (string.IsNullOrWhiteSpace(term))
{
requestUrl = Settings.BaseUrl;
}
else
{
var queryCollection = new NameValueCollection
{
// Remove season and episode info from search term cause it breaks search
{ "keywords", Regex.Replace(term, @"(?:[SsEe]?\d{1,4}){1,2}$", "").TrimEnd() },
{ "limit", "20" },
{ "orderby_sort", "entry_date|desc" }
};
requestUrl = string.Format("{0}/ajax/search_result/P0?{1}", Settings.BaseUrl.TrimEnd('/'), queryCollection.GetQueryString());
}
var request = new IndexerRequest(requestUrl, HttpAccept.Html);
yield return request;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
// Animedia doesn't support music, but this function required by interface
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
// Animedia doesn't support books, but this function required by interface
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class AnimediaParser : IParseIndexerResponse
{
private readonly AnimediaSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
private static readonly Regex EpisodesInfoQueryRegex = new Regex(@"сери[ия] (\d+)(?:-(\d+))? из.*", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ResolutionInfoQueryRegex = new Regex(@"качество (\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex SizeInfoQueryRegex = new Regex(@"размер:(.*)\n", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ReleaseDateInfoQueryRegex = new Regex(@"добавлен:(.*)\n", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex CategorieMovieRegex = new Regex(@"Фильм", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex CategorieOVARegex = new Regex(@"ОВА|OVA|ОНА|ONA|Special", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex CategorieDoramaRegex = new Regex(@"Дорама", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public IHttpClient HttpClient { get; set; }
public Logger Logger { get; set; }
public AnimediaParser(AnimediaSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
private string composeTitle(AngleSharp.Html.Dom.IHtmlDocument dom, AngleSharp.Dom.IElement t, AngleSharp.Dom.IElement tr)
{
var name_ru = dom.QuerySelector("div.media__post__header > h1").TextContent.Trim();
var name_en = dom.QuerySelector("div.media__panel > div:nth-of-type(1) > div.col-l:nth-of-type(1) > div > span").TextContent.Trim();
var name_orig = dom.QuerySelector("div.media__panel > div:nth-of-type(1) > div.col-l:nth-of-type(2) > div > span").TextContent.Trim();
var title = name_ru + " / " + name_en;
if (name_en != name_orig)
{
title += " / " + name_orig;
}
var tabName = t.TextContent;
tabName = tabName.Replace("Сезон", "Season");
if (tabName.Contains("Серии"))
{
tabName = "";
}
var heading = tr.QuerySelector("h3.tracker_info_bold").TextContent;
// Parse episodes info from heading if episods info present
var match = EpisodesInfoQueryRegex.Match(heading);
heading = tabName;
if (match.Success)
{
if (string.IsNullOrEmpty(match.Groups[2].Value))
{
heading += " E" + match.Groups[1].Value;
}
else
{
heading += string.Format(" E{0}-{1}", match.Groups[1].Value, match.Groups[2].Value);
}
}
return title + " - " + heading + " [" + getResolution(tr) + "p]";
}
private string getResolution(AngleSharp.Dom.IElement tr)
{
var resolution = tr.QuerySelector("div.tracker_info_left").TextContent;
return ResolutionInfoQueryRegex.Match(resolution).Groups[1].Value;
}
private long getReleaseSize(AngleSharp.Dom.IElement tr)
{
var sizeStr = tr.QuerySelector("div.tracker_info_left").TextContent;
return ReleaseInfo.GetBytes(SizeInfoQueryRegex.Match(sizeStr).Groups[1].Value.Trim());
}
private DateTime getReleaseDate(AngleSharp.Dom.IElement tr)
{
var sizeStr = tr.QuerySelector("div.tracker_info_left").TextContent;
return DateTime.Parse(ReleaseDateInfoQueryRegex.Match(sizeStr).Groups[1].Value.Trim());
}
private ICollection<IndexerCategory> MapCategories(AngleSharp.Html.Dom.IHtmlDocument dom, AngleSharp.Dom.IElement t, AngleSharp.Dom.IElement tr)
{
var rName = t.TextContent;
var rDesc = tr.QuerySelector("h3.tracker_info_bold").TextContent;
var type = dom.QuerySelector("div.releases-date:contains('Тип:')").TextContent;
// Check OVA first cause OVA looks like anime with OVA in release name or description
if (CategorieOVARegex.IsMatch(rName) || CategorieOVARegex.IsMatch(rDesc))
{
return _categories.MapTrackerCatDescToNewznab("OVA/ONA/Special");
}
// Check movies then, cause some of releases could be movies dorama and should go to movies category
if (CategorieMovieRegex.IsMatch(rName) || CategorieMovieRegex.IsMatch(rDesc))
{
return _categories.MapTrackerCatDescToNewznab("Movies");
}
// Check dorama. Most of doramas are flaged as doramas in type info, but type info could have a lot of types at same time (movie, etc)
if (CategorieDoramaRegex.IsMatch(rName) || CategorieDoramaRegex.IsMatch(type))
{
return _categories.MapTrackerCatDescToNewznab("Dorama");
}
return _categories.MapTrackerCatDescToNewznab("TV Anime");
}
private IList<TorrentInfo> ParseRelease(IndexerResponse indexerResponse)
{
var torrentInfos = new List<TorrentInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
foreach (var t in dom.QuerySelectorAll("ul.media__tabs__nav > li > a"))
{
var tr_id = t.Attributes["href"].Value;
var tr = dom.QuerySelector("div" + tr_id);
var seeders = int.Parse(tr.QuerySelector("div.circle_green_text_top").TextContent);
var url = indexerResponse.HttpRequest.Url.ToString();
var release = new TorrentInfo
{
Title = composeTitle(dom, t, tr),
InfoUrl = url,
DownloadVolumeFactor = 0,
UploadVolumeFactor = 1,
Guid = url + tr_id,
Seeders = seeders,
Peers = seeders + int.Parse(tr.QuerySelector("div.circle_red_text_top").TextContent),
Grabs = int.Parse(tr.QuerySelector("div.circle_grey_text_top").TextContent),
Categories = MapCategories(dom, t, tr),
PublishDate = getReleaseDate(tr),
DownloadUrl = tr.QuerySelector("div.download_tracker > a.btn__green").Attributes["href"].Value,
MagnetUrl = tr.QuerySelector("div.download_tracker > a.btn__d-gray").Attributes["href"].Value,
Size = getReleaseSize(tr),
Resolution = getResolution(tr)
};
torrentInfos.Add(release);
}
return torrentInfos;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
var links = dom.QuerySelectorAll("a.ads-list__item__title");
foreach (var link in links)
{
var url = link.GetAttribute("href");
// Some URLs in search are broken
if (url.StartsWith("//"))
{
url = "https:" + url;
}
var releaseRequest = new IndexerRequest(url, HttpAccept.Html);
var releaseResponse = new IndexerResponse(releaseRequest, HttpClient.Execute(releaseRequest.HttpRequest));
// Throw common http errors here before we try to parse
if (releaseResponse.HttpResponse.HasHttpError)
{
if ((int)releaseResponse.HttpResponse.StatusCode == 429)
{
throw new TooManyRequestsException(releaseRequest.HttpRequest, releaseResponse.HttpResponse);
}
else
{
throw new IndexerException(releaseResponse, "Http error code: " + releaseResponse.HttpResponse.StatusCode);
}
}
torrentInfos.AddRange(ParseRelease(releaseResponse));
}
return torrentInfos.ToArray();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class AnimediaSettingsValidator : AbstractValidator<AnimediaSettings>
{
public AnimediaSettingsValidator()
{
}
}
public class AnimediaSettings : IIndexerSettings
{
private static readonly AnimediaSettingsValidator Validator = new AnimediaSettingsValidator();
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
public string BaseUrl { get; set; }
[FieldDefinition(2)]
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -24,8 +24,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public class Anthelion : TorrentIndexerBase<AnthelionSettings>
{
public override string Name => "Anthelion";
public override string BaseUrl => "https://anthelion.me/";
private string LoginUrl => BaseUrl + "login.php";
public override string[] IndexerUrls => new string[] { "https://anthelion.me/" };
private string LoginUrl => Settings.BaseUrl + "login.php";
public override string Description => "A movies tracker";
public override string Language => "en-us";
public override Encoding Encoding => Encoding.UTF8;
@@ -40,12 +40,12 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AnthelionRequestGenerator() { Settings = Settings, Capabilities = Capabilities, BaseUrl = BaseUrl };
return new AnthelionRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new AnthelionParser(Settings, Capabilities.Categories, BaseUrl);
return new AnthelionParser(Settings, Capabilities.Categories);
}
protected override async Task DoLogin()
@@ -79,9 +79,13 @@ namespace NzbDrone.Core.Indexers.Definitions
var response = await ExecuteAuth(authLoginRequest);
if (!response.Content.Contains("logout.php"))
if (CheckIfLoginNeeded(response))
{
throw new IndexerAuthException("Anthelion Auth Failed");
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
var errorMessage = dom.QuerySelector("form#loginform").TextContent.Trim();
throw new IndexerAuthException(errorMessage);
}
cookies = response.GetCookies();
@@ -127,7 +131,6 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public AnthelionSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public string BaseUrl { get; set; }
public AnthelionRequestGenerator()
{
@@ -135,7 +138,7 @@ namespace NzbDrone.Core.Indexers.Definitions
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
{
var searchUrl = string.Format("{0}/torrents.php", BaseUrl.TrimEnd('/'));
var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/'));
// TODO: IMDB search is available but it requires to parse the details page
var qc = new NameValueCollection
@@ -174,8 +177,6 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
@@ -192,8 +193,6 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
@@ -214,13 +213,11 @@ namespace NzbDrone.Core.Indexers.Definitions
{
private readonly AnthelionSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
private readonly string _baseUrl;
public AnthelionParser(AnthelionSettings settings, IndexerCapabilitiesCategories categories, string baseurl)
public AnthelionParser(AnthelionSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
_baseUrl = baseurl;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@@ -237,11 +234,11 @@ namespace NzbDrone.Core.Indexers.Definitions
var tags = row.QuerySelector("div.torrent_info").FirstChild.TextContent.Replace(" / ", " ").Trim();
var title = $"{qDetailsLink.TextContent} {year} {tags}";
var description = row.QuerySelector("div.tags").TextContent.Trim();
var details = _baseUrl + qDetailsLink.GetAttribute("href");
var details = _settings.BaseUrl + qDetailsLink.GetAttribute("href");
var torrentId = qDetailsLink.GetAttribute("href").Split('=').Last();
var link = _baseUrl + "torrents.php?action=download&id=" + torrentId;
var link = _settings.BaseUrl + "torrents.php?action=download&id=" + torrentId;
var posterStr = qDetailsLink.GetAttribute("data-cover");
var poster = !string.IsNullOrWhiteSpace(posterStr) ? new Uri(qDetailsLink.GetAttribute("data-cover")) : null;
var poster = !string.IsNullOrWhiteSpace(posterStr) ? posterStr : null;
var files = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(3)").TextContent);
var publishDate = DateTimeUtil.FromTimeAgo(row.QuerySelector("td:nth-child(4)").TextContent);
@@ -279,6 +276,7 @@ namespace NzbDrone.Core.Indexers.Definitions
Categories = category,
DownloadUrl = link,
InfoUrl = details,
PosterUrl = poster,
Guid = link,
ImdbId = imdb.GetValueOrDefault(),
Seeders = seeders,
@@ -308,7 +306,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
public class AnthelionSettings : IProviderConfig
public class AnthelionSettings : IIndexerSettings
{
private static readonly AnthelionSettingsValidator Validator = new AnthelionSettingsValidator();
@@ -318,12 +316,18 @@ namespace NzbDrone.Core.Indexers.Definitions
Password = "";
}
[FieldDefinition(1, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(1, Label = "Password", HelpText = "Site Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
public string Password { get; set; }
[FieldDefinition(4)]
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -10,7 +10,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public class AvistaZ : AvistazBase
{
public override string Name => "AvistaZ";
public override string BaseUrl => "https://avistaz.to/";
public override string[] IndexerUrls => new string[] { "https://avistaz.to/" };
public override string Description => "Aka AsiaTorrents";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public AvistaZ(IIndexerRepository indexerRepository, IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
@@ -25,8 +26,7 @@ namespace NzbDrone.Core.Indexers.Definitions
Settings = Settings,
HttpClient = _httpClient,
Logger = _logger,
Capabilities = Capabilities,
BaseUrl = BaseUrl
Capabilities = Capabilities
};
}

View File

@@ -12,8 +12,8 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public abstract class AvistazBase : TorrentIndexerBase<AvistazSettings>
{
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override string BaseUrl => "";
protected virtual string LoginUrl => BaseUrl + "api/v1/jackett/auth";
public override string[] IndexerUrls => new string[] { "" };
protected virtual string LoginUrl => Settings.BaseUrl + "api/v1/jackett/auth";
public override bool SupportsRss => true;
public override bool SupportsSearch => true;
public override int PageSize => 50;
@@ -38,8 +38,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
Settings = Settings,
HttpClient = _httpClient,
Logger = _logger,
Capabilities = Capabilities,
BaseUrl = BaseUrl
Capabilities = Capabilities
};
}

View File

@@ -12,14 +12,13 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public class AvistazRequestGenerator : IIndexerRequestGenerator
{
public AvistazSettings Settings { get; set; }
public string BaseUrl { get; set; }
public IDictionary<string, string> AuthCookieCache { get; set; }
public IHttpClient HttpClient { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public Logger Logger { get; set; }
protected virtual string SearchUrl => BaseUrl + "api/v1/jackett/torrents";
protected virtual string SearchUrl => Settings.BaseUrl + "api/v1/jackett/torrents";
protected virtual bool ImdbInTags => false;
public Func<IDictionary<string, string>> GetCookies { get; set; }

View File

@@ -1,6 +1,5 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions.Avistaz
@@ -15,7 +14,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
}
}
public class AvistazSettings : IProviderConfig
public class AvistazSettings : IIndexerSettings
{
private static readonly AvistazSettingsValidator Validator = new AvistazSettingsValidator();
@@ -26,15 +25,21 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public string Token { get; set; }
[FieldDefinition(1, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(2, Label = "Password", HelpText = "Site Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
public string Password { get; set; }
[FieldDefinition(3, Label = "PID", HelpText = "PID from My Account or My Profile page")]
[FieldDefinition(4, Label = "PID", HelpText = "PID from My Account or My Profile page")]
public string Pid { get; set; }
[FieldDefinition(5)]
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -16,7 +16,6 @@ using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
@@ -25,8 +24,9 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public override string Name => "BakaBT";
public override string BaseUrl => "https://bakabt.me/";
private string LoginUrl => BaseUrl + "login.php";
public override string[] IndexerUrls => new string[] { "https://bakabt.me/" };
public override string Description => "Anime Community";
private string LoginUrl => Settings.BaseUrl + "login.php";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
@@ -38,12 +38,12 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new BakaBTRequestGenerator() { Settings = Settings, Capabilities = Capabilities, BaseUrl = BaseUrl };
return new BakaBTRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new BakaBTParser(Settings, Capabilities.Categories, BaseUrl);
return new BakaBTParser(Settings, Capabilities.Categories);
}
protected override async Task DoLogin()
@@ -110,6 +110,10 @@ namespace NzbDrone.Core.Indexers.Definitions
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
@@ -124,7 +128,7 @@ namespace NzbDrone.Core.Indexers.Definitions
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "OVA");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.AudioOther, "Soundtrack");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.BooksComics, "Manga");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVAnime, "Anime Movie");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Movies, "Anime Movie");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TVOther, "Live Action");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.BooksOther, "Artbook");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.AudioVideo, "Music Video");
@@ -138,16 +142,15 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public BakaBTSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public string BaseUrl { get; set; }
public BakaBTRequestGenerator()
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
private IEnumerable<IndexerRequest> GetPagedRequests(string term)
{
var searchString = term;
var searchUrl = BaseUrl + "browse.php?only=0&hentai=1&incomplete=1&lossless=1&hd=1&multiaudio=1&bonus=1&reorder=1&q=";
var searchUrl = Settings.BaseUrl + "browse.php?only=0&hentai=1&incomplete=1&lossless=1&hd=1&multiaudio=1&bonus=1&reorder=1&q=";
var match = Regex.Match(term, @".*(?=\s(?:[Ee]\d+|\d+)$)");
if (match.Success)
@@ -166,6 +169,8 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm)));
return pageableRequests;
}
@@ -173,7 +178,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm)));
return pageableRequests;
}
@@ -182,7 +187,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm)));
return pageableRequests;
}
@@ -191,7 +196,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm)));
return pageableRequests;
}
@@ -200,7 +205,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm)));
return pageableRequests;
}
@@ -213,14 +218,12 @@ namespace NzbDrone.Core.Indexers.Definitions
{
private readonly BakaBTSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
private readonly string _baseUrl;
private readonly List<IndexerCategory> _defaultCategories = new List<IndexerCategory> { NewznabStandardCategory.TVAnime };
public BakaBTParser(BakaBTSettings settings, IndexerCapabilitiesCategories categories, string baseUrl)
public BakaBTParser(BakaBTSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
_baseUrl = baseUrl;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@@ -299,11 +302,11 @@ namespace NzbDrone.Core.Indexers.Definitions
release.Categories = currentCategories;
//release.Description = row.QuerySelector("span.tags")?.TextContent;
release.Guid = _baseUrl + qTitleLink.GetAttribute("href");
release.Description = row.QuerySelector("span.tags")?.TextContent;
release.Guid = _settings.BaseUrl + qTitleLink.GetAttribute("href");
release.InfoUrl = release.Guid;
release.DownloadUrl = _baseUrl + row.QuerySelector(".peers a").GetAttribute("href");
release.DownloadUrl = _settings.BaseUrl + row.QuerySelector(".peers a").GetAttribute("href");
var grabs = row.QuerySelectorAll(".peers")[0].FirstChild.NodeValue.TrimEnd().TrimEnd('/').TrimEnd();
grabs = grabs.Replace("k", "000");
@@ -392,7 +395,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
public class BakaBTSettings : IProviderConfig
public class BakaBTSettings : IIndexerSettings
{
private static readonly BakaBTSettingsValidator Validator = new BakaBTSettingsValidator();
@@ -402,18 +405,24 @@ namespace NzbDrone.Core.Indexers.Definitions
Password = "";
}
[FieldDefinition(1, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(2, Label = "Password", HelpText = "Site Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
public string Password { get; set; }
[FieldDefinition(3, Label = "Add Romaji Title", Type = FieldType.Checkbox, HelpText = "Add releases for Romaji Title")]
[FieldDefinition(4, Label = "Add Romaji Title", Type = FieldType.Checkbox, HelpText = "Add releases for Romaji Title")]
public bool AddRomajiTitle { get; set; }
[FieldDefinition(4, Label = "Append Season", Type = FieldType.Checkbox, HelpText = "Append Season for Sonarr Compatibility")]
[FieldDefinition(5, Label = "Append Season", Type = FieldType.Checkbox, HelpText = "Append Season for Sonarr Compatibility")]
public bool AppendSeason { get; set; }
[FieldDefinition(6)]
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -16,7 +16,6 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
@@ -25,7 +24,8 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public override string Name => "BeyondHD";
public override string BaseUrl => "https://beyond-hd.me/";
public override string[] IndexerUrls => new string[] { "https://beyond-hd.me/" };
public override string Description => "BeyondHD (BHD) is a Private Torrent Tracker for HD MOVIES / TV";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
@@ -37,12 +37,12 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new BeyondHDRequestGenerator() { Settings = Settings, Capabilities = Capabilities, BaseUrl = BaseUrl };
return new BeyondHDRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new BeyondHDParser(Settings, Capabilities.Categories, BaseUrl);
return new BeyondHDParser(Settings, Capabilities.Categories);
}
private IndexerCapabilities SetCapabilities()
@@ -70,7 +70,6 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public BeyondHDSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public string BaseUrl { get; set; }
public BeyondHDRequestGenerator()
{
@@ -106,7 +105,7 @@ namespace NzbDrone.Core.Indexers.Definitions
body.Add("categories", string.Join(",", cats));
}
var searchUrl = BaseUrl + "api/torrents/" + Settings.ApiKey;
var searchUrl = Settings.BaseUrl + "api/torrents/" + Settings.ApiKey;
var request = new HttpRequest(searchUrl, HttpAccept.Json);
@@ -172,13 +171,11 @@ namespace NzbDrone.Core.Indexers.Definitions
{
private readonly BeyondHDSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
private readonly string _baseUrl;
public BeyondHDParser(BeyondHDSettings settings, IndexerCapabilitiesCategories categories, string baseUrl)
public BeyondHDParser(BeyondHDSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
_baseUrl = baseUrl;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@@ -242,7 +239,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
public class BeyondHDSettings : IProviderConfig
public class BeyondHDSettings : IIndexerSettings
{
private static readonly BeyondHDSettingsValidator Validator = new BeyondHDSettingsValidator();
@@ -250,12 +247,18 @@ namespace NzbDrone.Core.Indexers.Definitions
{
}
[FieldDefinition(1, Label = "API Key", HelpText = "API Key from Site", Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "API Key", HelpText = "API Key from the Site (Found in My Security => API Key)", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(2, Label = "RSS Key", HelpText = "RSS Key from Site", Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(3, Label = "RSS Key", HelpText = "RSS Key from the Site (Found in My Security => RSS Key)", Privacy = PrivacyLevel.ApiKey)]
public string RssKey { get; set; }
[FieldDefinition(4)]
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -10,7 +10,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public class Blutopia : Unit3dBase
{
public override string Name => "Blutopia";
public override string BaseUrl => "https://blutopia.xyz/";
public override string[] IndexerUrls => new string[] { "https://blutopia.xyz/" };
public override string Description => "Blutopia (BLU) is a Private Torrent Tracker for HD MOVIES / TV";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public Blutopia(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)

View File

@@ -17,7 +17,8 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
public override int PageSize => 100;
public override IndexerCapabilities Capabilities => SetCapabilities();
public override string BaseUrl => "http://api.broadcasthe.net/";
public override string[] IndexerUrls => new string[] { "http://api.broadcasthe.net/" };
public override string Description => "BroadcasTheNet (BTN) is an invite-only torrent tracker focused on TV shows";
public BroadcastheNet(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
@@ -26,7 +27,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
public override IIndexerRequestGenerator GetRequestGenerator()
{
var requestGenerator = new BroadcastheNetRequestGenerator() { Settings = Settings, PageSize = PageSize, BaseUrl = BaseUrl, Capabilities = Capabilities };
var requestGenerator = new BroadcastheNetRequestGenerator() { Settings = Settings, PageSize = PageSize, Capabilities = Capabilities };
var releaseInfo = _indexerStatusService.GetLastRssSyncReleaseInfo(Definition.Id);
if (releaseInfo != null)

View File

@@ -16,7 +16,6 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
public string BaseUrl { get; set; }
public BroadcastheNetRequestGenerator()
{
@@ -26,7 +25,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
private IEnumerable<IndexerRequest> GetPagedRequests(BroadcastheNetTorrentQuery parameters, int results, int offset)
{
var builder = new JsonRpcRequestBuilder(BaseUrl)
var builder = new JsonRpcRequestBuilder(Settings.BaseUrl)
.Call("getTorrents", Settings.ApiKey, parameters, results, offset);
builder.SuppressHttpError = true;

View File

@@ -1,6 +1,5 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.BroadcastheNet
@@ -13,7 +12,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
}
}
public class BroadcastheNetSettings : IProviderConfig
public class BroadcastheNetSettings : IIndexerSettings
{
private static readonly BroadcastheNetSettingsValidator Validator = new BroadcastheNetSettingsValidator();
@@ -21,9 +20,15 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
{
}
[FieldDefinition(1, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(3)]
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -8,7 +8,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public class BrokenStones : Gazelle.Gazelle
{
public override string Name => "BrokenStones";
public override string BaseUrl => "https://brokenstones.club/";
public override string[] IndexerUrls => new string[] { "https://brokenstones.club/" };
public override string Description => "Broken Stones is a Private site for MacOS and iOS APPS / GAMES";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public BrokenStones(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)

View File

@@ -8,7 +8,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public class CGPeers : Gazelle.Gazelle
{
public override string Name => "CGPeers";
public override string BaseUrl => "https://cgpeers.to/";
public override string[] IndexerUrls => new string[] { "https://cgpeers.to/" };
public override string Description => "CGPeers is a Private Torrent Tracker for GRAPHICS SOFTWARE / TUTORIALS / ETC";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public CGPeers(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
@@ -22,7 +23,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
private readonly ICached<CardigannRequestGenerator> _generatorCache;
public override string Name => "Cardigann";
public override string BaseUrl => "";
public override string[] IndexerUrls => new string[] { "" };
public override string Description => "";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
@@ -44,6 +46,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
generator = (CardigannRequestGenerator)SetCookieFunctions(generator);
generator.Settings = Settings;
_generatorCache.ClearExpired();
return generator;
@@ -120,6 +124,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
Language = definition.Language,
Description = definition.Description,
Implementation = GetType().Name,
IndexerUrls = definition.Links.ToArray(),
Settings = new CardigannSettings { DefinitionFile = definition.File },
Protocol = DownloadProtocol.Torrent,
Privacy = definition.Type == "private" ? IndexerPrivacy.Private : IndexerPrivacy.Public,
@@ -133,11 +138,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{
if (httpResponse.HasHttpError)
{
return true;
}
var generator = (CardigannRequestGenerator)GetRequestGenerator();
SetCookieFunctions(generator);
@@ -236,6 +236,16 @@ namespace NzbDrone.Core.Indexers.Cardigann
};
}
if (action == "getUrls")
{
var devices = ((IndexerDefinition)Definition).IndexerUrls;
return new
{
options = devices.Select(d => new { Value = d, Name = d })
};
}
return null;
}
}

View File

@@ -23,7 +23,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
protected readonly Encoding _encoding;
protected readonly IConfigService _configService;
protected string SiteLink { get; private set; }
protected virtual string SiteLink { get; private set; }
protected readonly List<CategoryMapping> _categoryMapping = new List<CategoryMapping>();
protected readonly List<string> _defaultCategories = new List<string>();

View File

@@ -18,6 +18,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
{
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
protected override string SiteLink => Settings?.BaseUrl ?? _definition.Links.First();
public CardigannParser(IConfigService configService,
CardigannDefinition definition,
Logger logger)
@@ -291,6 +293,15 @@ namespace NzbDrone.Core.Indexers.Cardigann
release.TvdbId = (int)ParseUtil.CoerceLong(tvdbId);
value = release.TvdbId.ToString();
break;
case "poster":
if (!string.IsNullOrWhiteSpace(value))
{
var poster = ResolvePath(value, searchUrlUri);
release.PosterUrl = poster.AbsoluteUri;
}
value = release.PosterUrl;
break;
//case "author":
// release.Author = value;
@@ -298,15 +309,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
//case "booktitle":
// release.BookTitle = value;
// break;
//case "banner":
// if (!string.IsNullOrWhiteSpace(value))
// {
// var bannerurl = ResolvePath(value, searchUrlUri);
// release.BannerUrl = bannerurl;
// }
// value = release.BannerUrl.ToString();
// break;
default:
break;
}

View File

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

View File

@@ -22,6 +22,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
public IDictionary<string, string> Cookies { get; set; }
protected HttpResponse landingResult;
protected IHtmlDocument landingResultDocument;
protected override string SiteLink => Settings?.BaseUrl ?? _definition.Links.First();
public CardigannRequestGenerator(IConfigService configService,
CardigannDefinition definition,
@@ -687,13 +688,20 @@ namespace NzbDrone.Core.Indexers.Cardigann
var httpRequest = new HttpRequestBuilder(requestLinkStr)
.SetCookies(Cookies ?? new Dictionary<string, string>())
.SetHeaders(pairs ?? new Dictionary<string, string>())
.SetHeader("Referer", referer)
.Build();
.SetHeader("Referer", referer);
httpRequest.Method = method;
var response = await HttpClient.ExecuteAsync(httpRequest);
// Add form data for POST requests
if (method == HttpMethod.POST)
{
foreach (var param in pairs)
{
httpRequest.AddFormParameter(param.Key, param.Value);
}
}
var response = await HttpClient.ExecuteAsync(httpRequest.Build());
_logger.Debug($"CardigannIndexer ({_definition.Id}): handleRequest() remote server returned {response.StatusCode.ToString()}");
return response;
@@ -797,6 +805,11 @@ namespace NzbDrone.Core.Indexers.Cardigann
return false;
}
if (response.HasHttpError)
{
return true;
}
var parser = new HtmlParser();
var document = parser.ParseDocument(response.Content);
@@ -927,7 +940,20 @@ namespace NzbDrone.Core.Indexers.Cardigann
_logger.Info($"Adding request: {searchUrl}");
var request = new CardigannRequest(searchUrl, HttpAccept.Html, variables);
var requestbuilder = new HttpRequestBuilder(searchUrl);
requestbuilder.Method = method;
// Add FormData for searchs that POST
if (method == HttpMethod.POST)
{
foreach (var param in queryCollection)
{
requestbuilder.AddFormParameter(param.Key, param.Value);
}
}
var request = new CardigannRequest(requestbuilder.Build(), variables);
// send HTTP request
if (search.Headers != null)
@@ -938,8 +964,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
}
}
request.HttpRequest.Method = method;
yield return request;
}
}

View File

@@ -1,7 +1,6 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Cardigann
@@ -13,7 +12,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
}
}
public class CardigannSettings : IProviderConfig
public class CardigannSettings : IIndexerSettings
{
private static readonly CardigannSettingsValidator Validator = new CardigannSettingsValidator();
@@ -25,6 +24,12 @@ namespace NzbDrone.Core.Indexers.Cardigann
[FieldDefinition(0, Hidden = HiddenType.Hidden)]
public string DefinitionFile { get; set; }
[FieldDefinition(2, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
public string BaseUrl { get; set; }
[FieldDefinition(1)]
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public Dictionary<string, object> ExtraFieldData { get; set; }
// Field 8 is used by TorznabSettings MinimumSeeders

View File

@@ -10,7 +10,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public class CinemaZ : AvistazBase
{
public override string Name => "CinemaZ";
public override string BaseUrl => "https://cinemaz.to/";
public override string[] IndexerUrls => new string[] { "https://cinemaz.to/" };
public override string Description => "CinemaZ (EuTorrents) is a Private Torrent Tracker for FOREIGN NON-ASIAN MOVIES.";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public CinemaZ(IIndexerRepository indexerRepository, IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
@@ -25,8 +26,7 @@ namespace NzbDrone.Core.Indexers.Definitions
Settings = Settings,
HttpClient = _httpClient,
Logger = _logger,
Capabilities = Capabilities,
BaseUrl = BaseUrl
Capabilities = Capabilities
};
}

View File

@@ -0,0 +1,344 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using FluentValidation;
using Newtonsoft.Json;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
public class DanishBytes : TorrentIndexerBase<DanishBytesSettings>
{
public override string Name => "DanishBytes";
public override string[] IndexerUrls => new string[] { "https://danishbytes.org/" };
public override string Description => "DanishBytes is a Private Danish Tracker";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
public DanishBytes(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new DanishBytesRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new DanishBytesParser(Settings, Capabilities.Categories);
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.ImdbId, TvSearchParam.TvdbId
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
}
};
caps.Categories.AddCategoryMapping("1", NewznabStandardCategory.Movies, "Movies");
caps.Categories.AddCategoryMapping("2", NewznabStandardCategory.TV, "TV");
caps.Categories.AddCategoryMapping("3", NewznabStandardCategory.Audio, "Music");
caps.Categories.AddCategoryMapping("4", NewznabStandardCategory.PCGames, "Games");
caps.Categories.AddCategoryMapping("5", NewznabStandardCategory.PC0day, "Appz");
caps.Categories.AddCategoryMapping("6", NewznabStandardCategory.Books, "Bookz");
return caps;
}
}
public class DanishBytesRequestGenerator : IIndexerRequestGenerator
{
public DanishBytesSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public DanishBytesRequestGenerator()
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null, int tmdbId = 0, int tvdbId = 0)
{
var qc = new NameValueCollection
{
{ "search", term },
{ "api_token", Settings.ApiKey },
};
if (imdbId.IsNotNullOrWhiteSpace())
{
qc.Add("imdb", imdbId);
}
if (tmdbId > 0)
{
qc.Add("tmdb", tmdbId.ToString());
}
if (tvdbId > 0)
{
qc.Add("tvdb", tvdbId.ToString());
}
var searchUrl = string.Format("{0}/api/torrents/v2/filter?{1}", Settings.BaseUrl, qc.GetQueryString());
foreach (var cat in Capabilities.Categories.MapTorznabCapsToTrackers(categories))
{
searchUrl += $"&categories[]={cat}";
}
var request = new HttpRequest(searchUrl, HttpAccept.Json);
var indexerRequest = new IndexerRequest(request);
yield return indexerRequest;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.FullImdbId, searchCriteria.TmdbId.GetValueOrDefault()));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories, searchCriteria.FullImdbId, 0, searchCriteria.TvdbId.Value));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class DanishBytesParser : IParseIndexerResponse
{
private readonly DanishBytesSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public DanishBytesParser(DanishBytesSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<TorrentInfo>();
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
}
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
{
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
}
var jsonResponse = new HttpResponse<DanishBytesResponse>(indexerResponse.HttpResponse);
foreach (var row in jsonResponse.Resource.Torrents)
{
var release = new TorrentInfo
{
Title = row.Name,
InfoUrl = $"{_settings.BaseUrl}torrents/{row.Id}",
DownloadUrl = $"{_settings.BaseUrl}torrent/download/{row.Id}.{jsonResponse.Resource.Rsskey}",
PosterUrl = row.PosterImage,
PublishDate = row.CreatedAt,
Categories = _categories.MapTrackerCatToNewznab(row.CategoryId),
Size = row.Size,
Seeders = row.Seeders,
Peers = row.Leechers + row.Seeders,
Grabs = row.TimesCompleted,
DownloadVolumeFactor = row.Free ? 0 : 1,
UploadVolumeFactor = row.Doubleup ? 2 : 1
};
torrentInfos.Add(release);
}
// order by date
return torrentInfos.OrderByDescending(o => o.PublishDate).ToArray();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class DanishBytesSettingsValidator : AbstractValidator<DanishBytesSettings>
{
public DanishBytesSettingsValidator()
{
RuleFor(c => c.ApiKey).NotEmpty();
}
}
public class DanishBytesSettings : IIndexerSettings
{
private static readonly DanishBytesSettingsValidator Validator = new DanishBytesSettingsValidator();
public DanishBytesSettings()
{
}
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "API Key", HelpText = "API Key from Site", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(3)]
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
public class DanishBytesTorrent
{
public int Id { get; set; }
public string Name { get; set; }
[JsonProperty(PropertyName = "info_hash")]
public string InfoHash { get; set; }
public long Size { get; set; }
public int Leechers { get; set; }
public int Seeders { get; set; }
[JsonProperty(PropertyName = "times_completed")]
public int TimesCompleted { get; set; }
[JsonProperty(PropertyName = "category_id")]
public string CategoryId { get; set; }
public string Tmdb { get; set; }
public string Igdb { get; set; }
public string Mal { get; set; }
public string Tvdb { get; set; }
public string Imdb { get; set; }
public int Stream { get; set; }
public bool Free { get; set; }
[JsonProperty(PropertyName = "on_fire")]
public bool OnFire { get; set; }
public bool Doubleup { get; set; }
public bool Highspeed { get; set; }
public bool Featured { get; set; }
public bool Webstream { get; set; }
public bool Anon { get; set; }
public bool Sticky { get; set; }
public bool Sd { get; set; }
[JsonProperty(PropertyName = "created_at")]
public DateTime CreatedAt { get; set; }
[JsonProperty(PropertyName = "bumped_at")]
public DateTime BumpedAt { get; set; }
[JsonProperty(PropertyName = "type_id")]
public int TypeId { get; set; }
[JsonProperty(PropertyName = "resolution_id")]
public int ResolutionId { get; set; }
[JsonProperty(PropertyName = "poster_image")]
public string PosterImage { get; set; }
public string Video { get; set; }
[JsonProperty(PropertyName = "thanks_count")]
public int ThanksCount { get; set; }
[JsonProperty(PropertyName = "comments_count")]
public int CommentsCount { get; set; }
public string GetSize { get; set; }
[JsonProperty(PropertyName = "created_at_human")]
public string CreatedAtHuman { get; set; }
public bool Bookmarked { get; set; }
public bool Liked { get; set; }
[JsonProperty(PropertyName = "show_last_torrents")]
public bool ShowLastTorrents { get; set; }
}
public class DanishBytesPageLinks
{
public int To { get; set; }
public string Qty { get; set; }
[JsonProperty(PropertyName = "current_page")]
public int CurrentPage { get; set; }
}
public class DanishBytesResponse
{
public DanishBytesTorrent[] Torrents { get; set; }
public int ResultsCount { get; set; }
public DanishBytesPageLinks Links { get; set; }
public string CurrentCount { get; set; }
public int TorrentCountTotal { get; set; }
public string Rsskey { get; set; }
}
}

View File

@@ -16,7 +16,6 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
@@ -24,7 +23,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public class DigitalCore : TorrentIndexerBase<DigitalCoreSettings>
{
public override string Name => "DigitalCore";
public override string BaseUrl => "https://digitalcore.club/";
public override string[] IndexerUrls => new string[] { "https://digitalcore.club/" };
public override string Description => "DigitalCore is a Private Torrent Tracker for MOVIES / TV / GENERAL";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
@@ -36,12 +36,12 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new DigitalCoreRequestGenerator() { Settings = Settings, PageSize = PageSize, Capabilities = Capabilities, BaseUrl = BaseUrl };
return new DigitalCoreRequestGenerator() { Settings = Settings, PageSize = PageSize, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new DigitalCoreParser(Settings, Capabilities.Categories, BaseUrl);
return new DigitalCoreParser(Settings, Capabilities.Categories);
}
protected override IDictionary<string, string> GetCookies()
@@ -123,7 +123,6 @@ namespace NzbDrone.Core.Indexers.Definitions
public class DigitalCoreRequestGenerator : IIndexerRequestGenerator
{
public string BaseUrl { get; set; }
public DigitalCoreSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
@@ -138,7 +137,7 @@ namespace NzbDrone.Core.Indexers.Definitions
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
{
var searchUrl = string.Format("{0}/api/v1/torrents", BaseUrl.TrimEnd('/'));
var searchUrl = string.Format("{0}/api/v1/torrents", Settings.BaseUrl.TrimEnd('/'));
var parameters = new NameValueCollection();
@@ -226,15 +225,13 @@ namespace NzbDrone.Core.Indexers.Definitions
public class DigitalCoreParser : IParseIndexerResponse
{
private readonly string _baseUrl;
private readonly DigitalCoreSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public DigitalCoreParser(DigitalCoreSettings settings, IndexerCapabilitiesCategories categories, string baseUrl)
public DigitalCoreParser(DigitalCoreSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
_baseUrl = baseUrl;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@@ -270,8 +267,11 @@ namespace NzbDrone.Core.Indexers.Definitions
release.Files = row.numfiles;
release.Grabs = row.times_completed;
release.Guid = new Uri(_baseUrl + "torrent/" + row.id.ToString() + "/").ToString();
release.DownloadUrl = _baseUrl + "api/v1/torrents/download/" + row.id.ToString();
var infoUrl = _settings.BaseUrl + "torrent/" + row.id.ToString() + "/";
release.Guid = infoUrl;
release.DownloadUrl = _settings.BaseUrl + "api/v1/torrents/download/" + row.id.ToString();
release.InfoUrl = infoUrl;
if (row.frileech == 1)
{
@@ -317,7 +317,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
public class DigitalCoreSettings : IProviderConfig
public class DigitalCoreSettings : IIndexerSettings
{
private static readonly DigitalCoreSettingsValidator Validator = new DigitalCoreSettingsValidator();
@@ -327,12 +327,18 @@ namespace NzbDrone.Core.Indexers.Definitions
Passphrase = "";
}
[FieldDefinition(1, Label = "UID", HelpText = "Uid from login cookie")]
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "UID", HelpText = "UID from login cookie", Privacy = PrivacyLevel.UserName)]
public string UId { get; set; }
[FieldDefinition(2, Label = "Passphrase", HelpText = "Pass from login cookie")]
[FieldDefinition(3, Label = "Passphrase", HelpText = "Passphrase from login cookie", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
public string Passphrase { get; set; }
[FieldDefinition(4)]
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -10,7 +10,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public class ExoticaZ : AvistazBase
{
public override string Name => "ExoticaZ";
public override string BaseUrl => "https://exoticaz.to/";
public override string[] IndexerUrls => new string[] { "https://exoticaz.to/" };
public override string Description => "ExoticaZ (YourExotic) is a Private Torrent Tracker for 3X";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public ExoticaZ(IIndexerRepository indexerRepository, IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
@@ -26,7 +27,6 @@ namespace NzbDrone.Core.Indexers.Definitions
HttpClient = _httpClient,
Logger = _logger,
Capabilities = Capabilities,
BaseUrl = BaseUrl
};
}
@@ -45,8 +45,9 @@ namespace NzbDrone.Core.Indexers.Definitions
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.XXXPack);
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.XXXPack);
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.XXXDVD);
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.XXXOther);
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.XXXx264);
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.XXXImageSet);
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.XXXImageSet);
return caps;
}

View File

@@ -9,7 +9,8 @@ namespace NzbDrone.Core.Indexers.FileList
public class FileList : TorrentIndexerBase<FileListSettings>
{
public override string Name => "FileList.io";
public override string BaseUrl => "https://filelist.io";
public override string[] IndexerUrls => new string[] { "https://filelist.io" };
public override string Description => "";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override bool SupportsRss => true;
@@ -24,12 +25,12 @@ namespace NzbDrone.Core.Indexers.FileList
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new FileListRequestGenerator() { Settings = Settings, BaseUrl = BaseUrl, Capabilities = Capabilities };
return new FileListRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new FileListParser(Settings, BaseUrl, Capabilities.Categories);
return new FileListParser(Settings, Capabilities.Categories);
}
private IndexerCapabilities SetCapabilities()

View File

@@ -10,14 +10,12 @@ namespace NzbDrone.Core.Indexers.FileList
{
public class FileListParser : IParseIndexerResponse
{
private readonly string _baseUrl;
private readonly FileListSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public FileListParser(FileListSettings settings, string baseUrl, IndexerCapabilitiesCategories categories)
public FileListParser(FileListSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_baseUrl = baseUrl;
_categories = categories;
}
@@ -78,7 +76,7 @@ namespace NzbDrone.Core.Indexers.FileList
private string GetDownloadUrl(string torrentId)
{
var url = new HttpUri(_baseUrl)
var url = new HttpUri(_settings.BaseUrl)
.CombinePath("/download.php")
.AddQueryParam("id", torrentId)
.AddQueryParam("passkey", _settings.Passkey);
@@ -88,7 +86,7 @@ namespace NzbDrone.Core.Indexers.FileList
private string GetInfoUrl(string torrentId)
{
var url = new HttpUri(_baseUrl)
var url = new HttpUri(_settings.BaseUrl)
.CombinePath("/details.php")
.AddQueryParam("id", torrentId);

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
@@ -9,7 +8,6 @@ namespace NzbDrone.Core.Indexers.FileList
{
public class FileListRequestGenerator : IIndexerRequestGenerator
{
public string BaseUrl { get; set; }
public FileListSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public Func<IDictionary<string, string>> GetCookies { get; set; }
@@ -109,7 +107,7 @@ namespace NzbDrone.Core.Indexers.FileList
{
var categoriesQuery = string.Join(",", Capabilities.Categories.MapTorznabCapsToTrackers(categories));
var baseUrl = string.Format("{0}/api.php?action={1}&category={2}&username={3}&passkey={4}{5}", BaseUrl.TrimEnd('/'), searchType, categoriesQuery, Settings.Username.Trim(), Settings.Passkey.Trim(), parameters);
var baseUrl = string.Format("{0}/api.php?action={1}&category={2}&username={3}&passkey={4}{5}", Settings.BaseUrl.TrimEnd('/'), searchType, categoriesQuery, Settings.Username.Trim(), Settings.Passkey.Trim(), parameters);
yield return new IndexerRequest(baseUrl, HttpAccept.Json);
}

View File

@@ -1,6 +1,5 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.FileList
@@ -14,20 +13,27 @@ namespace NzbDrone.Core.Indexers.FileList
}
}
public class FileListSettings : IProviderConfig
public class FileListSettings : IIndexerSettings
{
private static readonly FileListSettingsValidator Validator = new FileListSettingsValidator();
public FileListSettings()
{
BaseUrl = "https://filelist.io";
}
[FieldDefinition(0, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(1, Label = "Passkey", Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(3, Label = "Passkey", HelpText = "Site Passkey", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
public string Passkey { get; set; }
[FieldDefinition(4)]
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -10,8 +10,8 @@ namespace NzbDrone.Core.Indexers.Gazelle
public abstract class Gazelle : TorrentIndexerBase<GazelleSettings>
{
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override string BaseUrl => "";
protected virtual string LoginUrl => BaseUrl + "login.php";
public override string[] IndexerUrls => new string[] { "" };
protected virtual string LoginUrl => Settings.BaseUrl + "login.php";
public override bool SupportsRss => true;
public override bool SupportsSearch => true;
public override int PageSize => 50;
@@ -33,14 +33,13 @@ namespace NzbDrone.Core.Indexers.Gazelle
Settings = Settings,
HttpClient = _httpClient,
Logger = _logger,
Capabilities = Capabilities,
BaseUrl = BaseUrl
Capabilities = Capabilities
};
}
public override IParseIndexerResponse GetParser()
{
return new GazelleParser(Settings, Capabilities, BaseUrl);
return new GazelleParser(Settings, Capabilities);
}
protected virtual IndexerCapabilities SetCapabilities()

View File

@@ -13,13 +13,11 @@ namespace NzbDrone.Core.Indexers.Gazelle
{
protected readonly GazelleSettings _settings;
protected readonly IndexerCapabilities _capabilities;
protected readonly string _baseUrl;
public GazelleParser(GazelleSettings settings, IndexerCapabilities capabilities, string baseUrl)
public GazelleParser(GazelleSettings settings, IndexerCapabilities capabilities)
{
_settings = settings;
_capabilities = capabilities;
_baseUrl = baseUrl;
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
@@ -140,7 +138,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
protected virtual string GetDownloadUrl(int torrentId)
{
var url = new HttpUri(_baseUrl)
var url = new HttpUri(_settings.BaseUrl)
.CombinePath("/torrents.php")
.AddQueryParam("action", "download")
.AddQueryParam("useToken", _settings.UseFreeleechToken ? "1" : "0")
@@ -151,7 +149,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
private string GetInfoUrl(string groupId, int torrentId)
{
var url = new HttpUri(_baseUrl)
var url = new HttpUri(_settings.BaseUrl)
.CombinePath("/torrents.php")
.AddQueryParam("id", groupId)
.AddQueryParam("torrentid", torrentId);

View File

@@ -9,14 +9,13 @@ namespace NzbDrone.Core.Indexers.Gazelle
public class GazelleRequestGenerator : IIndexerRequestGenerator
{
public GazelleSettings Settings { get; set; }
public string BaseUrl { get; set; }
public IDictionary<string, string> AuthCookieCache { get; set; }
public IHttpClient HttpClient { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public Logger Logger { get; set; }
protected virtual string APIUrl => BaseUrl + "ajax.php";
protected virtual string APIUrl => Settings.BaseUrl + "ajax.php";
protected virtual bool ImdbInTags => false;
public Func<IDictionary<string, string>> GetCookies { get; set; }
@@ -76,11 +75,11 @@ namespace NzbDrone.Core.Indexers.Gazelle
{
if (ImdbInTags)
{
parameters += string.Format("&taglist={0}", searchCriteria.ImdbId);
parameters += string.Format("&taglist={0}", searchCriteria.FullImdbId);
}
else
{
parameters += string.Format("&cataloguenumber={0}", searchCriteria.ImdbId);
parameters += string.Format("&cataloguenumber={0}", searchCriteria.FullImdbId);
}
}
@@ -121,11 +120,11 @@ namespace NzbDrone.Core.Indexers.Gazelle
{
if (ImdbInTags)
{
parameters += string.Format("&taglist={0}", searchCriteria.ImdbId);
parameters += string.Format("&taglist={0}", searchCriteria.FullImdbId);
}
else
{
parameters += string.Format("&cataloguenumber={0}", searchCriteria.ImdbId);
parameters += string.Format("&cataloguenumber={0}", searchCriteria.FullImdbId);
}
}

View File

@@ -1,6 +1,5 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Gazelle
@@ -14,7 +13,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
}
}
public class GazelleSettings : IProviderConfig
public class GazelleSettings : IIndexerSettings
{
private static readonly GazelleSettingsValidator Validator = new GazelleSettingsValidator();
@@ -25,15 +24,21 @@ namespace NzbDrone.Core.Indexers.Gazelle
public string AuthKey;
public string PassKey;
[FieldDefinition(1, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(2, Label = "Password", HelpText = "Site Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
public string Password { get; set; }
[FieldDefinition(3, Type = FieldType.Checkbox, Label = "Use Freeleech Token", HelpText = "Use Freeleech Token")]
[FieldDefinition(4, Type = FieldType.Checkbox, Label = "Use Freeleech Token", HelpText = "Use Freeleech Token")]
public bool UseFreeleechToken { get; set; }
[FieldDefinition(5)]
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Text;
using AngleSharp.Html.Parser;
using FluentValidation;
@@ -21,8 +22,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public class GazelleGames : TorrentIndexerBase<GazelleGamesSettings>
{
public override string Name => "GazelleGames";
public override string BaseUrl => "https://gazellegames.net/";
public override string Description => "A gaming tracker.";
public override string[] IndexerUrls => new string[] { "https://gazellegames.net/" };
public override string Description => "GazelleGames (GGn) is a Private Torrent Tracker for GAMES";
public override string Language => "en-us";
public override Encoding Encoding => Encoding.UTF8;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
@@ -36,12 +37,12 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new GazelleGamesRequestGenerator() { Settings = Settings, Capabilities = Capabilities, BaseUrl = BaseUrl };
return new GazelleGamesRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new GazelleGamesParser(Settings, Capabilities.Categories, BaseUrl);
return new GazelleGamesParser(Settings, Capabilities.Categories);
}
protected override IDictionary<string, string> GetCookies()
@@ -188,7 +189,6 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public GazelleGamesSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public string BaseUrl { get; set; }
public GazelleGamesRequestGenerator()
{
@@ -196,7 +196,7 @@ namespace NzbDrone.Core.Indexers.Definitions
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{
var searchUrl = string.Format("{0}/torrents.php", BaseUrl.TrimEnd('/'));
var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/'));
var searchString = term;
@@ -278,13 +278,11 @@ namespace NzbDrone.Core.Indexers.Definitions
{
private readonly GazelleGamesSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
private readonly string _baseUrl;
public GazelleGamesParser(GazelleGamesSettings settings, IndexerCapabilitiesCategories categories, string baseurl)
public GazelleGamesParser(GazelleGamesSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
_baseUrl = baseurl;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@@ -355,12 +353,12 @@ namespace NzbDrone.Core.Indexers.Definitions
var qFreeLeech = row.QuerySelector("strong.freeleech_label");
var qNeutralLeech = row.QuerySelector("strong.neutralleech_label");
var time = qTime.GetAttribute("title");
var link = _baseUrl + qDLLink.GetAttribute("href");
var link = _settings.BaseUrl + qDLLink.GetAttribute("href");
var seeders = ParseUtil.CoerceInt(qSeeders.TextContent);
var publishDate = DateTime.SpecifyKind(
DateTime.ParseExact(time, "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture),
DateTimeKind.Unspecified).ToLocalTime();
var details = _baseUrl + qDetailsLink.GetAttribute("href");
var details = _settings.BaseUrl + qDetailsLink.GetAttribute("href");
var grabs = ParseUtil.CoerceInt(qGrabs.TextContent);
var leechers = ParseUtil.CoerceInt(qLeechers.TextContent);
var size = ReleaseInfo.GetBytes(sizeString);
@@ -402,7 +400,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
public class GazelleGamesSettings : IProviderConfig
public class GazelleGamesSettings : IIndexerSettings
{
private static readonly GazelleGamesSettingsValidator Validator = new GazelleGamesSettingsValidator();
@@ -412,12 +410,18 @@ namespace NzbDrone.Core.Indexers.Definitions
SearchGroupNames = false;
}
[FieldDefinition(1, Label = "Cookie", HelpText = "Login cookie from website")]
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "Cookie", HelpText = "Login cookie from website")]
public string Cookie { get; set; }
[FieldDefinition(2, Label = "Search Group Names", Type = FieldType.Checkbox, HelpText = "Search Group Names Only")]
[FieldDefinition(3, Label = "Search Group Names", Type = FieldType.Checkbox, HelpText = "Search Group Names Only")]
public bool SearchGroupNames { get; set; }
[FieldDefinition(4)]
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -9,7 +9,8 @@ namespace NzbDrone.Core.Indexers.HDBits
public class HDBits : TorrentIndexerBase<HDBitsSettings>
{
public override string Name => "HDBits";
public override string BaseUrl => "https://hdbits.org";
public override string[] IndexerUrls => new string[] { "https://hdbits.org" };
public override string Description => "Best HD Tracker";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
@@ -24,12 +25,12 @@ namespace NzbDrone.Core.Indexers.HDBits
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new HDBitsRequestGenerator() { Settings = Settings, BaseUrl = BaseUrl, Capabilities = Capabilities };
return new HDBitsRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new HDBitsParser(Settings, BaseUrl);
return new HDBitsParser(Settings);
}
private IndexerCapabilities SetCapabilities()

View File

@@ -11,13 +11,11 @@ namespace NzbDrone.Core.Indexers.HDBits
{
public class HDBitsParser : IParseIndexerResponse
{
private readonly string _baseUrl;
private readonly HDBitsSettings _settings;
public HDBitsParser(HDBitsSettings settings, string baseUrl)
public HDBitsParser(HDBitsSettings settings)
{
_settings = settings;
_baseUrl = baseUrl;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@@ -91,7 +89,7 @@ namespace NzbDrone.Core.Indexers.HDBits
private string GetDownloadUrl(string torrentId)
{
var url = new HttpUri(_baseUrl)
var url = new HttpUri(_settings.BaseUrl)
.CombinePath("/download.php")
.AddQueryParam("id", torrentId)
.AddQueryParam("passkey", _settings.ApiKey);
@@ -101,7 +99,7 @@ namespace NzbDrone.Core.Indexers.HDBits
private string GetInfoUrl(string torrentId)
{
var url = new HttpUri(_baseUrl)
var url = new HttpUri(_settings.BaseUrl)
.CombinePath("/details.php")
.AddQueryParam("id", torrentId);

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