mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-03-21 17:14:08 -04:00
Compare commits
3 Commits
v0.1.0.768
...
jackettmig
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0150988bcd | ||
|
|
593231c3af | ||
|
|
dbbb6bf0d1 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,6 +1,6 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: Prowlarr # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
github: # 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
|
||||
|
||||
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
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! -->
|
||||
**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
74
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,74 +0,0 @@
|
||||
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
|
||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,8 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Indexer Requests
|
||||
url: https://requests.prowlarr.com/
|
||||
about: Request new indexers to be added. Vote on existing requests.
|
||||
- name: Support via Discord
|
||||
url: https://prowlarr.com/discord
|
||||
about: Chat with users and devs on support and setup related topics.
|
||||
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
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
39
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,39 +0,0 @@
|
||||
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,15 +1,14 @@
|
||||
#### Database Migration
|
||||
YES - XXXX | NO
|
||||
YES | NO
|
||||
|
||||
#### Description
|
||||
A few sentences describing the overall goals of the pull request's commits.
|
||||
|
||||
#### Screenshot (if UI related)
|
||||
|
||||
#### Todos
|
||||
- [ ] Tests
|
||||
- [ ] Translation Keys (./src/NzbDrone.Core/Localization/Core/en.json)
|
||||
- [ ] [Wiki Updates](https://wiki.servarr.com)
|
||||
- [ ] Translation Keys
|
||||
- [ ] Wiki Updates
|
||||
|
||||
#### Issues Fixed or Closed by this PR
|
||||
|
||||
|
||||
41
.github/workflows/azuresync.yml
vendored
41
.github/workflows/azuresync.yml
vendored
@@ -1,41 +0,0 @@
|
||||
name: Sync issue to Azure DevOps work item
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
[opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
|
||||
|
||||
concurrency: azuresync-${{ github.event.issue.number }}
|
||||
|
||||
jobs:
|
||||
alert:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: danhellem/github-actions-issue-to-work-item@master
|
||||
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == true }}"
|
||||
env:
|
||||
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
|
||||
github_token: "${{ github.token }}"
|
||||
ado_organization: "Servarr"
|
||||
ado_project: "Servarr"
|
||||
ado_area_path: "Servarr\\Prowlarr"
|
||||
ado_wit: "Bug"
|
||||
ado_new_state: "New"
|
||||
ado_active_state: "Active"
|
||||
ado_close_state: "Closed"
|
||||
ado_bypassrules: true
|
||||
log_level: 100
|
||||
- uses: danhellem/github-actions-issue-to-work-item@master
|
||||
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == false }}"
|
||||
env:
|
||||
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
|
||||
github_token: "${{ github.token }}"
|
||||
ado_organization: "Servarr"
|
||||
ado_project: "Servarr"
|
||||
ado_area_path: "Servarr\\Prowlarr"
|
||||
ado_wit: "User Story"
|
||||
ado_new_state: "New"
|
||||
ado_active_state: "Active"
|
||||
ado_close_state: "Closed"
|
||||
ado_bypassrules: true
|
||||
log_level: 100
|
||||
@@ -2,30 +2,12 @@
|
||||
|
||||
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.
|
||||
Setup guides, FAQ, the more information we have on the wiki the better.
|
||||
|
||||
## Development ##
|
||||
|
||||
### Tools required ###
|
||||
- Visual Studio 2019 or higher (https://www.visualstudio.com/vs/). The community version is free and works (https://www.visualstudio.com/downloads/).
|
||||
- HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [NodeJS](https://nodejs.org/en/download/) (Node 12.X.X or higher)
|
||||
- [Yarn](https://yarnpkg.com/)
|
||||
- .NET Core 5.0.
|
||||
|
||||
### Getting started ###
|
||||
|
||||
1. Fork Prowlarr
|
||||
2. Clone the repository into your development machine. [*info*](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository-from-github)
|
||||
3. Install the required Node Packages `yarn install`
|
||||
4. Start gulp to monitor your dev environment for any changes that need post processing using `yarn start` command.
|
||||
5. Build the project in Visual Studio, Setting startup project to `Prowlarr.Console` and framework to `net5.0`
|
||||
6. Debug the project in Visual Studio
|
||||
7. Open http://localhost:9696
|
||||
See the readme for information on setting up your development environment.
|
||||
|
||||
### 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)
|
||||
@@ -38,10 +20,6 @@ Setup guides, FAQ, the more information we have on the [wiki](https://wiki.serva
|
||||
- One feature/bug fix per pull request to keep things clean and easy to understand
|
||||
- Use 4 spaces instead of tabs, this is the default for VS 2019 and WebStorm (to my knowledge)
|
||||
|
||||
### Contributing Indexers ###
|
||||
- If you're contributing an indexer please phrase your commit as something like: `New: (Indexer) {Indexer Name}`, `New: (Indexer) {Usenet|Torrent} {Indexer Name}`, `New: (Indexer) {Torznab|Newznab} {Indexer Name}`
|
||||
- If you're updating an indexer please phrase your commit as something like: `Fixed: (Indexer) {Indexer Name} {changes}` e.g. `Fixed: (Indexer) Changed BHD to use API`
|
||||
|
||||
### Pull Requesting ###
|
||||
- Only make pull requests to develop, never master, if you make a PR to master we'll comment on it and close it
|
||||
- You're probably going to get some comments or questions from us, they will be to ensure consistency and maintainability
|
||||
|
||||
17
README.md
17
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://dev.azure.com/Prowlarr/Prowlarr/_build/latest?definitionId=1&branchName=develop)
|
||||
[](https://translate.servarr.com/engage/prowlarr/?utm_source=widget)
|
||||
[](https://wiki.servarr.com/prowlarr/installation#docker)
|
||||
[](https://wiki.servarr.com/Prowlarr_Installation#Docker)
|
||||

|
||||
[](#backers)
|
||||
[](#sponsors)
|
||||
@@ -10,14 +10,12 @@
|
||||
Prowlarr is a indexer manager/proxy built on the popular arr .net/reactjs base stack to integrate with your various PVR apps. Prowlarr supports both Torrent Trackers and Usenet Indexers. It integrates seamlessly with Sonarr, Radarr, Lidarr, and Readarr offering complete management of your indexers with no per app Indexer setup required (we do it all).
|
||||
|
||||
## Major Features Include:
|
||||
- Usenet support for 24 indexers natively, including Headphones VIP, and support for any Newznab compatible indexer via "Generic Newznab"
|
||||
- Torrent support for over 500 trackers with more added all the time
|
||||
- Torrent support for any Torznab compatible tracker via "Generic Torznab"
|
||||
- Usenet support for any Newznab compatible indexer, including Headphones VIP
|
||||
- Torrent support 400+ trackers & more coming soon
|
||||
- Indexer Sync to Sonarr/Radarr/Readarr/Lidarr, so no manual configuration of the other applications are required
|
||||
- Indexer History and Statistics
|
||||
- Manual Searching of Trackers & Indexers at a category level
|
||||
- Support for pushing releases directly to your download clients from Prowlarr
|
||||
- Indexer health and status notifications
|
||||
|
||||
## Support
|
||||
Note: Prowlarr is currently early in life, thus bugs should be expected
|
||||
@@ -25,14 +23,11 @@ Note: Prowlarr is currently early in life, thus bugs should be expected
|
||||
[](https://prowlarr.com/discord)
|
||||
[](https://www.reddit.com/r/Prowlarr)
|
||||
[](https://github.com/Prowlarr/Prowlarr/issues)
|
||||
[](https://wiki.servarr.com/prowlarr)
|
||||
[](https://wiki.servarr.com/Prowlarr)
|
||||
|
||||
## Indexers/Trackers
|
||||
## Feature Requests
|
||||
|
||||
[Supported Indexers](https://wiki.servarr.com/en/prowlarr/supported-indexers)
|
||||
|
||||
[Indexer Requests](https://requests.prowlarr.com)
|
||||
- Request or vote on an existing request for a new tracker/indexer
|
||||
[Feature Requests](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).
|
||||
|
||||
@@ -13,7 +13,7 @@ variables:
|
||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||
sentryOrg: 'servarr'
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '5.0.302'
|
||||
dotnetVersion: '5.0.203'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
|
||||
trigger:
|
||||
@@ -879,7 +879,7 @@ stages:
|
||||
artifactName: 'WindowsAutomationScreenshots'
|
||||
targetPath: $(Build.SourcesDirectory)
|
||||
- checkout: none
|
||||
- pwsh: |
|
||||
- powershell: |
|
||||
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/Servarr/AzureDiscordNotify/master/DiscordNotify.ps1'))
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
|
||||
@@ -23,16 +23,6 @@ class BarChart extends Component {
|
||||
this.myChart = new Chart(this.canvasRef.current, {
|
||||
type: 'bar',
|
||||
options: {
|
||||
x: {
|
||||
ticks: {
|
||||
stepSize: this.props.stepSize
|
||||
}
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
stepSize: this.props.stepSize
|
||||
}
|
||||
},
|
||||
indexAxis: this.props.horizontal ? 'y' : 'x',
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
@@ -74,8 +64,7 @@ BarChart.propTypes = {
|
||||
horizontal: PropTypes.bool,
|
||||
legend: PropTypes.bool,
|
||||
title: PropTypes.string.isRequired,
|
||||
kind: PropTypes.oneOf(kinds.all).isRequired,
|
||||
stepSize: PropTypes.number
|
||||
kind: PropTypes.oneOf(kinds.all).isRequired
|
||||
};
|
||||
|
||||
BarChart.defaultProps = {
|
||||
@@ -83,8 +72,7 @@ BarChart.defaultProps = {
|
||||
horizontal: false,
|
||||
legend: false,
|
||||
title: '',
|
||||
kind: kinds.INFO,
|
||||
stepSize: 1
|
||||
kind: kinds.INFO
|
||||
};
|
||||
|
||||
export default BarChart;
|
||||
|
||||
@@ -16,16 +16,10 @@ class StackedBarChart extends Component {
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
ticks: {
|
||||
stepSize: this.props.stepSize
|
||||
}
|
||||
stacked: true
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
ticks: {
|
||||
stepSize: this.props.stepSize
|
||||
}
|
||||
stacked: true
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
@@ -69,13 +63,11 @@ class StackedBarChart extends Component {
|
||||
|
||||
StackedBarChart.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
stepSize: PropTypes.number
|
||||
title: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
StackedBarChart.defaultProps = {
|
||||
title: '',
|
||||
stepSize: 1
|
||||
title: ''
|
||||
};
|
||||
|
||||
export default StackedBarChart;
|
||||
|
||||
@@ -129,7 +129,7 @@ class FileBrowserModalContent extends Component {
|
||||
className={styles.mappedDrivesWarning}
|
||||
kind={kinds.WARNING}
|
||||
>
|
||||
<Link to="https://wiki.servarr.com/prowlarr/faq#why-cant-prowlarr-see-my-files-on-a-remote-server">
|
||||
<Link to="https://wiki.servarr.com/Prowlarr_FAQ#Why_cant_Prowlarr_see_my_files_on_a_remote_server">
|
||||
{translate('MappedDrivesRunningAsService')}
|
||||
</Link>
|
||||
</Alert>
|
||||
|
||||
@@ -25,7 +25,7 @@ function FormInputHelpText(props) {
|
||||
isCheckInput && styles.isCheckInput
|
||||
)}
|
||||
>
|
||||
<div dangerouslySetInnerHTML={{ __html: text }} />
|
||||
{text}
|
||||
|
||||
{
|
||||
link ?
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -73,7 +73,7 @@ class HistoryOptions extends Component {
|
||||
}
|
||||
|
||||
HistoryOptions.propTypes = {
|
||||
historyCleanupDays: PropTypes.number.isRequired,
|
||||
historyCleanupDays: PropTypes.bool.isRequired,
|
||||
dispatchSaveGeneralSettings: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -20,25 +20,19 @@ import styles from './AddIndexerModalContent.css';
|
||||
const columns = [
|
||||
{
|
||||
name: 'protocol',
|
||||
label: translate('Protocol'),
|
||||
label: 'Protocol',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: translate('Name'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'language',
|
||||
label: translate('Language'),
|
||||
label: 'Name',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'privacy',
|
||||
label: translate('Privacy'),
|
||||
label: 'Privacy',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
}
|
||||
|
||||
@@ -26,8 +26,7 @@ class SelectIndexerRow extends Component {
|
||||
const {
|
||||
protocol,
|
||||
privacy,
|
||||
name,
|
||||
language
|
||||
name
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@@ -42,10 +41,6 @@ class SelectIndexerRow extends Component {
|
||||
{name}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell>
|
||||
{language}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell>
|
||||
{privacy}
|
||||
</TableRowCell>
|
||||
@@ -58,7 +53,6 @@ SelectIndexerRow.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
protocol: PropTypes.string.isRequired,
|
||||
privacy: PropTypes.string.isRequired,
|
||||
language: PropTypes.string.isRequired,
|
||||
implementation: PropTypes.string.isRequired,
|
||||
onIndexerSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -13,7 +13,6 @@ 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';
|
||||
|
||||
@@ -32,7 +31,6 @@ function EditIndexerModalContent(props) {
|
||||
onSavePress,
|
||||
onTestPress,
|
||||
onDeleteIndexerPress,
|
||||
onAdvancedSettingsPress,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
@@ -101,7 +99,7 @@ function EditIndexerModalContent(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="redirect"
|
||||
helpText={translate('RedirectHelpText')}
|
||||
helpText={'Redirect incoming download requests for indexer instead of Proxying using Prowlarr'}
|
||||
isDisabled={!supportsRedirect.value}
|
||||
{...redirect}
|
||||
onChange={onInputChange}
|
||||
@@ -115,7 +113,6 @@ function EditIndexerModalContent(props) {
|
||||
type={inputTypes.APP_PROFILE_SELECT}
|
||||
name="appProfileId"
|
||||
{...appProfileId}
|
||||
helpText={translate('AppProfileSelectHelpText')}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -167,12 +164,6 @@ function EditIndexerModalContent(props) {
|
||||
</Button>
|
||||
}
|
||||
|
||||
<AdvancedSettingsButton
|
||||
advancedSettings={advancedSettings}
|
||||
onAdvancedSettingsPress={onAdvancedSettingsPress}
|
||||
showLabel={false}
|
||||
/>
|
||||
|
||||
<SpinnerErrorButton
|
||||
isSpinning={isTesting}
|
||||
error={saveError}
|
||||
@@ -212,7 +203,6 @@ EditIndexerModalContent.propTypes = {
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
onSavePress: PropTypes.func.isRequired,
|
||||
onTestPress: PropTypes.func.isRequired,
|
||||
onAdvancedSettingsPress: PropTypes.func.isRequired,
|
||||
onDeleteIndexerPress: PropTypes.func
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ 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';
|
||||
|
||||
@@ -24,8 +23,7 @@ const mapDispatchToProps = {
|
||||
setIndexerValue,
|
||||
setIndexerFieldValue,
|
||||
saveIndexer,
|
||||
testIndexer,
|
||||
toggleAdvancedSettings
|
||||
testIndexer
|
||||
};
|
||||
|
||||
class EditIndexerModalContentConnector extends Component {
|
||||
@@ -58,11 +56,6 @@ class EditIndexerModalContentConnector extends Component {
|
||||
this.props.testIndexer({ id: this.props.id });
|
||||
}
|
||||
|
||||
onAdvancedSettingsPress = () => {
|
||||
console.log('settings');
|
||||
this.props.toggleAdvancedSettings();
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -72,7 +65,6 @@ class EditIndexerModalContentConnector extends Component {
|
||||
{...this.props}
|
||||
onSavePress={this.onSavePress}
|
||||
onTestPress={this.onTestPress}
|
||||
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
|
||||
onInputChange={this.onInputChange}
|
||||
onFieldChange={this.onFieldChange}
|
||||
/>
|
||||
@@ -88,7 +80,6 @@ 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
|
||||
|
||||
@@ -50,7 +50,7 @@ class TagsModalContent extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
indexerTags,
|
||||
movieTags,
|
||||
tagList,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
@@ -108,7 +108,7 @@ class TagsModalContent extends Component {
|
||||
|
||||
<div className={styles.result}>
|
||||
{
|
||||
indexerTags.map((t) => {
|
||||
movieTags.map((t) => {
|
||||
const tag = _.find(tagList, { id: t });
|
||||
|
||||
if (!tag) {
|
||||
@@ -140,7 +140,7 @@ class TagsModalContent extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (indexerTags.indexOf(t) > -1) {
|
||||
if (movieTags.indexOf(t) > -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ class TagsModalContent extends Component {
|
||||
}
|
||||
|
||||
TagsModalContent.propTypes = {
|
||||
indexerTags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
movieTags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
onApplyTagsPress: PropTypes.func.isRequired
|
||||
|
||||
@@ -10,15 +10,15 @@ function createMapStateToProps() {
|
||||
(state, { indexerIds }) => indexerIds,
|
||||
createAllIndexersSelector(),
|
||||
createTagsSelector(),
|
||||
(indexerIds, allIndexers, tagList) => {
|
||||
const indexers = _.intersectionWith(allIndexers, indexerIds, (s, id) => {
|
||||
(indexerIds, allMovies, tagList) => {
|
||||
const movies = _.intersectionWith(allMovies, indexerIds, (s, id) => {
|
||||
return s.id === id;
|
||||
});
|
||||
|
||||
const indexerTags = _.uniq(_.concat(..._.map(indexers, 'tags')));
|
||||
const movieTags = _.uniq(_.concat(..._.map(movies, 'tags')));
|
||||
|
||||
return {
|
||||
indexerTags,
|
||||
movieTags,
|
||||
tagList
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import withScrollPosition from 'Components/withScrollPosition';
|
||||
import { testAllIndexers } from 'Store/Actions/indexerActions';
|
||||
import { saveIndexerEditor, setMovieFilter, setMovieSort, setMovieTableOption } from 'Store/Actions/indexerIndexActions';
|
||||
import { saveMovieEditor, setMovieFilter, setMovieSort, setMovieTableOption } from 'Store/Actions/indexerIndexActions';
|
||||
import scrollPositions from 'Store/scrollPositions';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createIndexerClientSideCollectionItemsSelector from 'Store/Selectors/createIndexerClientSideCollectionItemsSelector';
|
||||
@@ -40,8 +40,8 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
dispatch(setMovieFilter({ selectedFilterKey }));
|
||||
},
|
||||
|
||||
dispatchSaveIndexerEditor(payload) {
|
||||
dispatch(saveIndexerEditor(payload));
|
||||
dispatchSaveMovieEditor(payload) {
|
||||
dispatch(saveMovieEditor(payload));
|
||||
},
|
||||
|
||||
onTestAllPress() {
|
||||
@@ -56,7 +56,7 @@ class IndexerIndexConnector extends Component {
|
||||
// Listeners
|
||||
|
||||
onSaveSelected = (payload) => {
|
||||
this.props.dispatchSaveIndexerEditor(payload);
|
||||
this.props.dispatchSaveMovieEditor(payload);
|
||||
}
|
||||
|
||||
onScroll = ({ scrollTop }) => {
|
||||
@@ -79,7 +79,7 @@ class IndexerIndexConnector extends Component {
|
||||
|
||||
IndexerIndexConnector.propTypes = {
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
dispatchSaveIndexerEditor: PropTypes.func.isRequired,
|
||||
dispatchSaveMovieEditor: PropTypes.func.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object)
|
||||
};
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ class IndexerIndexRow extends Component {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
indexerUrls,
|
||||
baseUrl,
|
||||
enable,
|
||||
redirect,
|
||||
tags,
|
||||
@@ -248,7 +248,7 @@ class IndexerIndexRow extends Component {
|
||||
className={styles.externalLink}
|
||||
name={icons.EXTERNAL_LINK}
|
||||
title={'Website'}
|
||||
to={indexerUrls[0].replace('api.', '')}
|
||||
to={baseUrl}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
@@ -289,7 +289,7 @@ class IndexerIndexRow extends Component {
|
||||
|
||||
IndexerIndexRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
baseUrl: 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,
|
||||
indexerUrls,
|
||||
baseUrl,
|
||||
protocol,
|
||||
onModalClose
|
||||
} = props;
|
||||
@@ -54,10 +54,10 @@ function IndexerInfoModalContent(props) {
|
||||
|
||||
<DescriptionListItemTitle>Indexer Site</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to={indexerUrls[0]}>{indexerUrls[0]}</Link>
|
||||
<Link to={baseUrl}>{baseUrl}</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,
|
||||
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
baseUrl: PropTypes.string.isRequired,
|
||||
protocol: PropTypes.string.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ function createMapStateToProps() {
|
||||
(state) => state.settings.advancedSettings,
|
||||
createIndexerSelector(),
|
||||
(advancedSettings, indexer) => {
|
||||
console.log(indexer);
|
||||
return {
|
||||
advancedSettings,
|
||||
...indexer
|
||||
|
||||
@@ -19,10 +19,6 @@ function getAverageResponseTimeData(indexerStats) {
|
||||
};
|
||||
});
|
||||
|
||||
data.sort((a, b) => {
|
||||
return b.value - a.value;
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ function SearchIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Protocol')}
|
||||
Protocol
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -35,7 +35,7 @@ function SearchIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Age')}
|
||||
Age
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -53,7 +53,7 @@ function SearchIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Indexer')}
|
||||
Indexer
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -62,7 +62,7 @@ function SearchIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Size')}
|
||||
Size
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -71,7 +71,7 @@ function SearchIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Files')}
|
||||
Files
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -80,7 +80,7 @@ function SearchIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Grabs')}
|
||||
Grabs
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -89,7 +89,7 @@ function SearchIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Peers')}
|
||||
Peers
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -98,7 +98,7 @@ function SearchIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Category')}
|
||||
Category
|
||||
</SortMenuItem>
|
||||
</MenuContent>
|
||||
</SortMenu>
|
||||
|
||||
@@ -19,7 +19,7 @@ function NoSearchResults(props) {
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.message}>
|
||||
{translate('NoSearchResultsFound')}
|
||||
No search results found, try performing a new search below.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,10 +4,8 @@ import React, { Component } from 'react';
|
||||
import IndexersSelectInputConnector from 'Components/Form/IndexersSelectInputConnector';
|
||||
import NewznabCategorySelectInputConnector from 'Components/Form/NewznabCategorySelectInputConnector';
|
||||
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';
|
||||
|
||||
@@ -40,11 +38,9 @@ class SearchFooter extends Component {
|
||||
searchQuery
|
||||
} = this.state;
|
||||
|
||||
if (searchQuery !== '' || searchCategories.length > 0 || searchIndexerIds.length > 0) {
|
||||
if (searchQuery !== '' || searchCategories !== [] || searchIndexerIds !== []) {
|
||||
this.onSearchPress();
|
||||
}
|
||||
|
||||
this.props.bindShortcut('enter', this.onSearchPress, { isGlobal: true });
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -118,7 +114,6 @@ class SearchFooter extends Component {
|
||||
|
||||
<TextInput
|
||||
name='searchQuery'
|
||||
autoFocus={true}
|
||||
value={searchQuery}
|
||||
isDisabled={isFetching}
|
||||
onChange={onInputChange}
|
||||
@@ -168,7 +163,7 @@ class SearchFooter extends Component {
|
||||
isDisabled={isFetching || !hasIndexers}
|
||||
onPress={this.onSearchPress}
|
||||
>
|
||||
{translate('Search')}
|
||||
Search
|
||||
</SpinnerButton>
|
||||
</div>
|
||||
</div>
|
||||
@@ -186,8 +181,7 @@ SearchFooter.propTypes = {
|
||||
onSearchPress: PropTypes.func.isRequired,
|
||||
hasIndexers: PropTypes.bool.isRequired,
|
||||
onInputChange: PropTypes.func.isRequired,
|
||||
searchError: PropTypes.object,
|
||||
bindShortcut: PropTypes.func.isRequired
|
||||
searchError: PropTypes.object
|
||||
};
|
||||
|
||||
export default keyboardShortcuts(SearchFooter);
|
||||
export default SearchFooter;
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
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>
|
||||
{
|
||||
@@ -32,10 +20,6 @@ function CategoryLabel({ categories }) {
|
||||
);
|
||||
}
|
||||
|
||||
CategoryLabel.defaultProps = {
|
||||
categories: []
|
||||
};
|
||||
|
||||
CategoryLabel.propTypes = {
|
||||
categories: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||
};
|
||||
|
||||
@@ -10,8 +10,7 @@ import styles from './AdvancedSettingsButton.css';
|
||||
function AdvancedSettingsButton(props) {
|
||||
const {
|
||||
advancedSettings,
|
||||
onAdvancedSettingsPress,
|
||||
showLabel
|
||||
onAdvancedSettingsPress
|
||||
} = props;
|
||||
|
||||
return (
|
||||
@@ -44,27 +43,18 @@ function AdvancedSettingsButton(props) {
|
||||
/>
|
||||
</span>
|
||||
|
||||
{
|
||||
showLabel &&
|
||||
<div className={styles.labelContainer}>
|
||||
<div className={styles.label}>
|
||||
{advancedSettings ? translate('HideAdvanced') : translate('ShowAdvanced')}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<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,
|
||||
showLabel: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
AdvancedSettingsButton.defaultProps = {
|
||||
showLabel: true
|
||||
onAdvancedSettingsPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AdvancedSettingsButton;
|
||||
|
||||
@@ -18,9 +18,9 @@ import translate from 'Utilities/String/translate';
|
||||
import styles from './EditApplicationModalContent.css';
|
||||
|
||||
const syncLevelOptions = [
|
||||
{ key: 'disabled', value: translate('Disabled') },
|
||||
{ key: 'addOnly', value: translate('AddRemoveOnly') },
|
||||
{ key: 'fullSync', value: translate('FullSync') }
|
||||
{ key: 'disabled', value: 'Disabled' },
|
||||
{ key: 'addOnly', value: 'Add and Remove Only' },
|
||||
{ key: 'fullSync', value: 'Full Sync' }
|
||||
];
|
||||
|
||||
function EditApplicationModalContent(props) {
|
||||
@@ -53,7 +53,7 @@ function EditApplicationModalContent(props) {
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{`${id ? translate('Edit') : translate('Add')} ${translate('Application')}`}
|
||||
{`${id ? 'Edit' : 'Add'} Application`}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
@@ -94,13 +94,13 @@ function EditApplicationModalContent(props) {
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('SyncLevel')}</FormLabel>
|
||||
<FormLabel>{'Sync Level'}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
values={syncLevelOptions}
|
||||
name="syncLevel"
|
||||
helpText={`${translate('SyncLevelAddRemove')}<br>${translate('SyncLevelFull')}`}
|
||||
helpText={'Sync Level'}
|
||||
{...syncLevel}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
|
||||
@@ -53,9 +53,6 @@ class AddDownloadClientModalContent extends Component {
|
||||
<div>
|
||||
|
||||
<Alert kind={kinds.INFO}>
|
||||
<div>
|
||||
{translate('AddDownloadClientToProwlarr')}
|
||||
</div>
|
||||
<div>
|
||||
{translate('ProwlarrSupportsAnyDownloadClient')}
|
||||
</div>
|
||||
|
||||
@@ -55,7 +55,7 @@ function UpdateSettings(props) {
|
||||
type={inputTypes.TEXT}
|
||||
name="branch"
|
||||
helpText={usingExternalUpdateMechanism ? translate('BranchUpdateMechanism') : translate('BranchUpdate')}
|
||||
helpLink="https://wiki.servarr.com/prowlarr/settings#updates"
|
||||
helpLink="https://wiki.servarr.com/Prowlarr_Settings#Updates"
|
||||
{...branch}
|
||||
onChange={onInputChange}
|
||||
readOnly={usingExternalUpdateMechanism}
|
||||
@@ -92,7 +92,7 @@ function UpdateSettings(props) {
|
||||
name="updateMechanism"
|
||||
values={updateOptions}
|
||||
helpText={translate('UpdateMechanismHelpText')}
|
||||
helpLink="https://wiki.servarr.com/prowlarr/settings#updates"
|
||||
helpLink="https://wiki.servarr.com/Prowlarr_Settings#Updates"
|
||||
onChange={onInputChange}
|
||||
{...updateMechanism}
|
||||
/>
|
||||
|
||||
@@ -25,8 +25,8 @@ function NotificationEventItems(props) {
|
||||
<FormLabel>{translate('NotificationTriggers')}</FormLabel>
|
||||
<div>
|
||||
<FormInputHelpText
|
||||
text={translate('NotificationTriggersHelpText')}
|
||||
link="https://wiki.servarr.com/prowlarr/settings#connections"
|
||||
text={translate('NotifcationTriggersHelpText')}
|
||||
link="https://wiki.servarr.com/Prowlarr_Settings#Connections"
|
||||
/>
|
||||
<div className={styles.events}>
|
||||
<div>
|
||||
|
||||
@@ -176,7 +176,7 @@ export const SET_MOVIE_SORT = 'indexerIndex/setMovieSort';
|
||||
export const SET_MOVIE_FILTER = 'indexerIndex/setMovieFilter';
|
||||
export const SET_MOVIE_VIEW = 'indexerIndex/setMovieView';
|
||||
export const SET_MOVIE_TABLE_OPTION = 'indexerIndex/setMovieTableOption';
|
||||
export const SAVE_INDEXER_EDITOR = 'indexerIndex/saveIndexerEditor';
|
||||
export const SAVE_MOVIE_EDITOR = 'indexerIndex/saveMovieEditor';
|
||||
export const BULK_DELETE_INDEXERS = 'indexerIndex/bulkDeleteIndexers';
|
||||
|
||||
//
|
||||
@@ -186,14 +186,14 @@ export const setMovieSort = createAction(SET_MOVIE_SORT);
|
||||
export const setMovieFilter = createAction(SET_MOVIE_FILTER);
|
||||
export const setMovieView = createAction(SET_MOVIE_VIEW);
|
||||
export const setMovieTableOption = createAction(SET_MOVIE_TABLE_OPTION);
|
||||
export const saveIndexerEditor = createThunk(SAVE_INDEXER_EDITOR);
|
||||
export const saveMovieEditor = createThunk(SAVE_MOVIE_EDITOR);
|
||||
export const bulkDeleteIndexers = createThunk(BULK_DELETE_INDEXERS);
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
export const actionHandlers = handleThunks({
|
||||
[SAVE_INDEXER_EDITOR]: function(getState, payload, dispatch) {
|
||||
[SAVE_MOVIE_EDITOR]: function(getState, payload, dispatch) {
|
||||
dispatch(set({
|
||||
section,
|
||||
isSaving: true
|
||||
|
||||
@@ -202,8 +202,7 @@ export const defaultState = {
|
||||
|
||||
export const persistState = [
|
||||
'releases.customFilters',
|
||||
'releases.selectedFilterKey',
|
||||
'releases.columns'
|
||||
'releases.selectedFilterKey'
|
||||
];
|
||||
|
||||
//
|
||||
|
||||
@@ -13,7 +13,7 @@ function createHealthCheckSelector() {
|
||||
source: 'UI',
|
||||
type: 'warning',
|
||||
message: translate('CouldNotConnectSignalR'),
|
||||
wikiUrl: 'https://wiki.servarr.com/prowlarr/system#could-not-connect-to-signalr'
|
||||
wikiUrl: 'https://wiki.servarr.com/Prowlarr_System#Could_not_connect_to_signalR'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Link from 'Components/Link/Link';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from '../styles.css';
|
||||
|
||||
class Donations extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FieldSet legend={translate('Donations')}>
|
||||
<div className={styles.logoContainer} title="Radarr">
|
||||
<Link to="https://opencollective.com/radarr">
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-radarr.png`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.logoContainer} title="Lidarr">
|
||||
<Link to="https://opencollective.com/lidarr">
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-lidarr.png`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.logoContainer} title="Readarr">
|
||||
<Link to="https://opencollective.com/readarr">
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-readarr.png`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.logoContainer} title="Prowlarr">
|
||||
<Link to="https://opencollective.com/prowlarr">
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-prowlarr.png`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.logoContainer} title="Sonarr">
|
||||
<Link to="https://opencollective.com/sonarr">
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-sonarr.png`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</FieldSet>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Donations.propTypes = {
|
||||
|
||||
};
|
||||
|
||||
export default Donations;
|
||||
@@ -43,7 +43,6 @@ function getInternalLink(source) {
|
||||
function getTestLink(source, props) {
|
||||
switch (source) {
|
||||
case 'IndexerStatusCheck':
|
||||
case 'IndexerLongTermStatusCheck':
|
||||
return (
|
||||
<SpinnerIconButton
|
||||
name={icons.TEST}
|
||||
|
||||
@@ -15,32 +15,32 @@ class MoreInfo extends Component {
|
||||
return (
|
||||
<FieldSet legend={translate('MoreInfo')}>
|
||||
<DescriptionList>
|
||||
<DescriptionListItemTitle>{translate('HomePage')}</DescriptionListItemTitle>
|
||||
<DescriptionListItemTitle>Home page</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://prowlarr.com/">prowlarr.com</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>{translate('Wiki')}</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://wiki.servarr.com/prowlarr">wiki.servarr.com/prowlarr</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>{translate('Reddit')}</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://reddit.com/r/prowlarr">r/prowlarr</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>{translate('Discord')}</DescriptionListItemTitle>
|
||||
<DescriptionListItemTitle>Discord</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://prowlarr.com/discord">prowlarr.com/discord</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>{translate('Source')}</DescriptionListItemTitle>
|
||||
<DescriptionListItemTitle>Wiki</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://wiki.servarr.com/Prowlarr">wiki.servarr.com/Prowlarr</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>Donations</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://opencollective.com/prowlarr">opencollective.com/prowlarr</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>Source</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://github.com/Prowlarr/Prowlarr/">github.com/Prowlarr/Prowlarr</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>{translate('FeatureRequests')}</DescriptionListItemTitle>
|
||||
<DescriptionListItemTitle>Feature Requests</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://github.com/Prowlarr/Prowlarr/issues">github.com/Prowlarr/Prowlarr/issues</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
@@ -3,7 +3,6 @@ import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AboutConnector from './About/AboutConnector';
|
||||
import Donations from './Donations/Donations';
|
||||
import HealthConnector from './Health/HealthConnector';
|
||||
import MoreInfo from './MoreInfo/MoreInfo';
|
||||
|
||||
@@ -19,7 +18,6 @@ class Status extends Component {
|
||||
<HealthConnector />
|
||||
<AboutConnector />
|
||||
<MoreInfo />
|
||||
<Donations />
|
||||
</PageContentBody>
|
||||
</PageContent>
|
||||
);
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
.logo {
|
||||
margin: auto;
|
||||
padding: 9px;
|
||||
}
|
||||
|
||||
.logoContainer {
|
||||
display: inline-block;
|
||||
margin: 0.5em;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
outline: none;
|
||||
border: solid 1px #e6e6e6;
|
||||
border-radius: 0.5em;
|
||||
background: #f8f8ff;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ class UpdateChanges extends Component {
|
||||
<ul>
|
||||
{
|
||||
changes.map((change, index) => {
|
||||
const checkChange = change.replace(/#\d{3,5}\b/g, (match, contents) => {
|
||||
const checkChange = change.replace(/#\d{4,5}\b/g, (match, contents) => {
|
||||
return `[${match}](https://github.com/Prowlarr/Prowlarr/issues/${match.substring(1)})`;
|
||||
});
|
||||
|
||||
|
||||
@@ -252,7 +252,7 @@
|
||||
</span>
|
||||
|
||||
<a
|
||||
href="https://wiki.servarr.com/prowlarr/faq#help-i-have-locked-myself-out"
|
||||
href="https://wiki.servarr.com/Prowlarr_FAQ#Help_I_have_locked_my_self_out_of_Prowlarr_and_do_not_know_the_password"
|
||||
class="forgot-password"
|
||||
>Forgot your password?</a
|
||||
>
|
||||
|
||||
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.8",
|
||||
"@sentry/browser": "6.10.0",
|
||||
"@sentry/integrations": "6.10.0",
|
||||
"@microsoft/signalr": "5.0.6",
|
||||
"@sentry/browser": "6.3.1",
|
||||
"@sentry/integrations": "6.3.1",
|
||||
"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.31.0",
|
||||
"eslint": "7.25.0",
|
||||
"eslint-plugin-filenames": "1.3.2",
|
||||
"eslint-plugin-import": "2.23.4",
|
||||
"eslint-plugin-react": "7.24.0",
|
||||
"eslint-plugin-import": "2.22.1",
|
||||
"eslint-plugin-react": "7.23.2",
|
||||
"eslint-plugin-simple-import-sort": "7.0.0",
|
||||
"esprint": "3.1.0",
|
||||
"esprint": "2.0.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.1",
|
||||
"stylelint": "13.13.0",
|
||||
"stylelint-order": "4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
|
||||
<!-- Standard testing packages -->
|
||||
<ItemGroup Condition="'$(TestProject)'=='true'">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="NUnit" Version="3.13.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Include="NunitXml.TestLogger" Version="3.0.97" />
|
||||
|
||||
@@ -17,11 +17,6 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
[TestCase(@"https://baconbits.org/feeds.php?feed=torrents_tv&user=12345&auth=2b51db35e1910123321025a12b9933d2&passkey=mySecret&authkey=2b51db35e1910123321025a12b9933d2")]
|
||||
[TestCase(@"http://127.0.0.1:9117/dl/indexername?jackett_apikey=flwjiefewklfjacketmySecretsdfldskjfsdlk&path=we0re9f0sdfbase64sfdkfjsdlfjk&file=The+Torrent+File+Name.torrent")]
|
||||
[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"" }, ")]
|
||||
@@ -93,14 +88,5 @@ 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,16 +53,6 @@ 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,21 +194,5 @@ 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(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,13 +84,6 @@ namespace NzbDrone.Common.Http
|
||||
throw new WebException($"Too many automatic redirections were attempted for {autoRedirectChain.Join(" -> ")}", WebExceptionStatus.ProtocolError);
|
||||
}
|
||||
|
||||
// 302 or 303 should default to GET on redirect even if POST on original
|
||||
if (response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.RedirectMethod)
|
||||
{
|
||||
request.Method = HttpMethod.GET;
|
||||
request.ContentData = null;
|
||||
}
|
||||
|
||||
response = await ExecuteRequestAsync(request, cookieContainer);
|
||||
}
|
||||
while (response.HasHttpRedirect);
|
||||
|
||||
@@ -30,7 +30,6 @@ 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; }
|
||||
@@ -76,15 +75,8 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
public void SetContent(string data)
|
||||
{
|
||||
if (Encoding != null)
|
||||
{
|
||||
ContentData = Encoding.GetBytes(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
var encoding = HttpHeader.GetEncodingFromContentType(Headers.ContentType);
|
||||
ContentData = encoding.GetBytes(data);
|
||||
}
|
||||
var encoding = HttpHeader.GetEncodingFromContentType(Headers.ContentType);
|
||||
ContentData = encoding.GetBytes(data);
|
||||
}
|
||||
|
||||
public void AddBasicAuthentication(string username, string password)
|
||||
|
||||
@@ -47,14 +47,7 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
if (_content == null)
|
||||
{
|
||||
if (Request.Encoding != null)
|
||||
{
|
||||
_content = Request.Encoding.GetString(ResponseData);
|
||||
}
|
||||
else
|
||||
{
|
||||
_content = Headers.GetEncodingFromContentType().GetString(ResponseData);
|
||||
}
|
||||
_content = Headers.GetEncodingFromContentType().GetString(ResponseData);
|
||||
}
|
||||
|
||||
return _content;
|
||||
@@ -69,20 +62,6 @@ namespace NzbDrone.Common.Http
|
||||
StatusCode == HttpStatusCode.TemporaryRedirect ||
|
||||
StatusCode == HttpStatusCode.Found;
|
||||
|
||||
public string RedirectUrl
|
||||
{
|
||||
get
|
||||
{
|
||||
var newUrl = Headers["Location"];
|
||||
if (newUrl == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return (Request.Url += new HttpUri(newUrl)).FullUri;
|
||||
}
|
||||
}
|
||||
|
||||
public string[] GetCookieHeaders()
|
||||
{
|
||||
return Headers.GetValues("Set-Cookie") ?? Array.Empty<string>();
|
||||
|
||||
@@ -11,15 +11,13 @@ 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|pwd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&| )[^=]*?(_?(?<!use)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&|: |;)(apikey|token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&)[^=]*?(username|password)=(?<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),
|
||||
@@ -79,6 +77,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
private static string CleanseRemoteIP(Match match)
|
||||
{
|
||||
var group = match.Groups[1];
|
||||
var valueAll = match.Value;
|
||||
var valueIP = group.Value;
|
||||
|
||||
if (IPAddress.TryParse(valueIP, out var address) && !address.IsLocalAddress())
|
||||
|
||||
@@ -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.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
|
||||
<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.8.3" />
|
||||
<PackageReference Include="Sentry" Version="3.3.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" />
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Download
|
||||
{
|
||||
[TestFixture]
|
||||
public class DownloadClientProviderFixture : CoreTest<DownloadClientProvider>
|
||||
{
|
||||
private List<IDownloadClient> _downloadClients;
|
||||
private List<DownloadClientStatus> _blockedProviders;
|
||||
private int _nextId;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_downloadClients = new List<IDownloadClient>();
|
||||
_blockedProviders = new List<DownloadClientStatus>();
|
||||
_nextId = 1;
|
||||
|
||||
Mocker.GetMock<IDownloadClientFactory>()
|
||||
.Setup(v => v.GetAvailableProviders())
|
||||
.Returns(_downloadClients);
|
||||
|
||||
Mocker.GetMock<IDownloadClientStatusService>()
|
||||
.Setup(v => v.GetBlockedProviders())
|
||||
.Returns(_blockedProviders);
|
||||
}
|
||||
|
||||
private Mock<IDownloadClient> WithUsenetClient(int priority = 0)
|
||||
{
|
||||
var mock = new Mock<IDownloadClient>(MockBehavior.Default);
|
||||
mock.SetupGet(s => s.Definition)
|
||||
.Returns(Builder<DownloadClientDefinition>
|
||||
.CreateNew()
|
||||
.With(v => v.Id = _nextId++)
|
||||
.With(v => v.Priority = priority)
|
||||
.Build());
|
||||
|
||||
_downloadClients.Add(mock.Object);
|
||||
|
||||
mock.SetupGet(v => v.Protocol).Returns(DownloadProtocol.Usenet);
|
||||
|
||||
return mock;
|
||||
}
|
||||
|
||||
private Mock<IDownloadClient> WithTorrentClient(int priority = 0)
|
||||
{
|
||||
var mock = new Mock<IDownloadClient>(MockBehavior.Default);
|
||||
mock.SetupGet(s => s.Definition)
|
||||
.Returns(Builder<DownloadClientDefinition>
|
||||
.CreateNew()
|
||||
.With(v => v.Id = _nextId++)
|
||||
.With(v => v.Priority = priority)
|
||||
.Build());
|
||||
|
||||
_downloadClients.Add(mock.Object);
|
||||
|
||||
mock.SetupGet(v => v.Protocol).Returns(DownloadProtocol.Torrent);
|
||||
|
||||
return mock;
|
||||
}
|
||||
|
||||
private void GivenBlockedClient(int id)
|
||||
{
|
||||
_blockedProviders.Add(new DownloadClientStatus
|
||||
{
|
||||
ProviderId = id,
|
||||
DisabledTill = DateTime.UtcNow.AddHours(3)
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_roundrobin_over_usenet_client()
|
||||
{
|
||||
WithUsenetClient();
|
||||
WithUsenetClient();
|
||||
WithUsenetClient();
|
||||
WithTorrentClient();
|
||||
|
||||
var client1 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
|
||||
var client2 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
|
||||
var client3 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
|
||||
var client4 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
|
||||
var client5 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
|
||||
|
||||
client1.Definition.Id.Should().Be(1);
|
||||
client2.Definition.Id.Should().Be(2);
|
||||
client3.Definition.Id.Should().Be(3);
|
||||
client4.Definition.Id.Should().Be(1);
|
||||
client5.Definition.Id.Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_roundrobin_over_torrent_client()
|
||||
{
|
||||
WithUsenetClient();
|
||||
WithTorrentClient();
|
||||
WithTorrentClient();
|
||||
WithTorrentClient();
|
||||
|
||||
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
|
||||
client1.Definition.Id.Should().Be(2);
|
||||
client2.Definition.Id.Should().Be(3);
|
||||
client3.Definition.Id.Should().Be(4);
|
||||
client4.Definition.Id.Should().Be(2);
|
||||
client5.Definition.Id.Should().Be(3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_roundrobin_over_protocol_separately()
|
||||
{
|
||||
WithUsenetClient();
|
||||
WithTorrentClient();
|
||||
WithTorrentClient();
|
||||
|
||||
var client1 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
|
||||
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
|
||||
client1.Definition.Id.Should().Be(1);
|
||||
client2.Definition.Id.Should().Be(2);
|
||||
client3.Definition.Id.Should().Be(3);
|
||||
client4.Definition.Id.Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_skip_blocked_torrent_client()
|
||||
{
|
||||
WithUsenetClient();
|
||||
WithTorrentClient();
|
||||
WithTorrentClient();
|
||||
WithTorrentClient();
|
||||
|
||||
GivenBlockedClient(3);
|
||||
|
||||
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
|
||||
client1.Definition.Id.Should().Be(2);
|
||||
client2.Definition.Id.Should().Be(4);
|
||||
client3.Definition.Id.Should().Be(2);
|
||||
client4.Definition.Id.Should().Be(4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_skip_blocked_torrent_client_if_all_blocked()
|
||||
{
|
||||
WithUsenetClient();
|
||||
WithTorrentClient();
|
||||
WithTorrentClient();
|
||||
WithTorrentClient();
|
||||
|
||||
GivenBlockedClient(2);
|
||||
GivenBlockedClient(3);
|
||||
GivenBlockedClient(4);
|
||||
|
||||
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
|
||||
client1.Definition.Id.Should().Be(2);
|
||||
client2.Definition.Id.Should().Be(3);
|
||||
client3.Definition.Id.Should().Be(4);
|
||||
client4.Definition.Id.Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_skip_secondary_prio_torrent_client()
|
||||
{
|
||||
WithUsenetClient();
|
||||
WithTorrentClient(2);
|
||||
WithTorrentClient();
|
||||
WithTorrentClient();
|
||||
|
||||
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
|
||||
client1.Definition.Id.Should().Be(3);
|
||||
client2.Definition.Id.Should().Be(4);
|
||||
client3.Definition.Id.Should().Be(3);
|
||||
client4.Definition.Id.Should().Be(4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_skip_secondary_prio_torrent_client_if_primary_blocked()
|
||||
{
|
||||
WithUsenetClient();
|
||||
WithTorrentClient(2);
|
||||
WithTorrentClient(2);
|
||||
WithTorrentClient();
|
||||
|
||||
GivenBlockedClient(4);
|
||||
|
||||
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
|
||||
client1.Definition.Id.Should().Be(2);
|
||||
client2.Definition.Id.Should().Be(3);
|
||||
client3.Definition.Id.Should().Be(2);
|
||||
client4.Definition.Id.Should().Be(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Download
|
||||
{
|
||||
public class DownloadClientStatusServiceFixture : CoreTest<DownloadClientStatusService>
|
||||
{
|
||||
private DateTime _epoch;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_epoch = DateTime.UtcNow;
|
||||
|
||||
Mocker.GetMock<IRuntimeInfo>()
|
||||
.SetupGet(v => v.StartTime)
|
||||
.Returns(_epoch - TimeSpan.FromHours(1));
|
||||
}
|
||||
|
||||
private DownloadClientStatus WithStatus(DownloadClientStatus status)
|
||||
{
|
||||
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||
.Setup(v => v.FindByProviderId(1))
|
||||
.Returns(status);
|
||||
|
||||
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||
.Setup(v => v.All())
|
||||
.Returns(new[] { status });
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private void VerifyUpdate()
|
||||
{
|
||||
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||
.Verify(v => v.Upsert(It.IsAny<DownloadClientStatus>()), Times.Once());
|
||||
}
|
||||
|
||||
private void VerifyNoUpdate()
|
||||
{
|
||||
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||
.Verify(v => v.Upsert(It.IsAny<DownloadClientStatus>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_consider_blocked_within_5_minutes_since_initial_failure()
|
||||
{
|
||||
WithStatus(new DownloadClientStatus
|
||||
{
|
||||
InitialFailure = _epoch - TimeSpan.FromMinutes(4),
|
||||
MostRecentFailure = _epoch - TimeSpan.FromSeconds(4),
|
||||
EscalationLevel = 3
|
||||
});
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
VerifyUpdate();
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_consider_blocked_after_5_minutes_since_initial_failure()
|
||||
{
|
||||
WithStatus(new DownloadClientStatus
|
||||
{
|
||||
InitialFailure = _epoch - TimeSpan.FromMinutes(6),
|
||||
MostRecentFailure = _epoch - TimeSpan.FromSeconds(120),
|
||||
EscalationLevel = 3
|
||||
});
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
VerifyUpdate();
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_escalate_further_till_after_5_minutes_since_initial_failure()
|
||||
{
|
||||
var origStatus = WithStatus(new DownloadClientStatus
|
||||
{
|
||||
InitialFailure = _epoch - TimeSpan.FromMinutes(4),
|
||||
MostRecentFailure = _epoch - TimeSpan.FromSeconds(4),
|
||||
EscalationLevel = 3
|
||||
});
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().BeNull();
|
||||
|
||||
origStatus.EscalationLevel.Should().Be(3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_escalate_further_after_5_minutes_since_initial_failure()
|
||||
{
|
||||
WithStatus(new DownloadClientStatus
|
||||
{
|
||||
InitialFailure = _epoch - TimeSpan.FromMinutes(6),
|
||||
MostRecentFailure = _epoch - TimeSpan.FromSeconds(120),
|
||||
EscalationLevel = 3
|
||||
});
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().NotBeNull();
|
||||
|
||||
status.EscalationLevel.Should().BeGreaterThan(3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_escalate_beyond_3_hours()
|
||||
{
|
||||
WithStatus(new DownloadClientStatus
|
||||
{
|
||||
InitialFailure = _epoch - TimeSpan.FromMinutes(6),
|
||||
MostRecentFailure = _epoch - TimeSpan.FromSeconds(120),
|
||||
EscalationLevel = 3
|
||||
});
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().NotBeNull();
|
||||
status.DisabledTill.Should().HaveValue();
|
||||
status.DisabledTill.Should().NotBeAfter(_epoch + TimeSpan.FromHours(3.1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,14 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
Subject.Check().ShouldBeWarning();
|
||||
}
|
||||
|
||||
[TestCase("Develop")]
|
||||
[TestCase("develop")]
|
||||
public void should_return_error_when_branch_is_v1(string branch)
|
||||
{
|
||||
GivenValidBranch(branch);
|
||||
|
||||
Subject.Check().ShouldBeError();
|
||||
}
|
||||
|
||||
[TestCase("nightly")]
|
||||
[TestCase("Nightly")]
|
||||
public void should_return_no_warning_when_branch_valid(string branch)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using FluentAssertions;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.HealthCheck;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
@@ -10,9 +10,9 @@ namespace NzbDrone.Core.Test.HealthCheck
|
||||
{
|
||||
private const string WikiRoot = "https://wiki.servarr.com/";
|
||||
|
||||
[TestCase("I blew up because of some weird user mistake", null, WikiRoot + "prowlarr/system#i-blew-up-because-of-some-weird-user-mistake")]
|
||||
[TestCase("I blew up because of some weird user mistake", "#my-health-check", WikiRoot + "prowlarr/system#my-health-check")]
|
||||
[TestCase("I blew up because of some weird user mistake", "custom-page#my-health-check", WikiRoot + "prowlarr/custom-page#my-health-check")]
|
||||
[TestCase("I blew up because of some weird user mistake", null, WikiRoot + "Prowlarr_System#i_blew_up_because_of_some_weird_user_mistake")]
|
||||
[TestCase("I blew up because of some weird user mistake", "#my_health_check", WikiRoot + "Prowlarr_System#my_health_check")]
|
||||
[TestCase("I blew up because of some weird user mistake", "Custom-Page#my_health_check", WikiRoot + "Custom-Page#my_health_check")]
|
||||
public void should_format_wiki_url(string message, string wikiFragment, string expectedUrl)
|
||||
{
|
||||
var subject = new NzbDrone.Core.HealthCheck.HealthCheck(typeof(HealthCheckBase), HealthCheckResult.Warning, message, wikiFragment);
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.FileList;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
@@ -19,42 +18,16 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
|
||||
Subject.Settings = new FileListSettings()
|
||||
{
|
||||
Passkey = "abcd",
|
||||
Username = "somename",
|
||||
BaseUrl = "https://filelist.io"
|
||||
Username = "somename"
|
||||
};
|
||||
|
||||
Subject.Capabilities = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
},
|
||||
Flags = new List<IndexerFlag>
|
||||
{
|
||||
IndexerFlag.FreeLeech
|
||||
}
|
||||
};
|
||||
|
||||
Subject.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesSD, "Filme SD");
|
||||
Subject.Capabilities.Categories.AddCategoryMapping(2, NewznabStandardCategory.MoviesDVD, "Filme DVD");
|
||||
|
||||
_movieSearchCriteria = new MovieSearchCriteria
|
||||
{
|
||||
SearchTerm = "Star Wars",
|
||||
Categories = new int[] { 2000 }
|
||||
};
|
||||
|
||||
Subject.BaseUrl = "https://filelist.io";
|
||||
}
|
||||
|
||||
private void MovieWithoutIMDB()
|
||||
@@ -65,7 +38,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
|
||||
[Test]
|
||||
public void should_use_categories_for_feed()
|
||||
{
|
||||
var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new int[] { NewznabStandardCategory.MoviesSD.Id, NewznabStandardCategory.MoviesDVD.Id } });
|
||||
var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new int[] { 1, 2 } });
|
||||
|
||||
results.GetAllTiers().Should().HaveCount(1);
|
||||
|
||||
@@ -77,7 +50,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
|
||||
[Test]
|
||||
public void should_not_search_by_imdbid_if_not_supported()
|
||||
{
|
||||
_movieSearchCriteria.ImdbId = "0076759";
|
||||
_movieSearchCriteria.ImdbId = "tt0076759";
|
||||
var results = Subject.GetSearchRequests(_movieSearchCriteria);
|
||||
|
||||
results.GetAllTiers().Should().HaveCount(1);
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
|
||||
_movieSearchCriteria = new MovieSearchCriteria
|
||||
{
|
||||
Categories = new int[] { 2000, 2010 },
|
||||
ImdbId = "0076759"
|
||||
ImdbId = "tt0076759"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
|
||||
_movieSearchCriteria = new MovieSearchCriteria
|
||||
{
|
||||
Categories = new int[] { 2000, 2010 },
|
||||
ImdbId = "0076759"
|
||||
ImdbId = "tt0076759"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
|
||||
public void should_search_by_imdbid_if_supported()
|
||||
{
|
||||
var results = Subject.GetSearchRequests(_movieSearchCriteria);
|
||||
var imdbQuery = int.Parse(_movieSearchCriteria.ImdbId);
|
||||
var imdbQuery = int.Parse(_movieSearchCriteria.ImdbId.Substring(2));
|
||||
|
||||
results.GetAllTiers().Should().HaveCount(1);
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests
|
||||
{
|
||||
public class TestIndexer : UsenetIndexerBase<TestIndexerSettings>
|
||||
public class TestIndexer : HttpIndexerBase<TestIndexerSettings>
|
||||
{
|
||||
public override string Name => "Test Indexer";
|
||||
public override string[] IndexerUrls => new string[] { "http://testindexer.com" };
|
||||
public override string Description => "";
|
||||
public override string BaseUrl => "http://testindexer.com";
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
|
||||
|
||||
@@ -20,8 +18,8 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||
public int _supportedPageSize;
|
||||
public override int PageSize => _supportedPageSize;
|
||||
|
||||
public TestIndexer(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, IValidateNzbs nzbValidationService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, nzbValidationService, logger)
|
||||
public TestIndexer(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
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 : IIndexerSettings
|
||||
public class TestIndexerSettings : IProviderConfig
|
||||
{
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
@@ -12,6 +14,5 @@ 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.2.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="11.1.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.)\/",
|
||||
protected static readonly Regex AppIndexerRegex = new Regex(@"(?<indexer>\d*)/api",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
public abstract string Name { get; }
|
||||
|
||||
@@ -114,12 +114,7 @@ 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 = ExecuteAction(a => a.GetIndexerMappings(), app);
|
||||
|
||||
if (remoteMappings == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var remoteMappings = app.GetIndexerMappings();
|
||||
|
||||
//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)
|
||||
@@ -219,64 +214,5 @@ 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,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
@@ -32,25 +31,7 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
{
|
||||
var failures = new List<ValidationFailure>();
|
||||
|
||||
var testIndexer = new IndexerDefinition
|
||||
{
|
||||
Id = 0,
|
||||
Name = "Test",
|
||||
Protocol = DownloadProtocol.Usenet,
|
||||
Capabilities = new IndexerCapabilities()
|
||||
};
|
||||
|
||||
testIndexer.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.Audio);
|
||||
|
||||
try
|
||||
{
|
||||
failures.AddIfNotNull(_lidarrV1Proxy.TestConnection(BuildLidarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test message");
|
||||
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Lidarr"));
|
||||
}
|
||||
failures.AddIfNotNull(_lidarrV1Proxy.Test(Settings));
|
||||
|
||||
return new ValidationResult(failures);
|
||||
}
|
||||
@@ -165,7 +146,7 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
Fields = schema.Fields,
|
||||
};
|
||||
|
||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
|
||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/";
|
||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
|
||||
return other.EnableRss == EnableRss &&
|
||||
other.EnableAutomaticSearch == EnableAutomaticSearch &&
|
||||
other.EnableInteractiveSearch == EnableInteractiveSearch &&
|
||||
other.EnableInteractiveSearch == EnableAutomaticSearch &&
|
||||
other.Name == Name &&
|
||||
other.Implementation == Implementation &&
|
||||
other.Priority == Priority &&
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
|
||||
public IEnumerable<int> SyncCategories { get; set; }
|
||||
|
||||
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Lidarr sees it, including http(s)://, port, and urlbase if needed")]
|
||||
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Lidarr sees it, including http(s):// and port if needed")]
|
||||
public string ProwlarrUrl { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Lidarr Server", HelpText = "Lidarr server URL, including http(s):// and port if needed")]
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
List<LidarrIndexer> GetIndexerSchema(LidarrSettings settings);
|
||||
void RemoveIndexer(int indexerId, LidarrSettings settings);
|
||||
LidarrIndexer UpdateIndexer(LidarrIndexer indexer, LidarrSettings settings);
|
||||
ValidationFailure TestConnection(LidarrIndexer indexer, LidarrSettings settings);
|
||||
ValidationFailure Test(LidarrSettings settings);
|
||||
}
|
||||
|
||||
public class LidarrV1Proxy : ILidarrV1Proxy
|
||||
@@ -91,15 +91,11 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
return Execute<LidarrIndexer>(request);
|
||||
}
|
||||
|
||||
public ValidationFailure TestConnection(LidarrIndexer indexer, LidarrSettings settings)
|
||||
public ValidationFailure Test(LidarrSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings, $"/api/v1/indexer/test", HttpMethod.POST);
|
||||
|
||||
request.SetContent(indexer.ToJson());
|
||||
|
||||
try
|
||||
{
|
||||
Execute<LidarrIndexer>(request);
|
||||
GetStatus(settings);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
@@ -109,14 +105,8 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
return new ValidationFailure("ApiKey", "API Key is invalid");
|
||||
}
|
||||
|
||||
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
|
||||
{
|
||||
_logger.Error(ex, "Prowlarr URL is invalid");
|
||||
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Lidarr cannot connect to Prowlarr");
|
||||
}
|
||||
|
||||
_logger.Error(ex, "Unable to send test message");
|
||||
return new ValidationFailure("BaseUrl", "Unable to complete application test");
|
||||
return new ValidationFailure("ApiKey", "Unable to send test message");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
@@ -32,25 +31,7 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
{
|
||||
var failures = new List<ValidationFailure>();
|
||||
|
||||
var testIndexer = new IndexerDefinition
|
||||
{
|
||||
Id = 0,
|
||||
Name = "Test",
|
||||
Protocol = DownloadProtocol.Usenet,
|
||||
Capabilities = new IndexerCapabilities()
|
||||
};
|
||||
|
||||
testIndexer.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies);
|
||||
|
||||
try
|
||||
{
|
||||
failures.AddIfNotNull(_radarrV3Proxy.TestConnection(BuildRadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test message");
|
||||
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Radarr"));
|
||||
}
|
||||
failures.AddIfNotNull(_radarrV3Proxy.Test(Settings));
|
||||
|
||||
return new ValidationResult(failures);
|
||||
}
|
||||
@@ -165,7 +146,7 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
Fields = schema.Fields,
|
||||
};
|
||||
|
||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
|
||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/";
|
||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
|
||||
return other.EnableRss == EnableRss &&
|
||||
other.EnableAutomaticSearch == EnableAutomaticSearch &&
|
||||
other.EnableInteractiveSearch == EnableInteractiveSearch &&
|
||||
other.EnableInteractiveSearch == EnableAutomaticSearch &&
|
||||
other.Name == Name &&
|
||||
other.Implementation == Implementation &&
|
||||
other.Priority == Priority &&
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
|
||||
public IEnumerable<int> SyncCategories { get; set; }
|
||||
|
||||
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Radarr sees it, including http(s)://, port, and urlbase if needed")]
|
||||
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Radarr sees it, including http(s):// and port if needed")]
|
||||
public string ProwlarrUrl { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Radarr Server", HelpText = "Radarr server URL, including http(s):// and port if needed")]
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
List<RadarrIndexer> GetIndexerSchema(RadarrSettings settings);
|
||||
void RemoveIndexer(int indexerId, RadarrSettings settings);
|
||||
RadarrIndexer UpdateIndexer(RadarrIndexer indexer, RadarrSettings settings);
|
||||
ValidationFailure TestConnection(RadarrIndexer indexer, RadarrSettings settings);
|
||||
ValidationFailure Test(RadarrSettings settings);
|
||||
}
|
||||
|
||||
public class RadarrV3Proxy : IRadarrV3Proxy
|
||||
@@ -91,15 +91,11 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
return Execute<RadarrIndexer>(request);
|
||||
}
|
||||
|
||||
public ValidationFailure TestConnection(RadarrIndexer indexer, RadarrSettings settings)
|
||||
public ValidationFailure Test(RadarrSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings, $"/api/v3/indexer/test", HttpMethod.POST);
|
||||
|
||||
request.SetContent(indexer.ToJson());
|
||||
|
||||
try
|
||||
{
|
||||
Execute<RadarrIndexer>(request);
|
||||
GetStatus(settings);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
@@ -109,14 +105,8 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
return new ValidationFailure("ApiKey", "API Key is invalid");
|
||||
}
|
||||
|
||||
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
|
||||
{
|
||||
_logger.Error(ex, "Prowlarr URL is invalid");
|
||||
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Radarr cannot connect to Prowlarr");
|
||||
}
|
||||
|
||||
_logger.Error(ex, "Unable to send test message");
|
||||
return new ValidationFailure("BaseUrl", "Unable to complete application test");
|
||||
return new ValidationFailure("ApiKey", "Unable to send test message");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
@@ -32,25 +31,7 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
{
|
||||
var failures = new List<ValidationFailure>();
|
||||
|
||||
var testIndexer = new IndexerDefinition
|
||||
{
|
||||
Id = 0,
|
||||
Name = "Test",
|
||||
Protocol = DownloadProtocol.Usenet,
|
||||
Capabilities = new IndexerCapabilities()
|
||||
};
|
||||
|
||||
testIndexer.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.Books);
|
||||
|
||||
try
|
||||
{
|
||||
failures.AddIfNotNull(_readarrV1Proxy.TestConnection(BuildReadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test message");
|
||||
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Readarr"));
|
||||
}
|
||||
failures.AddIfNotNull(_readarrV1Proxy.Test(Settings));
|
||||
|
||||
return new ValidationResult(failures);
|
||||
}
|
||||
@@ -165,7 +146,7 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
Fields = schema.Fields,
|
||||
};
|
||||
|
||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
|
||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/";
|
||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
|
||||
return other.EnableRss == EnableRss &&
|
||||
other.EnableAutomaticSearch == EnableAutomaticSearch &&
|
||||
other.EnableInteractiveSearch == EnableInteractiveSearch &&
|
||||
other.EnableInteractiveSearch == EnableAutomaticSearch &&
|
||||
other.Name == Name &&
|
||||
other.Implementation == Implementation &&
|
||||
other.Priority == Priority &&
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
|
||||
public IEnumerable<int> SyncCategories { get; set; }
|
||||
|
||||
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Readarr sees it, including http(s)://, port, and urlbase if needed")]
|
||||
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Readarr sees it, including http(s):// and port if needed")]
|
||||
public string ProwlarrUrl { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Readarr Server", HelpText = "Readarr server URL, including http(s):// and port if needed")]
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
List<ReadarrIndexer> GetIndexerSchema(ReadarrSettings settings);
|
||||
void RemoveIndexer(int indexerId, ReadarrSettings settings);
|
||||
ReadarrIndexer UpdateIndexer(ReadarrIndexer indexer, ReadarrSettings settings);
|
||||
ValidationFailure TestConnection(ReadarrIndexer indexer, ReadarrSettings settings);
|
||||
ValidationFailure Test(ReadarrSettings settings);
|
||||
}
|
||||
|
||||
public class ReadarrV1Proxy : IReadarrV1Proxy
|
||||
@@ -91,15 +91,11 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
return Execute<ReadarrIndexer>(request);
|
||||
}
|
||||
|
||||
public ValidationFailure TestConnection(ReadarrIndexer indexer, ReadarrSettings settings)
|
||||
public ValidationFailure Test(ReadarrSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings, $"/api/v1/indexer/test", HttpMethod.POST);
|
||||
|
||||
request.SetContent(indexer.ToJson());
|
||||
|
||||
try
|
||||
{
|
||||
Execute<ReadarrIndexer>(request);
|
||||
GetStatus(settings);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
@@ -109,14 +105,8 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
return new ValidationFailure("ApiKey", "API Key is invalid");
|
||||
}
|
||||
|
||||
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
|
||||
{
|
||||
_logger.Error(ex, "Prowlarr URL is invalid");
|
||||
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Readarr cannot connect to Prowlarr");
|
||||
}
|
||||
|
||||
_logger.Error(ex, "Unable to send test message");
|
||||
return new ValidationFailure("BaseUrl", "Unable to complete application test");
|
||||
return new ValidationFailure("ApiKey", "Unable to send test message");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
@@ -32,25 +31,7 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
{
|
||||
var failures = new List<ValidationFailure>();
|
||||
|
||||
var testIndexer = new IndexerDefinition
|
||||
{
|
||||
Id = 0,
|
||||
Name = "Test",
|
||||
Protocol = DownloadProtocol.Usenet,
|
||||
Capabilities = new IndexerCapabilities()
|
||||
};
|
||||
|
||||
testIndexer.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.TV);
|
||||
|
||||
try
|
||||
{
|
||||
failures.AddIfNotNull(_sonarrV3Proxy.TestConnection(BuildSonarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test message");
|
||||
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Sonarr"));
|
||||
}
|
||||
failures.AddIfNotNull(_sonarrV3Proxy.Test(Settings));
|
||||
|
||||
return new ValidationResult(failures);
|
||||
}
|
||||
@@ -165,7 +146,7 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
Fields = schema.Fields,
|
||||
};
|
||||
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/";
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
|
||||
return other.EnableRss == EnableRss &&
|
||||
other.EnableAutomaticSearch == EnableAutomaticSearch &&
|
||||
other.EnableInteractiveSearch == EnableInteractiveSearch &&
|
||||
other.EnableInteractiveSearch == EnableAutomaticSearch &&
|
||||
other.Name == Name &&
|
||||
other.Implementation == Implementation &&
|
||||
other.Priority == Priority &&
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
|
||||
public IEnumerable<int> SyncCategories { get; set; }
|
||||
|
||||
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Sonarr sees it, including http(s)://, port, and urlbase if needed")]
|
||||
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Sonarr sees it, including http(s):// and port if needed")]
|
||||
public string ProwlarrUrl { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Sonarr Server", HelpText = "Sonarr server URL, including http(s):// and port if needed")]
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
List<SonarrIndexer> GetIndexerSchema(SonarrSettings settings);
|
||||
void RemoveIndexer(int indexerId, SonarrSettings settings);
|
||||
SonarrIndexer UpdateIndexer(SonarrIndexer indexer, SonarrSettings settings);
|
||||
ValidationFailure TestConnection(SonarrIndexer indexer, SonarrSettings settings);
|
||||
ValidationFailure Test(SonarrSettings settings);
|
||||
}
|
||||
|
||||
public class SonarrV3Proxy : ISonarrV3Proxy
|
||||
@@ -91,15 +91,11 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
return Execute<SonarrIndexer>(request);
|
||||
}
|
||||
|
||||
public ValidationFailure TestConnection(SonarrIndexer indexer, SonarrSettings settings)
|
||||
public ValidationFailure Test(SonarrSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings, $"/api/v3/indexer/test", HttpMethod.POST);
|
||||
|
||||
request.SetContent(indexer.ToJson());
|
||||
|
||||
try
|
||||
{
|
||||
Execute<SonarrIndexer>(request);
|
||||
GetStatus(settings);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
@@ -109,14 +105,8 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
return new ValidationFailure("ApiKey", "API Key is invalid");
|
||||
}
|
||||
|
||||
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
|
||||
{
|
||||
_logger.Error(ex, "Prowlarr URL is invalid");
|
||||
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Sonarr cannot connect to Prowlarr");
|
||||
}
|
||||
|
||||
_logger.Error(ex, "Unable to send test message");
|
||||
return new ValidationFailure("BaseUrl", "Unable to complete application test");
|
||||
return new ValidationFailure("ApiKey", "Unable to send test message");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
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", "develop").ToLowerInvariant();
|
||||
public string Branch => GetValue("Branch", "nightly").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 = "develop";
|
||||
DefaultBranch = "nightly";
|
||||
|
||||
if (Path.GetFileName(bin) == "bin" && diskProvider.FileExists(packageInfoPath))
|
||||
{
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Text.Json;
|
||||
using Dapper;
|
||||
using NzbDrone.Common.Serializer;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class CookieConverter : SqlMapper.TypeHandler<IDictionary<string, string>>
|
||||
{
|
||||
protected readonly JsonSerializerOptions SerializerSettings;
|
||||
|
||||
public CookieConverter()
|
||||
{
|
||||
var serializerSettings = new JsonSerializerOptions
|
||||
{
|
||||
AllowTrailingCommas = true,
|
||||
IgnoreNullValues = true,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
SerializerSettings = serializerSettings;
|
||||
}
|
||||
|
||||
public override void SetValue(IDbDataParameter parameter, IDictionary<string, string> value)
|
||||
{
|
||||
parameter.Value = JsonSerializer.Serialize(value, SerializerSettings);
|
||||
}
|
||||
|
||||
public override IDictionary<string, string> Parse(object value)
|
||||
{
|
||||
return JsonSerializer.Deserialize<Dictionary<string, string>>((string)value, SerializerSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,10 +108,10 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
if (OsInfo.IsOsx)
|
||||
{
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/prowlarr/faq#i-use-prowlarr-on-a-mac-and-it-suddenly-stopped-working-what-happened", e, fileName);
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/Prowlarr_FAQ#I_use_Prowlarr_on_a_Mac_and_it_suddenly_stopped_working_What_happened", e, fileName);
|
||||
}
|
||||
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/prowlarr/faq#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName);
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/Prowlarr_FAQ#I_am_getting_an_error_Database_disk_image_is_malformed", e, fileName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentMigrator;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
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.IndexerUrls)
|
||||
.Ignore(i => i.BaseUrl)
|
||||
.Ignore(i => i.Protocol)
|
||||
.Ignore(i => i.Privacy)
|
||||
.Ignore(i => i.SupportsRss)
|
||||
@@ -100,7 +100,7 @@ namespace NzbDrone.Core.Datastore
|
||||
SqlMapper.RemoveTypeMap(typeof(DateTime));
|
||||
SqlMapper.AddTypeHandler(new DapperUtcConverter());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<Dictionary<string, string>>());
|
||||
SqlMapper.AddTypeHandler(new CookieConverter());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<IDictionary<string, string>>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<int>>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<KeyValuePair<string, int>>>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<KeyValuePair<string, int>>());
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user