Compare commits

..

89 Commits

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

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

Currently translated at 65.6% (279 of 425 strings)

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

Currently translated at 65.6% (279 of 425 strings)

Translated using Weblate (French)

Currently translated at 100.0% (425 of 425 strings)

Added translation using Weblate (Chinese (Simplified))

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

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.8% (416 of 425 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.8% (416 of 425 strings)

Translated using Weblate (Portuguese)

Currently translated at 82.3% (350 of 425 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (425 of 425 strings)

Translated using Weblate (French)

Currently translated at 98.3% (418 of 425 strings)

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

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

This reverts commit 90c95b240f.

* fix(TableMapping): make own non Camel Case Converter for Cookies
2021-06-15 21:13:37 -04:00
bakerboy448
66dc53b92f Fixed: Remove Defunct Indexer - Usenet - NZBs2GO (#216)
Fixes #214
2021-06-14 15:12:57 -05:00
Qstick
334f3514df Cleanup conversion script issues 2021-06-13 18:26:32 -04:00
Qstick
6ce35f6a24 Fix Nebulance Settings copy paste issues 2021-06-13 17:49:15 -04:00
Qstick
f6a5f887ce New: Convert Shareisland to use API 2021-06-13 01:40:07 -04:00
Qstick
bc90415394 New: (Indexer) Nebulance 2021-06-13 01:10:46 -04:00
171 changed files with 3921 additions and 821 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

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

View File

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

View File

@@ -1,22 +0,0 @@
---
name: Indexer Request
about: Request an indexer for Prowlarr. Check the pinned Jackett parity issue prior to submitting a request. Duplicated requests will be closed without warning. Please search GitHub prior to requesting.
title: '(Indexer) '
labels: 'Type: Indexer Request'
assignees: ''
---
<!-- Check the pinned Jackett parity issue prior to submitting a request. Duplicated requests or requests covered in existing GitHub Issues will be closed without warning. Please search GitHub prior to requesting.-->
**Type:** <Usenet|Torrents>
**Tracker:** <Indexer/Tracker Name>
**URL:** <Indexer/Tracker URL>
**In Jackett?:** <Yes|No>
<!-- Check the pinned Jackett parity issue prior to submitting a request. Duplicated requests or requests covered in existing GitHub Issues will be closed without warning. Please search GitHub prior to requesting.-->
**Additional Context:**
<!-- Add any other context or screenshots about the request here. -->

View File

@@ -5,6 +5,7 @@ YES | NO
A few sentences describing the overall goals of the pull request's commits.
#### Screenshot (if UI related)
#### Todos
- [ ] Tests
- [ ] Translation Keys

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

@@ -3,7 +3,7 @@
We're always looking for people to help make Prowlarr even better, there are a number of ways to contribute.
## Documentation ##
Setup guides, FAQ, the more information we have on the [wiki](https://wikijs.servarr.com/prowlarr) the better.
Setup guides, FAQ, the more information we have on the [wiki](https://wiki.servarr.com/prowlarr) the better.
## Development ##
@@ -23,11 +23,11 @@ Setup guides, FAQ, the more information we have on the [wiki](https://wikijs.ser
4. Start gulp to monitor your dev environment for any changes that need post processing using `yarn start` command.
5. Build the project in Visual Studio, Setting startup project to `Prowlarr.Console` and framework to `net5.0`
6. Debug the project in Visual Studio
7. Open http://localhost:7878
7. Open http://localhost:9696
### Contributing Code ###
- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Prowlarr/Prowlarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first)
- Rebase from 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

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

View File

@@ -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

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

View File

@@ -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

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

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

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

View File

@@ -26,7 +26,7 @@ function NotificationEventItems(props) {
<div>
<FormInputHelpText
text={translate('NotificationTriggersHelpText')}
link="https://wikijs.servarr.com/prowlarr/settings#connections"
link="https://wiki.servarr.com/prowlarr/settings#connections"
/>
<div className={styles.events}>
<div>

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ class MoreInfo extends Component {
<DescriptionListItemTitle>{translate('Wiki')}</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to="https://wikijs.servarr.com/prowlarr">wikijs.servarr.com/prowlarr</Link>
<Link to="https://wiki.servarr.com/prowlarr">wiki.servarr.com/prowlarr</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>{translate('Reddit')}</DescriptionListItemTitle>

View File

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

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

@@ -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|password)=(?<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

@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Test.HealthCheck
[TestFixture]
public class HealthCheckFixture : CoreTest
{
private const string WikiRoot = "https://wikijs.servarr.com/";
private const string WikiRoot = "https://wiki.servarr.com/";
[TestCase("I blew up because of some weird user mistake", null, WikiRoot + "prowlarr/system#i-blew-up-because-of-some-weird-user-mistake")]
[TestCase("I blew up because of some weird user mistake", "#my-health-check", WikiRoot + "prowlarr/system#my-health-check")]

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()
{

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
@@ -31,7 +32,25 @@ namespace NzbDrone.Core.Applications.Lidarr
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_lidarrV1Proxy.Test(Settings));
var testIndexer = new IndexerDefinition
{
Id = 0,
Name = "Test",
Protocol = DownloadProtocol.Usenet,
Capabilities = new IndexerCapabilities()
};
testIndexer.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.Audio);
try
{
failures.AddIfNotNull(_lidarrV1Proxy.TestConnection(BuildLidarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Lidarr"));
}
return new ValidationResult(failures);
}

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
@@ -31,7 +32,25 @@ namespace NzbDrone.Core.Applications.Radarr
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_radarrV3Proxy.Test(Settings));
var testIndexer = new IndexerDefinition
{
Id = 0,
Name = "Test",
Protocol = DownloadProtocol.Usenet,
Capabilities = new IndexerCapabilities()
};
testIndexer.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies);
try
{
failures.AddIfNotNull(_radarrV3Proxy.TestConnection(BuildRadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Radarr"));
}
return new ValidationResult(failures);
}

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
@@ -31,7 +32,25 @@ namespace NzbDrone.Core.Applications.Readarr
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_readarrV1Proxy.Test(Settings));
var testIndexer = new IndexerDefinition
{
Id = 0,
Name = "Test",
Protocol = DownloadProtocol.Usenet,
Capabilities = new IndexerCapabilities()
};
testIndexer.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.Books);
try
{
failures.AddIfNotNull(_readarrV1Proxy.TestConnection(BuildReadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Readarr"));
}
return new ValidationResult(failures);
}

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
@@ -31,7 +32,25 @@ namespace NzbDrone.Core.Applications.Sonarr
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_sonarrV3Proxy.Test(Settings));
var testIndexer = new IndexerDefinition
{
Id = 0,
Name = "Test",
Protocol = DownloadProtocol.Usenet,
Capabilities = new IndexerCapabilities()
};
testIndexer.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.TV);
try
{
failures.AddIfNotNull(_sonarrV3Proxy.TestConnection(BuildSonarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Sonarr"));
}
return new ValidationResult(failures);
}

View File

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

View File

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

View File

@@ -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

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

View File

@@ -108,10 +108,10 @@ namespace NzbDrone.Core.Datastore
if (OsInfo.IsOsx)
{
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wikijs.servarr.com/prowlarr/faq#i-use-prowlarr-on-a-mac-and-it-suddenly-stopped-working-what-happened", e, fileName);
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/prowlarr/faq#i-use-prowlarr-on-a-mac-and-it-suddenly-stopped-working-what-happened", e, fileName);
}
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wikijs.servarr.com/prowlarr/faq#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName);
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/prowlarr/faq#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName);
}
catch (Exception e)
{

View File

@@ -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

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

View File

@@ -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

@@ -39,7 +39,7 @@ namespace NzbDrone.Core.HealthCheck
private static HttpUri MakeWikiUrl(string fragment)
{
return new HttpUri("https://wikijs.servarr.com/prowlarr/system#") + new HttpUri(fragment);
return new HttpUri("https://wiki.servarr.com/prowlarr/system#") + new HttpUri(fragment);
}
}

View File

@@ -6,6 +6,7 @@ using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Cardigann;
using NzbDrone.Core.Messaging.Commands;
@@ -24,7 +25,7 @@ namespace NzbDrone.Core.IndexerVersions
public class IndexerDefinitionUpdateService : IIndexerDefinitionUpdateService, IExecute<IndexerDefinitionUpdateCommand>
{
private const int DEFINITION_VERSION = 1;
private readonly List<string> _defintionBlacklist = new List<string>() { "aither", "animeworld", "blutopia", "beyond-hd", "beyond-hd-oneurl", "hdbits" };
private readonly List<string> _defintionBlacklist = new List<string>() { "aither", "animeworld", "blutopia", "beyond-hd", "beyond-hd-oneurl", "hdbits", "shareisland" };
private readonly IHttpClient _httpClient;
private readonly IAppFolderInfo _appFolderInfo;
@@ -59,6 +60,40 @@ namespace NzbDrone.Core.IndexerVersions
var request = new HttpRequest($"https://indexers.prowlarr.com/master/{DEFINITION_VERSION}");
var response = _httpClient.Get<List<CardigannMetaDefinition>>(request);
indexerList = response.Resource.Where(i => !_defintionBlacklist.Contains(i.File)).ToList();
var definitionFolder = Path.Combine(_appFolderInfo.AppDataFolder, "Definitions", "Custom");
var directoryInfo = new DirectoryInfo(definitionFolder);
if (directoryInfo.Exists)
{
var files = directoryInfo.GetFiles($"*.yml");
foreach (var file in files)
{
_logger.Debug("Loading Custom Cardigann definition " + file.FullName);
try
{
var definitionString = File.ReadAllText(file.FullName);
var definition = _deserializer.Deserialize<CardigannMetaDefinition>(definitionString);
definition.File = Path.GetFileNameWithoutExtension(file.Name);
if (indexerList.Any(i => i.File == definition.File || i.Name == definition.Name))
{
_logger.Warn("Custom Cardigann definition {0} does not have unique file name or Indexer name", file.FullName);
continue;
}
indexerList.Add(definition);
}
catch (Exception e)
{
_logger.Error($"Error while parsing custom Cardigann definition {file.FullName}\n{e}");
}
}
}
}
catch
{
@@ -108,7 +143,7 @@ namespace NzbDrone.Core.IndexerVersions
if (directoryInfo.Exists)
{
var files = directoryInfo.GetFiles($"{fileKey}.yml");
var files = directoryInfo.GetFiles($"{fileKey}.yml", SearchOption.AllDirectories);
if (files.Any())
{

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
};
}

View File

@@ -18,7 +18,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,7 +25,7 @@ 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[] IndexerUrls => new string[] { "https://animebytes.tv/" };
public override string Description => "Powered by Tentacles";
public override string Language => "en-us";
public override Encoding Encoding => Encoding.UTF8;
@@ -41,12 +40,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 +100,6 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public AnimeBytesSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public string BaseUrl { get; set; }
public AnimeBytesRequestGenerator()
{
@@ -109,7 +107,7 @@ 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
{
@@ -189,13 +187,11 @@ namespace NzbDrone.Core.Indexers.Definitions
{
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)
@@ -323,7 +319,7 @@ namespace NzbDrone.Core.Indexers.Definitions
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 details = new Uri(_settings.BaseUrl + "torrent/" + torrentId + "/group");
var size = (long)torrent["Size"];
var snatched = (int)torrent["Snatched"];
var seeders = (int)torrent["Seeders"];
@@ -484,7 +480,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
public class AnimeBytesSettings : IProviderConfig
public class AnimeBytesSettings : IIndexerSettings
{
private static readonly AnimeBytesSettingsValidator Validator = new AnimeBytesSettingsValidator();
@@ -494,10 +490,13 @@ namespace NzbDrone.Core.Indexers.Definitions
Username = "";
}
[FieldDefinition(1, Label = "Passkey", 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", Privacy = PrivacyLevel.Password, Type = FieldType.Password, HelpText = "Site Passkey")]
public string Passkey { get; set; }
[FieldDefinition(1, Label = "Username", HelpText = "Site username")]
[FieldDefinition(3, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
public NzbDroneValidationResult Validate()

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,10 +347,13 @@ namespace NzbDrone.Core.Indexers.Definitions
Password = "";
}
[FieldDefinition(1, Label = "Username", HelpText = "Site 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", Type = FieldType.Password, HelpText = "Site password", Privacy = PrivacyLevel.Password)]
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, HelpText = "Site Password", Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
public NzbDroneValidationResult Validate()

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,332 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AngleSharp.Html.Parser;
using FluentValidation;
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.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
public class Anthelion : TorrentIndexerBase<AnthelionSettings>
{
public override string Name => "Anthelion";
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;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
public Anthelion(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AnthelionRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new AnthelionParser(Settings, Capabilities.Categories);
}
protected override async Task DoLogin()
{
var requestBuilder = new HttpRequestBuilder(LoginUrl)
{
LogResponseContent = true,
AllowAutoRedirect = true
};
requestBuilder.Method = HttpMethod.POST;
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
var cookies = Cookies;
Cookies = null;
var authLoginRequest = requestBuilder
.AddFormParameter("username", Settings.Username)
.AddFormParameter("password", Settings.Password)
.AddFormParameter("keeplogged", "1")
.AddFormParameter("login", "Log+In!")
.SetHeader("Content-Type", "multipart/form-data")
.Build();
var headers = new NameValueCollection
{
{ "Referer", LoginUrl }
};
authLoginRequest.Headers.Add(headers);
var response = await ExecuteAuth(authLoginRequest);
if (CheckIfLoginNeeded(response))
{
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();
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
_logger.Debug("Anthelion authentication succeeded.");
}
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{
if (!httpResponse.Content.Contains("logout.php"))
{
return true;
}
return false;
}
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.Movies, "Film/Feature");
caps.Categories.AddCategoryMapping("2", NewznabStandardCategory.Movies, "Film/Short");
caps.Categories.AddCategoryMapping("3", NewznabStandardCategory.TV, "TV/Miniseries");
caps.Categories.AddCategoryMapping("4", NewznabStandardCategory.Other, "Other");
return caps;
}
}
public class AnthelionRequestGenerator : IIndexerRequestGenerator
{
public AnthelionSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public AnthelionRequestGenerator()
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
{
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
{
{ "order_by", "time" },
{ "order_way", "desc" },
{ "action", "basic" },
{ "searchsubmit", "1" },
{ "searchstr", imdbId.IsNotNullOrWhiteSpace() ? imdbId : term }
};
var catList = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
foreach (var cat in catList)
{
qc.Add($"filter_cat[{cat}]", "1");
}
searchUrl = searchUrl + "?" + qc.GetQueryString();
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
yield return request;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.FullImdbId));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories, searchCriteria.FullImdbId));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
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 AnthelionParser : IParseIndexerResponse
{
private readonly AnthelionSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public AnthelionParser(AnthelionSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var doc = parser.ParseDocument(indexerResponse.Content);
var rows = doc.QuerySelectorAll("table.torrent_table > tbody > tr.torrent");
foreach (var row in rows)
{
var qDetailsLink = row.QuerySelector("a.torrent_name");
var year = qDetailsLink.NextSibling.TextContent.Replace("[", "").Replace("]", "").Trim();
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 = _settings.BaseUrl + qDetailsLink.GetAttribute("href");
var torrentId = qDetailsLink.GetAttribute("href").Split('=').Last();
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 files = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(3)").TextContent);
var publishDate = DateTimeUtil.FromTimeAgo(row.QuerySelector("td:nth-child(4)").TextContent);
var size = ReleaseInfo.GetBytes(row.QuerySelector("td:nth-child(5)").FirstChild.TextContent);
var grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(6)").TextContent);
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(7)").TextContent);
var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(8)").TextContent);
var dlVolumeFactor = row.QuerySelector("strong.tl_free") != null ? 0 : 1;
var cat = row.QuerySelector("td.cats_col > div").GetAttribute("class").Replace("tooltip cats_", "");
var category = new List<IndexerCategory>
{
cat switch
{
"featurefilm" => NewznabStandardCategory.Movies,
"shortfilm" => NewznabStandardCategory.Movies,
"miniseries" => NewznabStandardCategory.TV,
"other" => NewznabStandardCategory.Other,
_ => throw new Exception($"Unknown category: {cat}")
}
};
// TODO: TMDb is also available
var qImdb = row.QuerySelector("a[href^=\"https://www.imdb.com\"]");
var imdb = qImdb != null ? ParseUtil.GetImdbID(qImdb.GetAttribute("href").Split('/').Last()) : null;
var release = new TorrentInfo
{
MinimumRatio = 1,
MinimumSeedTime = 259200,
Description = description,
Title = title,
PublishDate = publishDate,
Categories = category,
DownloadUrl = link,
InfoUrl = details,
Guid = link,
ImdbId = imdb.GetValueOrDefault(),
Seeders = seeders,
Peers = leechers + seeders,
Size = size,
Grabs = grabs,
Files = files,
DownloadVolumeFactor = dlVolumeFactor,
UploadVolumeFactor = 1
};
torrentInfos.Add(release);
}
return torrentInfos.ToArray();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class AnthelionSettingsValidator : AbstractValidator<AnthelionSettings>
{
public AnthelionSettingsValidator()
{
RuleFor(c => c.Username).NotEmpty();
RuleFor(c => c.Password).NotEmpty();
}
}
public class AnthelionSettings : IIndexerSettings
{
private static readonly AnthelionSettingsValidator Validator = new AnthelionSettingsValidator();
public AnthelionSettings()
{
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", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
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
@@ -11,10 +10,11 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{
RuleFor(c => c.Username).NotEmpty();
RuleFor(c => c.Password).NotEmpty();
RuleFor(c => c.Pid).NotEmpty();
}
}
public class AvistazSettings : IProviderConfig
public class AvistazSettings : IIndexerSettings
{
private static readonly AvistazSettingsValidator Validator = new AvistazSettingsValidator();
@@ -25,13 +25,16 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public string Token { get; set; }
[FieldDefinition(1, Label = "Username", HelpText = "Username", 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 = "Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(2, Label = "Password", Type = FieldType.Password, HelpText = "Password", Privacy = PrivacyLevel.Password)]
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, HelpText = "Password", Privacy = PrivacyLevel.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; }
public NzbDroneValidationResult Validate()

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 Comunity";
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()
@@ -138,7 +138,6 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public BakaBTSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public string BaseUrl { get; set; }
public BakaBTRequestGenerator()
{
@@ -147,7 +146,7 @@ namespace NzbDrone.Core.Indexers.Definitions
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{
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)
@@ -213,14 +212,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)
@@ -300,10 +297,10 @@ namespace NzbDrone.Core.Indexers.Definitions
release.Categories = currentCategories;
//release.Description = row.QuerySelector("span.tags")?.TextContent;
release.Guid = _baseUrl + qTitleLink.GetAttribute("href");
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 +389,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
public class BakaBTSettings : IProviderConfig
public class BakaBTSettings : IIndexerSettings
{
private static readonly BakaBTSettingsValidator Validator = new BakaBTSettingsValidator();
@@ -402,16 +399,19 @@ namespace NzbDrone.Core.Indexers.Definitions
Password = "";
}
[FieldDefinition(1, Label = "Username", HelpText = "Site 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", Type = FieldType.Password, HelpText = "Site password", Privacy = PrivacyLevel.Password)]
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, HelpText = "Site Password", Privacy = PrivacyLevel.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; }
public NzbDroneValidationResult Validate()

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 => "Without BeyondHD, your HDTV is just a 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)
@@ -190,11 +187,11 @@ namespace NzbDrone.Core.Indexers.Definitions
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
}
// TODO Have BHD fix their API response content type so we can proper check here
// 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}");
// }
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<BeyondHDResponse>(indexerResponse.HttpResponse);
foreach (var row in jsonResponse.Resource.Results)
@@ -217,10 +214,10 @@ namespace NzbDrone.Core.Indexers.Definitions
ImdbId = ParseUtil.GetImdbID(row.ImdbId).GetValueOrDefault(),
TmdbId = row.TmdbId.IsNullOrWhiteSpace() ? 0 : ParseUtil.CoerceInt(row.TmdbId.Split("/")[1]),
Peers = row.Leechers + row.Seeders,
DownloadVolumeFactor = row.Freeleech ? 0 : row.Promo75 ? 0.25 : row.Promo50 ? 0.5 : row.Promo25 ? 0.75 : 1,
DownloadVolumeFactor = row.Freeleech || row.Limited ? 0 : row.Promo75 ? 0.25 : row.Promo50 ? 0.5 : row.Promo25 ? 0.75 : 1,
UploadVolumeFactor = 1,
MinimumRatio = 1,
MinimumSeedTime = 172800, // 48 hours
MinimumSeedTime = 172800, // 120 hours
};
torrentInfos.Add(release);
@@ -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,10 +247,13 @@ 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 Site", 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 Site", Privacy = PrivacyLevel.ApiKey)]
public string RssKey { get; set; }
public NzbDroneValidationResult Validate()
@@ -300,5 +300,6 @@ namespace NzbDrone.Core.Indexers.Definitions
public bool Promo25 { get; set; }
public bool Promo50 { get; set; }
public bool Promo75 { get; set; }
public bool Limited { get; set; }
}
}

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,7 +20,10 @@ 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; }
public NzbDroneValidationResult Validate()

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;
@@ -59,6 +63,16 @@ namespace NzbDrone.Core.Indexers.Cardigann
};
}
protected override IDictionary<string, string> GetCookies()
{
if (Settings.ExtraFieldData.TryGetValue("cookie", out var cookies))
{
return CookieUtil.CookieHeaderToDictionary((string)cookies);
}
return base.GetCookies();
}
public override IEnumerable<ProviderDefinition> DefaultDefinitions
{
get
@@ -108,7 +122,9 @@ namespace NzbDrone.Core.Indexers.Cardigann
Enable = true,
Name = definition.Name,
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,
@@ -220,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>();
@@ -557,7 +557,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
case "querystring":
var param = (string)filter.Args;
// data = ParseUtil.GetArgumentFromQueryString(data, param);
data = ParseUtil.GetArgumentFromQueryString(data, param);
break;
case "timeparse":
case "dateparse":

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)
@@ -40,7 +42,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
.ContainsIgnoreCase("login.php"))
{
CookiesUpdater(null, null);
throw new IndexerException(indexerResponse, "We are being redirected to the PTP login page. Most likely your session expired or was killed. Try testing the indexer in the settings.");
throw new IndexerException(indexerResponse, "We are being redirected to the login page. Most likely your session expired or was killed. Try testing the indexer in the settings.");
}
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");

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,
@@ -797,6 +798,11 @@ namespace NzbDrone.Core.Indexers.Cardigann
return false;
}
if (response.HasHttpError)
{
return true;
}
var parser = new HtmlParser();
var document = parser.ParseDocument(response.Content);

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,9 @@ namespace NzbDrone.Core.Indexers.Cardigann
[FieldDefinition(0, Hidden = HiddenType.Hidden)]
public string DefinitionFile { get; set; }
[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; }
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 => "Part of the Avistaz network.";
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

@@ -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,8 @@ 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();
release.Guid = new Uri(_settings.BaseUrl + "torrent/" + row.id.ToString() + "/").ToString();
release.DownloadUrl = _settings.BaseUrl + "api/v1/torrents/download/" + row.id.ToString();
if (row.frileech == 1)
{
@@ -317,7 +314,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
public class DigitalCoreSettings : IProviderConfig
public class DigitalCoreSettings : IIndexerSettings
{
private static readonly DigitalCoreSettingsValidator Validator = new DigitalCoreSettingsValidator();
@@ -327,10 +324,13 @@ 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")]
public string UId { get; set; }
[FieldDefinition(2, Label = "Passphrase", HelpText = "Pass from login cookie")]
[FieldDefinition(3, Label = "Passphrase", HelpText = "Pass from login cookie")]
public string Passphrase { get; set; }
public NzbDroneValidationResult Validate()

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
};
}

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;
}
@@ -40,11 +38,6 @@ namespace NzbDrone.Core.Indexers.FileList
var flags = new List<IndexerFlag>();
if (result.FreeLeech)
{
flags.Add(IndexerFlag.FreeLeech);
}
var imdbId = 0;
if (result.ImdbId != null && result.ImdbId.Length > 2)
{
@@ -83,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);
@@ -93,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,18 +13,22 @@ 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", 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", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(1, Label = "Passkey", Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(3, Label = "Passkey", Privacy = PrivacyLevel.ApiKey)]
public string Passkey { get; set; }
public NzbDroneValidationResult Validate()

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

@@ -11,15 +11,13 @@ namespace NzbDrone.Core.Indexers.Gazelle
{
public class GazelleParser : IParseIndexerResponse
{
private readonly GazelleSettings _settings;
private readonly IndexerCapabilities _capabilities;
private readonly string _baseUrl;
protected readonly GazelleSettings _settings;
protected readonly IndexerCapabilities _capabilities;
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; }
@@ -138,11 +136,12 @@ namespace NzbDrone.Core.Indexers.Gazelle
.ToArray();
}
private string GetDownloadUrl(int torrentId)
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")
.AddQueryParam("id", torrentId);
return url.FullUri;
@@ -150,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,16 +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 DownloadUrl => BaseUrl + "torrents.php?action=download&usetoken=" + (Settings.UseFreeleechToken ? "1" : "0") + "&id=";
protected virtual string DetailsUrl => BaseUrl + "torrents.php?torrentid=";
protected virtual string APIUrl => Settings.BaseUrl + "ajax.php";
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.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,13 +24,16 @@ namespace NzbDrone.Core.Indexers.Gazelle
public string AuthKey;
public string PassKey;
[FieldDefinition(1, Label = "Username", HelpText = "Username", 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 = "Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(2, Label = "Password", Type = FieldType.Password, HelpText = "Password", Privacy = PrivacyLevel.Password)]
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, HelpText = "Password", Privacy = PrivacyLevel.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; }
public NzbDroneValidationResult Validate()

View File

@@ -0,0 +1,427 @@
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;
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.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
public class GazelleGames : TorrentIndexerBase<GazelleGamesSettings>
{
public override string Name => "GazelleGames";
public override string[] IndexerUrls => new string[] { "https://gazellegames.net/" };
public override string Description => "A gaming tracker.";
public override string Language => "en-us";
public override Encoding Encoding => Encoding.UTF8;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
public GazelleGames(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new GazelleGamesRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new GazelleGamesParser(Settings, Capabilities.Categories);
}
protected override IDictionary<string, string> GetCookies()
{
return CookieUtil.CookieHeaderToDictionary(Settings.Cookie);
}
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{
if (httpResponse.HasHttpRedirect && httpResponse.RedirectUrl.EndsWith("login.php"))
{
return true;
}
return false;
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
};
caps.Categories.AddCategoryMapping("Mac", NewznabStandardCategory.ConsoleOther, "Mac");
caps.Categories.AddCategoryMapping("iOS", NewznabStandardCategory.PCMobileiOS, "iOS");
caps.Categories.AddCategoryMapping("Apple Bandai Pippin", NewznabStandardCategory.ConsoleOther, "Apple Bandai Pippin");
caps.Categories.AddCategoryMapping("Android", NewznabStandardCategory.PCMobileAndroid, "Android");
caps.Categories.AddCategoryMapping("DOS", NewznabStandardCategory.PCGames, "DOS");
caps.Categories.AddCategoryMapping("Windows", NewznabStandardCategory.PCGames, "Windows");
caps.Categories.AddCategoryMapping("Xbox", NewznabStandardCategory.ConsoleXBox, "Xbox");
caps.Categories.AddCategoryMapping("Xbox 360", NewznabStandardCategory.ConsoleXBox360, "Xbox 360");
caps.Categories.AddCategoryMapping("Game Boy", NewznabStandardCategory.ConsoleOther, "Game Boy");
caps.Categories.AddCategoryMapping("Game Boy Advance", NewznabStandardCategory.ConsoleOther, "Game Boy Advance");
caps.Categories.AddCategoryMapping("Game Boy Color", NewznabStandardCategory.ConsoleOther, "Game Boy Color");
caps.Categories.AddCategoryMapping("NES", NewznabStandardCategory.ConsoleOther, "NES");
caps.Categories.AddCategoryMapping("Nintendo 64", NewznabStandardCategory.ConsoleOther, "Nintendo 64");
caps.Categories.AddCategoryMapping("Nintendo 3DS", NewznabStandardCategory.ConsoleOther, "Nintendo 3DS");
caps.Categories.AddCategoryMapping("New Nintendo 3DS", NewznabStandardCategory.ConsoleOther, "New Nintendo 3DS");
caps.Categories.AddCategoryMapping("Nintendo DS", NewznabStandardCategory.ConsoleNDS, "Nintendo DS");
caps.Categories.AddCategoryMapping("Nintendo GameCube", NewznabStandardCategory.ConsoleOther, "Nintendo GameCube");
caps.Categories.AddCategoryMapping("Pokemon Mini", NewznabStandardCategory.ConsoleOther, "Pokemon Mini");
caps.Categories.AddCategoryMapping("SNES", NewznabStandardCategory.ConsoleOther, "SNES");
caps.Categories.AddCategoryMapping("Virtual Boy", NewznabStandardCategory.ConsoleOther, "Virtual Boy");
caps.Categories.AddCategoryMapping("Wii", NewznabStandardCategory.ConsoleWii, "Wii");
caps.Categories.AddCategoryMapping("Wii U", NewznabStandardCategory.ConsoleWiiU, "Wii U");
caps.Categories.AddCategoryMapping("PlayStation 1", NewznabStandardCategory.ConsoleOther, "PlayStation 1");
caps.Categories.AddCategoryMapping("PlayStation 2", NewznabStandardCategory.ConsoleOther, "PlayStation 2");
caps.Categories.AddCategoryMapping("PlayStation 3", NewznabStandardCategory.ConsolePS3, "PlayStation 3");
caps.Categories.AddCategoryMapping("PlayStation 4", NewznabStandardCategory.ConsolePS4, "PlayStation 4");
caps.Categories.AddCategoryMapping("PlayStation Portable", NewznabStandardCategory.ConsolePSP, "PlayStation Portable");
caps.Categories.AddCategoryMapping("PlayStation Vita", NewznabStandardCategory.ConsolePSVita, "PlayStation Vita");
caps.Categories.AddCategoryMapping("Dreamcast", NewznabStandardCategory.ConsoleOther, "Dreamcast");
caps.Categories.AddCategoryMapping("Game Gear", NewznabStandardCategory.ConsoleOther, "Game Gear");
caps.Categories.AddCategoryMapping("Master System", NewznabStandardCategory.ConsoleOther, "Master System");
caps.Categories.AddCategoryMapping("Mega Drive", NewznabStandardCategory.ConsoleOther, "Mega Drive");
caps.Categories.AddCategoryMapping("Pico", NewznabStandardCategory.ConsoleOther, "Pico");
caps.Categories.AddCategoryMapping("Saturn", NewznabStandardCategory.ConsoleOther, "Saturn");
caps.Categories.AddCategoryMapping("SG-1000", NewznabStandardCategory.ConsoleOther, "SG-1000");
caps.Categories.AddCategoryMapping("Atari 2600", NewznabStandardCategory.ConsoleOther, "Atari 2600");
caps.Categories.AddCategoryMapping("Atari 5200", NewznabStandardCategory.ConsoleOther, "Atari 5200");
caps.Categories.AddCategoryMapping("Atari 7800", NewznabStandardCategory.ConsoleOther, "Atari 7800");
caps.Categories.AddCategoryMapping("Atari Jaguar", NewznabStandardCategory.ConsoleOther, "Atari Jaguar");
caps.Categories.AddCategoryMapping("Atari Lynx", NewznabStandardCategory.ConsoleOther, "Atari Lynx");
caps.Categories.AddCategoryMapping("Atari ST", NewznabStandardCategory.ConsoleOther, "Atari ST");
caps.Categories.AddCategoryMapping("Amstrad CPC", NewznabStandardCategory.ConsoleOther, "Amstrad CPC");
caps.Categories.AddCategoryMapping("ZX Spectrum", NewznabStandardCategory.ConsoleOther, "ZX Spectrum");
caps.Categories.AddCategoryMapping("MSX", NewznabStandardCategory.ConsoleOther, "MSX");
caps.Categories.AddCategoryMapping("MSX 2", NewznabStandardCategory.ConsoleOther, "MSX 2");
caps.Categories.AddCategoryMapping("Game.com", NewznabStandardCategory.ConsoleOther, "Game.com");
caps.Categories.AddCategoryMapping("Gizmondo", NewznabStandardCategory.ConsoleOther, "Gizmondo");
caps.Categories.AddCategoryMapping("V.Smile", NewznabStandardCategory.ConsoleOther, "V.Smile");
caps.Categories.AddCategoryMapping("CreatiVision", NewznabStandardCategory.ConsoleOther, "CreatiVision");
caps.Categories.AddCategoryMapping("Board Game", NewznabStandardCategory.ConsoleOther, "Board Game");
caps.Categories.AddCategoryMapping("Card Game", NewznabStandardCategory.ConsoleOther, "Card Game");
caps.Categories.AddCategoryMapping("Miniature Wargames", NewznabStandardCategory.ConsoleOther, "Miniature Wargames");
caps.Categories.AddCategoryMapping("Pen and Paper RPG", NewznabStandardCategory.ConsoleOther, "Pen and Paper RPG");
caps.Categories.AddCategoryMapping("3DO", NewznabStandardCategory.ConsoleOther, "3DO");
caps.Categories.AddCategoryMapping("Bandai WonderSwan", NewznabStandardCategory.ConsoleOther, "Bandai WonderSwan");
caps.Categories.AddCategoryMapping("Bandai WonderSwan Color", NewznabStandardCategory.ConsoleOther, "Bandai WonderSwan Color");
caps.Categories.AddCategoryMapping("Casio Loopy", NewznabStandardCategory.ConsoleOther, "Casio Loopy");
caps.Categories.AddCategoryMapping("Casio PV-1000", NewznabStandardCategory.ConsoleOther, "Casio PV-1000");
caps.Categories.AddCategoryMapping("Colecovision", NewznabStandardCategory.ConsoleOther, "Colecovision");
caps.Categories.AddCategoryMapping("Commodore 64", NewznabStandardCategory.ConsoleOther, "Commodore 64");
caps.Categories.AddCategoryMapping("Commodore 128", NewznabStandardCategory.ConsoleOther, "Commodore 128");
caps.Categories.AddCategoryMapping("Commodore Amiga", NewznabStandardCategory.ConsoleOther, "Commodore Amiga");
caps.Categories.AddCategoryMapping("Commodore Plus-4", NewznabStandardCategory.ConsoleOther, "Commodore Plus-4");
caps.Categories.AddCategoryMapping("Commodore VIC-20", NewznabStandardCategory.ConsoleOther, "Commodore VIC-20");
caps.Categories.AddCategoryMapping("Emerson Arcadia 2001", NewznabStandardCategory.ConsoleOther, "Emerson Arcadia 2001");
caps.Categories.AddCategoryMapping("Entex Adventure Vision", NewznabStandardCategory.ConsoleOther, "Entex Adventure Vision");
caps.Categories.AddCategoryMapping("Epoch Super Casette Vision", NewznabStandardCategory.ConsoleOther, "Epoch Super Casette Vision");
caps.Categories.AddCategoryMapping("Fairchild Channel F", NewznabStandardCategory.ConsoleOther, "Fairchild Channel F");
caps.Categories.AddCategoryMapping("Funtech Super Acan", NewznabStandardCategory.ConsoleOther, "Funtech Super Acan");
caps.Categories.AddCategoryMapping("GamePark GP32", NewznabStandardCategory.ConsoleOther, "GamePark GP32");
caps.Categories.AddCategoryMapping("General Computer Vectrex", NewznabStandardCategory.ConsoleOther, "General Computer Vectrex");
caps.Categories.AddCategoryMapping("Interactive DVD", NewznabStandardCategory.ConsoleOther, "Interactive DVD");
caps.Categories.AddCategoryMapping("Linux", NewznabStandardCategory.ConsoleOther, "Linux");
caps.Categories.AddCategoryMapping("Hartung Game Master", NewznabStandardCategory.ConsoleOther, "Hartung Game Master");
caps.Categories.AddCategoryMapping("Magnavox-Phillips Odyssey", NewznabStandardCategory.ConsoleOther, "Magnavox-Phillips Odyssey");
caps.Categories.AddCategoryMapping("Mattel Intellivision", NewznabStandardCategory.ConsoleOther, "Mattel Intellivision");
caps.Categories.AddCategoryMapping("Memotech MTX", NewznabStandardCategory.ConsoleOther, "Memotech MTX");
caps.Categories.AddCategoryMapping("Miles Gordon Sam Coupe", NewznabStandardCategory.ConsoleOther, "Miles Gordon Sam Coupe");
caps.Categories.AddCategoryMapping("NEC PC-98", NewznabStandardCategory.ConsoleOther, "NEC PC-98");
caps.Categories.AddCategoryMapping("NEC PC-FX", NewznabStandardCategory.ConsoleOther, "NEC PC-FX");
caps.Categories.AddCategoryMapping("NEC SuperGrafx", NewznabStandardCategory.ConsoleOther, "NEC SuperGrafx");
caps.Categories.AddCategoryMapping("NEC TurboGrafx-16", NewznabStandardCategory.ConsoleOther, "NEC TurboGrafx-16");
caps.Categories.AddCategoryMapping("Nokia N-Gage", NewznabStandardCategory.ConsoleOther, "Nokia N-Gage");
caps.Categories.AddCategoryMapping("Ouya", NewznabStandardCategory.ConsoleOther, "Ouya");
caps.Categories.AddCategoryMapping("Philips Videopac+", NewznabStandardCategory.ConsoleOther, "Philips Videopac+");
caps.Categories.AddCategoryMapping("Phone/PDA", NewznabStandardCategory.ConsoleOther, "Phone/PDA");
caps.Categories.AddCategoryMapping("RCA Studio II", NewznabStandardCategory.ConsoleOther, "RCA Studio II");
caps.Categories.AddCategoryMapping("Sharp X1", NewznabStandardCategory.ConsoleOther, "Sharp X1");
caps.Categories.AddCategoryMapping("Sharp X68000", NewznabStandardCategory.ConsoleOther, "Sharp X68000");
caps.Categories.AddCategoryMapping("SNK Neo Geo", NewznabStandardCategory.ConsoleOther, "SNK Neo Geo");
caps.Categories.AddCategoryMapping("SNK Neo Geo Pocket", NewznabStandardCategory.ConsoleOther, "SNK Neo Geo Pocket");
caps.Categories.AddCategoryMapping("Taito Type X", NewznabStandardCategory.ConsoleOther, "Taito Type X");
caps.Categories.AddCategoryMapping("Tandy Color Computer", NewznabStandardCategory.ConsoleOther, "Tandy Color Computer");
caps.Categories.AddCategoryMapping("Tangerine Oric", NewznabStandardCategory.ConsoleOther, "Tangerine Oric");
caps.Categories.AddCategoryMapping("Thomson MO5", NewznabStandardCategory.ConsoleOther, "Thomson MO5");
caps.Categories.AddCategoryMapping("Watara Supervision", NewznabStandardCategory.ConsoleOther, "Watara Supervision");
caps.Categories.AddCategoryMapping("Retro - Other", NewznabStandardCategory.ConsoleOther, "Retro - Other");
caps.Categories.AddCategoryMapping("OST", NewznabStandardCategory.AudioOther, "OST");
caps.Categories.AddCategoryMapping("Applications", NewznabStandardCategory.PC0day, "Applications");
caps.Categories.AddCategoryMapping("E-Books", NewznabStandardCategory.BooksEBook, "E-Books");
return caps;
}
}
public class GazelleGamesRequestGenerator : IIndexerRequestGenerator
{
public GazelleGamesSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public GazelleGamesRequestGenerator()
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{
var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/'));
var searchString = term;
var searchType = Settings.SearchGroupNames ? "groupname" : "searchstr";
var queryCollection = new NameValueCollection
{
{ searchType, searchString },
{ "order_by", "time" },
{ "order_way", "desc" },
{ "action", "basic" },
{ "searchsubmit", "1" }
};
var i = 0;
foreach (var cat in Capabilities.Categories.MapTorznabCapsToTrackers(categories))
{
queryCollection.Add($"artistcheck[{i++}]", cat);
}
searchUrl += "?" + queryCollection.GetQueryString();
var request = new IndexerRequest(searchUrl, 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(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));
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 GazelleGamesParser : IParseIndexerResponse
{
private readonly GazelleGamesSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public GazelleGamesParser(GazelleGamesSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
var rowsSelector = ".torrent_table > tbody > tr";
var searchResultParser = new HtmlParser();
var searchResultDocument = searchResultParser.ParseDocument(indexerResponse.Content);
var rows = searchResultDocument.QuerySelectorAll(rowsSelector);
var stickyGroup = false;
string categoryStr;
ICollection<IndexerCategory> groupCategory = null;
string groupTitle = null;
foreach (var row in rows)
{
if (row.ClassList.Contains("torrent"))
{
// garbage rows
continue;
}
else if (row.ClassList.Contains("group"))
{
stickyGroup = row.ClassList.Contains("sticky");
var dispalyname = row.QuerySelector("#displayname");
var qCat = row.QuerySelector("td.cats_col > div");
categoryStr = qCat.GetAttribute("title");
var qArtistLink = dispalyname.QuerySelector("#groupplatform > a");
if (qArtistLink != null)
{
categoryStr = ParseUtil.GetArgumentFromQueryString(qArtistLink.GetAttribute("href"), "artistname");
}
groupCategory = _categories.MapTrackerCatToNewznab(categoryStr);
var qDetailsLink = dispalyname.QuerySelector("#groupname > a");
groupTitle = qDetailsLink.TextContent;
}
else if (row.ClassList.Contains("group_torrent"))
{
if (row.QuerySelector("td.edition_info") != null)
{
continue;
}
var sizeString = row.QuerySelector("td:nth-child(4)").TextContent;
if (string.IsNullOrEmpty(sizeString))
{
continue;
}
var qDetailsLink = row.QuerySelector("a[href^=\"torrents.php?id=\"]");
var title = qDetailsLink.TextContent.Replace(", Freeleech!", "").Replace(", Neutral Leech!", "");
//if (stickyGroup && (query.ImdbID == null || !NewznabStandardCategory.MovieSearchImdbAvailable) && !query.MatchQueryStringAND(title)) // AND match for sticky releases
//{
// continue;
//}
var qDescription = qDetailsLink.QuerySelector("span.torrent_info_tags");
var qDLLink = row.QuerySelector("a[href^=\"torrents.php?action=download\"]");
var qTime = row.QuerySelector("span.time");
var qGrabs = row.QuerySelector("td:nth-child(5)");
var qSeeders = row.QuerySelector("td:nth-child(6)");
var qLeechers = row.QuerySelector("td:nth-child(7)");
var qFreeLeech = row.QuerySelector("strong.freeleech_label");
var qNeutralLeech = row.QuerySelector("strong.neutralleech_label");
var time = qTime.GetAttribute("title");
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 = _settings.BaseUrl + qDetailsLink.GetAttribute("href");
var grabs = ParseUtil.CoerceInt(qGrabs.TextContent);
var leechers = ParseUtil.CoerceInt(qLeechers.TextContent);
var size = ReleaseInfo.GetBytes(sizeString);
var release = new TorrentInfo
{
MinimumRatio = 1,
MinimumSeedTime = 288000, //80 hours
Categories = groupCategory,
PublishDate = publishDate,
Size = size,
InfoUrl = details,
DownloadUrl = link,
Guid = link,
Grabs = grabs,
Seeders = seeders,
Peers = leechers + seeders,
Title = title,
Description = qDescription?.TextContent,
UploadVolumeFactor = qNeutralLeech is null ? 1 : 0,
DownloadVolumeFactor = qFreeLeech != null || qNeutralLeech != null ? 0 : 1
};
torrentInfos.Add(release);
}
}
return torrentInfos.ToArray();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class GazelleGamesSettingsValidator : AbstractValidator<GazelleGamesSettings>
{
public GazelleGamesSettingsValidator()
{
RuleFor(c => c.Cookie).NotEmpty();
}
}
public class GazelleGamesSettings : IIndexerSettings
{
private static readonly GazelleGamesSettingsValidator Validator = new GazelleGamesSettingsValidator();
public GazelleGamesSettings()
{
Cookie = "";
SearchGroupNames = false;
}
[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(3, Label = "Search Group Names", Type = FieldType.Checkbox, HelpText = "Search Group Names Only")]
public bool SearchGroupNames { get; set; }
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)
@@ -57,11 +55,6 @@ namespace NzbDrone.Core.Indexers.HDBits
var flags = new List<IndexerFlag>();
if (result.FreeLeech == "yes")
{
flags.Add(IndexerFlag.FreeLeech);
}
if (internalRelease)
{
flags.Add(IndexerFlag.Internal);
@@ -83,6 +76,8 @@ namespace NzbDrone.Core.Indexers.HDBits
Internal = internalRelease,
ImdbId = result.ImdbInfo?.Id ?? 0,
TvdbId = result.TvdbInfo?.Id ?? 0,
DownloadVolumeFactor = result.FreeLeech == "yes" ? 0 : 1,
UploadVolumeFactor = 1,
IndexerFlags = flags
});
}
@@ -94,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);
@@ -104,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);

View File

@@ -12,7 +12,6 @@ namespace NzbDrone.Core.Indexers.HDBits
{
public IndexerCapabilities Capabilities { get; set; }
public HDBitsSettings Settings { get; set; }
public string BaseUrl { get; set; }
public virtual IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
@@ -50,7 +49,7 @@ namespace NzbDrone.Core.Indexers.HDBits
private IEnumerable<IndexerRequest> GetRequest(TorrentQuery query)
{
var request = new HttpRequestBuilder(BaseUrl)
var request = new HttpRequestBuilder(Settings.BaseUrl)
.Resource("/api/torrents")
.Build();

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.HDBits
@@ -14,7 +13,7 @@ namespace NzbDrone.Core.Indexers.HDBits
}
}
public class HDBitsSettings : IProviderConfig
public class HDBitsSettings : IIndexerSettings
{
private static readonly HDBitsSettingsValidator Validator = new HDBitsSettingsValidator();
@@ -24,16 +23,19 @@ namespace NzbDrone.Core.Indexers.HDBits
Mediums = System.Array.Empty<int>();
}
[FieldDefinition(0, Label = "Username", 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", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(3, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(5, Label = "Codecs", Type = FieldType.TagSelect, SelectOptions = typeof(HdBitsCodec), Advanced = true, HelpText = "Options: h264, Mpeg2, VC1, Xvid. If unspecified, all options are used.")]
[FieldDefinition(4, Label = "Codecs", Type = FieldType.TagSelect, SelectOptions = typeof(HdBitsCodec), Advanced = true, HelpText = "Options: h264, Mpeg2, VC1, Xvid. If unspecified, all options are used.")]
public IEnumerable<int> Codecs { get; set; }
[FieldDefinition(6, Label = "Mediums", Type = FieldType.TagSelect, SelectOptions = typeof(HdBitsMedium), Advanced = true, HelpText = "Options: BluRay, Encode, Capture, Remux, WebDL. If unspecified, all options are used.")]
[FieldDefinition(5, Label = "Mediums", Type = FieldType.TagSelect, SelectOptions = typeof(HdBitsMedium), Advanced = true, HelpText = "Options: BluRay, Encode, Capture, Remux, WebDL. If unspecified, all options are used.")]
public IEnumerable<int> Mediums { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -0,0 +1,349 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
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 HDSpace : TorrentIndexerBase<HDSpaceSettings>
{
public override string Name => "HD-Space";
public override string[] IndexerUrls => new string[] { "https://hd-space.org/" };
private string LoginUrl => Settings.BaseUrl + "index.php?page=login";
public override string Description => "Sharing The Universe";
public override string Language => "en-us";
public override Encoding Encoding => Encoding.UTF8;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
public HDSpace(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new HDSpaceRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new HDSpaceParser(Settings, Capabilities.Categories);
}
protected override async Task DoLogin()
{
var loginPage = await _httpClient.ExecuteAsync(new HttpRequest(LoginUrl));
var requestBuilder = new HttpRequestBuilder(LoginUrl)
{
LogResponseContent = true,
AllowAutoRedirect = true
};
requestBuilder.Method = HttpMethod.POST;
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
var cookies = Cookies;
Cookies = null;
var authLoginRequest = requestBuilder
.AddFormParameter("uid", Settings.Username)
.AddFormParameter("pwd", Settings.Password)
.SetCookies(loginPage.GetCookies())
.SetHeader("Content-Type", "multipart/form-data")
.SetHeader("Referer", LoginUrl)
.Build();
var response = await ExecuteAuth(authLoginRequest);
if (CheckIfLoginNeeded(response))
{
var errorStr = "Login Failed: You have {0} remaining login attempts";
var remainingAttemptSpan = new Regex(string.Format(errorStr, "(.*?)"))
.Match(loginPage.Content).Groups[1].ToString();
var attempts = Regex.Replace(remainingAttemptSpan, "<.*?>", string.Empty);
var errorMessage = string.Format(errorStr, attempts);
throw new IndexerAuthException(errorMessage);
}
cookies = response.GetCookies();
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
_logger.Debug("HDSpace authentication succeeded.");
}
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{
if (!httpResponse.Content.Contains("logout.php"))
{
return true;
}
return false;
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.MoviesBluRay, "Movie / Blu-ray");
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.MoviesHD, "Movie / 1080p");
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.MoviesHD, "Movie / 720p");
caps.Categories.AddCategoryMapping(40, NewznabStandardCategory.MoviesHD, "Movie / Remux");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.MoviesHD, "Movie / HD-DVD");
caps.Categories.AddCategoryMapping(41, NewznabStandardCategory.MoviesUHD, "Movie / 4K UHD");
caps.Categories.AddCategoryMapping(21, NewznabStandardCategory.TVHD, "TV Show / 720p HDTV");
caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.TVHD, "TV Show / 1080p HDTV");
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.TVDocumentary, "Documentary / 720p");
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.TVDocumentary, "Documentary / 1080p");
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.TVAnime, "Animation / 720p");
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.TVAnime, "Animation / 1080p");
caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.AudioLossless, "Music / HQ Audio");
caps.Categories.AddCategoryMapping(31, NewznabStandardCategory.AudioVideo, "Music / Videos");
caps.Categories.AddCategoryMapping(33, NewznabStandardCategory.XXX, "XXX / 720p");
caps.Categories.AddCategoryMapping(34, NewznabStandardCategory.XXX, "XXX / 1080p");
caps.Categories.AddCategoryMapping(36, NewznabStandardCategory.MoviesOther, "Trailers");
caps.Categories.AddCategoryMapping(37, NewznabStandardCategory.PC, "Software");
caps.Categories.AddCategoryMapping(38, NewznabStandardCategory.Other, "Others");
return caps;
}
}
public class HDSpaceRequestGenerator : IIndexerRequestGenerator
{
public HDSpaceSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public HDSpaceRequestGenerator()
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdb = null)
{
var searchUrl = string.Format("{0}/index.php?page=torrents&", Settings.BaseUrl.TrimEnd('/'));
var queryCollection = new NameValueCollection
{
{ "active", "0" },
{ "category", string.Join(";", Capabilities.Categories.MapTorznabCapsToTrackers(categories)) }
};
if (imdb != null)
{
queryCollection.Add("options", "2");
queryCollection.Add("search", imdb);
}
else
{
queryCollection.Add("options", "0");
queryCollection.Add("search", term);
}
searchUrl += queryCollection.GetQueryString();
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
yield return request;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.ImdbId));
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.ImdbId));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
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 HDSpaceParser : IParseIndexerResponse
{
private readonly HDSpaceSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public HDSpaceParser(HDSpaceSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
var resultParser = new HtmlParser();
var searchResultDocument = resultParser.ParseDocument(indexerResponse.Content);
var rows = searchResultDocument.QuerySelectorAll("table.lista > tbody > tr");
foreach (var row in rows)
{
// this tracker has horrible markup, find the result rows by looking for the style tag before each one
var prev = row.PreviousElementSibling;
if (prev == null || !string.Equals(prev.NodeName, "style", StringComparison.OrdinalIgnoreCase))
{
continue;
}
var release = new TorrentInfo();
release.MinimumRatio = 1;
release.MinimumSeedTime = 86400; // 24 hours
var qLink = row.Children[1].FirstElementChild;
release.Title = qLink.TextContent.Trim();
release.InfoUrl = _settings.BaseUrl + qLink.GetAttribute("href");
release.Guid = release.InfoUrl;
var imdbLink = row.Children[1].QuerySelector("a[href*=imdb]");
if (imdbLink != null)
{
release.ImdbId = ParseUtil.GetImdbID(imdbLink.GetAttribute("href").Split('/').Last()).GetValueOrDefault();
}
var qDownload = row.Children[3].FirstElementChild;
release.DownloadUrl = _settings.BaseUrl + qDownload.GetAttribute("href");
var dateStr = row.Children[4].TextContent.Trim();
//"July 11, 2015, 13:34:09", "Today|Yesterday at 20:04:23"
release.PublishDate = DateTimeUtil.FromUnknown(dateStr);
var sizeStr = row.Children[5].TextContent;
release.Size = ReleaseInfo.GetBytes(sizeStr);
release.Seeders = ParseUtil.CoerceInt(row.Children[7].TextContent);
release.Peers = ParseUtil.CoerceInt(row.Children[8].TextContent) + release.Seeders;
var grabs = row.QuerySelector("td:nth-child(10)").TextContent;
grabs = grabs.Replace("---", "0");
release.Grabs = ParseUtil.CoerceInt(grabs);
if (row.QuerySelector("img[title=\"FreeLeech\"]") != null)
{
release.DownloadVolumeFactor = 0;
}
else if (row.QuerySelector("img[src=\"images/sf.png\"]") != null)
{
release.DownloadVolumeFactor = 0;
}
else if (row.QuerySelector("img[title=\"Half FreeLeech\"]") != null)
{
release.DownloadVolumeFactor = 0.5;
}
else
{
release.DownloadVolumeFactor = 1;
}
release.UploadVolumeFactor = 1;
var qCat = row.QuerySelector("a[href^=\"index.php?page=torrents&category=\"]");
var cat = qCat.GetAttribute("href").Split('=')[2];
release.Categories = _categories.MapTrackerCatToNewznab(cat);
torrentInfos.Add(release);
}
return torrentInfos.ToArray();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class HDSpaceSettingsValidator : AbstractValidator<HDSpaceSettings>
{
public HDSpaceSettingsValidator()
{
RuleFor(c => c.Username).NotEmpty();
RuleFor(c => c.Password).NotEmpty();
}
}
public class HDSpaceSettings : IIndexerSettings
{
private static readonly HDSpaceSettingsValidator Validator = new HDSpaceSettingsValidator();
public HDSpaceSettings()
{
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", Privacy = PrivacyLevel.UserName, HelpText = "Site Username")]
public string Username { get; set; }
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password, HelpText = "Site Password")]
public string Password { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -15,7 +15,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,8 +23,9 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public override string Name => "HD-Torrents";
public override string BaseUrl => "https://hdts.ru/";
private string LoginUrl => BaseUrl + "login.php";
public override string[] IndexerUrls => new string[] { "https://hdts.ru/" };
public override string Description => "HD-Torrents is a private torrent website with HD torrents and strict rules on their content.";
private string LoginUrl => Settings.BaseUrl + "login.php";
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 HDTorrentsRequestGenerator() { Settings = Settings, Capabilities = Capabilities, BaseUrl = BaseUrl };
return new HDTorrentsRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new HDTorrentsParser(Settings, Capabilities.Categories, BaseUrl);
return new HDTorrentsParser(Settings, Capabilities.Categories);
}
protected override async Task DoLogin()
@@ -141,7 +141,6 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public HDTorrentsSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public string BaseUrl { get; set; }
public HDTorrentsRequestGenerator()
{
@@ -149,7 +148,7 @@ namespace NzbDrone.Core.Indexers.Definitions
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
{
var searchUrl = BaseUrl + "torrents.php?" + string.Join(string.Empty, Capabilities.Categories.MapTorznabCapsToTrackers(categories).Select(cat => $"category[]={cat}&"));
var searchUrl = Settings.BaseUrl + "torrents.php?" + string.Join(string.Empty, Capabilities.Categories.MapTorznabCapsToTrackers(categories).Select(cat => $"category[]={cat}&"));
var queryCollection = new NameValueCollection
{
@@ -219,7 +218,6 @@ namespace NzbDrone.Core.Indexers.Definitions
{
private readonly HDTorrentsSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
private readonly string _baseUrl;
private readonly Regex _posterRegex = new Regex(@"src=\\'./([^']+)\\'", RegexOptions.IgnoreCase);
private readonly HashSet<string> _freeleechRanks = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
@@ -232,11 +230,10 @@ namespace NzbDrone.Core.Indexers.Definitions
"Owner"
};
public HDTorrentsParser(HDTorrentsSettings settings, IndexerCapabilitiesCategories categories, string baseUrl)
public HDTorrentsParser(HDTorrentsSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
_baseUrl = baseUrl;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@@ -255,12 +252,12 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var mainLink = row.Children[2].QuerySelector("a");
var title = mainLink.TextContent;
var details = new Uri(_baseUrl + mainLink.GetAttribute("href"));
var details = new Uri(_settings.BaseUrl + mainLink.GetAttribute("href"));
var posterMatch = _posterRegex.Match(mainLink.GetAttribute("onmouseover"));
var poster = posterMatch.Success ? new Uri(_baseUrl + posterMatch.Groups[1].Value.Replace("\\", "/")) : null;
var poster = posterMatch.Success ? new Uri(_settings.BaseUrl + posterMatch.Groups[1].Value.Replace("\\", "/")) : null;
var link = new Uri(_baseUrl + row.Children[4].FirstElementChild.GetAttribute("href"));
var link = new Uri(_settings.BaseUrl + row.Children[4].FirstElementChild.GetAttribute("href"));
var description = row.Children[2].QuerySelector("span").TextContent;
var size = ReleaseInfo.GetBytes(row.Children[7].TextContent);
@@ -365,7 +362,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
public class HDTorrentsSettings : IProviderConfig
public class HDTorrentsSettings : IIndexerSettings
{
private static readonly HDTorrentsSettingsValidator Validator = new HDTorrentsSettingsValidator();
@@ -375,10 +372,13 @@ namespace NzbDrone.Core.Indexers.Definitions
Password = "";
}
[FieldDefinition(1, Label = "Username", HelpText = "Site 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", Type = FieldType.Password, HelpText = "Site password", Privacy = PrivacyLevel.Password)]
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, HelpText = "Site Password", Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -17,7 +17,8 @@ namespace NzbDrone.Core.Indexers.Headphones
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override string BaseUrl => "https://indexer.codeshy.com";
public override string[] IndexerUrls => new string[] { "https://indexer.codeshy.com" };
public override string Description => "";
public override IndexerCapabilities Capabilities => SetCapabilities();
public override IIndexerRequestGenerator GetRequestGenerator()
@@ -26,8 +27,7 @@ namespace NzbDrone.Core.Indexers.Headphones
{
PageSize = PageSize,
Settings = Settings,
Capabilities = Capabilities,
BaseUrl = BaseUrl
Capabilities = Capabilities
};
}

View File

@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Indexers.Headphones
public int PageSize { get; set; }
public HeadphonesSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public string BaseUrl { get; set; }
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
@@ -106,7 +106,7 @@ namespace NzbDrone.Core.Indexers.Headphones
private IEnumerable<IndexerRequest> GetPagedRequests(SearchCriteriaBase searchCriteria, NameValueCollection parameters)
{
var baseUrl = string.Format("{0}{1}?t={2}&extended=1", BaseUrl.TrimEnd('/'), Settings.ApiPath.TrimEnd('/'), searchCriteria.SearchType);
var baseUrl = string.Format("{0}{1}?t={2}&extended=1", Settings.BaseUrl.TrimEnd('/'), Settings.ApiPath.TrimEnd('/'), searchCriteria.SearchType);
var categories = searchCriteria.Categories;
if (categories != null && categories.Any())

View File

@@ -1,6 +1,5 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Headphones
@@ -14,7 +13,7 @@ namespace NzbDrone.Core.Indexers.Headphones
}
}
public class HeadphonesSettings : IProviderConfig
public class HeadphonesSettings : IIndexerSettings
{
private static readonly HeadphonesSettingsValidator Validator = new HeadphonesSettingsValidator();
@@ -28,10 +27,13 @@ namespace NzbDrone.Core.Indexers.Headphones
public string ApiKey { get; set; }
[FieldDefinition(1, Label = "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", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(2, Label = "Password", Type = FieldType.Password)]
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
public virtual NzbDroneValidationResult Validate()

View File

@@ -13,7 +13,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
@@ -22,7 +21,21 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public override string Name => "IPTorrents";
public override string BaseUrl => "https://iptorrents.com/";
public override string[] IndexerUrls => new string[]
{
"https://iptorrents.com/",
"https://iptorrents.me/",
"https://nemo.iptorrents.com/",
"https://ipt.getcrazy.me/",
"https://ipt.findnemo.net/",
"https://ipt.beelyrics.net/",
"https://ipt.venom.global/",
"https://ipt.workisboring.net/",
"https://ipt.lol/",
"https://ipt.cool/",
"https://ipt.world/"
};
public override string Description => "";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
@@ -34,12 +47,12 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new IPTorrentsRequestGenerator() { Settings = Settings, Capabilities = Capabilities, BaseUrl = BaseUrl };
return new IPTorrentsRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new IPTorrentsParser(Settings, Capabilities.Categories, BaseUrl);
return new IPTorrentsParser(Settings, Capabilities.Categories);
}
protected override IDictionary<string, string> GetCookies()
@@ -148,7 +161,6 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public IPTorrentsSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public string BaseUrl { get; set; }
public IPTorrentsRequestGenerator()
{
@@ -156,7 +168,7 @@ namespace NzbDrone.Core.Indexers.Definitions
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
{
var searchUrl = BaseUrl + "t";
var searchUrl = Settings.BaseUrl + "t";
var qc = new NameValueCollection();
@@ -169,6 +181,11 @@ namespace NzbDrone.Core.Indexers.Definitions
qc.Add("q", term);
}
if (Settings.FreeLeechOnly)
{
qc.Add("free", "on");
}
foreach (var cat in Capabilities.Categories.MapTorznabCapsToTrackers(categories))
{
qc.Add(cat, string.Empty);
@@ -234,13 +251,11 @@ namespace NzbDrone.Core.Indexers.Definitions
{
private readonly IPTorrentsSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
private readonly string _baseUrl;
public IPTorrentsParser(IPTorrentsSettings settings, IndexerCapabilitiesCategories categories, string baseUrl)
public IPTorrentsParser(IPTorrentsSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
_baseUrl = baseUrl;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@@ -263,10 +278,10 @@ namespace NzbDrone.Core.Indexers.Definitions
// drop invalid char that seems to have cropped up in some titles. #6582
var title = qTitleLink.TextContent.Trim().Replace("\u000f", "");
var details = new Uri(_baseUrl + qTitleLink.GetAttribute("href").TrimStart('/'));
var details = new Uri(_settings.BaseUrl + qTitleLink.GetAttribute("href").TrimStart('/'));
var qLink = row.QuerySelector("a[href^=\"/download.php/\"]");
var link = new Uri(_baseUrl + qLink.GetAttribute("href").TrimStart('/'));
var link = new Uri(_settings.BaseUrl + qLink.GetAttribute("href").TrimStart('/'));
var descrSplit = row.QuerySelector("div.sub").TextContent.Split('|');
var dateSplit = descrSplit.Last().Split(new[] { " by " }, StringSplitOptions.None);
@@ -337,7 +352,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
public class IPTorrentsSettings : IProviderConfig
public class IPTorrentsSettings : IIndexerSettings
{
private static readonly IPTorrentsSettingsValidator Validator = new IPTorrentsSettingsValidator();
@@ -346,11 +361,15 @@ namespace NzbDrone.Core.Indexers.Definitions
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(1, Label = "Cookie", HelpText = "Site Cookie")]
[FieldDefinition(2, Label = "Cookie", HelpText = "Site Cookie")]
public string Cookie { get; set; }
[FieldDefinition(3, Label = "FreeLeech Only", Type = FieldType.Checkbox, Advanced = true, HelpText = "Search Freeleech torrents only")]
public bool FreeLeechOnly { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

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