mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-03-25 17:54:32 -04:00
Compare commits
107 Commits
cookie-per
...
v0.1.0.768
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e47c7e6a47 | ||
|
|
d937e0324f | ||
|
|
ff623d4c39 | ||
|
|
0b05f5ef24 | ||
|
|
e6c3292485 | ||
|
|
ba1c1baeb5 | ||
|
|
5e7f4f3fc1 | ||
|
|
3dd11213fa | ||
|
|
50cae0719f | ||
|
|
2c6680e4fa | ||
|
|
96afb7f327 | ||
|
|
0d1025d60a | ||
|
|
0508dd2b66 | ||
|
|
026a503d5f | ||
|
|
b3f8e648cd | ||
|
|
13b458090d | ||
|
|
f97c3ff9bd | ||
|
|
841ff7b6ee | ||
|
|
377db47daf | ||
|
|
2addcab765 | ||
|
|
ddc676c608 | ||
|
|
924db7a394 | ||
|
|
580113d6ce | ||
|
|
d532a69edc | ||
|
|
01f7e11d5a | ||
|
|
de97ec95db | ||
|
|
6e66467ab3 | ||
|
|
4c5131708d | ||
|
|
bb2e1a6037 | ||
|
|
5949bd97fd | ||
|
|
eaff071b16 | ||
|
|
a922586aba | ||
|
|
80beea9bdb | ||
|
|
8c326fc5c2 | ||
|
|
e26081acff | ||
|
|
b3b0467d22 | ||
|
|
e1c98d2b38 | ||
|
|
e304461dfb | ||
|
|
a127e5a30f | ||
|
|
e252cd4d3e | ||
|
|
9a1bd3db4c | ||
|
|
acce098e02 | ||
|
|
afa87b7113 | ||
|
|
4cbd2cd8bc | ||
|
|
e81d0f3e97 | ||
|
|
34a6a0e0c9 | ||
|
|
4254a05ea3 | ||
|
|
d32a94c14d | ||
|
|
6a9155bcf5 | ||
|
|
de442cc659 | ||
|
|
7f514c8f1e | ||
|
|
4c51f09acb | ||
|
|
a60388fcf9 | ||
|
|
11b656dabf | ||
|
|
535f29bef4 | ||
|
|
0ddc530dd4 | ||
|
|
b5321d33c9 | ||
|
|
4116c10caa | ||
|
|
0fe2cf5c2d | ||
|
|
c94573e868 | ||
|
|
1945af060d | ||
|
|
cf399ffdcc | ||
|
|
5846188202 | ||
|
|
fc65a89fbc | ||
|
|
b62ae41de8 | ||
|
|
8107f309b4 | ||
|
|
c2c12297bd | ||
|
|
81cbdab5eb | ||
|
|
9ee5a3e94b | ||
|
|
a570fd2a8f | ||
|
|
5cffb10e08 | ||
|
|
b11bf284dc | ||
|
|
5c4c042b2e | ||
|
|
d1cb744efd | ||
|
|
79b910a80c | ||
|
|
01e08e0c31 | ||
|
|
dd3c9c268e | ||
|
|
725f738ee1 | ||
|
|
22c738f43e | ||
|
|
e45f88473c | ||
|
|
889591d0b1 | ||
|
|
fe8247df8a | ||
|
|
7a5721bcee | ||
|
|
eea5c3e9a4 | ||
|
|
f55493c9a9 | ||
|
|
79618adaf9 | ||
|
|
af13d6ed80 | ||
|
|
38c09277d9 | ||
|
|
1fb693d066 | ||
|
|
7414a2f690 | ||
|
|
000590bcf7 | ||
|
|
d5d6625a63 | ||
|
|
00f33cb48f | ||
|
|
fd265c5734 | ||
|
|
316543b9aa | ||
|
|
117ebcff2d | ||
|
|
aab394b2c8 | ||
|
|
6ae520c061 | ||
|
|
dfb254d2dc | ||
|
|
07c03b0a12 | ||
|
|
eb0cf2d5f6 | ||
|
|
69c04ebe7a | ||
|
|
135db6d2ff | ||
|
|
c3deace9e6 | ||
|
|
9042594b14 | ||
|
|
44df0f5c3d | ||
|
|
7b9446eb35 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -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
|
||||
|
||||
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,37 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Support Requests will be closed immediately, if you are not 100% certain this is a bug please go to our Reddit or Discord first. Exceptions do not mean you found a bug!
|
||||
title: ''
|
||||
labels: 'Type: Bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
<!-- Support Requests will be closed immediately, if you are unsure go to our Reddit or Discord first. Exceptions do not mean you found a bug! -->
|
||||
<!-- Note: Text between <!- and -> will be hidden -->
|
||||
**Describe the bug**
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**To Reproduce**
|
||||
<!-- Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error -->
|
||||
|
||||
**Expected behavior**
|
||||
<!-- A clear and concise description of what you expected to happen.-->
|
||||
|
||||
**Screenshots**
|
||||
<!-- If applicable, add screenshots to help explain your problem.-->
|
||||
|
||||
**Platform Information (please complete the following information):**
|
||||
- OS: <!-- [e.g. Windows 10 2004 / Ubuntu 20.04] -->
|
||||
- Docker: <!-- [Yes/No] -->
|
||||
- .NET Version (System -> Status): <!--[e.g. .NET 5.0.1] -->
|
||||
- Browser and Version (Only needed for UI issues): <!--[e.g. chrome 86.0.4240.198] -->
|
||||
- Prowlarr Version: <!--[e.g. 0.1.2.1854-->
|
||||
- Prowlarr Branch: <!--[e.g. develop, nightly]-->
|
||||
|
||||
Turn on Trace logs under Settings -> General and wait for the bug to occur again.
|
||||
**Upload the full log file here (or another site (e.g. pastebin) and link it). Issues will be closed, if they do not include this!**
|
||||
<!-- Trace logs are named Prowlarr.trace.txt or Prowlarr.trace.#.txt and will contain "trace" in them-->
|
||||
74
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
74
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
name: Bug Report
|
||||
title: "[BUG]: "
|
||||
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first'
|
||||
labels: ['Type: Bug', 'Status: Needs Triage']
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the bug you encountered.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
description: A concise description of what you're experiencing.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. In this environment...
|
||||
2. With this config...
|
||||
3. Run '...'
|
||||
4. See error...
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Environment
|
||||
description: |
|
||||
examples:
|
||||
- **OS**: Ubuntu 20.04
|
||||
- **Prowlarr**: Prowlarr 0.1.0.650
|
||||
- **Docker Install**: Yes
|
||||
- **Using Reverse Proxy**: No
|
||||
- **Browser**: Firefox 90 (If UI related)
|
||||
value: |
|
||||
- OS:
|
||||
- Prowlarr:
|
||||
- Docker Install:
|
||||
- Using Reverse Proxy:
|
||||
- Browser:
|
||||
render: markdown
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What branch are you running?
|
||||
options:
|
||||
- Master
|
||||
- Develop
|
||||
- Nightly
|
||||
- Other (This issue will be closed)
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Trace Logs (https://wiki.servarr.com/prowlarr/troubleshooting#logging-and-log-files)
|
||||
Links? References? Anything that will give us more context about the issue you are encountering!
|
||||
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
validations:
|
||||
required: true
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for Prowlarr
|
||||
title: ''
|
||||
labels: 'Type: Feature Request'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
39
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
39
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Feature Request
|
||||
title: "[FEAT]: "
|
||||
description: 'Suggest an idea for Prowlarr'
|
||||
labels: ['Type: Feature Request', 'Status: Needs Triage']
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the feature you are requesting.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe
|
||||
description: A clear and concise description of what the problem is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Links? References? Mockups? Anything that will give us more context about the feature you are encountering!
|
||||
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
validations:
|
||||
required: true
|
||||
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,14 +1,15 @@
|
||||
#### Database Migration
|
||||
YES | NO
|
||||
YES - XXXX | NO
|
||||
|
||||
#### Description
|
||||
A few sentences describing the overall goals of the pull request's commits.
|
||||
|
||||
#### Screenshot (if UI related)
|
||||
|
||||
#### Todos
|
||||
- [ ] Tests
|
||||
- [ ] Translation Keys
|
||||
- [ ] Wiki Updates
|
||||
- [ ] Translation Keys (./src/NzbDrone.Core/Localization/Core/en.json)
|
||||
- [ ] [Wiki Updates](https://wiki.servarr.com)
|
||||
|
||||
#### Issues Fixed or Closed by this PR
|
||||
|
||||
|
||||
41
.github/workflows/azuresync.yml
vendored
Normal file
41
.github/workflows/azuresync.yml
vendored
Normal 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
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
We're always looking for people to help make Prowlarr even better, there are a number of ways to contribute.
|
||||
|
||||
This file is updated on an ad-hoc basis, for the latest details please see the [contributing wiki page](https://wiki.servarr.com/prowlarr/contributing).
|
||||
|
||||
## Documentation ##
|
||||
Setup guides, FAQ, the more information we have on the [wiki](https://wiki.servarr.com/prowlarr) the better.
|
||||
|
||||
@@ -27,7 +29,7 @@ Setup guides, FAQ, the more information we have on the [wiki](https://wiki.serva
|
||||
|
||||
### Contributing Code ###
|
||||
- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Prowlarr/Prowlarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first)
|
||||
- Rebase from Radarr's develop branch, don't merge
|
||||
- Rebase from Prowlarr's develop branch, don't merge
|
||||
- Make meaningful commits, or squash them
|
||||
- Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements
|
||||
- Reach out to us on the discord if you have any questions
|
||||
|
||||
@@ -11,7 +11,7 @@ Prowlarr is a indexer manager/proxy built on the popular arr .net/reactjs base s
|
||||
|
||||
## Major Features Include:
|
||||
- Usenet support for 24 indexers natively, including Headphones VIP, and support for any Newznab compatible indexer via "Generic Newznab"
|
||||
- Torrent support for almost 500 trackers & more coming soon
|
||||
- Torrent support for over 500 trackers with more added all the time
|
||||
- Torrent support for any Torznab compatible tracker via "Generic Torznab"
|
||||
- Indexer Sync to Sonarr/Radarr/Readarr/Lidarr, so no manual configuration of the other applications are required
|
||||
- Indexer History and Statistics
|
||||
@@ -34,10 +34,6 @@ Note: Prowlarr is currently early in life, thus bugs should be expected
|
||||
[Indexer Requests](https://requests.prowlarr.com)
|
||||
- Request or vote on an existing request for a new tracker/indexer
|
||||
|
||||
## Feature Requests
|
||||
|
||||
[Feature Requests](https://github.com/Prowlarr/Prowlarr/issues/new?assignees=&template=feature_request.md&Type%3A%20Feature%20Request&title=)
|
||||
|
||||
## Contributors & Developers
|
||||
This project exists thanks to all the people who contribute. [Contribute](CONTRIBUTING.md).
|
||||
<a href="https://github.com/Prowlarr/Prowlarr/graphs/contributors"><img src="https://opencollective.com/Prowlarr/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
@@ -13,7 +13,7 @@ variables:
|
||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||
sentryOrg: 'servarr'
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '5.0.203'
|
||||
dotnetVersion: '5.0.302'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
|
||||
trigger:
|
||||
@@ -879,7 +879,7 @@ stages:
|
||||
artifactName: 'WindowsAutomationScreenshots'
|
||||
targetPath: $(Build.SourcesDirectory)
|
||||
- checkout: none
|
||||
- powershell: |
|
||||
- pwsh: |
|
||||
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/Servarr/AzureDiscordNotify/master/DiscordNotify.ps1'))
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -18,7 +18,7 @@ function IndexerInfoModalContent(props) {
|
||||
description,
|
||||
encoding,
|
||||
language,
|
||||
baseUrl,
|
||||
indexerUrls,
|
||||
protocol,
|
||||
onModalClose
|
||||
} = props;
|
||||
@@ -54,10 +54,10 @@ function IndexerInfoModalContent(props) {
|
||||
|
||||
<DescriptionListItemTitle>Indexer Site</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to={baseUrl}>{baseUrl}</Link>
|
||||
<Link to={indexerUrls[0]}>{indexerUrls[0]}</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>{protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url</DescriptionListItemTitle>
|
||||
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
{`${window.location.origin}${window.Prowlarr.urlBase}/${id}/api`}
|
||||
</DescriptionListItemDescription>
|
||||
@@ -74,7 +74,7 @@ IndexerInfoModalContent.propTypes = {
|
||||
description: PropTypes.string.isRequired,
|
||||
encoding: PropTypes.string.isRequired,
|
||||
language: PropTypes.string.isRequired,
|
||||
baseUrl: PropTypes.string.isRequired,
|
||||
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
protocol: PropTypes.string.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -10,7 +10,6 @@ function createMapStateToProps() {
|
||||
(state) => state.settings.advancedSettings,
|
||||
createIndexerSelector(),
|
||||
(advancedSettings, indexer) => {
|
||||
console.log(indexer);
|
||||
return {
|
||||
advancedSettings,
|
||||
...indexer
|
||||
|
||||
@@ -26,7 +26,7 @@ function SearchIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Protocol
|
||||
{translate('Protocol')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -35,7 +35,7 @@ function SearchIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Age
|
||||
{translate('Age')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -53,7 +53,7 @@ function SearchIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Indexer
|
||||
{translate('Indexer')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -62,7 +62,7 @@ function SearchIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Size
|
||||
{translate('Size')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -71,7 +71,7 @@ function SearchIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Files
|
||||
{translate('Files')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -80,7 +80,7 @@ function SearchIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Grabs
|
||||
{translate('Grabs')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -89,7 +89,7 @@ function SearchIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Peers
|
||||
{translate('Peers')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -98,7 +98,7 @@ function SearchIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Category
|
||||
{translate('Category')}
|
||||
</SortMenuItem>
|
||||
</MenuContent>
|
||||
</SortMenu>
|
||||
|
||||
@@ -19,7 +19,7 @@ function NoSearchResults(props) {
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.message}>
|
||||
No search results found, try performing a new search below.
|
||||
{translate('NoSearchResultsFound')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import TextInput from 'Components/Form/TextInput';
|
||||
import keyboardShortcuts from 'Components/keyboardShortcuts';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
import PageContentFooter from 'Components/Page/PageContentFooter';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import SearchFooterLabel from './SearchFooterLabel';
|
||||
import styles from './SearchFooter.css';
|
||||
|
||||
@@ -167,7 +168,7 @@ class SearchFooter extends Component {
|
||||
isDisabled={isFetching || !hasIndexers}
|
||||
onPress={this.onSearchPress}
|
||||
>
|
||||
Search
|
||||
{translate('Search')}
|
||||
</SpinnerButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -202,7 +202,8 @@ export const defaultState = {
|
||||
|
||||
export const persistState = [
|
||||
'releases.customFilters',
|
||||
'releases.selectedFilterKey'
|
||||
'releases.selectedFilterKey',
|
||||
'releases.columns'
|
||||
];
|
||||
|
||||
//
|
||||
|
||||
@@ -43,6 +43,7 @@ function getInternalLink(source) {
|
||||
function getTestLink(source, props) {
|
||||
switch (source) {
|
||||
case 'IndexerStatusCheck':
|
||||
case 'IndexerLongTermStatusCheck':
|
||||
return (
|
||||
<SpinnerIconButton
|
||||
name={icons.TEST}
|
||||
|
||||
16
package.json
16
package.json
@@ -30,9 +30,9 @@
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.3",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.3",
|
||||
"@fortawesome/react-fontawesome": "0.1.14",
|
||||
"@microsoft/signalr": "5.0.6",
|
||||
"@sentry/browser": "6.3.1",
|
||||
"@sentry/integrations": "6.3.1",
|
||||
"@microsoft/signalr": "5.0.8",
|
||||
"@sentry/browser": "6.10.0",
|
||||
"@sentry/integrations": "6.10.0",
|
||||
"chart.js": "3.2.0",
|
||||
"classnames": "2.3.1",
|
||||
"clipboard": "2.0.8",
|
||||
@@ -98,12 +98,12 @@
|
||||
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
||||
"core-js": "3.11.0",
|
||||
"css-loader": "5.2.4",
|
||||
"eslint": "7.25.0",
|
||||
"eslint": "7.31.0",
|
||||
"eslint-plugin-filenames": "1.3.2",
|
||||
"eslint-plugin-import": "2.22.1",
|
||||
"eslint-plugin-react": "7.23.2",
|
||||
"eslint-plugin-import": "2.23.4",
|
||||
"eslint-plugin-react": "7.24.0",
|
||||
"eslint-plugin-simple-import-sort": "7.0.0",
|
||||
"esprint": "2.0.0",
|
||||
"esprint": "3.1.0",
|
||||
"file-loader": "6.2.0",
|
||||
"filemanager-webpack-plugin": "5.0.0",
|
||||
"html-webpack-plugin": "5.3.1",
|
||||
@@ -125,7 +125,7 @@
|
||||
"webpack": "5.35.1",
|
||||
"webpack-cli": "4.6.0",
|
||||
"webpack-livereload-plugin": "3.0.1",
|
||||
"stylelint": "13.13.0",
|
||||
"stylelint": "13.13.1",
|
||||
"stylelint-order": "4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
|
||||
<!-- Standard testing packages -->
|
||||
<ItemGroup Condition="'$(TestProject)'=='true'">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Include="NunitXml.TestLogger" Version="3.0.97" />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,16 @@ namespace NzbDrone.Common.Extensions
|
||||
return dateTime >= afterDateTime && dateTime <= beforeDateTime;
|
||||
}
|
||||
|
||||
public static DateTime EndOfDay(this DateTime date)
|
||||
{
|
||||
return new DateTime(date.Year, date.Month, date.Day, 23, 59, 59, 999);
|
||||
}
|
||||
|
||||
public static DateTime StartOfDay(this DateTime date)
|
||||
{
|
||||
return new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
public static DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,5 +194,21 @@ namespace NzbDrone.Common.Extensions
|
||||
var inputBytes = encoding.GetBytes(searchString);
|
||||
return encoding.GetString(WebUtility.UrlDecodeToBytes(inputBytes, 0, inputBytes.Length));
|
||||
}
|
||||
|
||||
public static string CleanFileName(this string name)
|
||||
{
|
||||
string result = name;
|
||||
string[] badCharacters = { "\\", "/", "<", ">", "?", "*", ":", "|", "\"" };
|
||||
string[] goodCharacters = { "+", "+", "", "", "!", "-", "-", "", "" };
|
||||
|
||||
result = result.Replace(": ", " - ");
|
||||
|
||||
for (int i = 0; i < badCharacters.Length; i++)
|
||||
{
|
||||
result = result.Replace(badCharacters[i], goodCharacters[i]);
|
||||
}
|
||||
|
||||
return result.TrimStart(' ', '.').TrimEnd(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace NzbDrone.Common.Http
|
||||
public HttpUri Url { get; set; }
|
||||
public HttpMethod Method { get; set; }
|
||||
public HttpHeader Headers { get; set; }
|
||||
public Encoding Encoding { get; set; }
|
||||
public byte[] ContentData { get; set; }
|
||||
public string ContentSummary { get; set; }
|
||||
public bool SuppressHttpError { get; set; }
|
||||
@@ -75,8 +76,15 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
public void SetContent(string data)
|
||||
{
|
||||
var encoding = HttpHeader.GetEncodingFromContentType(Headers.ContentType);
|
||||
ContentData = encoding.GetBytes(data);
|
||||
if (Encoding != null)
|
||||
{
|
||||
ContentData = Encoding.GetBytes(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
var encoding = HttpHeader.GetEncodingFromContentType(Headers.ContentType);
|
||||
ContentData = encoding.GetBytes(data);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddBasicAuthentication(string username, string password)
|
||||
|
||||
@@ -47,7 +47,14 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
if (_content == null)
|
||||
{
|
||||
_content = Headers.GetEncodingFromContentType().GetString(ResponseData);
|
||||
if (Request.Encoding != null)
|
||||
{
|
||||
_content = Request.Encoding.GetString(ResponseData);
|
||||
}
|
||||
else
|
||||
{
|
||||
_content = Headers.GetEncodingFromContentType().GetString(ResponseData);
|
||||
}
|
||||
}
|
||||
|
||||
return _content;
|
||||
|
||||
@@ -11,13 +11,15 @@ namespace NzbDrone.Common.Instrumentation
|
||||
private static readonly Regex[] CleansingRules = new[]
|
||||
{
|
||||
// Url
|
||||
new Regex(@"(?<=\?|&|: |;)(apikey|token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&| )[^=]*?(_?token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&|: |;)(apikey|token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd|pwd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&| )[^=]*?(_?(?<!use)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
|
||||
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=authkey = "")(?<secret>[^&=]+?)(?="")", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=beyond-hd\.[a-z]+/api/torrents/)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Path
|
||||
new Regex(@"""C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DotNet4.SocksProxy" Version="1.4.0.1" />
|
||||
<PackageReference Include="DryIoc.dll" Version="4.7.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NLog" Version="4.7.9" />
|
||||
<PackageReference Include="Sentry" Version="3.3.3" />
|
||||
<PackageReference Include="Sentry" Version="3.8.3" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.1" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.113.0-0" />
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests
|
||||
{
|
||||
public class TestIndexerSettings : IProviderConfig
|
||||
public class TestIndexerSettings : IIndexerSettings
|
||||
{
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
@@ -14,5 +12,6 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||
}
|
||||
|
||||
public string BaseUrl { get; set; }
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<PackageReference Include="Dapper" Version="2.0.78" />
|
||||
<PackageReference Include="NBuilder" Version="6.1.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.113.0-0" />
|
||||
<PackageReference Include="YamlDotNet" Version="11.1.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Applications
|
||||
protected readonly IAppIndexerMapService _appIndexerMapService;
|
||||
protected readonly Logger _logger;
|
||||
|
||||
protected static readonly Regex AppIndexerRegex = new Regex(@"(?<indexer>\d*)/api",
|
||||
protected static readonly Regex AppIndexerRegex = new Regex(@"\/(?<indexer>\d.)\/",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
public abstract string Name { get; }
|
||||
|
||||
@@ -114,7 +114,12 @@ namespace NzbDrone.Core.Applications
|
||||
var prowlarrMappings = indexerMappings.ToDictionary(i => i.RemoteIndexerId, i => i.IndexerId);
|
||||
|
||||
//Get Dictionary of Remote Indexers point to Prowlarr and what they are mapped to
|
||||
var remoteMappings = app.GetIndexerMappings();
|
||||
var remoteMappings = ExecuteAction(a => a.GetIndexerMappings(), app);
|
||||
|
||||
if (remoteMappings == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//Add mappings if not already in db, these were setup manually in the app or orphaned by a table wipe
|
||||
foreach (var mapping in remoteMappings)
|
||||
@@ -214,5 +219,64 @@ namespace NzbDrone.Core.Applications
|
||||
_logger.Error(ex, "An error occurred while talking to remote application.");
|
||||
}
|
||||
}
|
||||
|
||||
private TResult ExecuteAction<TResult>(Func<IApplication, TResult> applicationAction, IApplication application)
|
||||
{
|
||||
TResult result;
|
||||
|
||||
try
|
||||
{
|
||||
result = applicationAction(application);
|
||||
_applicationStatusService.RecordSuccess(application.Definition.Id);
|
||||
return result;
|
||||
}
|
||||
catch (WebException webException)
|
||||
{
|
||||
if (webException.Status == WebExceptionStatus.NameResolutionFailure ||
|
||||
webException.Status == WebExceptionStatus.ConnectFailure)
|
||||
{
|
||||
_applicationStatusService.RecordConnectionFailure(application.Definition.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
_applicationStatusService.RecordFailure(application.Definition.Id);
|
||||
}
|
||||
|
||||
if (webException.Message.Contains("502") || webException.Message.Contains("503") ||
|
||||
webException.Message.Contains("timed out"))
|
||||
{
|
||||
_logger.Warn("{0} server is currently unavailable. {1}", this, webException.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("{0} {1}", this, webException.Message);
|
||||
}
|
||||
}
|
||||
catch (TooManyRequestsException ex)
|
||||
{
|
||||
if (ex.RetryAfter != TimeSpan.Zero)
|
||||
{
|
||||
_applicationStatusService.RecordFailure(application.Definition.Id, ex.RetryAfter);
|
||||
}
|
||||
else
|
||||
{
|
||||
_applicationStatusService.RecordFailure(application.Definition.Id, TimeSpan.FromHours(1));
|
||||
}
|
||||
|
||||
_logger.Warn("API Request Limit reached for {0}", this);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
_applicationStatusService.RecordFailure(application.Definition.Id);
|
||||
_logger.Warn("{0} {1}", this, ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_applicationStatusService.RecordFailure(application.Definition.Id);
|
||||
_logger.Error(ex, "An error occurred while talking to remote application.");
|
||||
}
|
||||
|
||||
return default(TResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using FluentMigrator;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
|
||||
63
src/NzbDrone.Core/Datastore/Migration/008_redacted_api.cs
Normal file
63
src/NzbDrone.Core/Datastore/Migration/008_redacted_api.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Data;
|
||||
using FluentMigrator;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(8)]
|
||||
public class redacted_api : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Execute.WithConnection(MigrateToRedactedApi);
|
||||
}
|
||||
|
||||
private void MigrateToRedactedApi(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.Transaction = tran;
|
||||
cmd.CommandText = "SELECT Id, Settings FROM Indexers WHERE Implementation = 'Redacted'";
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
var id = reader.GetInt32(0);
|
||||
var settings = reader.GetString(1);
|
||||
if (!string.IsNullOrWhiteSpace(settings))
|
||||
{
|
||||
var jsonObject = Json.Deserialize<JObject>(settings);
|
||||
|
||||
// Remove username
|
||||
if (jsonObject.ContainsKey("username"))
|
||||
{
|
||||
jsonObject.Remove("username");
|
||||
}
|
||||
|
||||
// Remove password
|
||||
if (jsonObject.ContainsKey("password"))
|
||||
{
|
||||
jsonObject.Remove("password");
|
||||
}
|
||||
|
||||
// write new json back to db, switch to new ConfigContract, and disable the indexer
|
||||
settings = jsonObject.ToJson();
|
||||
using (var updateCmd = conn.CreateCommand())
|
||||
{
|
||||
updateCmd.Transaction = tran;
|
||||
updateCmd.CommandText = "UPDATE Indexers SET Settings = ?, ConfigContract = ?, Enable = 0 WHERE Id = ?";
|
||||
updateCmd.AddParameter(settings);
|
||||
updateCmd.AddParameter("RedactedSettings");
|
||||
updateCmd.AddParameter(id);
|
||||
updateCmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
{
|
||||
public class TorrentBlackhole : TorrentClientBase<TorrentBlackholeSettings>
|
||||
{
|
||||
public override bool PreferTorrentFile => true;
|
||||
|
||||
public TorrentBlackhole(ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink)
|
||||
{
|
||||
throw new NotImplementedException("Blackhole does not support redirected indexers.");
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
|
||||
{
|
||||
if (!Settings.SaveMagnetFiles)
|
||||
{
|
||||
throw new NotSupportedException("Blackhole does not support magnet links.");
|
||||
}
|
||||
|
||||
var title = release.Title;
|
||||
|
||||
title = title.CleanFileName();
|
||||
|
||||
var filepath = Path.Combine(Settings.TorrentFolder, $"{title}.{Settings.MagnetFileExtension.Trim('.')}");
|
||||
|
||||
var fileContent = Encoding.UTF8.GetBytes(magnetLink);
|
||||
using (var stream = _diskProvider.OpenWriteStream(filepath))
|
||||
{
|
||||
stream.Write(fileContent, 0, fileContent.Length);
|
||||
}
|
||||
|
||||
_logger.Debug("Saving magnet link succeeded, saved to: {0}", filepath);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
var title = release.Title;
|
||||
|
||||
title = title.CleanFileName();
|
||||
|
||||
var filepath = Path.Combine(Settings.TorrentFolder, string.Format("{0}.torrent", title));
|
||||
|
||||
using (var stream = _diskProvider.OpenWriteStream(filepath))
|
||||
{
|
||||
stream.Write(fileContent, 0, fileContent.Length);
|
||||
}
|
||||
|
||||
_logger.Debug("Torrent Download succeeded, saved to: {0}", filepath);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override string Name => "Torrent Blackhole";
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
{
|
||||
failures.AddIfNotNull(TestFolder(Settings.TorrentFolder, "TorrentFolder"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.ComponentModel;
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
{
|
||||
public class TorrentBlackholeSettingsValidator : AbstractValidator<TorrentBlackholeSettings>
|
||||
{
|
||||
public TorrentBlackholeSettingsValidator()
|
||||
{
|
||||
//Todo: Validate that the path actually exists
|
||||
RuleFor(c => c.TorrentFolder).IsValidPath();
|
||||
RuleFor(c => c.MagnetFileExtension).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class TorrentBlackholeSettings : IProviderConfig
|
||||
{
|
||||
public TorrentBlackholeSettings()
|
||||
{
|
||||
MagnetFileExtension = ".magnet";
|
||||
}
|
||||
|
||||
private static readonly TorrentBlackholeSettingsValidator Validator = new TorrentBlackholeSettingsValidator();
|
||||
|
||||
[FieldDefinition(0, Label = "Torrent Folder", Type = FieldType.Path, HelpText = "Folder in which Prowlarr will store the .torrent file")]
|
||||
public string TorrentFolder { get; set; }
|
||||
|
||||
[DefaultValue(false)]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
[FieldDefinition(1, Label = "Save Magnet Files", Type = FieldType.Checkbox, HelpText = "Save a .magnet file with the magnet link if no .torrent file is available (only useful if the download client supports .magnet files)")]
|
||||
public bool SaveMagnetFiles { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Save Magnet Files", Type = FieldType.Textbox, HelpText = "Extension to use for magnet links, defaults to '.magnet'")]
|
||||
public string MagnetFileExtension { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
{
|
||||
public class UsenetBlackhole : UsenetClientBase<UsenetBlackholeSettings>
|
||||
{
|
||||
public UsenetBlackhole(IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(httpClient, configService, diskProvider, logger)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string AddFromLink(ReleaseInfo release)
|
||||
{
|
||||
throw new NotSupportedException("Blackhole does not support redirected indexers.");
|
||||
}
|
||||
|
||||
protected override string AddFromNzbFile(ReleaseInfo release, string filename, byte[] fileContent)
|
||||
{
|
||||
var title = release.Title;
|
||||
|
||||
title = title.CleanFileName();
|
||||
|
||||
var filepath = Path.Combine(Settings.NzbFolder, title + ".nzb");
|
||||
|
||||
using (var stream = _diskProvider.OpenWriteStream(filepath))
|
||||
{
|
||||
stream.Write(fileContent, 0, fileContent.Length);
|
||||
}
|
||||
|
||||
_logger.Debug("NZB Download succeeded, saved to: {0}", filepath);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override string Name => "Usenet Blackhole";
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
{
|
||||
failures.AddIfNotNull(TestFolder(Settings.NzbFolder, "NzbFolder"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
{
|
||||
public class UsenetBlackholeSettingsValidator : AbstractValidator<UsenetBlackholeSettings>
|
||||
{
|
||||
public UsenetBlackholeSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.NzbFolder).IsValidPath();
|
||||
}
|
||||
}
|
||||
|
||||
public class UsenetBlackholeSettings : IProviderConfig
|
||||
{
|
||||
private static readonly UsenetBlackholeSettingsValidator Validator = new UsenetBlackholeSettingsValidator();
|
||||
|
||||
[FieldDefinition(0, Label = "Nzb Folder", Type = FieldType.Path, HelpText = "Folder in which Prowlarr will store the .nzb file")]
|
||||
public string NzbFolder { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MonoTorrent;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using NzbDrone.Common.Exceptions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Exceptions
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Exceptions
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
@@ -16,6 +17,7 @@ namespace NzbDrone.Core.History
|
||||
History MostRecentForIndexer(int indexerId);
|
||||
List<History> Since(DateTime date, HistoryEventType? eventType);
|
||||
void Cleanup(int days);
|
||||
int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes);
|
||||
}
|
||||
|
||||
public class HistoryRepository : BasicRepository<History>, IHistoryRepository
|
||||
@@ -87,5 +89,21 @@ namespace NzbDrone.Core.History
|
||||
|
||||
return Query(builder).OrderBy(h => h.Date).ToList();
|
||||
}
|
||||
|
||||
public int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes)
|
||||
{
|
||||
var builder = new SqlBuilder()
|
||||
.SelectCount()
|
||||
.Where<History>(x => x.IndexerId == indexerId)
|
||||
.Where<History>(x => x.Date >= date)
|
||||
.Where<History>(x => eventTypes.Contains(x.EventType));
|
||||
|
||||
var sql = builder.AddPageCountTemplate(typeof(History));
|
||||
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
return conn.ExecuteScalar<int>(sql.RawSql, sql.Parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace NzbDrone.Core.History
|
||||
List<History> GetByIndexerId(int indexerId, HistoryEventType? eventType);
|
||||
void UpdateMany(List<History> toUpdate);
|
||||
List<History> Since(DateTime date, HistoryEventType? eventType);
|
||||
int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes);
|
||||
}
|
||||
|
||||
public class HistoryService : IHistoryService,
|
||||
@@ -205,5 +206,10 @@ namespace NzbDrone.Core.History
|
||||
{
|
||||
_historyRepository.Purge(vacuum: true);
|
||||
}
|
||||
|
||||
public int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes)
|
||||
{
|
||||
return _historyRepository.CountSince(indexerId, date, eventTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
@"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
public List<ReleaseInfo> Releases;
|
||||
public List<ReleaseInfo> Releases { get; set; }
|
||||
|
||||
private static string RemoveInvalidXMLChars(string text)
|
||||
{
|
||||
@@ -100,7 +100,8 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
GetNabElement("minimumratio", t.MinimumRatio, protocol),
|
||||
GetNabElement("minimumseedtime", t.MinimumSeedTime, protocol),
|
||||
GetNabElement("downloadvolumefactor", t.DownloadVolumeFactor, protocol),
|
||||
GetNabElement("uploadvolumefactor", t.UploadVolumeFactor, protocol)))));
|
||||
GetNabElement("uploadvolumefactor", t.UploadVolumeFactor, protocol),
|
||||
GetNabElement("coverurl", r.PosterUrl, protocol)))));
|
||||
|
||||
return xdoc.Declaration + Environment.NewLine + xdoc;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.Events;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@@ -20,19 +19,19 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
|
||||
public class NzbSearchService : ISearchForNzb
|
||||
{
|
||||
private readonly IIndexerLimitService _indexerLimitService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
private readonly IDownloadMappingService _downloadMappingService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public NzbSearchService(IEventAggregator eventAggregator,
|
||||
IIndexerFactory indexerFactory,
|
||||
IDownloadMappingService downloadMappingService,
|
||||
IIndexerLimitService indexerLimitService,
|
||||
Logger logger)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_indexerFactory = indexerFactory;
|
||||
_downloadMappingService = downloadMappingService;
|
||||
_indexerLimitService = indexerLimitService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -163,16 +162,36 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
|
||||
private async Task<IList<ReleaseInfo>> DispatchIndexer(Func<IIndexer, Task<IndexerPageableQueryResult>> searchAction, IIndexer indexer, SearchCriteriaBase criteriaBase)
|
||||
{
|
||||
if (_indexerLimitService.AtQueryLimit((IndexerDefinition)indexer.Definition))
|
||||
{
|
||||
return new List<ReleaseInfo>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var indexerReports = await searchAction(indexer);
|
||||
|
||||
var releases = indexerReports.Releases;
|
||||
|
||||
//Filter results to only those in searched categories
|
||||
if (criteriaBase.Categories.Length > 0)
|
||||
{
|
||||
var expandedQueryCats = ((IndexerDefinition)indexer.Definition).Capabilities.Categories.ExpandTorznabQueryCategories(criteriaBase.Categories);
|
||||
|
||||
releases = releases.Where(result => result.Categories?.Any() != true || expandedQueryCats.Intersect(result.Categories.Select(c => c.Id)).Any()).ToList();
|
||||
|
||||
if (releases.Count != indexerReports.Releases.Count)
|
||||
{
|
||||
_logger.Trace("{0} {1} Releases which didn't contain search categories [{2}] were filtered", indexerReports.Releases.Count - releases.Count, indexer.Name, string.Join(", ", expandedQueryCats));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var query in indexerReports.Queries)
|
||||
{
|
||||
_eventAggregator.PublishEvent(new IndexerQueryEvent(indexer.Definition.Id, criteriaBase, query.ElapsedTime, query.StatusCode == 200, query.Releases.Count()));
|
||||
}
|
||||
|
||||
return indexerReports.Releases;
|
||||
return releases;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,17 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
public class IndexerDefinitionUpdateService : IIndexerDefinitionUpdateService, IExecute<IndexerDefinitionUpdateCommand>
|
||||
{
|
||||
private const int DEFINITION_VERSION = 1;
|
||||
private readonly List<string> _defintionBlacklist = new List<string>() { "aither", "animeworld", "blutopia", "beyond-hd", "beyond-hd-oneurl", "hdbits", "shareisland" };
|
||||
private readonly List<string> _defintionBlacklist = new List<string>()
|
||||
{
|
||||
"aither",
|
||||
"animeworld",
|
||||
"blutopia",
|
||||
"beyond-hd",
|
||||
"beyond-hd-oneurl",
|
||||
"danishbytes",
|
||||
"hdbits",
|
||||
"shareisland"
|
||||
};
|
||||
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class AlphaRatio : Gazelle.Gazelle
|
||||
{
|
||||
public override string Name => "AlphaRatio";
|
||||
public override string BaseUrl => "https://alpharatio.cc/";
|
||||
public override string[] IndexerUrls => new string[] { "https://alpharatio.cc/" };
|
||||
public override string Description => "AlphaRatio(AR) is a Private Torrent Tracker for 0DAY / GENERAL";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
@@ -25,8 +25,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
Settings = Settings,
|
||||
HttpClient = _httpClient,
|
||||
Logger = _logger,
|
||||
Capabilities = Capabilities,
|
||||
BaseUrl = BaseUrl
|
||||
Capabilities = Capabilities
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,7 +39,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
||||
MovieSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
550
src/NzbDrone.Core/Indexers/Definitions/Anidub.cs
Normal file
550
src/NzbDrone.Core/Indexers/Definitions/Anidub.cs
Normal file
@@ -0,0 +1,550 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Html.Parser;
|
||||
using FluentValidation;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class Anidub : TorrentIndexerBase<AnidubSettings>
|
||||
{
|
||||
public override string Name => "Anidub";
|
||||
public override string[] IndexerUrls => new string[] { "https://tr.anidub.com/" };
|
||||
public override string Description => "Anidub is russian anime voiceover group and eponymous anime tracker.";
|
||||
public override string Language => "ru-ru";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.SemiPublic;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public Anidub(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new AnidubRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new AnidubParser(Settings, Capabilities.Categories) { HttpClient = _httpClient, Logger = _logger };
|
||||
}
|
||||
|
||||
protected override async Task DoLogin()
|
||||
{
|
||||
UpdateCookies(null, null);
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl + "index.php")
|
||||
{
|
||||
LogResponseContent = true,
|
||||
AllowAutoRedirect = true
|
||||
};
|
||||
|
||||
var mainPage = await _httpClient.ExecuteAsync(new HttpRequest(Settings.BaseUrl));
|
||||
|
||||
requestBuilder.Method = Common.Http.HttpMethod.POST;
|
||||
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
|
||||
requestBuilder.SetCookies(mainPage.GetCookies());
|
||||
|
||||
var authLoginRequest = requestBuilder
|
||||
.AddFormParameter("login_name", Settings.Username)
|
||||
.AddFormParameter("login_password", Settings.Password)
|
||||
.AddFormParameter("login", "submit")
|
||||
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
.Build();
|
||||
|
||||
var response = await ExecuteAuth(authLoginRequest);
|
||||
|
||||
if (response.Content != null && !CheckIfLoginNeeded(response))
|
||||
{
|
||||
UpdateCookies(response.GetCookies(), DateTime.Now + TimeSpan.FromDays(30));
|
||||
_logger.Debug("Anidub authentication succeeded");
|
||||
}
|
||||
else
|
||||
{
|
||||
const string ErrorSelector = "#content .berror .berror_c";
|
||||
var parser = new HtmlParser();
|
||||
var document = await parser.ParseDocumentAsync(response.Content);
|
||||
var errorMessage = document.QuerySelector(ErrorSelector).TextContent.Trim();
|
||||
throw new IndexerAuthException("Anidub authentication failed. Error: " + errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
{
|
||||
if (httpResponse.Content.Contains("index.php?action=logout"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "Аниме TV");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Movies, "Аниме Фильмы");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVAnime, "Аниме OVA");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVAnime, "Аниме OVA |- Аниме ONA");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TV, "Дорамы / Японские Сериалы и Фильмы");
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.TV, "Дорамы / Корейские Сериалы и Фильмы");
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.TV, "Дорамы / Китайские Сериалы и Фильмы");
|
||||
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.TV, "Дорамы");
|
||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.TVAnime, "Аниме TV / Аниме Ongoing");
|
||||
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.TVAnime, "Аниме TV / Многосерийный сёнэн");
|
||||
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.Other, "Аниме Ongoing Анонсы");
|
||||
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.XXX, "18+");
|
||||
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.TVAnime, "Аниме TV / Законченные сериалы");
|
||||
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.BooksComics, "Манга");
|
||||
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.Audio, "OST");
|
||||
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.Audio, "Подкасты");
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class AnidubRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public AnidubSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
|
||||
public AnidubRequestGenerator()
|
||||
{
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
|
||||
{
|
||||
var requestUrl = string.Empty;
|
||||
var isSearch = !string.IsNullOrWhiteSpace(term);
|
||||
|
||||
if (isSearch)
|
||||
{
|
||||
requestUrl = string.Format("{0}/index.php?do=search", Settings.BaseUrl.TrimEnd('/'));
|
||||
}
|
||||
else
|
||||
{
|
||||
requestUrl = Settings.BaseUrl;
|
||||
}
|
||||
|
||||
var request = new IndexerRequest(requestUrl, HttpAccept.Html);
|
||||
|
||||
if (isSearch)
|
||||
{
|
||||
request.HttpRequest.Method = NzbDrone.Common.Http.HttpMethod.POST;
|
||||
var postData = new NameValueCollection
|
||||
{
|
||||
{ "do", "search" },
|
||||
{ "subaction", "search" },
|
||||
{ "search_start", "1" },
|
||||
{ "full_search", "1" },
|
||||
{ "result_from", "1" },
|
||||
|
||||
// Remove season and episode info from search term cause it breaks search
|
||||
{ "story", Regex.Replace(term, @"(?:[SsEe]?\d{1,4}){1,2}$", "").TrimEnd() },
|
||||
{ "titleonly", "3" },
|
||||
{ "searchuser", "" },
|
||||
{ "replyless", "0" },
|
||||
{ "replylimit", "0" },
|
||||
{ "searchdate", "0" },
|
||||
{ "beforeafter", "after" },
|
||||
{ "sortby", "" },
|
||||
{ "resorder", "desc" },
|
||||
{ "showposts", "1" },
|
||||
{ "catlist[]", "0" }
|
||||
};
|
||||
var headers = new NameValueCollection
|
||||
{
|
||||
{ "Content-Type", "application/x-www-form-urlencoded" }
|
||||
};
|
||||
|
||||
request.HttpRequest.SetContent(postData.GetQueryString());
|
||||
request.HttpRequest.Headers.Add(headers);
|
||||
}
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class AnidubParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly AnidubSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
public IHttpClient HttpClient { get; set; }
|
||||
public Logger Logger { get; set; }
|
||||
|
||||
private static Dictionary<string, string> CategoriesMap => new Dictionary<string, string>
|
||||
{
|
||||
{ "/anime_tv/full", "14" },
|
||||
{ "/anime_tv/anime_ongoing", "10" },
|
||||
{ "/anime_tv/shonen", "11" },
|
||||
{ "/anime_tv", "2" },
|
||||
{ "/xxx", "13" },
|
||||
{ "/manga", "15" },
|
||||
{ "/ost", "16" },
|
||||
{ "/podcast", "17" },
|
||||
{ "/anime_movie", "3" },
|
||||
{ "/anime_ova/anime_ona", "5" },
|
||||
{ "/anime_ova", "4" },
|
||||
{ "/dorama/japan_dorama", "6" },
|
||||
{ "/dorama/korea_dorama", "7" },
|
||||
{ "/dorama/china_dorama", "8" },
|
||||
{ "/dorama", "9" },
|
||||
{ "/anons_ongoing", "12" }
|
||||
};
|
||||
|
||||
public AnidubParser(AnidubSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
private static string GetTitle(AngleSharp.Html.Dom.IHtmlDocument content, AngleSharp.Dom.IElement tabNode)
|
||||
{
|
||||
var domTitle = content.QuerySelector("#news-title");
|
||||
var baseTitle = domTitle.TextContent.Trim();
|
||||
var quality = GetQuality(tabNode.ParentElement);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(quality))
|
||||
{
|
||||
return $"{baseTitle} [{quality}]";
|
||||
}
|
||||
|
||||
return baseTitle;
|
||||
}
|
||||
|
||||
private static string GetQuality(AngleSharp.Dom.IElement releaseNode)
|
||||
{
|
||||
// For some releases there's no block with quality
|
||||
if (string.IsNullOrWhiteSpace(releaseNode.Id))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var quality = releaseNode.Id.Trim();
|
||||
switch (quality.ToLowerInvariant())
|
||||
{
|
||||
case "tv720":
|
||||
return "HDTV 720p";
|
||||
case "tv1080":
|
||||
return "HDTV 1080p";
|
||||
case "bd720":
|
||||
return "BDRip 720p";
|
||||
case "bd1080":
|
||||
return "BDRip 1080p";
|
||||
case "hwp":
|
||||
return "SDTV";
|
||||
default:
|
||||
return quality.ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetReleaseLeechers(AngleSharp.Dom.IElement tabNode)
|
||||
{
|
||||
const string LeechersSelector = ".list.down > .li_swing_m";
|
||||
|
||||
var leechersStr = tabNode.QuerySelector(LeechersSelector).TextContent;
|
||||
int.TryParse(leechersStr, out var leechers);
|
||||
return leechers;
|
||||
}
|
||||
|
||||
private static int GetReleaseSeeders(AngleSharp.Dom.IElement tabNode)
|
||||
{
|
||||
const string SeedersSelector = ".list.down > .li_distribute_m";
|
||||
|
||||
var seedersStr = tabNode.QuerySelector(SeedersSelector).TextContent;
|
||||
int.TryParse(seedersStr, out var seeders);
|
||||
return seeders;
|
||||
}
|
||||
|
||||
private static int GetReleaseGrabs(AngleSharp.Dom.IElement tabNode)
|
||||
{
|
||||
const string GrabsSelector = ".list.down > .li_download_m";
|
||||
|
||||
var grabsStr = tabNode.QuerySelector(GrabsSelector).TextContent;
|
||||
int.TryParse(grabsStr, out var grabs);
|
||||
return grabs;
|
||||
}
|
||||
|
||||
private static string GetDateFromDocument(AngleSharp.Html.Dom.IHtmlDocument content)
|
||||
{
|
||||
const string DateSelector = ".story_inf > li:nth-child(2)";
|
||||
|
||||
var domDate = content.QuerySelector(DateSelector).LastChild;
|
||||
|
||||
if (domDate?.NodeName != "#text")
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return domDate.NodeValue.Trim();
|
||||
}
|
||||
|
||||
private DateTime GetDateFromShowPage(AngleSharp.Html.Dom.IHtmlDocument content)
|
||||
{
|
||||
const string dateFormat = "d-MM-yyyy";
|
||||
const string dateTimeFormat = dateFormat + ", HH:mm";
|
||||
|
||||
// Would be better to use AssumeLocal and provide "ru-RU" culture,
|
||||
// but doesn't work cross-platform
|
||||
const DateTimeStyles style = DateTimeStyles.AssumeUniversal;
|
||||
|
||||
var culture = CultureInfo.InvariantCulture;
|
||||
|
||||
var dateText = GetDateFromDocument(content);
|
||||
|
||||
//Correct way but will not always work on cross-platform
|
||||
//var localTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time");
|
||||
//var nowLocal = TimeZoneInfo.ConvertTime(DateTime.UtcNow, localTimeZone);
|
||||
|
||||
// Russian Standard Time is +03:00, no DST
|
||||
const int russianStandardTimeDiff = 3;
|
||||
var nowLocal = DateTime.UtcNow.AddHours(russianStandardTimeDiff);
|
||||
|
||||
dateText = dateText
|
||||
.Replace("Вчера", nowLocal.AddDays(-1).ToString(dateFormat))
|
||||
.Replace("Сегодня", nowLocal.ToString(dateFormat));
|
||||
|
||||
if (DateTime.TryParseExact(dateText, dateTimeFormat, culture, style, out var date))
|
||||
{
|
||||
var utcDate = date.ToUniversalTime();
|
||||
return utcDate.AddHours(-russianStandardTimeDiff);
|
||||
}
|
||||
|
||||
Logger.Warn($"[AniDub] Date time couldn't be parsed on. Date text: {dateText}");
|
||||
|
||||
return DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private static long GetReleaseSize(AngleSharp.Dom.IElement tabNode)
|
||||
{
|
||||
const string SizeSelector = ".list.down > .red";
|
||||
|
||||
var sizeStr = tabNode.QuerySelector(SizeSelector).TextContent;
|
||||
return ReleaseInfo.GetBytes(sizeStr);
|
||||
}
|
||||
|
||||
private string GetReleaseLink(AngleSharp.Dom.IElement tabNode)
|
||||
{
|
||||
return $"{_settings.BaseUrl}engine/download.php?id={GetTorrentId(tabNode)}";
|
||||
}
|
||||
|
||||
private static string GetTorrentId(AngleSharp.Dom.IElement tabNode)
|
||||
{
|
||||
var nodeId = tabNode.Id;
|
||||
|
||||
// Format is "torrent_{id}_info"
|
||||
return nodeId
|
||||
.Replace("torrent_", string.Empty)
|
||||
.Replace("_info", string.Empty);
|
||||
}
|
||||
|
||||
private ICollection<IndexerCategory> ParseCategories(string uriPath)
|
||||
{
|
||||
var categoriesMap = CategoriesMap;
|
||||
|
||||
return categoriesMap
|
||||
.Where(categoryMap => uriPath.StartsWith(categoryMap.Key))
|
||||
.Select(categoryMap => _categories.MapTrackerCatToNewznab(categoryMap.Value))
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
private IList<TorrentInfo> ParseRelease(IndexerResponse indexerResponse)
|
||||
{
|
||||
var torrentInfos = new List<TorrentInfo>();
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
foreach (var t in dom.QuerySelectorAll("#tabs .torrent_c > div"))
|
||||
{
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Title = GetTitle(dom, t),
|
||||
InfoUrl = indexerResponse.Request.Url.ToString(),
|
||||
DownloadVolumeFactor = 0,
|
||||
UploadVolumeFactor = 1,
|
||||
|
||||
Guid = indexerResponse.Request.Url.ToString() + t.Id,
|
||||
Seeders = GetReleaseSeeders(t),
|
||||
Peers = GetReleaseSeeders(t) + GetReleaseLeechers(t),
|
||||
Grabs = GetReleaseGrabs(t),
|
||||
Categories = ParseCategories(indexerResponse.Request.Url.Path),
|
||||
PublishDate = GetDateFromShowPage(dom),
|
||||
DownloadUrl = GetReleaseLink(t),
|
||||
Size = GetReleaseSize(t),
|
||||
Resolution = GetQuality(t)
|
||||
};
|
||||
torrentInfos.Add(release);
|
||||
}
|
||||
|
||||
return torrentInfos;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
var domQuery = string.Empty;
|
||||
|
||||
if (indexerResponse.Request.Url.Query.Contains("do=search"))
|
||||
{
|
||||
domQuery = ".searchitem > h3 > a";
|
||||
}
|
||||
else
|
||||
{
|
||||
domQuery = "#dle-content > .story > .story_h > .lcol > h2 > a";
|
||||
}
|
||||
|
||||
var links = dom.QuerySelectorAll(domQuery);
|
||||
foreach (var link in links)
|
||||
{
|
||||
var url = link.GetAttribute("href");
|
||||
|
||||
var releaseRequest = new IndexerRequest(url, HttpAccept.Html);
|
||||
var releaseResponse = new IndexerResponse(releaseRequest, HttpClient.Execute(releaseRequest.HttpRequest));
|
||||
|
||||
// Throw common http errors here before we try to parse
|
||||
if (releaseResponse.HttpResponse.HasHttpError)
|
||||
{
|
||||
if ((int)releaseResponse.HttpResponse.StatusCode == 429)
|
||||
{
|
||||
throw new TooManyRequestsException(releaseRequest.HttpRequest, releaseResponse.HttpResponse);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IndexerException(releaseResponse, "Http error code: " + releaseResponse.HttpResponse.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
torrentInfos.AddRange(ParseRelease(releaseResponse));
|
||||
}
|
||||
|
||||
return torrentInfos.ToArray();
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class AnidubSettingsValidator : AbstractValidator<AnidubSettings>
|
||||
{
|
||||
public AnidubSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.Username).NotEmpty();
|
||||
RuleFor(c => c.Password).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class AnidubSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly AnidubSettingsValidator Validator = new AnidubSettingsValidator();
|
||||
|
||||
public AnidubSettings()
|
||||
{
|
||||
Username = "";
|
||||
Password = "";
|
||||
}
|
||||
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
|
||||
public string Username { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
294
src/NzbDrone.Core/Indexers/Definitions/Anilibria.cs
Normal file
294
src/NzbDrone.Core/Indexers/Definitions/Anilibria.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class Anilibria : TorrentIndexerBase<AnilibriaSettings>
|
||||
{
|
||||
public override string Name => "Anilibria";
|
||||
public override string[] IndexerUrls => new string[] { "https://anilibria.tv/" };
|
||||
public override string Description => "Anilibria is russian anime voiceover group and eponymous anime tracker.";
|
||||
public override string Language => "ru-ru";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public Anilibria(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new AnilibriaRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new AnilibriaParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
}
|
||||
};
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "ТВ");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVAnime, "ONA");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVAnime, "OVA");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Movies, "Фильм");
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class AnilibriaRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public AnilibriaSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
|
||||
public AnilibriaRequestGenerator()
|
||||
{
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
|
||||
{
|
||||
var apiUrl = Regex.Replace(Settings.BaseUrl, @"(https?:\/\/)(.*)", "$1api.$2v2");
|
||||
var queryCollection = new NameValueCollection
|
||||
{
|
||||
{ "limit", "100" },
|
||||
{ "filter", "names,code,torrents.list,season.year,type.string" }
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(term))
|
||||
{
|
||||
apiUrl += "/getUpdates?" + queryCollection.GetQueryString();
|
||||
}
|
||||
else
|
||||
{
|
||||
apiUrl += "/searchTitles?" + queryCollection.GetQueryString() + "&search=" + Uri.EscapeDataString(term);
|
||||
}
|
||||
|
||||
var request = new IndexerRequest(apiUrl, HttpAccept.Json);
|
||||
yield return request;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
// Anilibria doesn't support music, but this function required by interface
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
// Anilibria doesn't support books, but this function required by interface
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class AnilibriaParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly AnilibriaSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
public AnilibriaParser(AnilibriaSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
private string composeTitle(AnilibriaTitle tl, AnilibriaTorrent tr)
|
||||
{
|
||||
var title = tl.Names.Ru;
|
||||
title += " / " + tl.Names.En;
|
||||
if (tl.Names.Alternative is string)
|
||||
{
|
||||
title += " / " + tl.Names.Alternative;
|
||||
}
|
||||
|
||||
title += " " + tl.Season.Year;
|
||||
title += " [" + tr.Quality.String + "]";
|
||||
if (!string.IsNullOrWhiteSpace(tr.Series.String))
|
||||
{
|
||||
title += " - E" + tr.Series.String;
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
var queryResponseItems = JsonConvert.DeserializeObject<List<AnilibriaTitle>>(indexerResponse.Content);
|
||||
|
||||
foreach (var tl in queryResponseItems)
|
||||
{
|
||||
foreach (var tr in tl.Torrents.List)
|
||||
{
|
||||
var torrentInfo = new TorrentInfo
|
||||
{
|
||||
Title = composeTitle(tl, tr),
|
||||
InfoUrl = string.Format("{0}/release/{1}.html", _settings.BaseUrl.TrimEnd('/'), tl.Code),
|
||||
DownloadVolumeFactor = 0,
|
||||
UploadVolumeFactor = 1,
|
||||
Seeders = tr.Seeders,
|
||||
Peers = tr.Leechers + tr.Seeders,
|
||||
Grabs = tr.Downloads,
|
||||
Categories = _categories.MapTrackerCatDescToNewznab(tl.Type.String),
|
||||
|
||||
// API provides timestamp in UTC+3 timezone, so we need to substract 3 hours
|
||||
PublishDate = DateTimeUtil.UnixTimestampToDateTime(tr.UploadedTimestamp).AddHours(-3),
|
||||
Guid = _settings.BaseUrl + tr.Url,
|
||||
DownloadUrl = _settings.BaseUrl + tr.Url,
|
||||
Size = tr.TotalSize,
|
||||
Resolution = tr.Quality.Resolution,
|
||||
Codec = tr.Quality.Encoder
|
||||
};
|
||||
|
||||
torrentInfos.Add(torrentInfo);
|
||||
}
|
||||
}
|
||||
|
||||
return torrentInfos.ToArray();
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class AnilibriaSettingsValidator : AbstractValidator<AnilibriaSettings>
|
||||
{
|
||||
public AnilibriaSettingsValidator()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class AnilibriaSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly AnilibriaSettingsValidator Validator = new AnilibriaSettingsValidator();
|
||||
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
|
||||
public class AnilibriaTitle
|
||||
{
|
||||
public AnilibriaNames Names { get; set; }
|
||||
public string Code { get; set; }
|
||||
public AnilibriaTorrents Torrents { get; set; }
|
||||
public AnilibriaSeason Season { get; set; }
|
||||
public AnilibriaTitleType Type { get; set; }
|
||||
}
|
||||
|
||||
public class AnilibriaTitleType
|
||||
{
|
||||
public string String { get; set; }
|
||||
}
|
||||
|
||||
public class AnilibriaNames
|
||||
{
|
||||
public string Ru { get; set; }
|
||||
public string En { get; set; }
|
||||
public object Alternative { get; set; }
|
||||
}
|
||||
|
||||
public class AnilibriaSeason
|
||||
{
|
||||
public long Year { get; set; }
|
||||
}
|
||||
|
||||
public class AnilibriaTorrents
|
||||
{
|
||||
public AnilibriaTorrent[] List { get; set; }
|
||||
}
|
||||
|
||||
public class AnilibriaTorrent
|
||||
{
|
||||
public AnilibriaSeries Series { get; set; }
|
||||
public AnilibriaQuality Quality { get; set; }
|
||||
public int Leechers { get; set; }
|
||||
public int Seeders { get; set; }
|
||||
public int Downloads { get; set; }
|
||||
|
||||
[JsonProperty("total_size")]
|
||||
public long TotalSize { get; set; }
|
||||
public string Url { get; set; }
|
||||
|
||||
[JsonProperty("uploaded_timestamp")]
|
||||
public long UploadedTimestamp { get; set; }
|
||||
}
|
||||
|
||||
public class AnilibriaQuality
|
||||
{
|
||||
public string String { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Resolution { get; set; }
|
||||
public string Encoder { get; set; }
|
||||
}
|
||||
|
||||
public class AnilibriaSeries
|
||||
{
|
||||
public long First { get; set; }
|
||||
public long Last { get; set; }
|
||||
public string String { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
@@ -18,7 +19,6 @@ using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
@@ -26,8 +26,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class AnimeBytes : TorrentIndexerBase<AnimeBytesSettings>
|
||||
{
|
||||
public override string Name => "AnimeBytes";
|
||||
public override string BaseUrl => "https://animebytes.tv/";
|
||||
public override string Description => "Powered by Tentacles";
|
||||
public override string[] IndexerUrls => new string[] { "https://animebytes.tv/" };
|
||||
public override string Description => "AnimeBytes (AB) is the largest private torrent tracker that specialises in anime and anime-related content.";
|
||||
public override string Language => "en-us";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
@@ -41,12 +41,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new AnimeBytesRequestGenerator() { Settings = Settings, Capabilities = Capabilities, BaseUrl = BaseUrl };
|
||||
return new AnimeBytesRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new AnimeBytesParser(Settings, Capabilities.Categories, BaseUrl);
|
||||
return new AnimeBytesParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
@@ -101,7 +101,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public AnimeBytesSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
public AnimeBytesRequestGenerator()
|
||||
{
|
||||
@@ -109,14 +108,14 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string searchType, string term, int[] categories)
|
||||
{
|
||||
var searchUrl = string.Format("{0}/scrape.php", BaseUrl.TrimEnd('/'));
|
||||
var searchUrl = string.Format("{0}/scrape.php", Settings.BaseUrl.TrimEnd('/'));
|
||||
|
||||
var queryCollection = new NameValueCollection
|
||||
{
|
||||
{ "username", Settings.Username },
|
||||
{ "torrent_pass", Settings.Passkey },
|
||||
{ "type", searchType },
|
||||
{ "searchstr", term }
|
||||
{ "searchstr", StripEpisodeNumber(term) }
|
||||
};
|
||||
|
||||
var queryCats = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
||||
@@ -183,19 +182,26 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
private string StripEpisodeNumber(string term)
|
||||
{
|
||||
// Tracer does not support searching with episode number so strip it if we have one
|
||||
term = Regex.Replace(term, @"\W(\dx)?\d?\d$", string.Empty);
|
||||
term = Regex.Replace(term, @"\W(S\d\d?E)?\d?\d$", string.Empty);
|
||||
term = Regex.Replace(term, @"\W\d+$", string.Empty);
|
||||
return term;
|
||||
}
|
||||
}
|
||||
|
||||
public class AnimeBytesParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly AnimeBytesSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public AnimeBytesParser(AnimeBytesSettings settings, IndexerCapabilitiesCategories categories, string baseUrl)
|
||||
public AnimeBytesParser(AnimeBytesSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
_baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
@@ -212,29 +218,17 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
|
||||
}
|
||||
|
||||
//TODO: Create API Resource Type
|
||||
var json = JsonConvert.DeserializeObject<dynamic>(indexerResponse.Content);
|
||||
var response = JsonConvert.DeserializeObject<AnimeBytesResponse>(indexerResponse.Content);
|
||||
|
||||
if (json["error"] != null)
|
||||
if (response.Matches > 0)
|
||||
{
|
||||
throw new Exception(json["error"].ToString());
|
||||
}
|
||||
|
||||
var matches = (long)json["Matches"];
|
||||
|
||||
if (matches > 0)
|
||||
{
|
||||
var groups = (JArray)json.Groups;
|
||||
|
||||
foreach (var group in groups)
|
||||
foreach (var group in response.Groups)
|
||||
{
|
||||
var synonyms = new List<string>();
|
||||
var posterStr = (string)group["Image"];
|
||||
var poster = string.IsNullOrWhiteSpace(posterStr) ? null : new Uri(posterStr);
|
||||
var year = (int)group["Year"];
|
||||
var groupName = (string)group["GroupName"];
|
||||
var seriesName = (string)group["SeriesName"];
|
||||
var mainTitle = WebUtility.HtmlDecode((string)group["FullName"]);
|
||||
var year = group.Year;
|
||||
var groupName = group.GroupName;
|
||||
var seriesName = group.SeriesName;
|
||||
var mainTitle = WebUtility.HtmlDecode(group.FullName);
|
||||
if (seriesName != null)
|
||||
{
|
||||
mainTitle = seriesName;
|
||||
@@ -242,105 +236,111 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
synonyms.Add(mainTitle);
|
||||
|
||||
// TODO: Do we need all these options?
|
||||
//if (group["Synonymns"].HasValues)
|
||||
//{
|
||||
// if (group["Synonymns"] is JArray)
|
||||
// {
|
||||
// var allSyonyms = group["Synonymns"].ToObject<List<string>>();
|
||||
|
||||
// if (AddJapaneseTitle && allSyonyms.Count >= 1)
|
||||
// synonyms.Add(allSyonyms[0]);
|
||||
// if (AddRomajiTitle && allSyonyms.Count >= 2)
|
||||
// synonyms.Add(allSyonyms[1]);
|
||||
// if (AddAlternativeTitles && allSyonyms.Count >= 3)
|
||||
// synonyms.AddRange(allSyonyms[2].Split(',').Select(t => t.Trim()));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// var allSynonyms = group["Synonymns"].ToObject<Dictionary<int, string>>();
|
||||
|
||||
// if (AddJapaneseTitle && allSynonyms.ContainsKey(0))
|
||||
// synonyms.Add(allSynonyms[0]);
|
||||
// if (AddRomajiTitle && allSynonyms.ContainsKey(1))
|
||||
// synonyms.Add(allSynonyms[1]);
|
||||
// if (AddAlternativeTitles && allSynonyms.ContainsKey(2))
|
||||
// {
|
||||
// synonyms.AddRange(allSynonyms[2].Split(',').Select(t => t.Trim()));
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
List<IndexerCategory> category = null;
|
||||
var categoryName = (string)group["CategoryName"];
|
||||
|
||||
var description = (string)group["Description"];
|
||||
|
||||
foreach (var torrent in group["Torrents"])
|
||||
if (group.Synonymns.StringArray != null)
|
||||
{
|
||||
var releaseInfo = "S01";
|
||||
string episode = null;
|
||||
synonyms.AddRange(group.Synonymns.StringArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
synonyms.AddRange(group.Synonymns.StringMap.Values);
|
||||
}
|
||||
|
||||
List<IndexerCategory> category = null;
|
||||
var categoryName = group.CategoryName;
|
||||
|
||||
var description = group.Description;
|
||||
|
||||
foreach (var torrent in group.Torrents)
|
||||
{
|
||||
var releaseInfo = _settings.EnableSonarrCompatibility ? "S01" : "";
|
||||
int? episode = null;
|
||||
int? season = null;
|
||||
var editionTitle = (string)torrent["EditionData"]["EditionTitle"];
|
||||
var editionTitle = torrent.EditionData.EditionTitle;
|
||||
if (!string.IsNullOrWhiteSpace(editionTitle))
|
||||
{
|
||||
releaseInfo = WebUtility.HtmlDecode(editionTitle);
|
||||
|
||||
if (_settings.EnableSonarrCompatibility)
|
||||
{
|
||||
var simpleSeasonRegEx = new Regex(@"Season (\d+)", RegexOptions.Compiled);
|
||||
var simpleSeasonRegExMatch = simpleSeasonRegEx.Match(releaseInfo);
|
||||
if (simpleSeasonRegExMatch.Success)
|
||||
{
|
||||
season = ParseUtil.CoerceInt(simpleSeasonRegExMatch.Groups[1].Value);
|
||||
}
|
||||
}
|
||||
|
||||
var episodeRegEx = new Regex(@"Episode (\d+)", RegexOptions.Compiled);
|
||||
var episodeRegExMatch = episodeRegEx.Match(releaseInfo);
|
||||
if (episodeRegExMatch.Success)
|
||||
{
|
||||
episode = ParseUtil.CoerceInt(episodeRegExMatch.Groups[1].Value);
|
||||
}
|
||||
}
|
||||
|
||||
var seasonRegEx = new Regex(@"Season (\d+)", RegexOptions.Compiled);
|
||||
var seasonRegExMatch = seasonRegEx.Match(releaseInfo);
|
||||
if (seasonRegExMatch.Success)
|
||||
if (_settings.EnableSonarrCompatibility)
|
||||
{
|
||||
season = ParseUtil.CoerceInt(seasonRegExMatch.Groups[1].Value);
|
||||
var advancedSeasonRegEx = new Regex(@"(\d+)(st|nd|rd|th) Season", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
var advancedSeasonRegExMatch = advancedSeasonRegEx.Match(mainTitle);
|
||||
if (advancedSeasonRegExMatch.Success)
|
||||
{
|
||||
season = ParseUtil.CoerceInt(advancedSeasonRegExMatch.Groups[1].Value);
|
||||
}
|
||||
|
||||
var seasonCharactersRegEx = new Regex(@"(I{2,})$", RegexOptions.Compiled);
|
||||
var seasonCharactersRegExMatch = seasonCharactersRegEx.Match(mainTitle);
|
||||
if (seasonCharactersRegExMatch.Success)
|
||||
{
|
||||
season = seasonCharactersRegExMatch.Groups[1].Value.Length;
|
||||
}
|
||||
|
||||
var seasonNumberRegEx = new Regex(@"([2-9])$", RegexOptions.Compiled);
|
||||
var seasonNumberRegExMatch = seasonNumberRegEx.Match(mainTitle);
|
||||
if (seasonNumberRegExMatch.Success)
|
||||
{
|
||||
season = ParseUtil.CoerceInt(seasonNumberRegExMatch.Groups[1].Value);
|
||||
}
|
||||
}
|
||||
|
||||
var episodeRegEx = new Regex(@"Episode (\d+)", RegexOptions.Compiled);
|
||||
var episodeRegExMatch = episodeRegEx.Match(releaseInfo);
|
||||
if (episodeRegExMatch.Success)
|
||||
if (episode != null)
|
||||
{
|
||||
episode = episodeRegExMatch.Groups[1].Value;
|
||||
releaseInfo = episode is > 0 and < 10
|
||||
? "0" + episode
|
||||
: episode.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (season != null && _settings.EnableSonarrCompatibility)
|
||||
{
|
||||
releaseInfo = $"S{season}";
|
||||
}
|
||||
}
|
||||
|
||||
releaseInfo = releaseInfo.Replace("Episode ", "");
|
||||
releaseInfo = releaseInfo.Replace("Season ", "S");
|
||||
releaseInfo = releaseInfo.Trim();
|
||||
|
||||
//if (PadEpisode && int.TryParse(releaseInfo, out _) && releaseInfo.Length == 1)
|
||||
//{
|
||||
// releaseInfo = "0" + releaseInfo;
|
||||
//}
|
||||
|
||||
//if (FilterSeasonEpisode)
|
||||
//{
|
||||
// if (query.Season != 0 && season != null && season != query.Season) // skip if season doesn't match
|
||||
// continue;
|
||||
// if (query.Episode != null && episode != null && episode != query.Episode) // skip if episode doesn't match
|
||||
// continue;
|
||||
//}
|
||||
var torrentId = (long)torrent["ID"];
|
||||
var property = ((string)torrent["Property"]).Replace(" | Freeleech", "");
|
||||
var link = (string)torrent["Link"];
|
||||
var linkUri = new Uri(link);
|
||||
var uploadTimeString = (string)torrent["UploadTime"];
|
||||
var uploadTime = DateTime.ParseExact(uploadTimeString, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
|
||||
var publishDate = DateTime.SpecifyKind(uploadTime, DateTimeKind.Utc).ToLocalTime();
|
||||
var details = new Uri(_baseUrl + "torrent/" + torrentId + "/group");
|
||||
var size = (long)torrent["Size"];
|
||||
var snatched = (int)torrent["Snatched"];
|
||||
var seeders = (int)torrent["Seeders"];
|
||||
var leechers = (int)torrent["Leechers"];
|
||||
var fileCount = (int)torrent["FileCount"];
|
||||
var torrentId = torrent.Id;
|
||||
var property = torrent.Property.Replace(" | Freeleech", string.Empty);
|
||||
var link = torrent.Link;
|
||||
var uploadTime = torrent.UploadTime;
|
||||
var publishDate = DateTime.SpecifyKind(uploadTime.DateTime, DateTimeKind.Utc).ToLocalTime();
|
||||
var details = new Uri(_settings.BaseUrl + "torrent/" + torrentId + "/group");
|
||||
var size = torrent.Size;
|
||||
var snatched = torrent.Snatched;
|
||||
var seeders = torrent.Seeders;
|
||||
var leechers = torrent.Leechers;
|
||||
var fileCount = torrent.FileCount;
|
||||
var peers = seeders + leechers;
|
||||
|
||||
var rawDownMultiplier = (int?)torrent["RawDownMultiplier"] ?? 0;
|
||||
var rawUpMultiplier = (int?)torrent["RawUpMultiplier"] ?? 0;
|
||||
var rawDownMultiplier = torrent.RawDownMultiplier;
|
||||
var rawUpMultiplier = torrent.RawUpMultiplier;
|
||||
|
||||
// Ignore these categories as they'll cause hell with the matcher
|
||||
// TV Special, ONA, DVD Special, BD Special
|
||||
if (groupName == "TV Series" || groupName == "OVA")
|
||||
{
|
||||
category = new List<IndexerCategory> { NewznabStandardCategory.TVAnime };
|
||||
}
|
||||
|
||||
// Ignore these categories as they'll cause hell with the matcher
|
||||
// TV Special, OVA, ONA, DVD Special, BD Special
|
||||
if (groupName == "Movie" || groupName == "Live Action Movie")
|
||||
{
|
||||
category = new List<IndexerCategory> { NewznabStandardCategory.Movies };
|
||||
@@ -426,7 +426,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
//{
|
||||
// continue;
|
||||
//}
|
||||
var infoString = releaseTags.Aggregate("", (prev, cur) => prev + "[" + cur + "]");
|
||||
var infoString = releaseTags.Aggregate(string.Empty, (prev, cur) => prev + "[" + cur + "]");
|
||||
var minimumSeedTime = 259200;
|
||||
|
||||
// Additional 5 hours per GB
|
||||
@@ -447,7 +447,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
Title = releaseTitle,
|
||||
InfoUrl = details.AbsoluteUri,
|
||||
Guid = guid.AbsoluteUri,
|
||||
DownloadUrl = linkUri.AbsoluteUri,
|
||||
DownloadUrl = link.AbsoluteUri,
|
||||
PublishDate = publishDate,
|
||||
Categories = category,
|
||||
Description = description,
|
||||
@@ -457,7 +457,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
Grabs = snatched,
|
||||
Files = fileCount,
|
||||
DownloadVolumeFactor = rawDownMultiplier,
|
||||
UploadVolumeFactor = rawUpMultiplier
|
||||
UploadVolumeFactor = rawUpMultiplier,
|
||||
};
|
||||
|
||||
torrentInfos.Add(release);
|
||||
@@ -484,7 +484,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
}
|
||||
|
||||
public class AnimeBytesSettings : IProviderConfig
|
||||
public class AnimeBytesSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly AnimeBytesSettingsValidator Validator = new AnimeBytesSettingsValidator();
|
||||
|
||||
@@ -492,17 +492,321 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
Passkey = "";
|
||||
Username = "";
|
||||
EnableSonarrCompatibility = true;
|
||||
}
|
||||
|
||||
[FieldDefinition(1, Label = "Passkey", Privacy = PrivacyLevel.Password, Type = FieldType.Password, HelpText = "Site Passkey")]
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Passkey", HelpText = "Site Passkey", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
public string Passkey { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
|
||||
[FieldDefinition(3, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
|
||||
public string Username { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Enable Sonarr Compatibility", Type = FieldType.Checkbox, HelpText = "Makes Prowlarr try to add Season information into Release names, without this Sonarr can't match any Seasons, but it has a lot of false positives as well")]
|
||||
public bool EnableSonarrCompatibility { get; set; }
|
||||
|
||||
[FieldDefinition(5)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
|
||||
public class AnimeBytesResponse
|
||||
{
|
||||
[JsonProperty("Matches")]
|
||||
public long Matches { get; set; }
|
||||
|
||||
[JsonProperty("Limit")]
|
||||
public long Limit { get; set; }
|
||||
|
||||
[JsonProperty("Results")]
|
||||
[JsonConverter(typeof(ParseStringConverter))]
|
||||
public long Results { get; set; }
|
||||
|
||||
[JsonProperty("Groups")]
|
||||
public Group[] Groups { get; set; }
|
||||
}
|
||||
|
||||
public class Group
|
||||
{
|
||||
[JsonProperty("ID")]
|
||||
public long Id { get; set; }
|
||||
|
||||
[JsonProperty("CategoryName")]
|
||||
public string CategoryName { get; set; }
|
||||
|
||||
[JsonProperty("FullName")]
|
||||
public string FullName { get; set; }
|
||||
|
||||
[JsonProperty("GroupName")]
|
||||
public string GroupName { get; set; }
|
||||
|
||||
[JsonProperty("SeriesID")]
|
||||
[JsonConverter(typeof(ParseStringConverter))]
|
||||
public long SeriesId { get; set; }
|
||||
|
||||
[JsonProperty("SeriesName")]
|
||||
public string SeriesName { get; set; }
|
||||
|
||||
[JsonProperty("Artists")]
|
||||
public object Artists { get; set; }
|
||||
|
||||
[JsonProperty("Year")]
|
||||
[JsonConverter(typeof(ParseStringConverter))]
|
||||
public long Year { get; set; }
|
||||
|
||||
[JsonProperty("Image")]
|
||||
public Uri Image { get; set; }
|
||||
|
||||
[JsonProperty("Synonymns")]
|
||||
[JsonConverter(typeof(SynonymnsConverter))]
|
||||
public Synonymns Synonymns { get; set; }
|
||||
|
||||
[JsonProperty("Snatched")]
|
||||
public long Snatched { get; set; }
|
||||
|
||||
[JsonProperty("Comments")]
|
||||
public long Comments { get; set; }
|
||||
|
||||
[JsonProperty("Links")]
|
||||
[JsonConverter(typeof(LinksUnionConverter))]
|
||||
public LinksUnion Links { get; set; }
|
||||
|
||||
[JsonProperty("Votes")]
|
||||
public long Votes { get; set; }
|
||||
|
||||
[JsonProperty("AvgVote")]
|
||||
public double AvgVote { get; set; }
|
||||
|
||||
[JsonProperty("Associations")]
|
||||
public object Associations { get; set; }
|
||||
|
||||
[JsonProperty("Description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[JsonProperty("DescriptionHTML")]
|
||||
public string DescriptionHtml { get; set; }
|
||||
|
||||
[JsonProperty("EpCount")]
|
||||
public long EpCount { get; set; }
|
||||
|
||||
[JsonProperty("StudioList")]
|
||||
public string StudioList { get; set; }
|
||||
|
||||
[JsonProperty("PastWeek")]
|
||||
public long PastWeek { get; set; }
|
||||
|
||||
[JsonProperty("Incomplete")]
|
||||
public bool Incomplete { get; set; }
|
||||
|
||||
[JsonProperty("Ongoing")]
|
||||
public bool Ongoing { get; set; }
|
||||
|
||||
[JsonProperty("Tags")]
|
||||
public List<string> Tags { get; set; }
|
||||
|
||||
[JsonProperty("Torrents")]
|
||||
public List<Torrent> Torrents { get; set; }
|
||||
}
|
||||
|
||||
public class LinksClass
|
||||
{
|
||||
[JsonProperty("ANN", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public Uri Ann { get; set; }
|
||||
|
||||
[JsonProperty("Manga-Updates", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public Uri MangaUpdates { get; set; }
|
||||
|
||||
[JsonProperty("Wikipedia", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public Uri Wikipedia { get; set; }
|
||||
|
||||
[JsonProperty("MAL", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public Uri Mal { get; set; }
|
||||
|
||||
[JsonProperty("AniDB", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public Uri AniDb { get; set; }
|
||||
}
|
||||
|
||||
public class Torrent
|
||||
{
|
||||
[JsonProperty("ID")]
|
||||
public long Id { get; set; }
|
||||
|
||||
[JsonProperty("EditionData")]
|
||||
public EditionData EditionData { get; set; }
|
||||
|
||||
[JsonProperty("RawDownMultiplier")]
|
||||
public double? RawDownMultiplier { get; set; }
|
||||
|
||||
[JsonProperty("RawUpMultiplier")]
|
||||
public double? RawUpMultiplier { get; set; }
|
||||
|
||||
[JsonProperty("Link")]
|
||||
public Uri Link { get; set; }
|
||||
|
||||
[JsonProperty("Property")]
|
||||
public string Property { get; set; }
|
||||
|
||||
[JsonProperty("Snatched")]
|
||||
public int Snatched { get; set; }
|
||||
|
||||
[JsonProperty("Seeders")]
|
||||
public int Seeders { get; set; }
|
||||
|
||||
[JsonProperty("Leechers")]
|
||||
public int Leechers { get; set; }
|
||||
|
||||
[JsonProperty("Size")]
|
||||
public long Size { get; set; }
|
||||
|
||||
[JsonProperty("FileCount")]
|
||||
public int FileCount { get; set; }
|
||||
|
||||
[JsonProperty("UploadTime")]
|
||||
public DateTimeOffset UploadTime { get; set; }
|
||||
}
|
||||
|
||||
public class EditionData
|
||||
{
|
||||
[JsonProperty("EditionTitle")]
|
||||
public string EditionTitle { get; set; }
|
||||
}
|
||||
|
||||
public struct LinksUnion
|
||||
{
|
||||
public List<object> AnythingArray;
|
||||
public LinksClass LinksClass;
|
||||
|
||||
public static implicit operator LinksUnion(List<object> anythingArray) => new LinksUnion { AnythingArray = anythingArray };
|
||||
|
||||
public static implicit operator LinksUnion(LinksClass linksClass) => new LinksUnion { LinksClass = linksClass };
|
||||
}
|
||||
|
||||
public struct Synonymns
|
||||
{
|
||||
public List<string> StringArray;
|
||||
public Dictionary<string, string> StringMap;
|
||||
|
||||
public static implicit operator Synonymns(List<string> stringArray) => new Synonymns { StringArray = stringArray };
|
||||
|
||||
public static implicit operator Synonymns(Dictionary<string, string> stringMap) => new Synonymns { StringMap = stringMap };
|
||||
}
|
||||
|
||||
internal class LinksUnionConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type t) => t == typeof(LinksUnion) || t == typeof(LinksUnion?);
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonToken.StartObject:
|
||||
var objectValue = serializer.Deserialize<LinksClass>(reader);
|
||||
return new LinksUnion { LinksClass = objectValue };
|
||||
case JsonToken.StartArray:
|
||||
var arrayValue = serializer.Deserialize<List<object>>(reader);
|
||||
return new LinksUnion { AnythingArray = arrayValue };
|
||||
}
|
||||
|
||||
throw new Exception("Cannot unmarshal type LinksUnion");
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
|
||||
{
|
||||
var value = (LinksUnion)untypedValue;
|
||||
if (value.AnythingArray != null)
|
||||
{
|
||||
serializer.Serialize(writer, value.AnythingArray);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.LinksClass == null)
|
||||
{
|
||||
throw new Exception("Cannot marshal type LinksUnion");
|
||||
}
|
||||
|
||||
serializer.Serialize(writer, value.LinksClass);
|
||||
}
|
||||
|
||||
public static readonly LinksUnionConverter Singleton = new LinksUnionConverter();
|
||||
}
|
||||
|
||||
internal class ParseStringConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type t) => t == typeof(long) || t == typeof(long?);
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var value = serializer.Deserialize<string>(reader);
|
||||
if (long.TryParse(value, out var l))
|
||||
{
|
||||
return l;
|
||||
}
|
||||
|
||||
throw new Exception("Cannot unmarshal type long");
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
|
||||
{
|
||||
if (untypedValue == null)
|
||||
{
|
||||
serializer.Serialize(writer, null);
|
||||
return;
|
||||
}
|
||||
|
||||
var value = (long)untypedValue;
|
||||
serializer.Serialize(writer, value.ToString());
|
||||
}
|
||||
|
||||
public static readonly ParseStringConverter Singleton = new ParseStringConverter();
|
||||
}
|
||||
|
||||
internal class SynonymnsConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type t) => t == typeof(Synonymns) || t == typeof(Synonymns?);
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonToken.StartObject:
|
||||
var objectValue = serializer.Deserialize<Dictionary<string, string>>(reader);
|
||||
return new Synonymns { StringMap = objectValue };
|
||||
case JsonToken.StartArray:
|
||||
var arrayValue = serializer.Deserialize<List<string>>(reader);
|
||||
return new Synonymns { StringArray = arrayValue };
|
||||
}
|
||||
|
||||
throw new Exception("Cannot unmarshal type Synonymns");
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
|
||||
{
|
||||
var value = (Synonymns)untypedValue;
|
||||
if (value.StringArray != null)
|
||||
{
|
||||
serializer.Serialize(writer, value.StringArray);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.StringMap == null)
|
||||
{
|
||||
throw new Exception("Cannot marshal type Synonymns");
|
||||
}
|
||||
|
||||
serializer.Serialize(writer, value.StringMap);
|
||||
}
|
||||
|
||||
public static readonly SynonymnsConverter Singleton = new SynonymnsConverter();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
@@ -25,8 +24,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public override string Name => "AnimeTorrents";
|
||||
|
||||
public override string BaseUrl => "https://animetorrents.me/";
|
||||
private string LoginUrl => BaseUrl + "login.php";
|
||||
public override string[] IndexerUrls => new string[] { "https://animetorrents.me/" };
|
||||
public override string Description => "Definitive source for anime and manga";
|
||||
private string LoginUrl => Settings.BaseUrl + "login.php";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
@@ -38,12 +38,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new AnimeTorrentsRequestGenerator() { Settings = Settings, Capabilities = Capabilities, BaseUrl = BaseUrl };
|
||||
return new AnimeTorrentsRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new AnimeTorrentsParser(Settings, Capabilities.Categories, BaseUrl);
|
||||
return new AnimeTorrentsParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override async Task DoLogin()
|
||||
@@ -135,7 +135,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public AnimeTorrentsSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
public AnimeTorrentsRequestGenerator()
|
||||
{
|
||||
@@ -148,8 +147,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
// replace any space, special char, etc. with % (wildcard)
|
||||
var replaceRegex = new Regex("[^a-zA-Z0-9]+");
|
||||
searchString = replaceRegex.Replace(searchString, "%");
|
||||
var searchUrl = BaseUrl + "ajax/torrents_data.php";
|
||||
var searchUrlReferer = BaseUrl + "torrents.php?cat=0&searchin=filename&search=";
|
||||
var searchUrl = Settings.BaseUrl + "ajax/torrents_data.php";
|
||||
var searchUrlReferer = Settings.BaseUrl + "torrents.php?cat=0&searchin=filename&search=";
|
||||
|
||||
var trackerCats = Capabilities.Categories.MapTorznabCapsToTrackers(categories) ?? new List<string>();
|
||||
|
||||
@@ -229,13 +228,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
private readonly AnimeTorrentsSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public AnimeTorrentsParser(AnimeTorrentsSettings settings, IndexerCapabilitiesCategories categories, string baseUrl)
|
||||
public AnimeTorrentsParser(AnimeTorrentsSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
_baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
@@ -340,7 +337,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
}
|
||||
|
||||
public class AnimeTorrentsSettings : IProviderConfig
|
||||
public class AnimeTorrentsSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly AnimeTorrentsSettingsValidator Validator = new AnimeTorrentsSettingsValidator();
|
||||
|
||||
@@ -350,12 +347,18 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
Password = "";
|
||||
}
|
||||
|
||||
[FieldDefinition(1, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
|
||||
public string Username { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Password", HelpText = "Site Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
350
src/NzbDrone.Core/Indexers/Definitions/Animedia.cs
Normal file
350
src/NzbDrone.Core/Indexers/Definitions/Animedia.cs
Normal file
@@ -0,0 +1,350 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using AngleSharp.Html.Parser;
|
||||
using FluentValidation;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class Animedia : TorrentIndexerBase<AnimediaSettings>
|
||||
{
|
||||
public override string Name => "Animedia";
|
||||
public override string[] IndexerUrls => new string[] { "https://tt.animedia.tv/" };
|
||||
public override string Description => "Animedia is russian anime voiceover group and eponymous anime tracker.";
|
||||
public override string Language => "ru-ru";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public Animedia(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new AnimediaRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new AnimediaParser(Settings, Capabilities.Categories) { HttpClient = _httpClient, Logger = _logger };
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
}
|
||||
};
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "TV Anime");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "OVA/ONA/Special");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TV, "Dorama");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.Movies, "Movies");
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class AnimediaRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public AnimediaSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
|
||||
public AnimediaRequestGenerator()
|
||||
{
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
|
||||
{
|
||||
var requestUrl = string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(term))
|
||||
{
|
||||
requestUrl = Settings.BaseUrl;
|
||||
}
|
||||
else
|
||||
{
|
||||
var queryCollection = new NameValueCollection
|
||||
{
|
||||
// Remove season and episode info from search term cause it breaks search
|
||||
{ "keywords", Regex.Replace(term, @"(?:[SsEe]?\d{1,4}){1,2}$", "").TrimEnd() },
|
||||
{ "limit", "20" },
|
||||
{ "orderby_sort", "entry_date|desc" }
|
||||
};
|
||||
|
||||
requestUrl = string.Format("{0}/ajax/search_result/P0?{1}", Settings.BaseUrl.TrimEnd('/'), queryCollection.GetQueryString());
|
||||
}
|
||||
|
||||
var request = new IndexerRequest(requestUrl, HttpAccept.Html);
|
||||
yield return request;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
// Animedia doesn't support music, but this function required by interface
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
// Animedia doesn't support books, but this function required by interface
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class AnimediaParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly AnimediaSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
private static readonly Regex EpisodesInfoQueryRegex = new Regex(@"сери[ия] (\d+)(?:-(\d+))? из.*", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex ResolutionInfoQueryRegex = new Regex(@"качество (\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex SizeInfoQueryRegex = new Regex(@"размер:(.*)\n", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex ReleaseDateInfoQueryRegex = new Regex(@"добавлен:(.*)\n", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex CategorieMovieRegex = new Regex(@"Фильм", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex CategorieOVARegex = new Regex(@"ОВА|OVA|ОНА|ONA|Special", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex CategorieDoramaRegex = new Regex(@"Дорама", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
public IHttpClient HttpClient { get; set; }
|
||||
public Logger Logger { get; set; }
|
||||
|
||||
public AnimediaParser(AnimediaSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
private string composeTitle(AngleSharp.Html.Dom.IHtmlDocument dom, AngleSharp.Dom.IElement t, AngleSharp.Dom.IElement tr)
|
||||
{
|
||||
var name_ru = dom.QuerySelector("div.media__post__header > h1").TextContent.Trim();
|
||||
var name_en = dom.QuerySelector("div.media__panel > div:nth-of-type(1) > div.col-l:nth-of-type(1) > div > span").TextContent.Trim();
|
||||
var name_orig = dom.QuerySelector("div.media__panel > div:nth-of-type(1) > div.col-l:nth-of-type(2) > div > span").TextContent.Trim();
|
||||
|
||||
var title = name_ru + " / " + name_en;
|
||||
if (name_en != name_orig)
|
||||
{
|
||||
title += " / " + name_orig;
|
||||
}
|
||||
|
||||
var tabName = t.TextContent;
|
||||
tabName = tabName.Replace("Сезон", "Season");
|
||||
if (tabName.Contains("Серии"))
|
||||
{
|
||||
tabName = "";
|
||||
}
|
||||
|
||||
var heading = tr.QuerySelector("h3.tracker_info_bold").TextContent;
|
||||
|
||||
// Parse episodes info from heading if episods info present
|
||||
var match = EpisodesInfoQueryRegex.Match(heading);
|
||||
heading = tabName;
|
||||
if (match.Success)
|
||||
{
|
||||
if (string.IsNullOrEmpty(match.Groups[2].Value))
|
||||
{
|
||||
heading += " E" + match.Groups[1].Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
heading += string.Format(" E{0}-{1}", match.Groups[1].Value, match.Groups[2].Value);
|
||||
}
|
||||
}
|
||||
|
||||
return title + " - " + heading + " [" + getResolution(tr) + "p]";
|
||||
}
|
||||
|
||||
private string getResolution(AngleSharp.Dom.IElement tr)
|
||||
{
|
||||
var resolution = tr.QuerySelector("div.tracker_info_left").TextContent;
|
||||
return ResolutionInfoQueryRegex.Match(resolution).Groups[1].Value;
|
||||
}
|
||||
|
||||
private long getReleaseSize(AngleSharp.Dom.IElement tr)
|
||||
{
|
||||
var sizeStr = tr.QuerySelector("div.tracker_info_left").TextContent;
|
||||
return ReleaseInfo.GetBytes(SizeInfoQueryRegex.Match(sizeStr).Groups[1].Value.Trim());
|
||||
}
|
||||
|
||||
private DateTime getReleaseDate(AngleSharp.Dom.IElement tr)
|
||||
{
|
||||
var sizeStr = tr.QuerySelector("div.tracker_info_left").TextContent;
|
||||
return DateTime.Parse(ReleaseDateInfoQueryRegex.Match(sizeStr).Groups[1].Value.Trim());
|
||||
}
|
||||
|
||||
private ICollection<IndexerCategory> MapCategories(AngleSharp.Html.Dom.IHtmlDocument dom, AngleSharp.Dom.IElement t, AngleSharp.Dom.IElement tr)
|
||||
{
|
||||
var rName = t.TextContent;
|
||||
var rDesc = tr.QuerySelector("h3.tracker_info_bold").TextContent;
|
||||
var type = dom.QuerySelector("div.releases-date:contains('Тип:')").TextContent;
|
||||
|
||||
// Check OVA first cause OVA looks like anime with OVA in release name or description
|
||||
if (CategorieOVARegex.IsMatch(rName) || CategorieOVARegex.IsMatch(rDesc))
|
||||
{
|
||||
return _categories.MapTrackerCatDescToNewznab("OVA/ONA/Special");
|
||||
}
|
||||
|
||||
// Check movies then, cause some of releases could be movies dorama and should go to movies category
|
||||
if (CategorieMovieRegex.IsMatch(rName) || CategorieMovieRegex.IsMatch(rDesc))
|
||||
{
|
||||
return _categories.MapTrackerCatDescToNewznab("Movies");
|
||||
}
|
||||
|
||||
// Check dorama. Most of doramas are flaged as doramas in type info, but type info could have a lot of types at same time (movie, etc)
|
||||
if (CategorieDoramaRegex.IsMatch(rName) || CategorieDoramaRegex.IsMatch(type))
|
||||
{
|
||||
return _categories.MapTrackerCatDescToNewznab("Dorama");
|
||||
}
|
||||
|
||||
return _categories.MapTrackerCatDescToNewznab("TV Anime");
|
||||
}
|
||||
|
||||
private IList<TorrentInfo> ParseRelease(IndexerResponse indexerResponse)
|
||||
{
|
||||
var torrentInfos = new List<TorrentInfo>();
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
foreach (var t in dom.QuerySelectorAll("ul.media__tabs__nav > li > a"))
|
||||
{
|
||||
var tr_id = t.Attributes["href"].Value;
|
||||
var tr = dom.QuerySelector("div" + tr_id);
|
||||
var seeders = int.Parse(tr.QuerySelector("div.circle_green_text_top").TextContent);
|
||||
var url = indexerResponse.HttpRequest.Url.ToString();
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Title = composeTitle(dom, t, tr),
|
||||
InfoUrl = url,
|
||||
DownloadVolumeFactor = 0,
|
||||
UploadVolumeFactor = 1,
|
||||
|
||||
Guid = url + tr_id,
|
||||
Seeders = seeders,
|
||||
Peers = seeders + int.Parse(tr.QuerySelector("div.circle_red_text_top").TextContent),
|
||||
Grabs = int.Parse(tr.QuerySelector("div.circle_grey_text_top").TextContent),
|
||||
Categories = MapCategories(dom, t, tr),
|
||||
PublishDate = getReleaseDate(tr),
|
||||
DownloadUrl = tr.QuerySelector("div.download_tracker > a.btn__green").Attributes["href"].Value,
|
||||
MagnetUrl = tr.QuerySelector("div.download_tracker > a.btn__d-gray").Attributes["href"].Value,
|
||||
Size = getReleaseSize(tr),
|
||||
Resolution = getResolution(tr)
|
||||
};
|
||||
torrentInfos.Add(release);
|
||||
}
|
||||
|
||||
return torrentInfos;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
var links = dom.QuerySelectorAll("a.ads-list__item__title");
|
||||
foreach (var link in links)
|
||||
{
|
||||
var url = link.GetAttribute("href");
|
||||
|
||||
// Some URLs in search are broken
|
||||
if (url.StartsWith("//"))
|
||||
{
|
||||
url = "https:" + url;
|
||||
}
|
||||
|
||||
var releaseRequest = new IndexerRequest(url, HttpAccept.Html);
|
||||
var releaseResponse = new IndexerResponse(releaseRequest, HttpClient.Execute(releaseRequest.HttpRequest));
|
||||
|
||||
// Throw common http errors here before we try to parse
|
||||
if (releaseResponse.HttpResponse.HasHttpError)
|
||||
{
|
||||
if ((int)releaseResponse.HttpResponse.StatusCode == 429)
|
||||
{
|
||||
throw new TooManyRequestsException(releaseRequest.HttpRequest, releaseResponse.HttpResponse);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IndexerException(releaseResponse, "Http error code: " + releaseResponse.HttpResponse.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
torrentInfos.AddRange(ParseRelease(releaseResponse));
|
||||
}
|
||||
|
||||
return torrentInfos.ToArray();
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class AnimediaSettingsValidator : AbstractValidator<AnimediaSettings>
|
||||
{
|
||||
public AnimediaSettingsValidator()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class AnimediaSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly AnimediaSettingsValidator Validator = new AnimediaSettingsValidator();
|
||||
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class Anthelion : TorrentIndexerBase<AnthelionSettings>
|
||||
{
|
||||
public override string Name => "Anthelion";
|
||||
public override string BaseUrl => "https://anthelion.me/";
|
||||
private string LoginUrl => BaseUrl + "login.php";
|
||||
public override string[] IndexerUrls => new string[] { "https://anthelion.me/" };
|
||||
private string LoginUrl => Settings.BaseUrl + "login.php";
|
||||
public override string Description => "A movies tracker";
|
||||
public override string Language => "en-us";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
@@ -40,12 +40,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new AnthelionRequestGenerator() { Settings = Settings, Capabilities = Capabilities, BaseUrl = BaseUrl };
|
||||
return new AnthelionRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new AnthelionParser(Settings, Capabilities.Categories, BaseUrl);
|
||||
return new AnthelionParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override async Task DoLogin()
|
||||
@@ -79,9 +79,13 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var response = await ExecuteAuth(authLoginRequest);
|
||||
|
||||
if (!response.Content.Contains("logout.php"))
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
throw new IndexerAuthException("Anthelion Auth Failed");
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
var errorMessage = dom.QuerySelector("form#loginform").TextContent.Trim();
|
||||
|
||||
throw new IndexerAuthException(errorMessage);
|
||||
}
|
||||
|
||||
cookies = response.GetCookies();
|
||||
@@ -127,7 +131,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public AnthelionSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
public AnthelionRequestGenerator()
|
||||
{
|
||||
@@ -135,7 +138,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
|
||||
{
|
||||
var searchUrl = string.Format("{0}/torrents.php", BaseUrl.TrimEnd('/'));
|
||||
var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/'));
|
||||
|
||||
// TODO: IMDB search is available but it requires to parse the details page
|
||||
var qc = new NameValueCollection
|
||||
@@ -174,8 +177,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
@@ -192,8 +193,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
@@ -214,13 +213,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
private readonly AnthelionSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public AnthelionParser(AnthelionSettings settings, IndexerCapabilitiesCategories categories, string baseurl)
|
||||
public AnthelionParser(AnthelionSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
_baseUrl = baseurl;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
@@ -237,11 +234,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var tags = row.QuerySelector("div.torrent_info").FirstChild.TextContent.Replace(" / ", " ").Trim();
|
||||
var title = $"{qDetailsLink.TextContent} {year} {tags}";
|
||||
var description = row.QuerySelector("div.tags").TextContent.Trim();
|
||||
var details = _baseUrl + qDetailsLink.GetAttribute("href");
|
||||
var details = _settings.BaseUrl + qDetailsLink.GetAttribute("href");
|
||||
var torrentId = qDetailsLink.GetAttribute("href").Split('=').Last();
|
||||
var link = _baseUrl + "torrents.php?action=download&id=" + torrentId;
|
||||
var link = _settings.BaseUrl + "torrents.php?action=download&id=" + torrentId;
|
||||
var posterStr = qDetailsLink.GetAttribute("data-cover");
|
||||
var poster = !string.IsNullOrWhiteSpace(posterStr) ? new Uri(qDetailsLink.GetAttribute("data-cover")) : null;
|
||||
var poster = !string.IsNullOrWhiteSpace(posterStr) ? posterStr : null;
|
||||
|
||||
var files = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(3)").TextContent);
|
||||
var publishDate = DateTimeUtil.FromTimeAgo(row.QuerySelector("td:nth-child(4)").TextContent);
|
||||
@@ -279,6 +276,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
Categories = category,
|
||||
DownloadUrl = link,
|
||||
InfoUrl = details,
|
||||
PosterUrl = poster,
|
||||
Guid = link,
|
||||
ImdbId = imdb.GetValueOrDefault(),
|
||||
Seeders = seeders,
|
||||
@@ -308,7 +306,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
}
|
||||
|
||||
public class AnthelionSettings : IProviderConfig
|
||||
public class AnthelionSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly AnthelionSettingsValidator Validator = new AnthelionSettingsValidator();
|
||||
|
||||
@@ -318,12 +316,18 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
Password = "";
|
||||
}
|
||||
|
||||
[FieldDefinition(1, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
|
||||
public string Username { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Password", HelpText = "Site Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
@@ -15,7 +14,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
}
|
||||
}
|
||||
|
||||
public class AvistazSettings : IProviderConfig
|
||||
public class AvistazSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly AvistazSettingsValidator Validator = new AvistazSettingsValidator();
|
||||
|
||||
@@ -26,15 +25,21 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
|
||||
public string Token { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
|
||||
public string Username { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Password", HelpText = "Site Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "PID", HelpText = "PID from My Account or My Profile page")]
|
||||
[FieldDefinition(4, Label = "PID", HelpText = "PID from My Account or My Profile page")]
|
||||
public string Pid { get; set; }
|
||||
|
||||
[FieldDefinition(5)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -16,7 +16,6 @@ using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
@@ -25,8 +24,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public override string Name => "BakaBT";
|
||||
|
||||
public override string BaseUrl => "https://bakabt.me/";
|
||||
private string LoginUrl => BaseUrl + "login.php";
|
||||
public override string[] IndexerUrls => new string[] { "https://bakabt.me/" };
|
||||
public override string Description => "Anime Community";
|
||||
private string LoginUrl => Settings.BaseUrl + "login.php";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
@@ -38,12 +38,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new BakaBTRequestGenerator() { Settings = Settings, Capabilities = Capabilities, BaseUrl = BaseUrl };
|
||||
return new BakaBTRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new BakaBTParser(Settings, Capabilities.Categories, BaseUrl);
|
||||
return new BakaBTParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override async Task DoLogin()
|
||||
@@ -110,6 +110,10 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
@@ -124,7 +128,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "OVA");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.AudioOther, "Soundtrack");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.BooksComics, "Manga");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVAnime, "Anime Movie");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Movies, "Anime Movie");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TVOther, "Live Action");
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.BooksOther, "Artbook");
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.AudioVideo, "Music Video");
|
||||
@@ -138,16 +142,15 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public BakaBTSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
public BakaBTRequestGenerator()
|
||||
{
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term)
|
||||
{
|
||||
var searchString = term;
|
||||
var searchUrl = BaseUrl + "browse.php?only=0&hentai=1&incomplete=1&lossless=1&hd=1&multiaudio=1&bonus=1&reorder=1&q=";
|
||||
var searchUrl = Settings.BaseUrl + "browse.php?only=0&hentai=1&incomplete=1&lossless=1&hd=1&multiaudio=1&bonus=1&reorder=1&q=";
|
||||
|
||||
var match = Regex.Match(term, @".*(?=\s(?:[Ee]\d+|\d+)$)");
|
||||
if (match.Success)
|
||||
@@ -166,6 +169,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm)));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
@@ -173,7 +178,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm)));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -182,7 +187,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm)));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -191,7 +196,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm)));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -200,7 +205,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm)));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -213,14 +218,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
private readonly BakaBTSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
private readonly string _baseUrl;
|
||||
private readonly List<IndexerCategory> _defaultCategories = new List<IndexerCategory> { NewznabStandardCategory.TVAnime };
|
||||
|
||||
public BakaBTParser(BakaBTSettings settings, IndexerCapabilitiesCategories categories, string baseUrl)
|
||||
public BakaBTParser(BakaBTSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
_baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
@@ -299,11 +302,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
release.Categories = currentCategories;
|
||||
|
||||
//release.Description = row.QuerySelector("span.tags")?.TextContent;
|
||||
release.Guid = _baseUrl + qTitleLink.GetAttribute("href");
|
||||
release.Description = row.QuerySelector("span.tags")?.TextContent;
|
||||
release.Guid = _settings.BaseUrl + qTitleLink.GetAttribute("href");
|
||||
release.InfoUrl = release.Guid;
|
||||
|
||||
release.DownloadUrl = _baseUrl + row.QuerySelector(".peers a").GetAttribute("href");
|
||||
release.DownloadUrl = _settings.BaseUrl + row.QuerySelector(".peers a").GetAttribute("href");
|
||||
|
||||
var grabs = row.QuerySelectorAll(".peers")[0].FirstChild.NodeValue.TrimEnd().TrimEnd('/').TrimEnd();
|
||||
grabs = grabs.Replace("k", "000");
|
||||
@@ -392,7 +395,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
}
|
||||
|
||||
public class BakaBTSettings : IProviderConfig
|
||||
public class BakaBTSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly BakaBTSettingsValidator Validator = new BakaBTSettingsValidator();
|
||||
|
||||
@@ -402,18 +405,24 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
Password = "";
|
||||
}
|
||||
|
||||
[FieldDefinition(1, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
|
||||
public string Username { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Password", HelpText = "Site Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Add Romaji Title", Type = FieldType.Checkbox, HelpText = "Add releases for Romaji Title")]
|
||||
[FieldDefinition(4, Label = "Add Romaji Title", Type = FieldType.Checkbox, HelpText = "Add releases for Romaji Title")]
|
||||
public bool AddRomajiTitle { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Append Season", Type = FieldType.Checkbox, HelpText = "Append Season for Sonarr Compatibility")]
|
||||
[FieldDefinition(5, Label = "Append Season", Type = FieldType.Checkbox, HelpText = "Append Season for Sonarr Compatibility")]
|
||||
public bool AppendSeason { get; set; }
|
||||
|
||||
[FieldDefinition(6)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -16,7 +16,6 @@ using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
@@ -25,7 +24,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public override string Name => "BeyondHD";
|
||||
|
||||
public override string BaseUrl => "https://beyond-hd.me/";
|
||||
public override string[] IndexerUrls => new string[] { "https://beyond-hd.me/" };
|
||||
public override string Description => "BeyondHD (BHD) is a Private Torrent Tracker for HD MOVIES / TV";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
@@ -37,12 +37,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new BeyondHDRequestGenerator() { Settings = Settings, Capabilities = Capabilities, BaseUrl = BaseUrl };
|
||||
return new BeyondHDRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new BeyondHDParser(Settings, Capabilities.Categories, BaseUrl);
|
||||
return new BeyondHDParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
@@ -70,7 +70,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public BeyondHDSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
public BeyondHDRequestGenerator()
|
||||
{
|
||||
@@ -106,7 +105,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
body.Add("categories", string.Join(",", cats));
|
||||
}
|
||||
|
||||
var searchUrl = BaseUrl + "api/torrents/" + Settings.ApiKey;
|
||||
var searchUrl = Settings.BaseUrl + "api/torrents/" + Settings.ApiKey;
|
||||
|
||||
var request = new HttpRequest(searchUrl, HttpAccept.Json);
|
||||
|
||||
@@ -172,13 +171,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
private readonly BeyondHDSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public BeyondHDParser(BeyondHDSettings settings, IndexerCapabilitiesCategories categories, string baseUrl)
|
||||
public BeyondHDParser(BeyondHDSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
_baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
@@ -242,7 +239,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
}
|
||||
|
||||
public class BeyondHDSettings : IProviderConfig
|
||||
public class BeyondHDSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly BeyondHDSettingsValidator Validator = new BeyondHDSettingsValidator();
|
||||
|
||||
@@ -250,12 +247,18 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
}
|
||||
|
||||
[FieldDefinition(1, Label = "API Key", HelpText = "API Key from Site", Privacy = PrivacyLevel.ApiKey)]
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "API Key", HelpText = "API Key from the Site (Found in My Security => API Key)", Privacy = PrivacyLevel.ApiKey)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "RSS Key", HelpText = "RSS Key from Site", Privacy = PrivacyLevel.ApiKey)]
|
||||
[FieldDefinition(3, Label = "RSS Key", HelpText = "RSS Key from the Site (Found in My Security => RSS Key)", Privacy = PrivacyLevel.ApiKey)]
|
||||
public string RssKey { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.BroadcastheNet
|
||||
@@ -13,7 +12,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
|
||||
}
|
||||
}
|
||||
|
||||
public class BroadcastheNetSettings : IProviderConfig
|
||||
public class BroadcastheNetSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly BroadcastheNetSettingsValidator Validator = new BroadcastheNetSettingsValidator();
|
||||
|
||||
@@ -21,9 +20,15 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
|
||||
{
|
||||
}
|
||||
|
||||
[FieldDefinition(1, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(3)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -22,7 +23,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
private readonly ICached<CardigannRequestGenerator> _generatorCache;
|
||||
|
||||
public override string Name => "Cardigann";
|
||||
public override string BaseUrl => "";
|
||||
public override string[] IndexerUrls => new string[] { "" };
|
||||
public override string Description => "";
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
@@ -44,6 +46,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
generator = (CardigannRequestGenerator)SetCookieFunctions(generator);
|
||||
|
||||
generator.Settings = Settings;
|
||||
|
||||
_generatorCache.ClearExpired();
|
||||
|
||||
return generator;
|
||||
@@ -120,6 +124,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
Language = definition.Language,
|
||||
Description = definition.Description,
|
||||
Implementation = GetType().Name,
|
||||
IndexerUrls = definition.Links.ToArray(),
|
||||
Settings = new CardigannSettings { DefinitionFile = definition.File },
|
||||
Protocol = DownloadProtocol.Torrent,
|
||||
Privacy = definition.Type == "private" ? IndexerPrivacy.Private : IndexerPrivacy.Public,
|
||||
@@ -133,11 +138,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
{
|
||||
if (httpResponse.HasHttpError)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var generator = (CardigannRequestGenerator)GetRequestGenerator();
|
||||
|
||||
SetCookieFunctions(generator);
|
||||
@@ -236,6 +236,16 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
};
|
||||
}
|
||||
|
||||
if (action == "getUrls")
|
||||
{
|
||||
var devices = ((IndexerDefinition)Definition).IndexerUrls;
|
||||
|
||||
return new
|
||||
{
|
||||
options = devices.Select(d => new { Value = d, Name = d })
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
{
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
protected override string SiteLink => Settings?.BaseUrl ?? _definition.Links.First();
|
||||
|
||||
public CardigannParser(IConfigService configService,
|
||||
CardigannDefinition definition,
|
||||
Logger logger)
|
||||
@@ -291,6 +293,15 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
release.TvdbId = (int)ParseUtil.CoerceLong(tvdbId);
|
||||
value = release.TvdbId.ToString();
|
||||
break;
|
||||
case "poster":
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
var poster = ResolvePath(value, searchUrlUri);
|
||||
release.PosterUrl = poster.AbsoluteUri;
|
||||
}
|
||||
|
||||
value = release.PosterUrl;
|
||||
break;
|
||||
|
||||
//case "author":
|
||||
// release.Author = value;
|
||||
@@ -298,15 +309,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
//case "booktitle":
|
||||
// release.BookTitle = value;
|
||||
// break;
|
||||
//case "banner":
|
||||
// if (!string.IsNullOrWhiteSpace(value))
|
||||
// {
|
||||
// var bannerurl = ResolvePath(value, searchUrlUri);
|
||||
// release.BannerUrl = bannerurl;
|
||||
// }
|
||||
|
||||
// value = release.BannerUrl.ToString();
|
||||
// break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,13 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
public Dictionary<string, object> Variables { get; private set; }
|
||||
|
||||
public CardigannRequest(string url, HttpAccept httpAccept, Dictionary<string, object> variables)
|
||||
: base(url, httpAccept)
|
||||
: base(url, httpAccept)
|
||||
{
|
||||
Variables = variables;
|
||||
}
|
||||
|
||||
public CardigannRequest(HttpRequest httpRequest, Dictionary<string, object> variables)
|
||||
: base(httpRequest)
|
||||
{
|
||||
Variables = variables;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
public IDictionary<string, string> Cookies { get; set; }
|
||||
protected HttpResponse landingResult;
|
||||
protected IHtmlDocument landingResultDocument;
|
||||
protected override string SiteLink => Settings?.BaseUrl ?? _definition.Links.First();
|
||||
|
||||
public CardigannRequestGenerator(IConfigService configService,
|
||||
CardigannDefinition definition,
|
||||
@@ -687,13 +688,20 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
var httpRequest = new HttpRequestBuilder(requestLinkStr)
|
||||
.SetCookies(Cookies ?? new Dictionary<string, string>())
|
||||
.SetHeaders(pairs ?? new Dictionary<string, string>())
|
||||
.SetHeader("Referer", referer)
|
||||
.Build();
|
||||
.SetHeader("Referer", referer);
|
||||
|
||||
httpRequest.Method = method;
|
||||
|
||||
var response = await HttpClient.ExecuteAsync(httpRequest);
|
||||
// Add form data for POST requests
|
||||
if (method == HttpMethod.POST)
|
||||
{
|
||||
foreach (var param in pairs)
|
||||
{
|
||||
httpRequest.AddFormParameter(param.Key, param.Value);
|
||||
}
|
||||
}
|
||||
|
||||
var response = await HttpClient.ExecuteAsync(httpRequest.Build());
|
||||
|
||||
_logger.Debug($"CardigannIndexer ({_definition.Id}): handleRequest() remote server returned {response.StatusCode.ToString()}");
|
||||
return response;
|
||||
@@ -797,6 +805,11 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.HasHttpError)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var document = parser.ParseDocument(response.Content);
|
||||
|
||||
@@ -927,7 +940,20 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
_logger.Info($"Adding request: {searchUrl}");
|
||||
|
||||
var request = new CardigannRequest(searchUrl, HttpAccept.Html, variables);
|
||||
var requestbuilder = new HttpRequestBuilder(searchUrl);
|
||||
|
||||
requestbuilder.Method = method;
|
||||
|
||||
// Add FormData for searchs that POST
|
||||
if (method == HttpMethod.POST)
|
||||
{
|
||||
foreach (var param in queryCollection)
|
||||
{
|
||||
requestbuilder.AddFormParameter(param.Key, param.Value);
|
||||
}
|
||||
}
|
||||
|
||||
var request = new CardigannRequest(requestbuilder.Build(), variables);
|
||||
|
||||
// send HTTP request
|
||||
if (search.Headers != null)
|
||||
@@ -938,8 +964,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
}
|
||||
}
|
||||
|
||||
request.HttpRequest.Method = method;
|
||||
|
||||
yield return request;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Cardigann
|
||||
@@ -13,7 +12,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
}
|
||||
}
|
||||
|
||||
public class CardigannSettings : IProviderConfig
|
||||
public class CardigannSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly CardigannSettingsValidator Validator = new CardigannSettingsValidator();
|
||||
|
||||
@@ -25,6 +24,12 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
[FieldDefinition(0, Hidden = HiddenType.Hidden)]
|
||||
public string DefinitionFile { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(1)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public Dictionary<string, object> ExtraFieldData { get; set; }
|
||||
|
||||
// Field 8 is used by TorznabSettings MinimumSeeders
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class CinemaZ : AvistazBase
|
||||
{
|
||||
public override string Name => "CinemaZ";
|
||||
public override string BaseUrl => "https://cinemaz.to/";
|
||||
public override string[] IndexerUrls => new string[] { "https://cinemaz.to/" };
|
||||
public override string Description => "CinemaZ (EuTorrents) is a Private Torrent Tracker for FOREIGN NON-ASIAN MOVIES.";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public CinemaZ(IIndexerRepository indexerRepository, IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
@@ -25,8 +26,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
Settings = Settings,
|
||||
HttpClient = _httpClient,
|
||||
Logger = _logger,
|
||||
Capabilities = Capabilities,
|
||||
BaseUrl = BaseUrl
|
||||
Capabilities = Capabilities
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
344
src/NzbDrone.Core/Indexers/Definitions/DanishBytes.cs
Normal file
344
src/NzbDrone.Core/Indexers/Definitions/DanishBytes.cs
Normal file
@@ -0,0 +1,344 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class DanishBytes : TorrentIndexerBase<DanishBytesSettings>
|
||||
{
|
||||
public override string Name => "DanishBytes";
|
||||
public override string[] IndexerUrls => new string[] { "https://danishbytes.org/" };
|
||||
public override string Description => "DanishBytes is a Private Danish Tracker";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public DanishBytes(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new DanishBytesRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new DanishBytesParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.ImdbId, TvSearchParam.TvdbId
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping("1", NewznabStandardCategory.Movies, "Movies");
|
||||
caps.Categories.AddCategoryMapping("2", NewznabStandardCategory.TV, "TV");
|
||||
caps.Categories.AddCategoryMapping("3", NewznabStandardCategory.Audio, "Music");
|
||||
caps.Categories.AddCategoryMapping("4", NewznabStandardCategory.PCGames, "Games");
|
||||
caps.Categories.AddCategoryMapping("5", NewznabStandardCategory.PC0day, "Appz");
|
||||
caps.Categories.AddCategoryMapping("6", NewznabStandardCategory.Books, "Bookz");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class DanishBytesRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public DanishBytesSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
|
||||
public DanishBytesRequestGenerator()
|
||||
{
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null, int tmdbId = 0, int tvdbId = 0)
|
||||
{
|
||||
var qc = new NameValueCollection
|
||||
{
|
||||
{ "search", term },
|
||||
{ "api_token", Settings.ApiKey },
|
||||
};
|
||||
|
||||
if (imdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
qc.Add("imdb", imdbId);
|
||||
}
|
||||
|
||||
if (tmdbId > 0)
|
||||
{
|
||||
qc.Add("tmdb", tmdbId.ToString());
|
||||
}
|
||||
|
||||
if (tvdbId > 0)
|
||||
{
|
||||
qc.Add("tvdb", tvdbId.ToString());
|
||||
}
|
||||
|
||||
var searchUrl = string.Format("{0}/api/torrents/v2/filter?{1}", Settings.BaseUrl, qc.GetQueryString());
|
||||
|
||||
foreach (var cat in Capabilities.Categories.MapTorznabCapsToTrackers(categories))
|
||||
{
|
||||
searchUrl += $"&categories[]={cat}";
|
||||
}
|
||||
|
||||
var request = new HttpRequest(searchUrl, HttpAccept.Json);
|
||||
|
||||
var indexerRequest = new IndexerRequest(request);
|
||||
|
||||
yield return indexerRequest;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.FullImdbId, searchCriteria.TmdbId.GetValueOrDefault()));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories, searchCriteria.FullImdbId, 0, searchCriteria.TvdbId.Value));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class DanishBytesParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly DanishBytesSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
public DanishBytesParser(DanishBytesSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var torrentInfos = new List<TorrentInfo>();
|
||||
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
||||
}
|
||||
|
||||
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
|
||||
}
|
||||
|
||||
var jsonResponse = new HttpResponse<DanishBytesResponse>(indexerResponse.HttpResponse);
|
||||
|
||||
foreach (var row in jsonResponse.Resource.Torrents)
|
||||
{
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Title = row.Name,
|
||||
InfoUrl = $"{_settings.BaseUrl}torrents/{row.Id}",
|
||||
DownloadUrl = $"{_settings.BaseUrl}torrent/download/{row.Id}.{jsonResponse.Resource.Rsskey}",
|
||||
PosterUrl = row.PosterImage,
|
||||
PublishDate = row.CreatedAt,
|
||||
Categories = _categories.MapTrackerCatToNewznab(row.CategoryId),
|
||||
Size = row.Size,
|
||||
Seeders = row.Seeders,
|
||||
Peers = row.Leechers + row.Seeders,
|
||||
Grabs = row.TimesCompleted,
|
||||
DownloadVolumeFactor = row.Free ? 0 : 1,
|
||||
UploadVolumeFactor = row.Doubleup ? 2 : 1
|
||||
};
|
||||
|
||||
torrentInfos.Add(release);
|
||||
}
|
||||
|
||||
// order by date
|
||||
return torrentInfos.OrderByDescending(o => o.PublishDate).ToArray();
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class DanishBytesSettingsValidator : AbstractValidator<DanishBytesSettings>
|
||||
{
|
||||
public DanishBytesSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.ApiKey).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class DanishBytesSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly DanishBytesSettingsValidator Validator = new DanishBytesSettingsValidator();
|
||||
|
||||
public DanishBytesSettings()
|
||||
{
|
||||
}
|
||||
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "API Key", HelpText = "API Key from Site", Privacy = PrivacyLevel.ApiKey)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(3)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
|
||||
public class DanishBytesTorrent
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "info_hash")]
|
||||
public string InfoHash { get; set; }
|
||||
public long Size { get; set; }
|
||||
public int Leechers { get; set; }
|
||||
public int Seeders { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "times_completed")]
|
||||
public int TimesCompleted { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "category_id")]
|
||||
public string CategoryId { get; set; }
|
||||
public string Tmdb { get; set; }
|
||||
public string Igdb { get; set; }
|
||||
public string Mal { get; set; }
|
||||
public string Tvdb { get; set; }
|
||||
public string Imdb { get; set; }
|
||||
public int Stream { get; set; }
|
||||
public bool Free { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "on_fire")]
|
||||
public bool OnFire { get; set; }
|
||||
public bool Doubleup { get; set; }
|
||||
public bool Highspeed { get; set; }
|
||||
public bool Featured { get; set; }
|
||||
public bool Webstream { get; set; }
|
||||
public bool Anon { get; set; }
|
||||
public bool Sticky { get; set; }
|
||||
public bool Sd { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "bumped_at")]
|
||||
public DateTime BumpedAt { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "type_id")]
|
||||
public int TypeId { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "resolution_id")]
|
||||
public int ResolutionId { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "poster_image")]
|
||||
public string PosterImage { get; set; }
|
||||
public string Video { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "thanks_count")]
|
||||
public int ThanksCount { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "comments_count")]
|
||||
public int CommentsCount { get; set; }
|
||||
public string GetSize { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "created_at_human")]
|
||||
public string CreatedAtHuman { get; set; }
|
||||
public bool Bookmarked { get; set; }
|
||||
public bool Liked { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "show_last_torrents")]
|
||||
public bool ShowLastTorrents { get; set; }
|
||||
}
|
||||
|
||||
public class DanishBytesPageLinks
|
||||
{
|
||||
public int To { get; set; }
|
||||
public string Qty { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "current_page")]
|
||||
public int CurrentPage { get; set; }
|
||||
}
|
||||
|
||||
public class DanishBytesResponse
|
||||
{
|
||||
public DanishBytesTorrent[] Torrents { get; set; }
|
||||
public int ResultsCount { get; set; }
|
||||
public DanishBytesPageLinks Links { get; set; }
|
||||
public string CurrentCount { get; set; }
|
||||
public int TorrentCountTotal { get; set; }
|
||||
public string Rsskey { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
@@ -24,7 +23,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class DigitalCore : TorrentIndexerBase<DigitalCoreSettings>
|
||||
{
|
||||
public override string Name => "DigitalCore";
|
||||
public override string BaseUrl => "https://digitalcore.club/";
|
||||
public override string[] IndexerUrls => new string[] { "https://digitalcore.club/" };
|
||||
public override string Description => "DigitalCore is a Private Torrent Tracker for MOVIES / TV / GENERAL";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
@@ -36,12 +36,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new DigitalCoreRequestGenerator() { Settings = Settings, PageSize = PageSize, Capabilities = Capabilities, BaseUrl = BaseUrl };
|
||||
return new DigitalCoreRequestGenerator() { Settings = Settings, PageSize = PageSize, Capabilities = Capabilities };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new DigitalCoreParser(Settings, Capabilities.Categories, BaseUrl);
|
||||
return new DigitalCoreParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override IDictionary<string, string> GetCookies()
|
||||
@@ -123,7 +123,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public class DigitalCoreRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public string BaseUrl { get; set; }
|
||||
public DigitalCoreSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
|
||||
@@ -138,7 +137,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
|
||||
{
|
||||
var searchUrl = string.Format("{0}/api/v1/torrents", BaseUrl.TrimEnd('/'));
|
||||
var searchUrl = string.Format("{0}/api/v1/torrents", Settings.BaseUrl.TrimEnd('/'));
|
||||
|
||||
var parameters = new NameValueCollection();
|
||||
|
||||
@@ -226,15 +225,13 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public class DigitalCoreParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly string _baseUrl;
|
||||
private readonly DigitalCoreSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
public DigitalCoreParser(DigitalCoreSettings settings, IndexerCapabilitiesCategories categories, string baseUrl)
|
||||
public DigitalCoreParser(DigitalCoreSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
_baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
@@ -270,8 +267,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
release.Files = row.numfiles;
|
||||
release.Grabs = row.times_completed;
|
||||
|
||||
release.Guid = new Uri(_baseUrl + "torrent/" + row.id.ToString() + "/").ToString();
|
||||
release.DownloadUrl = _baseUrl + "api/v1/torrents/download/" + row.id.ToString();
|
||||
var infoUrl = _settings.BaseUrl + "torrent/" + row.id.ToString() + "/";
|
||||
|
||||
release.Guid = infoUrl;
|
||||
release.DownloadUrl = _settings.BaseUrl + "api/v1/torrents/download/" + row.id.ToString();
|
||||
release.InfoUrl = infoUrl;
|
||||
|
||||
if (row.frileech == 1)
|
||||
{
|
||||
@@ -317,7 +317,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
}
|
||||
|
||||
public class DigitalCoreSettings : IProviderConfig
|
||||
public class DigitalCoreSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly DigitalCoreSettingsValidator Validator = new DigitalCoreSettingsValidator();
|
||||
|
||||
@@ -327,12 +327,18 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
Passphrase = "";
|
||||
}
|
||||
|
||||
[FieldDefinition(1, Label = "UID", HelpText = "Uid from login cookie")]
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "UID", HelpText = "UID from login cookie", Privacy = PrivacyLevel.UserName)]
|
||||
public string UId { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Passphrase", HelpText = "Pass from login cookie")]
|
||||
[FieldDefinition(3, Label = "Passphrase", HelpText = "Passphrase from login cookie", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
public string Passphrase { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class ExoticaZ : AvistazBase
|
||||
{
|
||||
public override string Name => "ExoticaZ";
|
||||
public override string BaseUrl => "https://exoticaz.to/";
|
||||
public override string[] IndexerUrls => new string[] { "https://exoticaz.to/" };
|
||||
public override string Description => "ExoticaZ (YourExotic) is a Private Torrent Tracker for 3X";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public ExoticaZ(IIndexerRepository indexerRepository, IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
@@ -26,7 +27,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
HttpClient = _httpClient,
|
||||
Logger = _logger,
|
||||
Capabilities = Capabilities,
|
||||
BaseUrl = BaseUrl
|
||||
};
|
||||
}
|
||||
|
||||
@@ -45,8 +45,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.XXXPack);
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.XXXPack);
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.XXXDVD);
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.XXXOther);
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.XXXx264);
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.XXXImageSet);
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.XXXImageSet);
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -10,14 +10,12 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
{
|
||||
public class FileListParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly string _baseUrl;
|
||||
private readonly FileListSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
public FileListParser(FileListSettings settings, string baseUrl, IndexerCapabilitiesCategories categories)
|
||||
public FileListParser(FileListSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_baseUrl = baseUrl;
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
@@ -78,7 +76,7 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
|
||||
private string GetDownloadUrl(string torrentId)
|
||||
{
|
||||
var url = new HttpUri(_baseUrl)
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/download.php")
|
||||
.AddQueryParam("id", torrentId)
|
||||
.AddQueryParam("passkey", _settings.Passkey);
|
||||
@@ -88,7 +86,7 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
|
||||
private string GetInfoUrl(string torrentId)
|
||||
{
|
||||
var url = new HttpUri(_baseUrl)
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/details.php")
|
||||
.AddQueryParam("id", torrentId);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.FileList
|
||||
@@ -14,20 +13,27 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
}
|
||||
}
|
||||
|
||||
public class FileListSettings : IProviderConfig
|
||||
public class FileListSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly FileListSettingsValidator Validator = new FileListSettingsValidator();
|
||||
|
||||
public FileListSettings()
|
||||
{
|
||||
BaseUrl = "https://filelist.io";
|
||||
}
|
||||
|
||||
[FieldDefinition(0, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
|
||||
public string Username { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Passkey", Privacy = PrivacyLevel.ApiKey)]
|
||||
[FieldDefinition(3, Label = "Passkey", HelpText = "Site Passkey", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
public string Passkey { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -13,13 +13,11 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
{
|
||||
protected readonly GazelleSettings _settings;
|
||||
protected readonly IndexerCapabilities _capabilities;
|
||||
protected readonly string _baseUrl;
|
||||
|
||||
public GazelleParser(GazelleSettings settings, IndexerCapabilities capabilities, string baseUrl)
|
||||
public GazelleParser(GazelleSettings settings, IndexerCapabilities capabilities)
|
||||
{
|
||||
_settings = settings;
|
||||
_capabilities = capabilities;
|
||||
_baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
@@ -140,7 +138,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
|
||||
protected virtual string GetDownloadUrl(int torrentId)
|
||||
{
|
||||
var url = new HttpUri(_baseUrl)
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/torrents.php")
|
||||
.AddQueryParam("action", "download")
|
||||
.AddQueryParam("useToken", _settings.UseFreeleechToken ? "1" : "0")
|
||||
@@ -151,7 +149,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
|
||||
private string GetInfoUrl(string groupId, int torrentId)
|
||||
{
|
||||
var url = new HttpUri(_baseUrl)
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/torrents.php")
|
||||
.AddQueryParam("id", groupId)
|
||||
.AddQueryParam("torrentid", torrentId);
|
||||
|
||||
@@ -9,14 +9,13 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
public class GazelleRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public GazelleSettings Settings { get; set; }
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
public IDictionary<string, string> AuthCookieCache { get; set; }
|
||||
public IHttpClient HttpClient { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
public Logger Logger { get; set; }
|
||||
|
||||
protected virtual string APIUrl => BaseUrl + "ajax.php";
|
||||
protected virtual string APIUrl => Settings.BaseUrl + "ajax.php";
|
||||
protected virtual bool ImdbInTags => false;
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
@@ -76,11 +75,11 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
{
|
||||
if (ImdbInTags)
|
||||
{
|
||||
parameters += string.Format("&taglist={0}", searchCriteria.ImdbId);
|
||||
parameters += string.Format("&taglist={0}", searchCriteria.FullImdbId);
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters += string.Format("&cataloguenumber={0}", searchCriteria.ImdbId);
|
||||
parameters += string.Format("&cataloguenumber={0}", searchCriteria.FullImdbId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,11 +120,11 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
{
|
||||
if (ImdbInTags)
|
||||
{
|
||||
parameters += string.Format("&taglist={0}", searchCriteria.ImdbId);
|
||||
parameters += string.Format("&taglist={0}", searchCriteria.FullImdbId);
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters += string.Format("&cataloguenumber={0}", searchCriteria.ImdbId);
|
||||
parameters += string.Format("&cataloguenumber={0}", searchCriteria.FullImdbId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Gazelle
|
||||
@@ -14,7 +13,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
}
|
||||
}
|
||||
|
||||
public class GazelleSettings : IProviderConfig
|
||||
public class GazelleSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly GazelleSettingsValidator Validator = new GazelleSettingsValidator();
|
||||
|
||||
@@ -25,15 +24,21 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
public string AuthKey;
|
||||
public string PassKey;
|
||||
|
||||
[FieldDefinition(1, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
|
||||
public string Username { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Password", HelpText = "Site Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(3, Type = FieldType.Checkbox, Label = "Use Freeleech Token", HelpText = "Use Freeleech Token")]
|
||||
[FieldDefinition(4, Type = FieldType.Checkbox, Label = "Use Freeleech Token", HelpText = "Use Freeleech Token")]
|
||||
public bool UseFreeleechToken { get; set; }
|
||||
|
||||
[FieldDefinition(5)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using AngleSharp.Html.Parser;
|
||||
using FluentValidation;
|
||||
@@ -21,8 +22,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class GazelleGames : TorrentIndexerBase<GazelleGamesSettings>
|
||||
{
|
||||
public override string Name => "GazelleGames";
|
||||
public override string BaseUrl => "https://gazellegames.net/";
|
||||
public override string Description => "A gaming tracker.";
|
||||
public override string[] IndexerUrls => new string[] { "https://gazellegames.net/" };
|
||||
public override string Description => "GazelleGames (GGn) is a Private Torrent Tracker for GAMES";
|
||||
public override string Language => "en-us";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
@@ -36,12 +37,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new GazelleGamesRequestGenerator() { Settings = Settings, Capabilities = Capabilities, BaseUrl = BaseUrl };
|
||||
return new GazelleGamesRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new GazelleGamesParser(Settings, Capabilities.Categories, BaseUrl);
|
||||
return new GazelleGamesParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override IDictionary<string, string> GetCookies()
|
||||
@@ -188,7 +189,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public GazelleGamesSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
public GazelleGamesRequestGenerator()
|
||||
{
|
||||
@@ -196,7 +196,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
|
||||
{
|
||||
var searchUrl = string.Format("{0}/torrents.php", BaseUrl.TrimEnd('/'));
|
||||
var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/'));
|
||||
|
||||
var searchString = term;
|
||||
|
||||
@@ -278,13 +278,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
private readonly GazelleGamesSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public GazelleGamesParser(GazelleGamesSettings settings, IndexerCapabilitiesCategories categories, string baseurl)
|
||||
public GazelleGamesParser(GazelleGamesSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
_baseUrl = baseurl;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
@@ -355,12 +353,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var qFreeLeech = row.QuerySelector("strong.freeleech_label");
|
||||
var qNeutralLeech = row.QuerySelector("strong.neutralleech_label");
|
||||
var time = qTime.GetAttribute("title");
|
||||
var link = _baseUrl + qDLLink.GetAttribute("href");
|
||||
var link = _settings.BaseUrl + qDLLink.GetAttribute("href");
|
||||
var seeders = ParseUtil.CoerceInt(qSeeders.TextContent);
|
||||
var publishDate = DateTime.SpecifyKind(
|
||||
DateTime.ParseExact(time, "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture),
|
||||
DateTimeKind.Unspecified).ToLocalTime();
|
||||
var details = _baseUrl + qDetailsLink.GetAttribute("href");
|
||||
var details = _settings.BaseUrl + qDetailsLink.GetAttribute("href");
|
||||
var grabs = ParseUtil.CoerceInt(qGrabs.TextContent);
|
||||
var leechers = ParseUtil.CoerceInt(qLeechers.TextContent);
|
||||
var size = ReleaseInfo.GetBytes(sizeString);
|
||||
@@ -402,7 +400,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
}
|
||||
|
||||
public class GazelleGamesSettings : IProviderConfig
|
||||
public class GazelleGamesSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly GazelleGamesSettingsValidator Validator = new GazelleGamesSettingsValidator();
|
||||
|
||||
@@ -412,12 +410,18 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
SearchGroupNames = false;
|
||||
}
|
||||
|
||||
[FieldDefinition(1, Label = "Cookie", HelpText = "Login cookie from website")]
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Cookie", HelpText = "Login cookie from website")]
|
||||
public string Cookie { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Search Group Names", Type = FieldType.Checkbox, HelpText = "Search Group Names Only")]
|
||||
[FieldDefinition(3, Label = "Search Group Names", Type = FieldType.Checkbox, HelpText = "Search Group Names Only")]
|
||||
public bool SearchGroupNames { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -11,13 +11,11 @@ namespace NzbDrone.Core.Indexers.HDBits
|
||||
{
|
||||
public class HDBitsParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly string _baseUrl;
|
||||
private readonly HDBitsSettings _settings;
|
||||
|
||||
public HDBitsParser(HDBitsSettings settings, string baseUrl)
|
||||
public HDBitsParser(HDBitsSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
_baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
@@ -91,7 +89,7 @@ namespace NzbDrone.Core.Indexers.HDBits
|
||||
|
||||
private string GetDownloadUrl(string torrentId)
|
||||
{
|
||||
var url = new HttpUri(_baseUrl)
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/download.php")
|
||||
.AddQueryParam("id", torrentId)
|
||||
.AddQueryParam("passkey", _settings.ApiKey);
|
||||
@@ -101,7 +99,7 @@ namespace NzbDrone.Core.Indexers.HDBits
|
||||
|
||||
private string GetInfoUrl(string torrentId)
|
||||
{
|
||||
var url = new HttpUri(_baseUrl)
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/details.php")
|
||||
.AddQueryParam("id", torrentId);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user