mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-04-17 21:44:48 -04:00
Compare commits
70 Commits
v0.1.1.842
...
v0.1.1.103
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ac721a30b | ||
|
|
3bbadb516d | ||
|
|
f5f0dd6fae | ||
|
|
0cfb7da411 | ||
|
|
b65f4205fc | ||
|
|
3e243eafdd | ||
|
|
327fd08059 | ||
|
|
827741db17 | ||
|
|
c21e323992 | ||
|
|
e49d03ab7b | ||
|
|
4347e1cf7a | ||
|
|
d7e1043b79 | ||
|
|
d4bdb73b7c | ||
|
|
eeebf3ecf0 | ||
|
|
76d73aa6a9 | ||
|
|
5dfe530cf3 | ||
|
|
8b8b5ba1c8 | ||
|
|
c5caf22375 | ||
|
|
293b32ea0e | ||
|
|
25bb10d62b | ||
|
|
9eba50d9db | ||
|
|
234995cbaf | ||
|
|
918071903b | ||
|
|
dcfa3ad48e | ||
|
|
5dd6cde61a | ||
|
|
d18ddcaa50 | ||
|
|
0cca9525a1 | ||
|
|
3455f3c92a | ||
|
|
af03c17892 | ||
|
|
6b39fa5ce6 | ||
|
|
1ee79f16fc | ||
|
|
e73f2466cc | ||
|
|
2e56b7681e | ||
|
|
87650c83c6 | ||
|
|
34a09af01e | ||
|
|
5a3d429d52 | ||
|
|
40c49bce9b | ||
|
|
063083a1f1 | ||
|
|
dbbc913809 | ||
|
|
f0f2c88c4a | ||
|
|
1bfcb99f31 | ||
|
|
4ea0e6c016 | ||
|
|
1de845c8f5 | ||
|
|
f3a33cf817 | ||
|
|
593a0e9658 | ||
|
|
a854ce6f4e | ||
|
|
043b1a0e46 | ||
|
|
4c7c7e8a62 | ||
|
|
89a4c03dd2 | ||
|
|
baed2960b6 | ||
|
|
e4ef1c3af0 | ||
|
|
b4f8fb733f | ||
|
|
1a6ea21b9f | ||
|
|
16834e0f24 | ||
|
|
658724b315 | ||
|
|
a2c8cec27e | ||
|
|
3c9fbeabaa | ||
|
|
04e84f3a90 | ||
|
|
77a76fe5a1 | ||
|
|
1d20b9d429 | ||
|
|
46e1cce632 | ||
|
|
03f821f484 | ||
|
|
c72222a696 | ||
|
|
f4cee1d5f4 | ||
|
|
ab7bc85368 | ||
|
|
d50e1d7cc0 | ||
|
|
ab1545e834 | ||
|
|
c8cc48229c | ||
|
|
b513fac2f7 | ||
|
|
368e0755a0 |
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -68,6 +68,7 @@ body:
|
||||
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!
|
||||
***Generally speaking, all bug reports must have trace logs provided.***
|
||||
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
validations:
|
||||
|
||||
@@ -1,55 +1,13 @@
|
||||
# How to Contribute #
|
||||
# How to Contribute
|
||||
|
||||
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).
|
||||
This file has been moved to the wiki 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.
|
||||
## Documentation
|
||||
|
||||
## Development ##
|
||||
Setup guides, [FAQ](https://wiki.servarr.com/prowlarr/faq), the more information we have on the [wiki](https://wiki.servarr.com/prowlarr) the better.
|
||||
|
||||
### 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.
|
||||
## Development
|
||||
|
||||
### 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
|
||||
|
||||
### Contributing Code ###
|
||||
- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Prowlarr/Prowlarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first)
|
||||
- Rebase from Prowlarr's develop branch, don't merge
|
||||
- Make meaningful commits, or squash them
|
||||
- Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements
|
||||
- Reach out to us on the discord if you have any questions
|
||||
- Add tests (unit/integration)
|
||||
- Commit with *nix line endings for consistency (We checkout Windows and commit *nix)
|
||||
- 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
|
||||
- We'll try to respond to pull requests as soon as possible, if its been a day or two, please reach out to us, we may have missed it
|
||||
- Each PR should come from its own [feature branch](http://martinfowler.com/bliki/FeatureBranch.html) not develop in your fork, it should have a meaningful branch name (what is being added/fixed)
|
||||
- new-feature (Good)
|
||||
- fix-bug (Good)
|
||||
- patch (Bad)
|
||||
- develop (Bad)
|
||||
|
||||
If you have any questions about any of this, please let us know.
|
||||
See the [Wiki Page](https://wiki.servarr.com/prowlarr/contributing)
|
||||
|
||||
10
README.md
10
README.md
@@ -7,7 +7,7 @@
|
||||
[](#backers)
|
||||
[](#sponsors)
|
||||
|
||||
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).
|
||||
Prowlarr is an indexer manager/proxy built on the popular arr .net/reactjs base stack to integrate with your various PVR apps. Prowlarr supports management of both Torrent Trackers and Usenet Indexers. It integrates seamlessly with Lidarr, Mylar3, Radarr, Readarr, and Sonarr 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"
|
||||
@@ -36,9 +36,13 @@ Note: Prowlarr is currently early in life, thus bugs should be expected
|
||||
- Request or vote on an existing request for a new tracker/indexer
|
||||
|
||||
## Contributors & Developers
|
||||
This project exists thanks to all the people who contribute. [Contribute](CONTRIBUTING.md).
|
||||
<a href="https://github.com/Prowlarr/Prowlarr/graphs/contributors"><img src="https://opencollective.com/Prowlarr/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
- [Contribute (GitHub)](CONTRIBUTING.md)
|
||||
- [Contribution (Wiki Article)](https://wiki.servarr.com/prowlarr/contributing)
|
||||
- [YML Indexer Defintion (Wiki Article)](https://wiki.servarr.com/prowlarr/cardigann-yml-definition)
|
||||
|
||||
This project exists thanks to all the people who contribute.
|
||||
<a href="https://github.com/Prowlarr/Prowlarr/graphs/contributors"><img src="https://opencollective.com/Prowlarr/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
## Backers
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import FormInputHelpText from './FormInputHelpText';
|
||||
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
|
||||
import InfoInput from './InfoInput';
|
||||
import KeyValueListInput from './KeyValueListInput';
|
||||
import MovieMonitoredSelectInput from './MovieMonitoredSelectInput';
|
||||
import NumberInput from './NumberInput';
|
||||
import OAuthInputConnector from './OAuthInputConnector';
|
||||
import PasswordInput from './PasswordInput';
|
||||
@@ -69,9 +68,6 @@ function getComponent(type) {
|
||||
case inputTypes.PATH:
|
||||
return PathInputConnector;
|
||||
|
||||
case inputTypes.MOVIE_MONITORED_SELECT:
|
||||
return MovieMonitoredSelectInput;
|
||||
|
||||
case inputTypes.INDEXER_FLAGS_SELECT:
|
||||
return IndexerFlagsSelectInputConnector;
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import SelectInput from './SelectInput';
|
||||
|
||||
const monitorTypesOptions = [
|
||||
{ key: 'true', value: 'True' },
|
||||
{ key: 'false', value: 'False' }
|
||||
];
|
||||
|
||||
function MovieMonitoredSelectInput(props) {
|
||||
const values = [...monitorTypesOptions];
|
||||
|
||||
const {
|
||||
includeNoChange,
|
||||
includeMixed
|
||||
} = props;
|
||||
|
||||
if (includeNoChange) {
|
||||
values.unshift({
|
||||
key: 'noChange',
|
||||
value: 'No Change',
|
||||
disabled: true
|
||||
});
|
||||
}
|
||||
|
||||
if (includeMixed) {
|
||||
values.unshift({
|
||||
key: 'mixed',
|
||||
value: '(Mixed)',
|
||||
disabled: true
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<SelectInput
|
||||
{...props}
|
||||
values={values}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
MovieMonitoredSelectInput.propTypes = {
|
||||
includeNoChange: PropTypes.bool.isRequired,
|
||||
includeMixed: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
MovieMonitoredSelectInput.defaultProps = {
|
||||
includeNoChange: false,
|
||||
includeMixed: false
|
||||
};
|
||||
|
||||
export default MovieMonitoredSelectInput;
|
||||
@@ -53,7 +53,8 @@ function getSelectValues(selectOptions) {
|
||||
result.push({
|
||||
key: option.value,
|
||||
value: option.name,
|
||||
hint: option.hint
|
||||
hint: option.hint,
|
||||
parentKey: option.parentValue
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
@@ -60,7 +60,7 @@ function createMapStateToProps() {
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onGoToAddNewMovie(query) {
|
||||
dispatch(setSearchDefault({ searchQuery: query, searchIndexerIds: [-1, -2] }));
|
||||
dispatch(setSearchDefault({ searchQuery: query }));
|
||||
dispatch(push(`${window.Prowlarr.urlBase}/search`));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -168,9 +168,9 @@ class SignalRConnector extends Component {
|
||||
this.props.dispatchFetchIndexerStatus();
|
||||
}
|
||||
|
||||
handleMovie = (body) => {
|
||||
handleIndexer = (body) => {
|
||||
const action = body.action;
|
||||
const section = 'movies';
|
||||
const section = 'indexers';
|
||||
|
||||
if (action === 'updated') {
|
||||
this.props.dispatchUpdateItem({ section, ...body.resource });
|
||||
|
||||
@@ -270,6 +270,7 @@ class IndexerIndex extends Component {
|
||||
isSaving,
|
||||
saveError,
|
||||
isDeleting,
|
||||
isTestingAll,
|
||||
deleteError,
|
||||
onScroll,
|
||||
onSortSelect,
|
||||
@@ -310,7 +311,7 @@ class IndexerIndex extends Component {
|
||||
<PageToolbarButton
|
||||
label={'Test All Indexers'}
|
||||
iconName={icons.TEST}
|
||||
spinningName={icons.TEST}
|
||||
isSpinning={isTestingAll}
|
||||
isDisabled={hasNoIndexer}
|
||||
onPress={this.props.onTestAllPress}
|
||||
/>
|
||||
@@ -489,6 +490,7 @@ IndexerIndex.propTypes = {
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
saveError: PropTypes.object,
|
||||
isDeleting: PropTypes.bool.isRequired,
|
||||
isTestingAll: PropTypes.bool.isRequired,
|
||||
deleteError: PropTypes.object,
|
||||
onSortSelect: PropTypes.func.isRequired,
|
||||
onFilterSelect: PropTypes.func.isRequired,
|
||||
|
||||
@@ -52,6 +52,7 @@ class SearchFooter extends Component {
|
||||
isFetching,
|
||||
defaultIndexerIds,
|
||||
defaultCategories,
|
||||
defaultSearchQuery,
|
||||
searchError
|
||||
} = this.props;
|
||||
|
||||
@@ -62,6 +63,10 @@ class SearchFooter extends Component {
|
||||
|
||||
const newState = {};
|
||||
|
||||
if (defaultSearchQuery && defaultSearchQuery !== prevProps.defaultSearchQuery) {
|
||||
newState.searchQuery = defaultSearchQuery;
|
||||
}
|
||||
|
||||
if (searchIndexerIds !== defaultIndexerIds) {
|
||||
newState.searchIndexerIds = defaultIndexerIds;
|
||||
}
|
||||
|
||||
@@ -85,21 +85,21 @@ class Tag extends Component {
|
||||
{
|
||||
!!indexerIds.length &&
|
||||
<div>
|
||||
{indexerIds.length} indexer{indexerIds.length > 1 && 's'}
|
||||
{indexerIds.length} {indexerIds.length > 1 ? translate('Indexers') : translate('Indexer')}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!!notificationIds.length &&
|
||||
<div>
|
||||
{notificationIds.length} connection{notificationIds.length > 1 && 's'}
|
||||
{notificationIds.length} {notificationIds.length > 1 ? translate('Notifications') : translate('Notification')}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!!indexerProxyIds.length &&
|
||||
<div>
|
||||
{indexerProxyIds.length} indexerProxy{indexerProxyIds.length > 1 && 's'}
|
||||
{indexerProxyIds.length} {indexerProxyIds.length > 1 ? translate('IndexerProxies') : translate('IndexerProxy')}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -108,7 +108,7 @@ class Tag extends Component {
|
||||
{
|
||||
!isTagUsed &&
|
||||
<div>
|
||||
No links
|
||||
{translate('NoLinks')}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -7,18 +7,6 @@ function isRelative(ajaxOptions) {
|
||||
return !absUrlRegex.test(ajaxOptions.url);
|
||||
}
|
||||
|
||||
function moveBodyToQuery(ajaxOptions) {
|
||||
if (ajaxOptions.data && ajaxOptions.type === 'DELETE') {
|
||||
if (ajaxOptions.url.contains('?')) {
|
||||
ajaxOptions.url += '&';
|
||||
} else {
|
||||
ajaxOptions.url += '?';
|
||||
}
|
||||
ajaxOptions.url += $.param(ajaxOptions.data);
|
||||
delete ajaxOptions.data;
|
||||
}
|
||||
}
|
||||
|
||||
function addRootUrl(ajaxOptions) {
|
||||
ajaxOptions.url = apiRoot + ajaxOptions.url;
|
||||
}
|
||||
@@ -32,7 +20,7 @@ function addContentType(ajaxOptions) {
|
||||
if (
|
||||
ajaxOptions.contentType == null &&
|
||||
ajaxOptions.dataType === 'json' &&
|
||||
(ajaxOptions.method === 'PUT' || ajaxOptions.method === 'POST')) {
|
||||
(ajaxOptions.method === 'PUT' || ajaxOptions.method === 'POST' || ajaxOptions.method === 'DELETE')) {
|
||||
ajaxOptions.contentType = 'application/json';
|
||||
}
|
||||
}
|
||||
@@ -52,7 +40,6 @@ export default function createAjaxRequest(originalAjaxOptions) {
|
||||
const ajaxOptions = { dataType: 'json', ...originalAjaxOptions };
|
||||
|
||||
if (isRelative(ajaxOptions)) {
|
||||
moveBodyToQuery(ajaxOptions);
|
||||
addRootUrl(ajaxOptions);
|
||||
addApiKey(ajaxOptions);
|
||||
addContentType(ajaxOptions);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
<add key="FluentMigrator" value="https://pkgs.dev.azure.com/fluentmigrator/fluentmigrator/_packaging/fluentmigrator/nuget/v3/index.json" />
|
||||
<add key="dotnet-bsd-crossbuild" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/dotnet-bsd-crossbuild/nuget/v3/index.json" />
|
||||
<add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" />
|
||||
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
|
||||
|
||||
24
src/NzbDrone.Common.Test/Http/UserAgentParserFixture.cs
Normal file
24
src/NzbDrone.Common.Test/Http/UserAgentParserFixture.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
[TestFixture]
|
||||
public class UserAgentParserFixture : TestBase
|
||||
{
|
||||
// Ref *Arr `_userAgent = $"{BuildInfo.AppName}/{BuildInfo.Version} ({osName} {osVersion})";`
|
||||
// Ref Mylar `Mylar3/' +str(hash) +'(' +vers +') +http://www.github.com/mylar3/mylar3/`
|
||||
[TestCase("Mylar3/ 3ee23rh23irqfq (13123123) http://www.github.com/mylar3/mylar3/", "Mylar3")]
|
||||
[TestCase("Lidarr/1.0.0.2300 (ubuntu 20.04)", "Lidarr")]
|
||||
[TestCase("Radarr/1.0.0.2300 (ubuntu 20.04)", "Radarr")]
|
||||
[TestCase("Readarr/1.0.0.2300 (ubuntu 20.04)", "Readarr")]
|
||||
[TestCase("Sonarr/3.0.6.9999 (ubuntu 20.04)", "Sonarr")]
|
||||
[TestCase("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36", "Other")]
|
||||
public void should_parse_user_agent(string userAgent, string parsedAgent)
|
||||
{
|
||||
UserAgentParser.ParseSource(userAgent).Should().Be(parsedAgent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
[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")]
|
||||
[TestCase(@"Req: [POST] https://www3.yggtorrent.nz/user/login: id=mySecret&pass=mySecret&ci_csrf_token=2b51db35e1912ffc138825a12b9933d2")]
|
||||
|
||||
// NzbGet
|
||||
[TestCase(@"{ ""Name"" : ""ControlUsername"", ""Value"" : ""mySecret"" }, { ""Name"" : ""ControlPassword"", ""Value"" : ""mySecret"" }, ")]
|
||||
|
||||
@@ -160,5 +160,16 @@ namespace NzbDrone.Common.Extensions
|
||||
{
|
||||
return new HashSet<T>(source, comparer);
|
||||
}
|
||||
|
||||
public static T FirstIfSingleOrDefault<T>(this IEnumerable<T> source, T replace = default)
|
||||
{
|
||||
if (source is ICollection<T> collection)
|
||||
{
|
||||
return collection.Count == 1 ? collection.First() : replace;
|
||||
}
|
||||
|
||||
var test = source.Take(2).ToList();
|
||||
return test.Count == 1 ? test[0] : replace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
public static class UserAgentParser
|
||||
{
|
||||
private static readonly Regex AppSourceRegex = new Regex(@"(?<agent>.*)\/.*(\(.*\))?",
|
||||
private static readonly Regex AppSourceRegex = new Regex(@"(?<agent>[a-z0-9]*)\/.*(?:\(.*\))?",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
public static string SimplifyUserAgent(string userAgent)
|
||||
|
||||
@@ -11,8 +11,8 @@ 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|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pwd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=[?& ])[^=]*?(_?(?<!use)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"rss\.torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"rss\.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),
|
||||
|
||||
@@ -19,6 +19,10 @@ namespace NzbDrone.Core.Test.HealthCheck
|
||||
|
||||
Mocker.SetConstant<IEnumerable<IProvideHealthCheck>>(new[] { _healthCheck });
|
||||
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
|
||||
|
||||
Mocker.GetMock<IServerSideNotificationService>()
|
||||
.Setup(v => v.GetServerChecks())
|
||||
.Returns(new List<Core.HealthCheck.HealthCheck>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -2,6 +2,8 @@ using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||
using NzbDrone.Core.IndexerProxies;
|
||||
using NzbDrone.Core.IndexerProxies.Http;
|
||||
using NzbDrone.Core.Tags;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
@@ -22,5 +24,30 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||
Subject.Clean();
|
||||
AllStoredModels.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_delete_used_tags()
|
||||
{
|
||||
var tags = Builder<Tag>
|
||||
.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(x => x.Id = 0)
|
||||
.BuildList();
|
||||
|
||||
Db.InsertMany(tags);
|
||||
|
||||
var settings = Builder<HttpSettings>.CreateNew().Build();
|
||||
|
||||
var restrictions = Builder<IndexerProxyDefinition>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(x => x.Id = 0)
|
||||
.With(x => x.Settings = settings)
|
||||
.With(v => v.Tags.Add(tags[0].Id))
|
||||
.BuildList();
|
||||
Db.InsertMany(restrictions);
|
||||
|
||||
Subject.Clean();
|
||||
AllStoredModels.Should().HaveCount(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/PrivateHD/recentfeed.json");
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
|
||||
|
||||
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } })).Releases;
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/FileList/recentfeed.json");
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), recentFeed)));
|
||||
|
||||
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } })).Releases;
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
|
||||
var responseJson = ReadAllText(fileName);
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.POST), Subject.Definition))
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.POST), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), responseJson)));
|
||||
|
||||
var torrents = (await Subject.Fetch(_movieSearchCriteria)).Releases;
|
||||
@@ -74,7 +74,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
|
||||
var responseJson = new { status = 5, message = "Invalid authentication credentials" }.ToJson();
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteAsync(It.IsAny<HttpRequest>(), Subject.Definition))
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.IsAny<HttpRequest>(), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), Encoding.UTF8.GetBytes(responseJson))));
|
||||
|
||||
var torrents = (await Subject.Fetch(_movieSearchCriteria)).Releases;
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
private void GivenCapsResponse(string caps)
|
||||
{
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.Execute(It.IsAny<HttpRequest>(), It.IsAny<IndexerDefinition>()))
|
||||
.Setup(o => o.ExecuteProxied(It.IsAny<HttpRequest>(), It.IsAny<IndexerDefinition>()))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => new HttpResponse(r, new HttpHeader(), new CookieCollection(), caps));
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
Subject.GetCapabilities(_settings, _definition);
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Verify(o => o.Execute(It.IsAny<HttpRequest>(), It.IsAny<IndexerDefinition>()), Times.Once());
|
||||
.Verify(o => o.ExecuteProxied(It.IsAny<HttpRequest>(), It.IsAny<IndexerDefinition>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -85,7 +85,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
public void should_throw_if_failed_to_get()
|
||||
{
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.Execute(It.IsAny<HttpRequest>(), It.IsAny<IndexerDefinition>()))
|
||||
.Setup(o => o.ExecuteProxied(It.IsAny<HttpRequest>(), It.IsAny<IndexerDefinition>()))
|
||||
.Throws<Exception>();
|
||||
|
||||
Assert.Throws<Exception>(() => Subject.GetCapabilities(_settings, _definition));
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Newznab/newznab_nzb_su.xml");
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), recentFeed)));
|
||||
|
||||
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 }, Limit = 100, Offset = 0 })).Releases;
|
||||
|
||||
@@ -37,11 +37,11 @@ namespace NzbDrone.Core.Test.IndexerTests.PTPTests
|
||||
var responseJson = ReadAllText(fileName);
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.POST), Subject.Definition))
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.POST), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), authStream.ToString())));
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { ContentType = HttpAccept.Json.Value }, new CookieCollection(), responseJson)));
|
||||
|
||||
var torrents = (await Subject.Fetch(new MovieSearchCriteria())).Releases;
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Rarbg/RecentFeed_v2.json");
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), recentFeed)));
|
||||
|
||||
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } })).Releases;
|
||||
@@ -65,7 +65,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
|
||||
public async Task should_parse_error_20_as_empty_results()
|
||||
{
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), "{ error_code: 20, error: \"some message\" }")));
|
||||
|
||||
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } })).Releases;
|
||||
@@ -77,7 +77,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
|
||||
public async Task should_warn_on_unknown_error()
|
||||
{
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), "{ error_code: 25, error: \"some message\" }")));
|
||||
|
||||
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } })).Releases;
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_hdaccess_net.xml");
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), recentFeed)));
|
||||
|
||||
var releases = (await Subject.Fetch(new MovieSearchCriteria())).Releases;
|
||||
@@ -73,7 +73,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_tpb.xml");
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), recentFeed)));
|
||||
|
||||
var releases = (await Subject.Fetch(new MovieSearchCriteria())).Releases;
|
||||
@@ -103,7 +103,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_animetosho.xml");
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), recentFeed)));
|
||||
|
||||
var releases = (await Subject.Fetch(new MovieSearchCriteria())).Releases;
|
||||
|
||||
24
src/NzbDrone.Core.Test/ParserTests/ParseUtilFixture.cs
Normal file
24
src/NzbDrone.Core.Test/ParserTests/ParseUtilFixture.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.ParserTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ParseUtilFixture : CoreTest
|
||||
{
|
||||
[TestCase("1023.4 KB", 1047961)]
|
||||
[TestCase("1023.4 MB", 1073112704)]
|
||||
[TestCase("1,023.4 MB", 1073112704)]
|
||||
[TestCase("1.023,4 MB", 1073112704)]
|
||||
[TestCase("1 023,4 MB", 1073112704)]
|
||||
[TestCase("1.023.4 MB", 1073112704)]
|
||||
[TestCase("1023.4 GB", 1098867408896)]
|
||||
[TestCase("1023.4 TB", 1125240226709504)]
|
||||
public void should_parse_size(string stringSize, long size)
|
||||
{
|
||||
ParseUtil.GetBytes(stringSize).Should().Be(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,6 @@ namespace NzbDrone.Core.Annotations
|
||||
public string Name { get; set; }
|
||||
public int Order { get; set; }
|
||||
public string Hint { get; set; }
|
||||
public int? ParentValue { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,9 +159,11 @@ namespace NzbDrone.Core.Applications
|
||||
|
||||
if (removeRemote)
|
||||
{
|
||||
var allIndexers = _indexerFactory.All();
|
||||
|
||||
foreach (var mapping in indexerMappings)
|
||||
{
|
||||
if (!indexers.Any(x => x.Id == mapping.IndexerId))
|
||||
if (!allIndexers.Any(x => x.Id == mapping.IndexerId))
|
||||
{
|
||||
_logger.Info("Indexer with the ID {0} was found within {1} but is no longer defined within Prowlarr, this is being removed.", mapping.IndexerId, app.Name);
|
||||
ExecuteAction(a => a.RemoveIndexer(mapping.IndexerId), app);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Applications.Lidarr
|
||||
@@ -26,8 +27,6 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
SyncCategories = new[] { 3000, 3010, 3030, 3040, 3050, 3060 };
|
||||
}
|
||||
|
||||
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")]
|
||||
public string ProwlarrUrl { get; set; }
|
||||
|
||||
@@ -37,6 +36,9 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Lidarr in Settings/General")]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]
|
||||
public IEnumerable<int> SyncCategories { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -19,10 +19,10 @@ namespace NzbDrone.Core.Applications.Mylar
|
||||
private readonly IMylarV3Proxy _mylarV3Proxy;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
|
||||
public Mylar(IMylarV3Proxy lidarrV1Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, Logger logger)
|
||||
public Mylar(IMylarV3Proxy mylarV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, Logger logger)
|
||||
: base(appIndexerMapService, logger)
|
||||
{
|
||||
_mylarV3Proxy = lidarrV1Proxy;
|
||||
_mylarV3Proxy = mylarV3Proxy;
|
||||
_configFileProvider = configFileProvider;
|
||||
}
|
||||
|
||||
@@ -70,9 +70,9 @@ namespace NzbDrone.Core.Applications.Mylar
|
||||
{
|
||||
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
|
||||
{
|
||||
var lidarrIndexer = BuildMylarIndexer(indexer, indexer.Protocol);
|
||||
var mylarIndexer = BuildMylarIndexer(indexer, indexer.Protocol);
|
||||
|
||||
var remoteIndexer = _mylarV3Proxy.AddIndexer(lidarrIndexer, Settings);
|
||||
var remoteIndexer = _mylarV3Proxy.AddIndexer(mylarIndexer, Settings);
|
||||
_appIndexerMapService.Insert(new AppIndexerMap { AppId = Definition.Id, IndexerId = indexer.Id, RemoteIndexerName = $"{remoteIndexer.Type},{remoteIndexer.Name}" });
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@ namespace NzbDrone.Core.Applications.Mylar
|
||||
{
|
||||
var schema = protocol == DownloadProtocol.Usenet ? MylarProviderType.Newznab : MylarProviderType.Torznab;
|
||||
|
||||
var lidarrIndexer = new MylarIndexer
|
||||
var mylarIndexer = new MylarIndexer
|
||||
{
|
||||
Name = originalName ?? $"{indexer.Name} (Prowlarr)",
|
||||
Altername = $"{indexer.Name} (Prowlarr)",
|
||||
@@ -148,7 +148,7 @@ namespace NzbDrone.Core.Applications.Mylar
|
||||
Type = schema,
|
||||
};
|
||||
|
||||
return lidarrIndexer;
|
||||
return mylarIndexer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NzbDrone.Core.Applications.Mylar
|
||||
{
|
||||
public class MylarError
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
namespace NzbDrone.Core.Applications.Mylar
|
||||
{
|
||||
public class MylarField
|
||||
{
|
||||
public int Order { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Label { get; set; }
|
||||
public string Unit { get; set; }
|
||||
public string HelpText { get; set; }
|
||||
public string HelpLink { get; set; }
|
||||
public object Value { get; set; }
|
||||
public string Type { get; set; }
|
||||
public bool Advanced { get; set; }
|
||||
public string Section { get; set; }
|
||||
public string Hidden { get; set; }
|
||||
|
||||
public MylarField Clone()
|
||||
{
|
||||
return (MylarField)MemberwiseClone();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ namespace NzbDrone.Core.Applications.Mylar
|
||||
RuleFor(c => c.BaseUrl).IsValidUrl();
|
||||
RuleFor(c => c.ProwlarrUrl).IsValidUrl();
|
||||
RuleFor(c => c.ApiKey).NotEmpty();
|
||||
RuleFor(c => c.SyncCategories).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +28,6 @@ namespace NzbDrone.Core.Applications.Mylar
|
||||
SyncCategories = new[] { NewznabStandardCategory.BooksComics.Id };
|
||||
}
|
||||
|
||||
public IEnumerable<int> SyncCategories { get; set; }
|
||||
|
||||
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Mylar sees it, including http(s)://, port, and urlbase if needed")]
|
||||
public string ProwlarrUrl { get; set; }
|
||||
|
||||
@@ -38,6 +37,9 @@ namespace NzbDrone.Core.Applications.Mylar
|
||||
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Mylar in Settings/Web Interface")]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]
|
||||
public IEnumerable<int> SyncCategories { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Indexers;
|
||||
|
||||
namespace NzbDrone.Core.Applications.Mylar
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Applications.Radarr
|
||||
@@ -12,6 +13,7 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
RuleFor(c => c.BaseUrl).IsValidUrl();
|
||||
RuleFor(c => c.ProwlarrUrl).IsValidUrl();
|
||||
RuleFor(c => c.ApiKey).NotEmpty();
|
||||
RuleFor(c => c.SyncCategories).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +28,6 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
SyncCategories = new[] { 2000, 2010, 2020, 2030, 2040, 2045, 2050, 2060, 2070, 2080 };
|
||||
}
|
||||
|
||||
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")]
|
||||
public string ProwlarrUrl { get; set; }
|
||||
|
||||
@@ -37,6 +37,9 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Radarr in Settings/General")]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]
|
||||
public IEnumerable<int> SyncCategories { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Applications.Readarr
|
||||
@@ -12,6 +13,7 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
RuleFor(c => c.BaseUrl).IsValidUrl();
|
||||
RuleFor(c => c.ProwlarrUrl).IsValidUrl();
|
||||
RuleFor(c => c.ApiKey).NotEmpty();
|
||||
RuleFor(c => c.SyncCategories).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +28,6 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
SyncCategories = new[] { 3030, 7000, 7010, 7020, 7030, 7040, 7050, 7060 };
|
||||
}
|
||||
|
||||
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")]
|
||||
public string ProwlarrUrl { get; set; }
|
||||
|
||||
@@ -37,6 +37,9 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Readarr in Settings/General")]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]
|
||||
public IEnumerable<int> SyncCategories { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Applications.Sonarr
|
||||
@@ -27,9 +28,6 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
AnimeSyncCategories = new[] { 5070 };
|
||||
}
|
||||
|
||||
public IEnumerable<int> SyncCategories { get; set; }
|
||||
public IEnumerable<int> AnimeSyncCategories { get; set; }
|
||||
|
||||
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Sonarr sees it, including http(s)://, port, and urlbase if needed")]
|
||||
public string ProwlarrUrl { get; set; }
|
||||
|
||||
@@ -39,6 +37,12 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Sonarr in Settings/General")]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]
|
||||
public IEnumerable<int> SyncCategories { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Anime Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]
|
||||
public IEnumerable<int> AnimeSyncCategories { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(13)]
|
||||
public class desi_gazelle_to_unit3d : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Update.Table("Indexers").Set(new { ConfigContract = "Unit3dSettings", Enable = 0 }).Where(new { Implementation = "DesiTorrents" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,15 +41,18 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
switch (MigrationContext.Current.MigrationType)
|
||||
{
|
||||
case MigrationType.Main:
|
||||
_logger.Info("Starting migration to " + Version);
|
||||
LogMigrationMessage(MigrationType.Main);
|
||||
MainDbUpgrade();
|
||||
return;
|
||||
case MigrationType.Log:
|
||||
_logger.Info("Starting migration to " + Version);
|
||||
LogMigrationMessage(MigrationType.Log);
|
||||
LogDbUpgrade();
|
||||
return;
|
||||
default:
|
||||
LogMigrationMessage(MigrationType.Log);
|
||||
LogDbUpgrade();
|
||||
|
||||
LogMigrationMessage(MigrationType.Main);
|
||||
MainDbUpgrade();
|
||||
return;
|
||||
}
|
||||
@@ -59,5 +62,10 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private void LogMigrationMessage(MigrationType type)
|
||||
{
|
||||
_logger.Info("Starting migration of {0} DB to {1}", type.ToString(), Version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -20,8 +20,9 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
ILogger<NzbDroneSQLiteProcessor> logger,
|
||||
IOptionsSnapshot<ProcessorOptions> options,
|
||||
IConnectionStringAccessor connectionStringAccessor,
|
||||
IServiceProvider serviceProvider)
|
||||
: base(factory, generator, logger, options, connectionStringAccessor, serviceProvider)
|
||||
IServiceProvider serviceProvider,
|
||||
SQLiteQuoter quoter)
|
||||
: base(factory, generator, logger, options, connectionStringAccessor, serviceProvider, quoter)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -214,16 +214,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
DetailedDescription = "Prowlarr will not attempt to import completed downloads without a category."
|
||||
};
|
||||
}
|
||||
|
||||
// Complain if qBittorrent is configured to remove torrents on max ratio
|
||||
var config = Proxy.GetConfig(Settings);
|
||||
if ((config.MaxRatioEnabled || config.MaxSeedingTimeEnabled) && (config.MaxRatioAction == QBittorrentMaxRatioAction.Remove || config.MaxRatioAction == QBittorrentMaxRatioAction.DeleteFiles))
|
||||
{
|
||||
return new NzbDroneValidationFailure(string.Empty, "qBittorrent is configured to remove torrents when they reach their Share Ratio Limit")
|
||||
{
|
||||
DetailedDescription = "Prowlarr will be unable to perform Completed Download Handling as configured. You can fix this in qBittorrent ('Tools -> Options...' in the menu) by changing 'Options -> BitTorrent -> Share Ratio Limiting' from 'Remove them' to 'Pause them'."
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (DownloadClientAuthenticationException ex)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.Newznab;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.ThingiProvider.Events;
|
||||
|
||||
@@ -12,11 +11,11 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
[CheckOn(typeof(ProviderAddedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderUpdatedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderDeletedEvent<IIndexer>))]
|
||||
public class NewznabVIPCheck : HealthCheckBase
|
||||
public class IndexerVIPCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
|
||||
public NewznabVIPCheck(IIndexerFactory indexerFactory, ILocalizationService localizationService)
|
||||
public IndexerVIPCheck(IIndexerFactory indexerFactory, ILocalizationService localizationService)
|
||||
: base(localizationService)
|
||||
{
|
||||
_indexerFactory = indexerFactory;
|
||||
@@ -25,13 +24,20 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
var enabled = _indexerFactory.Enabled(false);
|
||||
var newznabProviders = enabled.Where(i => i.Definition.Implementation == typeof(Newznab).Name);
|
||||
var expiringProviders = new List<IIndexer>();
|
||||
var expiredProviders = new List<IIndexer>();
|
||||
|
||||
foreach (var provider in newznabProviders)
|
||||
foreach (var provider in enabled)
|
||||
{
|
||||
var expiration = ((NewznabSettings)provider.Definition.Settings).VipExpiration;
|
||||
var settingsType = provider.Definition.Settings.GetType();
|
||||
var vipProp = settingsType.GetProperty("VipExpiration");
|
||||
|
||||
if (vipProp == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var expiration = (string)vipProp.GetValue(provider.Definition.Settings);
|
||||
|
||||
if (expiration.IsNullOrWhiteSpace())
|
||||
{
|
||||
@@ -53,18 +59,18 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Warning,
|
||||
string.Format(_localizationService.GetLocalizedString("NewznabVipCheckExpiringClientMessage"),
|
||||
string.Format(_localizationService.GetLocalizedString("IndexerVipCheckExpiringClientMessage"),
|
||||
string.Join(", ", expiringProviders.Select(v => v.Definition.Name))),
|
||||
"#newznab-vip-expiring");
|
||||
"#indexer-vip-expiring");
|
||||
}
|
||||
|
||||
if (!expiredProviders.Empty())
|
||||
{
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Warning,
|
||||
string.Format(_localizationService.GetLocalizedString("NewznabVipCheckExpiredClientMessage"),
|
||||
string.Format(_localizationService.GetLocalizedString("IndexerVipCheckExpiredClientMessage"),
|
||||
string.Join(", ", expiredProviders.Select(v => v.Definition.Name))),
|
||||
"#newznab-vip-expired");
|
||||
"#indexer-vip-expired");
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType());
|
||||
@@ -25,6 +25,7 @@ namespace NzbDrone.Core.HealthCheck
|
||||
private readonly IProvideHealthCheck[] _startupHealthChecks;
|
||||
private readonly IProvideHealthCheck[] _scheduledHealthChecks;
|
||||
private readonly Dictionary<Type, IEventDrivenHealthCheck[]> _eventDrivenHealthChecks;
|
||||
private readonly IServerSideNotificationService _serverSideNotificationService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ICacheManager _cacheManager;
|
||||
private readonly Logger _logger;
|
||||
@@ -32,11 +33,13 @@ namespace NzbDrone.Core.HealthCheck
|
||||
private readonly ICached<HealthCheck> _healthCheckResults;
|
||||
|
||||
public HealthCheckService(IEnumerable<IProvideHealthCheck> healthChecks,
|
||||
IServerSideNotificationService serverSideNotificationService,
|
||||
IEventAggregator eventAggregator,
|
||||
ICacheManager cacheManager,
|
||||
Logger logger)
|
||||
{
|
||||
_healthChecks = healthChecks.ToArray();
|
||||
_serverSideNotificationService = serverSideNotificationService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_cacheManager = cacheManager;
|
||||
_logger = logger;
|
||||
@@ -72,6 +75,8 @@ namespace NzbDrone.Core.HealthCheck
|
||||
var results = healthChecks.Select(c => c.Check())
|
||||
.ToList();
|
||||
|
||||
results.AddRange(_serverSideNotificationService.GetServerChecks());
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
if (result.Type == HealthCheckResult.Ok)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Core.HealthCheck
|
||||
{
|
||||
public interface IServerSideNotificationService
|
||||
{
|
||||
public List<HealthCheck> GetServerChecks();
|
||||
}
|
||||
|
||||
public class ServerSideNotificationService : IServerSideNotificationService
|
||||
{
|
||||
private readonly IHttpClient _client;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly IHttpRequestBuilderFactory _cloudRequestBuilder;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ServerSideNotificationService(IHttpClient client, IConfigFileProvider configFileProvider, IProwlarrCloudRequestBuilder cloudRequestBuilder, Logger logger)
|
||||
{
|
||||
_client = client;
|
||||
_configFileProvider = configFileProvider;
|
||||
_cloudRequestBuilder = cloudRequestBuilder.Services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<HealthCheck> GetServerChecks()
|
||||
{
|
||||
var request = _cloudRequestBuilder.Create()
|
||||
.Resource("/notification")
|
||||
.AddQueryParam("version", BuildInfo.Version)
|
||||
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
|
||||
.AddQueryParam("arch", RuntimeInformation.OSArchitecture)
|
||||
.AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant())
|
||||
.AddQueryParam("branch", _configFileProvider.Branch)
|
||||
.Build();
|
||||
try
|
||||
{
|
||||
_logger.Trace("Getting server side health notifications");
|
||||
var response = _client.Execute(request);
|
||||
var result = Json.Deserialize<List<ServerNotificationResponse>>(response.Content);
|
||||
return result.Select(x => new HealthCheck(GetType(), x.Type, x.Message, x.WikiUrl)).ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Failed to retrieve server notifications");
|
||||
return new List<HealthCheck>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ServerNotificationResponse
|
||||
{
|
||||
public HealthCheckResult Type { get; set; }
|
||||
public string Message { get; set; }
|
||||
public string WikiUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
var usedTags = new[] { "Notifications" }
|
||||
var usedTags = new[] { "Notifications", "IndexerProxies", "Indexers", "Applications" }
|
||||
.SelectMany(v => GetUsedTags(v, mapper))
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
@@ -7,14 +7,15 @@ using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
{
|
||||
public class FlareSolverr : HttpIndexerProxyBase<FlareSolverrSettings>
|
||||
{
|
||||
public FlareSolverr(IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger)
|
||||
: base(cloudRequestBuilder, httpClient, logger)
|
||||
public FlareSolverr(IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger, ILocalizationService localizationService)
|
||||
: base(cloudRequestBuilder, httpClient, logger, localizationService)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -133,13 +134,13 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
if (response.StatusCode == HttpStatusCode.BadRequest)
|
||||
{
|
||||
_logger.Error("Proxy Health Check failed: {0}", response.StatusCode);
|
||||
failures.Add(new NzbDroneValidationFailure("Host", "ProxyCheckBadRequestMessage"));
|
||||
failures.Add(new NzbDroneValidationFailure("Host", string.Format(_localizationService.GetLocalizedString("ProxyCheckBadRequestMessage"), response.StatusCode)));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Proxy Health Check failed");
|
||||
failures.Add(new NzbDroneValidationFailure("Host", "ProxyCheckFailedToTestMessage"));
|
||||
failures.Add(new NzbDroneValidationFailure("Host", string.Format(_localizationService.GetLocalizedString("ProxyCheckFailedToTestMessage"), request.Url.Host)));
|
||||
}
|
||||
|
||||
return new ValidationResult(failures);
|
||||
|
||||
@@ -3,13 +3,14 @@ using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Localization;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies.Http
|
||||
{
|
||||
public class Http : HttpIndexerProxyBase<HttpSettings>
|
||||
{
|
||||
public Http(IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger)
|
||||
: base(cloudRequestBuilder, httpClient, logger)
|
||||
public Http(IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger, ILocalizationService localizationService)
|
||||
: base(cloudRequestBuilder, httpClient, logger, localizationService)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies
|
||||
@@ -16,12 +17,14 @@ namespace NzbDrone.Core.IndexerProxies
|
||||
protected readonly IHttpClient _httpClient;
|
||||
protected readonly IHttpRequestBuilderFactory _cloudRequestBuilder;
|
||||
protected readonly Logger _logger;
|
||||
protected readonly ILocalizationService _localizationService;
|
||||
|
||||
public HttpIndexerProxyBase(IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger)
|
||||
public HttpIndexerProxyBase(IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger, ILocalizationService localizationService)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
_cloudRequestBuilder = cloudRequestBuilder.Services;
|
||||
_localizationService = localizationService;
|
||||
}
|
||||
|
||||
public override ValidationResult Test()
|
||||
@@ -31,7 +34,7 @@ namespace NzbDrone.Core.IndexerProxies
|
||||
var addresses = Dns.GetHostAddresses(Settings.Host);
|
||||
if (!addresses.Any())
|
||||
{
|
||||
failures.Add(new NzbDroneValidationFailure("Host", "ProxyCheckResolveIpMessage"));
|
||||
failures.Add(new NzbDroneValidationFailure("Host", string.Format(_localizationService.GetLocalizedString("ProxyCheckResolveIpMessage"), addresses)));
|
||||
}
|
||||
|
||||
var request = PreRequest(_cloudRequestBuilder.Create()
|
||||
|
||||
@@ -8,13 +8,14 @@ using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Localization;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies.Socks4
|
||||
{
|
||||
public class Socks4 : HttpIndexerProxyBase<Socks4Settings>
|
||||
{
|
||||
public Socks4(IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger)
|
||||
: base(cloudRequestBuilder, httpClient, logger)
|
||||
public Socks4(IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger, ILocalizationService localizationService)
|
||||
: base(cloudRequestBuilder, httpClient, logger, localizationService)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,14 @@ using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Localization;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies.Socks5
|
||||
{
|
||||
public class Socks5 : HttpIndexerProxyBase<Socks5Settings>
|
||||
{
|
||||
public Socks5(IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger)
|
||||
: base(cloudRequestBuilder, httpClient, logger)
|
||||
public Socks5(IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger, ILocalizationService localizationService)
|
||||
: base(cloudRequestBuilder, httpClient, logger, localizationService)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Text;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Parser;
|
||||
|
||||
@@ -23,5 +24,42 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
}
|
||||
|
||||
public string FullImdbId => ParseUtil.GetFullImdbId(ImdbId);
|
||||
|
||||
public override string SearchQuery
|
||||
{
|
||||
get
|
||||
{
|
||||
var searchQueryTerm = $"Term: []";
|
||||
if (SearchTerm.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
searchQueryTerm = $"Term: [{SearchTerm}]";
|
||||
}
|
||||
|
||||
if (!ImdbId.IsNotNullOrWhiteSpace() && !TmdbId.HasValue && !TraktId.HasValue)
|
||||
{
|
||||
return searchQueryTerm;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder(searchQueryTerm);
|
||||
builder = builder.Append(" | ID(s):");
|
||||
|
||||
if (ImdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
builder = builder.Append($" IMDbId:[{ImdbId}]");
|
||||
}
|
||||
|
||||
if (TmdbId.HasValue)
|
||||
{
|
||||
builder = builder.Append($" TMDbId:[{TmdbId}]");
|
||||
}
|
||||
|
||||
if (TraktId.HasValue)
|
||||
{
|
||||
builder = builder.Append($" TraktId:[{TraktId}]");
|
||||
}
|
||||
|
||||
return builder.ToString().Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,17 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
public string Source { get; set; }
|
||||
public string Host { get; set; }
|
||||
|
||||
public virtual string SearchQuery
|
||||
{
|
||||
get
|
||||
{
|
||||
return $"Term: [{SearchTerm}]";
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{{Term: {SearchTerm}, Offset: {Offset ?? 0}, Limit: {Limit ?? 0}, Categories: [{string.Join(", ", Categories)}]}}";
|
||||
return $"{SearchQuery}, Offset: {Offset ?? 0}, Limit: {Limit ?? 0}, Categories: [{string.Join(", ", Categories)}]";
|
||||
}
|
||||
|
||||
public virtual bool RssSearch
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Parser;
|
||||
|
||||
@@ -34,6 +35,50 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
}
|
||||
}
|
||||
|
||||
public override string SearchQuery
|
||||
{
|
||||
get
|
||||
{
|
||||
var searchQueryTerm = $"Term: []";
|
||||
var searchEpisodeTerm = $" for Season / Episode:[{EpisodeSearchString}]";
|
||||
if (SearchTerm.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
searchQueryTerm = $"Term: [{SearchTerm}]";
|
||||
}
|
||||
|
||||
if (!ImdbId.IsNotNullOrWhiteSpace() && !TvdbId.HasValue && !RId.HasValue && !TraktId.HasValue)
|
||||
{
|
||||
return $"{searchQueryTerm}{searchEpisodeTerm}";
|
||||
}
|
||||
|
||||
var builder = new StringBuilder(searchQueryTerm);
|
||||
builder = builder.Append(" | ID(s):");
|
||||
|
||||
if (ImdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
builder.Append($" IMDbId:[{ImdbId}]");
|
||||
}
|
||||
|
||||
if (TvdbId.HasValue)
|
||||
{
|
||||
builder.Append($" TVDbId:[{TvdbId}]");
|
||||
}
|
||||
|
||||
if (RId.HasValue)
|
||||
{
|
||||
builder.Append($" TVRageId:[{RId}]");
|
||||
}
|
||||
|
||||
if (TraktId.HasValue)
|
||||
{
|
||||
builder.Append($" TraktId:[{TraktId}]");
|
||||
}
|
||||
|
||||
builder = builder.Append(searchEpisodeTerm);
|
||||
return builder.ToString().Trim();
|
||||
}
|
||||
}
|
||||
|
||||
private string GetEpisodeSearchString()
|
||||
{
|
||||
if (Season == null || Season == 0)
|
||||
|
||||
@@ -147,7 +147,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
.ToList();
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("Searching indexers: [{0}] for {1}", string.Join(", ", indexers.Select(i => i.Definition.Name).ToList()), criteriaBase.ToString());
|
||||
_logger.ProgressInfo("Searching indexer(s): [{0}] for {1}", string.Join(", ", indexers.Select(i => i.Definition.Name).ToList()), criteriaBase.ToString());
|
||||
|
||||
var tasks = indexers.Select(x => DispatchIndexer(searchAction, x, criteriaBase));
|
||||
|
||||
@@ -155,7 +155,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
|
||||
var reports = batch.SelectMany(x => x).ToList();
|
||||
|
||||
_logger.Debug("Total of {0} reports were found for {1} from {2} indexers", reports.Count, criteriaBase, indexers.Count);
|
||||
_logger.Debug("Total of {0} reports were found for {1} from {2} indexer(s)", reports.Count, criteriaBase, indexers.Count);
|
||||
|
||||
return reports;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,9 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
|
||||
public class IndexerDefinitionUpdateService : IIndexerDefinitionUpdateService, IExecute<IndexerDefinitionUpdateCommand>
|
||||
{
|
||||
private const int DEFINITION_VERSION = 1;
|
||||
/* Update Service will fall back if version # does not exist for an indexer per Ta */
|
||||
|
||||
private const int DEFINITION_VERSION = 2;
|
||||
private readonly List<string> _defintionBlocklist = new List<string>()
|
||||
{
|
||||
"aither",
|
||||
@@ -33,8 +35,10 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
"beyond-hd",
|
||||
"beyond-hd-oneurl",
|
||||
"danishbytes",
|
||||
"desitorrents",
|
||||
"hdbits",
|
||||
"shareisland"
|
||||
"shareisland",
|
||||
"lat-team"
|
||||
};
|
||||
|
||||
private readonly IHttpClient _httpClient;
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Name => "Aither";
|
||||
public override string[] IndexerUrls => new string[] { "https://aither.cc/" };
|
||||
public override string Description => "Aither is a Private Torrent Tracker for HD MOVIES / TV";
|
||||
public override string Language => "en-us";
|
||||
public override string Language => "en-US";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public Aither(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Name => "Anidub";
|
||||
public override string[] IndexerUrls => new string[] { "https://tr.anidub.com/" };
|
||||
public override string Description => "Anidub is russian anime voiceover group and eponymous anime tracker.";
|
||||
public override string Language => "ru-ru";
|
||||
public override string Language => "ru-RU";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.SemiPublic;
|
||||
@@ -405,7 +405,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
const string SizeSelector = ".list.down > .red";
|
||||
|
||||
var sizeStr = tabNode.QuerySelector(SizeSelector).TextContent;
|
||||
return ReleaseInfo.GetBytes(sizeStr);
|
||||
return ParseUtil.GetBytes(sizeStr);
|
||||
}
|
||||
|
||||
private string GetReleaseLink(AngleSharp.Dom.IElement tabNode)
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Name => "Anilibria";
|
||||
public override string[] IndexerUrls => new string[] { "https://anilibria.tv/" };
|
||||
public override string Description => "Anilibria is russian anime voiceover group and eponymous anime tracker.";
|
||||
public override string Language => "ru-ru";
|
||||
public override string Language => "ru-RU";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
@@ -172,6 +172,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
var queryResponseItems = JsonConvert.DeserializeObject<List<AnilibriaTitle>>(indexerResponse.Content);
|
||||
var wwwUrl = Regex.Replace(_settings.BaseUrl, @"(https?:\/\/)(.*)", "$1www.$2/");
|
||||
|
||||
foreach (var tl in queryResponseItems)
|
||||
{
|
||||
@@ -190,8 +191,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
// API provides timestamp in UTC+3 timezone, so we need to substract 3 hours
|
||||
PublishDate = DateTimeUtil.UnixTimestampToDateTime(tr.UploadedTimestamp).AddHours(-3),
|
||||
Guid = _settings.BaseUrl + tr.Url,
|
||||
DownloadUrl = _settings.BaseUrl + tr.Url,
|
||||
Guid = wwwUrl + tr.Url,
|
||||
DownloadUrl = wwwUrl + tr.Url,
|
||||
Size = tr.TotalSize,
|
||||
Resolution = tr.Quality.Resolution,
|
||||
Codec = tr.Quality.Encoder
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Name => "AnimeBytes";
|
||||
public override string[] IndexerUrls => new string[] { "https://animebytes.tv/" };
|
||||
public override string Description => "AnimeBytes (AB) is the largest private torrent tracker that specialises in anime and anime-related content.";
|
||||
public override string Language => "en-us";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
@@ -276,7 +276,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
|
||||
var sizeStr = row.QuerySelector("td:nth-of-type(6)").TextContent;
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
release.Size = ParseUtil.GetBytes(sizeStr);
|
||||
|
||||
var connections = row.QuerySelector("td:nth-of-type(8)").TextContent.Trim().Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Name => "AnimeWorld";
|
||||
public override string[] IndexerUrls => new string[] { "https://animeworld.cx/" };
|
||||
public override string Description => "AnimeWorld (AW) is a GERMAN Private site for ANIME / MANGA / HENTAI";
|
||||
public override string Language => "de-de";
|
||||
public override string Language => "de-DE";
|
||||
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public AnimeWorld(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Name => "Animedia";
|
||||
public override string[] IndexerUrls => new string[] { "https://tt.animedia.tv/" };
|
||||
public override string Description => "Animedia is russian anime voiceover group and eponymous anime tracker.";
|
||||
public override string Language => "ru-ru";
|
||||
public override string Language => "ru-RU";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
@@ -210,7 +210,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
private long getReleaseSize(AngleSharp.Dom.IElement tr)
|
||||
{
|
||||
var sizeStr = tr.QuerySelector("div.tracker_info_left").TextContent;
|
||||
return ReleaseInfo.GetBytes(SizeInfoQueryRegex.Match(sizeStr).Groups[1].Value.Trim());
|
||||
return ParseUtil.GetBytes(SizeInfoQueryRegex.Match(sizeStr).Groups[1].Value.Trim());
|
||||
}
|
||||
|
||||
private DateTime getReleaseDate(AngleSharp.Dom.IElement tr)
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string[] IndexerUrls => new string[] { "https://anthelion.me/" };
|
||||
private string LoginUrl => Settings.BaseUrl + "login.php";
|
||||
public override string Description => "A movies tracker";
|
||||
public override string Language => "en-us";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
@@ -242,7 +242,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var files = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(3)").TextContent);
|
||||
var publishDate = DateTimeUtil.FromTimeAgo(row.QuerySelector("td:nth-child(4)").TextContent);
|
||||
var size = ReleaseInfo.GetBytes(row.QuerySelector("td:nth-child(5)").FirstChild.TextContent);
|
||||
var size = ParseUtil.GetBytes(row.QuerySelector("td:nth-child(5)").FirstChild.TextContent);
|
||||
var grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(6)").TextContent);
|
||||
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(7)").TextContent);
|
||||
var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(8)").TextContent);
|
||||
|
||||
@@ -25,8 +25,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Name => "BB";
|
||||
public override string[] IndexerUrls => new string[] { StringUtil.FromBase64("aHR0cHM6Ly9iYWNvbmJpdHMub3JnLw==") };
|
||||
private string LoginUrl => Settings.BaseUrl + "login.php";
|
||||
public override string Description => "bB is a Private Torrent Tracker for 0DAY / GENERAL";
|
||||
public override string Language => "en-us";
|
||||
public override string Description => "BB is a Private Torrent Tracker for 0DAY / GENERAL";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
@@ -103,6 +103,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
{
|
||||
if (!httpResponse.Content.Contains("logout.php"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -279,7 +284,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
release.PublishDate = DateTimeUtil.FromTimeAgo(dateStr);
|
||||
|
||||
var sizeStr = row.Children[4].TextContent;
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
release.Size = ParseUtil.GetBytes(sizeStr);
|
||||
|
||||
release.Files = ParseUtil.CoerceInt(row.Children[2].TextContent.Trim());
|
||||
release.Seeders = ParseUtil.CoerceInt(row.Children[7].TextContent.Trim());
|
||||
|
||||
@@ -15,6 +15,7 @@ using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
@@ -52,7 +53,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
.SetCookies(GetCookies() ?? new Dictionary<string, string>())
|
||||
.Build();
|
||||
|
||||
var response = await _httpClient.ExecuteAsync(request, Definition);
|
||||
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
@@ -170,7 +171,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term)
|
||||
{
|
||||
var searchString = term;
|
||||
var searchUrl = Settings.BaseUrl + "browse.php?only=0&hentai=1&incomplete=1&lossless=1&hd=1&multiaudio=1&bonus=1&reorder=1&q=";
|
||||
var searchUrl = Settings.BaseUrl + " browse.php?only=0&incomplete=1&lossless=1&hd=1&multiaudio=1&bonus=1&reorder=1&q=";
|
||||
if (Settings.AdultContent)
|
||||
{
|
||||
searchUrl = Settings.BaseUrl + "browse.php?only=0&hentai=1&incomplete=1&lossless=1&hd=1&multiaudio=1&bonus=1&reorder=1&q=";
|
||||
}
|
||||
|
||||
var match = Regex.Match(term, @".*(?=\s(?:[Ee]\d+|\d+)$)");
|
||||
if (match.Success)
|
||||
@@ -338,7 +343,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
release.MinimumSeedTime = 172800; // 48 hours
|
||||
|
||||
var size = row.QuerySelector(".size").TextContent;
|
||||
release.Size = ReleaseInfo.GetBytes(size);
|
||||
release.Size = ParseUtil.GetBytes(size);
|
||||
|
||||
//22 Jul 15
|
||||
var dateStr = row.QuerySelector(".added").TextContent.Replace("'", string.Empty);
|
||||
@@ -440,7 +445,10 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
[FieldDefinition(5, Label = "Append Season", Type = FieldType.Checkbox, HelpText = "Append Season for Sonarr Compatibility")]
|
||||
public bool AppendSeason { get; set; }
|
||||
|
||||
[FieldDefinition(6)]
|
||||
[FieldDefinition(6, Label = "Adult Content", Type = FieldType.Checkbox, HelpText = "Allow Adult Content (Must be enabled in BakaBT settings)")]
|
||||
public bool AdultContent { get; set; }
|
||||
|
||||
[FieldDefinition(7)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Name => "BinSearch";
|
||||
public override string[] IndexerUrls => new string[] { "https://binsearch.info/" };
|
||||
public override string Description => "The binary Usenet search engine";
|
||||
public override string Language => "en-us";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
@@ -194,7 +194,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
Guid = guid,
|
||||
Title = parsedTitle.Groups["title"].Value,
|
||||
Size = ReleaseInfo.GetBytes(string.Format("{0} {1}", size.Groups["size"].Value, size.Groups["unit"].Value)),
|
||||
Size = ParseUtil.GetBytes(string.Format("{0} {1}", size.Groups["size"].Value, size.Groups["unit"].Value)),
|
||||
PublishDate = publishDate,
|
||||
Categories = new List<IndexerCategory> { NewznabStandardCategory.Other },
|
||||
InfoUrl = infoUrl,
|
||||
|
||||
302
src/NzbDrone.Core/Indexers/Definitions/BitHDTV.cs
Normal file
302
src/NzbDrone.Core/Indexers/Definitions/BitHDTV.cs
Normal file
@@ -0,0 +1,302 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using AngleSharp.Html.Parser;
|
||||
using FluentValidation;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class BitHDTV : TorrentIndexerBase<BitHDTVSettings>
|
||||
{
|
||||
public override string Name => "BitHDTV";
|
||||
public override string[] IndexerUrls => new string[] { "https://www.bit-hdtv.com/" };
|
||||
public override string Description => "BIT-HDTV - Home of High Definition";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.GetEncoding("iso-8859-1");
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public BitHDTV(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new BitHDTVRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new BitHDTVParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override IDictionary<string, string> GetCookies()
|
||||
{
|
||||
return CookieUtil.CookieHeaderToDictionary(Settings.Cookie);
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "Anime");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.MoviesBluRay, "Movies/Blu-ray");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVDocumentary, "Documentaries");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.AudioLossless, "HQ Audio");
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.Movies, "Movies");
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.AudioVideo, "Music Videos");
|
||||
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.Other, "Other");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVSport, "Sports");
|
||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.TV, "TV");
|
||||
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.TV, "TV/Seasonpack");
|
||||
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.XXX, "XXX");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class BitHDTVRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public BitHDTVSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
|
||||
public BitHDTVRequestGenerator()
|
||||
{
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
|
||||
{
|
||||
var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/'));
|
||||
|
||||
var qc = new NameValueCollection
|
||||
{
|
||||
{ "cat", Capabilities.Categories.MapTorznabCapsToTrackers(categories, true).FirstIfSingleOrDefault("0") }
|
||||
};
|
||||
|
||||
var search = new UriBuilder(searchUrl);
|
||||
|
||||
if (imdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
qc.Add("search", imdbId);
|
||||
qc.Add("options", "4"); //Search URL field for IMDB link
|
||||
search.Query = qc.GetQueryString();
|
||||
yield return new IndexerRequest(search.ToString(), HttpAccept.Html);
|
||||
|
||||
qc["Options"] = "1"; //Search Title and Description
|
||||
search.Query = qc.GetQueryString();
|
||||
yield return new IndexerRequest(search.ToString(), HttpAccept.Html);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Site handles empty string on search param. No need to check for IsNullOrEmpty()
|
||||
qc.Add("search", term);
|
||||
qc.Add("options", "0"); //Search Title Only
|
||||
search.Query = qc.GetQueryString();
|
||||
yield return new IndexerRequest(search.ToString(), HttpAccept.Html);
|
||||
}
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.FullImdbId));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories, searchCriteria.FullImdbId));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class BitHDTVParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly BitHDTVSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
public BitHDTVParser(BitHDTVSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
foreach (var child in dom.QuerySelectorAll("#needseed"))
|
||||
{
|
||||
child.Remove();
|
||||
}
|
||||
|
||||
var table = dom.QuerySelector("table[align=center] + br + table > tbody");
|
||||
|
||||
// No results, so skip this search
|
||||
if (table == null)
|
||||
{
|
||||
return torrentInfos;
|
||||
}
|
||||
|
||||
foreach (var row in table.Children.Skip(1))
|
||||
{
|
||||
var release = new TorrentInfo();
|
||||
var qLink = row.Children[2].QuerySelector("a");
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800; // 48 hours
|
||||
release.Title = qLink.GetAttribute("title");
|
||||
var detailsLink = new Uri(qLink.GetAttribute("href"));
|
||||
|
||||
//Skip irrelevant and duplicate entries
|
||||
if (torrentInfos.Any(r => r.Guid == detailsLink.AbsoluteUri))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
release.Files = ParseUtil.CoerceInt(row.Children[3].TextContent);
|
||||
release.Grabs = ParseUtil.CoerceInt(row.Children[7].TextContent);
|
||||
release.Guid = detailsLink.AbsoluteUri;
|
||||
release.InfoUrl = release.Guid;
|
||||
release.DownloadUrl = new Uri(_settings.BaseUrl + row.QuerySelector("a[href^=\"download.php\"]").GetAttribute("href")).AbsoluteUri;
|
||||
var catUrl = new Uri(_settings.BaseUrl + row.Children[1].FirstElementChild.GetAttribute("href"));
|
||||
var catQuery = HttpUtility.ParseQueryString(catUrl.Query);
|
||||
var catNum = catQuery["cat"];
|
||||
release.Categories = _categories.MapTrackerCatToNewznab(catNum);
|
||||
|
||||
var dateString = row.Children[5].TextContent.Trim();
|
||||
var pubDate = DateTime.ParseExact(dateString, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture);
|
||||
release.PublishDate = DateTime.SpecifyKind(pubDate, DateTimeKind.Local);
|
||||
var sizeStr = row.Children[6].TextContent;
|
||||
release.Size = ParseUtil.GetBytes(sizeStr);
|
||||
release.Seeders = ParseUtil.CoerceInt(row.Children[8].TextContent.Trim());
|
||||
release.Peers = ParseUtil.CoerceInt(row.Children[9].TextContent.Trim()) + release.Seeders;
|
||||
switch (row.GetAttribute("bgcolor"))
|
||||
{
|
||||
case "#DDDDDD":
|
||||
release.DownloadVolumeFactor = 1;
|
||||
release.UploadVolumeFactor = 2;
|
||||
break;
|
||||
case "#FFFF99":
|
||||
release.DownloadVolumeFactor = 0;
|
||||
release.UploadVolumeFactor = 1;
|
||||
break;
|
||||
case "#CCFF99":
|
||||
release.DownloadVolumeFactor = 0;
|
||||
release.UploadVolumeFactor = 2;
|
||||
break;
|
||||
default:
|
||||
release.DownloadVolumeFactor = 1;
|
||||
release.UploadVolumeFactor = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
torrentInfos.Add(release);
|
||||
}
|
||||
|
||||
return torrentInfos.ToArray();
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class BitHDTVSettingsValidator : AbstractValidator<BitHDTVSettings>
|
||||
{
|
||||
public BitHDTVSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.Cookie).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class BitHDTVSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly BitHDTVSettingsValidator Validator = new BitHDTVSettingsValidator();
|
||||
|
||||
public BitHDTVSettings()
|
||||
{
|
||||
Cookie = "";
|
||||
}
|
||||
|
||||
[FieldDefinition(1, Label = "Base Url", HelpText = "Select which baseurl Prowlarr will use for requests to the site", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Cookie", HelpText = "Login cookie from website")]
|
||||
public string Cookie { get; set; }
|
||||
|
||||
[FieldDefinition(3)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
@@ -69,17 +71,26 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
|
||||
}
|
||||
|
||||
// If only the season/episode is searched for then change format to match expected format
|
||||
if (searchCriteria.Season > 0 && searchCriteria.Episode == null)
|
||||
if (searchCriteria.Season > 0 && searchCriteria.Episode.IsNullOrWhiteSpace())
|
||||
{
|
||||
// Season Only
|
||||
parameters.Name = string.Format("Season {0}%", searchCriteria.Season.Value);
|
||||
parameters.Category = "Season";
|
||||
}
|
||||
else if (searchCriteria.Season > 0 && int.Parse(searchCriteria.Episode) > 0)
|
||||
else if (searchCriteria.Season > 0 && Regex.IsMatch(searchCriteria.EpisodeSearchString, "(\\d{4}\\.\\d{2}\\.\\d{2})"))
|
||||
{
|
||||
// Daily Episode
|
||||
parameters.Name = searchCriteria.EpisodeSearchString;
|
||||
parameters.Category = "Episode";
|
||||
}
|
||||
else if (searchCriteria.Season > 0 && int.Parse(searchCriteria.Episode) > 0)
|
||||
{
|
||||
// Standard (S/E) Episode
|
||||
parameters.Name = string.Format("S{0:00}E{1:00}", searchCriteria.Season.Value, int.Parse(searchCriteria.Episode));
|
||||
parameters.Category = "Episode";
|
||||
}
|
||||
|
||||
// Neither a season only search nor daily nor standard, fall back to query
|
||||
parameters.Search = searchString.Replace(" ", "%");
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(parameters, btnResults, btnOffset));
|
||||
|
||||
@@ -177,7 +177,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.ExecuteAsync(request, Definition);
|
||||
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
|
||||
downloadBytes = response.ResponseData;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
public List<string> Links { get; set; }
|
||||
public List<string> Legacylinks { get; set; }
|
||||
public bool Followredirect { get; set; } = false;
|
||||
public bool TestLinkTorrent { get; set; } = true;
|
||||
public List<string> Certificates { get; set; }
|
||||
public CapabilitiesBlock Caps { get; set; }
|
||||
public LoginBlock Login { get; set; }
|
||||
@@ -70,6 +71,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
public Dictionary<string, string> Categories { get; set; }
|
||||
public List<CategorymappingBlock> Categorymappings { get; set; }
|
||||
public Dictionary<string, List<string>> Modes { get; set; }
|
||||
public bool AllowRawSearch { get; set; }
|
||||
}
|
||||
|
||||
public class CaptchaBlock
|
||||
@@ -166,11 +168,30 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
}
|
||||
|
||||
public class DownloadBlock
|
||||
{
|
||||
public List<SelectorField> Selectors { get; set; }
|
||||
public string Method { get; set; }
|
||||
public BeforeBlock Before { get; set; }
|
||||
public InfohashBlock Infohash { get; set; }
|
||||
}
|
||||
|
||||
public class InfohashBlock
|
||||
{
|
||||
public SelectorField Hash { get; set; }
|
||||
public SelectorField Title { get; set; }
|
||||
public bool UseBeforeResponse { get; set; }
|
||||
}
|
||||
|
||||
public class SelectorField
|
||||
{
|
||||
public string Selector { get; set; }
|
||||
public string Attribute { get; set; }
|
||||
public bool UseBeforeResponse { get; set; }
|
||||
public List<FilterBlock> Filters { get; set; }
|
||||
public string Method { get; set; }
|
||||
public RequestBlock Before { get; set; }
|
||||
}
|
||||
|
||||
public class BeforeBlock : RequestBlock
|
||||
{
|
||||
public SelectorField Pathselector { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
value = release.Categories.ToString();
|
||||
break;
|
||||
case "size":
|
||||
release.Size = ReleaseInfo.GetBytes(value);
|
||||
release.Size = ParseUtil.GetBytes(value);
|
||||
value = release.Size.ToString();
|
||||
break;
|
||||
case "leechers":
|
||||
|
||||
@@ -194,7 +194,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
requestBuilder.Headers.Add("Referer", SiteLink);
|
||||
|
||||
var response = await HttpClient.ExecuteAsync(requestBuilder.Build(), Definition);
|
||||
var response = await HttpClient.ExecuteProxiedAsync(requestBuilder.Build(), Definition);
|
||||
|
||||
Cookies = response.GetCookies();
|
||||
|
||||
@@ -331,7 +331,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
requestBuilder.Headers.Add("Referer", loginUrl);
|
||||
|
||||
var simpleCaptchaResult = await HttpClient.ExecuteAsync(requestBuilder.Build(), Definition);
|
||||
var simpleCaptchaResult = await HttpClient.ExecuteProxiedAsync(requestBuilder.Build(), Definition);
|
||||
|
||||
var simpleCaptchaJSON = JObject.Parse(simpleCaptchaResult.Content);
|
||||
var captchaSelection = simpleCaptchaJSON["images"][0]["hash"].ToString();
|
||||
@@ -411,7 +411,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
var request = requestBuilder.Build();
|
||||
request.SetContent(body);
|
||||
|
||||
loginResult = await HttpClient.ExecuteAsync(request, Definition);
|
||||
loginResult = await HttpClient.ExecuteProxiedAsync(request, Definition);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -431,7 +431,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
requestBuilder.AddFormParameter(pair.Key, pair.Value);
|
||||
}
|
||||
|
||||
loginResult = await HttpClient.ExecuteAsync(requestBuilder.Build(), Definition);
|
||||
loginResult = await HttpClient.ExecuteProxiedAsync(requestBuilder.Build(), Definition);
|
||||
}
|
||||
|
||||
Cookies = loginResult.GetCookies();
|
||||
@@ -466,7 +466,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
requestBuilder.Headers.Add("Referer", SiteLink);
|
||||
|
||||
var response = await HttpClient.ExecuteAsync(requestBuilder.Build(), Definition);
|
||||
var response = await HttpClient.ExecuteProxiedAsync(requestBuilder.Build(), Definition);
|
||||
|
||||
Cookies = response.GetCookies();
|
||||
|
||||
@@ -490,7 +490,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
requestBuilder.Headers.Add("Referer", SiteLink);
|
||||
|
||||
var response = await HttpClient.ExecuteAsync(requestBuilder.Build(), Definition);
|
||||
var response = await HttpClient.ExecuteProxiedAsync(requestBuilder.Build(), Definition);
|
||||
|
||||
Cookies = response.GetCookies();
|
||||
|
||||
@@ -569,7 +569,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
|
||||
landingResult = await HttpClient.ExecuteAsync(request, Definition);
|
||||
landingResult = await HttpClient.ExecuteProxiedAsync(request, Definition);
|
||||
|
||||
Cookies = landingResult.GetCookies();
|
||||
|
||||
@@ -590,7 +590,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
if (captcha != null && automaticlogin)
|
||||
{
|
||||
_logger.Error(string.Format("CardigannIndexer ({0}): Found captcha during automatic login, aborting", _definition.Id));
|
||||
_logger.Error("CardigannIndexer ({0}): Found captcha during automatic login, aborting", _definition.Id);
|
||||
}
|
||||
|
||||
return captcha;
|
||||
@@ -613,7 +613,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
.SetHeader("Referer", loginUrl.AbsoluteUri)
|
||||
.Build();
|
||||
|
||||
var response = await HttpClient.ExecuteAsync(request, Definition);
|
||||
var response = await HttpClient.ExecuteProxiedAsync(request, Definition);
|
||||
|
||||
return new Captcha
|
||||
{
|
||||
@@ -623,7 +623,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug(string.Format("CardigannIndexer ({0}): No captcha image found", _definition.Id));
|
||||
_logger.Debug("CardigannIndexer ({0}): No captcha image found", _definition.Id);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -650,7 +650,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
protected async Task<HttpResponse> HandleRequest(RequestBlock request, Dictionary<string, object> variables = null, string referer = null)
|
||||
{
|
||||
var requestLinkStr = ResolvePath(ApplyGoTemplateText(request.Path, variables)).ToString();
|
||||
_logger.Debug($"CardigannIndexer ({_definition.Id}): handleRequest() requestLinkStr= {requestLinkStr}");
|
||||
|
||||
_logger.Debug("CardigannIndexer ({0}): handleRequest() requestLinkStr= {1}", _definition.Id, requestLinkStr);
|
||||
|
||||
Dictionary<string, string> pairs = null;
|
||||
var queryCollection = new NameValueCollection();
|
||||
@@ -703,9 +704,9 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
}
|
||||
}
|
||||
|
||||
var response = await HttpClient.ExecuteAsync(httpRequest.Build(), Definition);
|
||||
var response = await HttpClient.ExecuteProxiedAsync(httpRequest.Build(), Definition);
|
||||
|
||||
_logger.Debug($"CardigannIndexer ({_definition.Id}): handleRequest() remote server returned {response.StatusCode.ToString()}");
|
||||
_logger.Debug("CardigannIndexer ({0}): handleRequest() remote server returned {1}", _definition.Id, response.StatusCode);
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -721,9 +722,26 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
AddTemplateVariablesFromUri(variables, link, ".DownloadUri");
|
||||
|
||||
if (download.Before != null)
|
||||
var headers = ParseCustomHeaders(_definition.Search?.Headers, variables);
|
||||
HttpResponse response = null;
|
||||
|
||||
var request = new HttpRequestBuilder(link.ToString())
|
||||
.SetCookies(Cookies ?? new Dictionary<string, string>())
|
||||
.SetHeaders(headers ?? new Dictionary<string, string>())
|
||||
.Build();
|
||||
|
||||
request.AllowAutoRedirect = true;
|
||||
|
||||
var beforeBlock = download.Before;
|
||||
if (beforeBlock != null)
|
||||
{
|
||||
await HandleRequest(download.Before, variables, link.ToString());
|
||||
if (beforeBlock.Pathselector != null)
|
||||
{
|
||||
response = await HttpClient.ExecuteProxiedAsync(request, Definition);
|
||||
beforeBlock.Path = MatchSelector(response, beforeBlock.Pathselector, variables);
|
||||
}
|
||||
|
||||
response = await HandleRequest(beforeBlock, variables, link.ToString());
|
||||
}
|
||||
|
||||
if (download.Method == "post")
|
||||
@@ -731,49 +749,105 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
method = HttpMethod.POST;
|
||||
}
|
||||
|
||||
if (download.Selector != null)
|
||||
if (download.Infohash != null)
|
||||
{
|
||||
var selector = ApplyGoTemplateText(download.Selector, variables);
|
||||
var headers = ParseCustomHeaders(_definition.Search?.Headers, variables);
|
||||
|
||||
var request = new HttpRequestBuilder(link.ToString())
|
||||
.SetCookies(Cookies ?? new Dictionary<string, string>())
|
||||
.SetHeaders(headers ?? new Dictionary<string, string>())
|
||||
.Build();
|
||||
|
||||
request.AllowAutoRedirect = true;
|
||||
|
||||
var response = await HttpClient.ExecuteAsync(request, Definition);
|
||||
|
||||
var results = response.Content;
|
||||
var searchResultParser = new HtmlParser();
|
||||
var searchResultDocument = searchResultParser.ParseDocument(results);
|
||||
var downloadElement = searchResultDocument.QuerySelector(selector);
|
||||
if (downloadElement != null)
|
||||
try
|
||||
{
|
||||
_logger.Debug(string.Format("CardigannIndexer ({0}): Download selector {1} matched:{2}", _definition.Id, selector, downloadElement.ToHtmlPretty()));
|
||||
headers = ParseCustomHeaders(_definition.Search?.Headers, variables);
|
||||
|
||||
var href = "";
|
||||
if (download.Attribute != null)
|
||||
if (!download.Infohash.UseBeforeResponse || download.Before == null || response == null)
|
||||
{
|
||||
href = downloadElement.GetAttribute(download.Attribute);
|
||||
response = await HttpClient.ExecuteProxiedAsync(request, Definition);
|
||||
}
|
||||
|
||||
var hash = MatchSelector(response, download.Infohash.Hash, variables);
|
||||
if (hash == null)
|
||||
{
|
||||
throw new Exception($"InfoHash selectors didn't match");
|
||||
}
|
||||
|
||||
var title = MatchSelector(response, download.Infohash.Title, variables);
|
||||
if (title == null)
|
||||
{
|
||||
throw new Exception($"InfoHash selectors didn't match");
|
||||
}
|
||||
|
||||
var magnet = MagnetLinkBuilder.BuildPublicMagnetLink(hash, title);
|
||||
var torrentLink = ResolvePath(magnet, link);
|
||||
|
||||
var hashDownloadRequest = new HttpRequestBuilder(torrentLink.AbsoluteUri)
|
||||
.SetCookies(Cookies ?? new Dictionary<string, string>())
|
||||
.Build();
|
||||
|
||||
hashDownloadRequest.Method = method;
|
||||
|
||||
return hashDownloadRequest;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error("CardigannIndexer ({0}): Exception with InfoHash block with hashSelector {1} and titleSelector {2}",
|
||||
_definition.Id,
|
||||
download.Infohash.Hash.Selector,
|
||||
download.Infohash.Title.Selector);
|
||||
}
|
||||
}
|
||||
else if (download.Selectors != null)
|
||||
{
|
||||
headers = ParseCustomHeaders(_definition.Search?.Headers, variables);
|
||||
|
||||
foreach (var selector in download.Selectors)
|
||||
{
|
||||
var queryselector = ApplyGoTemplateText(selector.Selector, variables);
|
||||
|
||||
try
|
||||
{
|
||||
if (!selector.UseBeforeResponse || download.Before == null || response == null)
|
||||
{
|
||||
response = await HttpClient.ExecuteProxiedAsync(request, Definition);
|
||||
}
|
||||
|
||||
var href = MatchSelector(response, selector, variables, debugMatch: true);
|
||||
if (href == null)
|
||||
{
|
||||
throw new Exception(string.Format("Attribute \"{0}\" is not set for element {1}", download.Attribute, downloadElement.ToHtmlPretty()));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
href = downloadElement.TextContent;
|
||||
}
|
||||
|
||||
href = ApplyFilters(href, download.Filters, variables);
|
||||
link = ResolvePath(href, link);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(string.Format("CardigannIndexer ({0}): Download selector {1} didn't match:\n{2}", _definition.Id, download.Selector, results));
|
||||
throw new Exception(string.Format("Download selector {0} didn't match", download.Selector));
|
||||
var torrentLink = ResolvePath(href, link);
|
||||
if (torrentLink.Scheme != "magnet" && _definition.TestLinkTorrent)
|
||||
{
|
||||
// Test link
|
||||
var testLinkRequest = new HttpRequestBuilder(torrentLink.ToString())
|
||||
.SetCookies(Cookies ?? new Dictionary<string, string>())
|
||||
.SetHeaders(headers ?? new Dictionary<string, string>())
|
||||
.Build();
|
||||
|
||||
response = await HttpClient.ExecuteProxiedAsync(testLinkRequest, Definition);
|
||||
|
||||
var content = response.Content;
|
||||
if (content.Length >= 1 && content[0] != 'd')
|
||||
{
|
||||
_logger.Debug("CardigannIndexer ({0}): Download selector {1}'s torrent file is invalid, retrying with next available selector", _definition.Id, queryselector);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
link = torrentLink;
|
||||
|
||||
var selectorDownloadRequest = new HttpRequestBuilder(link.AbsoluteUri)
|
||||
.SetCookies(Cookies ?? new Dictionary<string, string>())
|
||||
.Build();
|
||||
|
||||
selectorDownloadRequest.Method = method;
|
||||
|
||||
return selectorDownloadRequest;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error("{0} CardigannIndexer ({1}): An exception occurred while trying selector {2}, retrying with next available selector", e, _definition.Id, queryselector);
|
||||
|
||||
throw new Exception(string.Format("An exception occurred while trying selector {0}", queryselector));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -787,6 +861,44 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
return downloadRequest;
|
||||
}
|
||||
|
||||
protected string MatchSelector(HttpResponse response, SelectorField selector, Dictionary<string, object> variables, bool debugMatch = false)
|
||||
{
|
||||
var selectorText = ApplyGoTemplateText(selector.Selector, variables);
|
||||
var parser = new HtmlParser();
|
||||
|
||||
var results = response.Content;
|
||||
var resultDocument = parser.ParseDocument(results);
|
||||
|
||||
var element = resultDocument.QuerySelector(selectorText);
|
||||
if (element == null)
|
||||
{
|
||||
_logger.Debug($"CardigannIndexer ({_definition.Id}): Selector {selectorText} could not match any elements.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (debugMatch)
|
||||
{
|
||||
_logger.Debug($"CardigannIndexer ({_definition.Id}): Download selector {selector} matched:{element.ToHtmlPretty()}");
|
||||
}
|
||||
|
||||
string val;
|
||||
if (selector.Attribute != null)
|
||||
{
|
||||
val = element.GetAttribute(selector.Attribute);
|
||||
if (val == null)
|
||||
{
|
||||
throw new Exception($"Attribute \"{selector.Attribute}\" is not set for element {element.ToHtmlPretty()}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
val = element.TextContent;
|
||||
}
|
||||
|
||||
val = ApplyFilters(val, selector.Filters, variables);
|
||||
return val;
|
||||
}
|
||||
|
||||
public bool CheckIfLoginIsNeeded(HttpResponse response)
|
||||
{
|
||||
if (response.HasHttpRedirect)
|
||||
|
||||
82
src/NzbDrone.Core/Indexers/Definitions/DesiTorrents.cs
Normal file
82
src/NzbDrone.Core/Indexers/Definitions/DesiTorrents.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Definitions.UNIT3D;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class DesiTorrents : Unit3dBase
|
||||
{
|
||||
public override string Name => "DesiTorrents";
|
||||
public override string Language => "en-US";
|
||||
public override string[] IndexerUrls => new[] { "https://desitorrents.tv/" };
|
||||
public override string Description => "Desitorrents is a Private Torrent Tracker for BOLLYWOOD / TOLLYWOOD / GENERAL";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public DesiTorrents(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new DesiTorrentsParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies, "Movies");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TV, "TV");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio, "Music");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.BooksEBook, "ebooks");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVSport, "Sports");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.PCGames, "Games");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class DesiTorrentsParser : Unit3dParser
|
||||
{
|
||||
public DesiTorrentsParser(Unit3dSettings settings, IndexerCapabilitiesCategories categories)
|
||||
: base(settings, categories)
|
||||
{
|
||||
}
|
||||
|
||||
public override IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var releases = base.ParseResponse(indexerResponse);
|
||||
|
||||
foreach (TorrentInfo release in releases)
|
||||
{
|
||||
release.MinimumRatio = 0.6;
|
||||
release.MinimumSeedTime = 259200;
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
{
|
||||
public override string Name => "FileList.io";
|
||||
public override string[] IndexerUrls => new string[] { "https://filelist.io" };
|
||||
public override string Description => "";
|
||||
public override string Description => "FileList (FL) is a ROMANIAN Private Torrent Tracker for 0DAY / GENERAL";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override bool SupportsRss => true;
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
public virtual IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/torrents.php")
|
||||
.AddQueryParam("action", "download")
|
||||
.AddQueryParam("useToken", _settings.UseFreeleechToken ? "1" : "0")
|
||||
.AddQueryParam("usetoken", _settings.UseFreeleechToken ? "1" : "0")
|
||||
.AddQueryParam("id", torrentId);
|
||||
|
||||
return url.FullUri;
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Name => "GazelleGames";
|
||||
public override string[] IndexerUrls => new string[] { "https://gazellegames.net/" };
|
||||
public override string Description => "GazelleGames (GGn) is a Private Torrent Tracker for GAMES";
|
||||
public override string Language => "en-us";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
@@ -361,7 +361,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var details = _settings.BaseUrl + qDetailsLink.GetAttribute("href");
|
||||
var grabs = ParseUtil.CoerceInt(qGrabs.TextContent);
|
||||
var leechers = ParseUtil.CoerceInt(qLeechers.TextContent);
|
||||
var size = ReleaseInfo.GetBytes(sizeString);
|
||||
var size = ParseUtil.GetBytes(sizeString);
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.HDBits
|
||||
{
|
||||
@@ -17,26 +18,22 @@ namespace NzbDrone.Core.Indexers.HDBits
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
var query = new TorrentQuery();
|
||||
var imdbId = ParseUtil.GetImdbID(searchCriteria.ImdbId).GetValueOrDefault(0);
|
||||
|
||||
if (searchCriteria.Categories?.Length > 0)
|
||||
{
|
||||
query.Category = Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories).Select(int.Parse).ToArray();
|
||||
}
|
||||
|
||||
if (searchCriteria.ImdbId.IsNullOrWhiteSpace() && searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
|
||||
if (imdbId == 0 && searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
query.Search = searchCriteria.SanitizedSearchTerm;
|
||||
}
|
||||
|
||||
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
|
||||
if (imdbId != 0)
|
||||
{
|
||||
var imdbId = int.Parse(searchCriteria.ImdbId.Substring(2));
|
||||
|
||||
if (imdbId != 0)
|
||||
{
|
||||
query.ImdbInfo = query.ImdbInfo ?? new ImdbInfo();
|
||||
query.ImdbInfo.Id = imdbId;
|
||||
}
|
||||
query.ImdbInfo = query.ImdbInfo ?? new ImdbInfo();
|
||||
query.ImdbInfo.Id = imdbId;
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetRequest(query));
|
||||
@@ -78,19 +75,19 @@ namespace NzbDrone.Core.Indexers.HDBits
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
var query = new TorrentQuery();
|
||||
var tvdbId = searchCriteria.TvdbId.GetValueOrDefault(0);
|
||||
var imdbId = ParseUtil.GetImdbID(searchCriteria.ImdbId).GetValueOrDefault(0);
|
||||
|
||||
if (searchCriteria.Categories?.Length > 0)
|
||||
{
|
||||
query.Category = Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories).Select(int.Parse).ToArray();
|
||||
}
|
||||
|
||||
if (searchCriteria.TvdbId == 0 && searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
|
||||
if (tvdbId == 0 && imdbId == 0 && searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
query.Search = searchCriteria.SanitizedTvSearchString;
|
||||
}
|
||||
|
||||
var tvdbId = searchCriteria.TvdbId;
|
||||
|
||||
if (tvdbId != 0)
|
||||
{
|
||||
query.TvdbInfo = query.TvdbInfo ?? new TvdbInfo();
|
||||
@@ -99,6 +96,12 @@ namespace NzbDrone.Core.Indexers.HDBits
|
||||
query.TvdbInfo.Episode = searchCriteria.Episode;
|
||||
}
|
||||
|
||||
if (imdbId != 0)
|
||||
{
|
||||
query.ImdbInfo = query.ImdbInfo ?? new ImdbInfo();
|
||||
query.ImdbInfo.Id = imdbId;
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetRequest(query));
|
||||
|
||||
return pageableRequests;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string[] IndexerUrls => new string[] { "https://hd-space.org/" };
|
||||
private string LoginUrl => Settings.BaseUrl + "index.php?page=login";
|
||||
public override string Description => "HD-Space (HDS) is a Private Torrent Tracker for HD MOVIES / TV";
|
||||
public override string Language => "en-us";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
@@ -276,7 +276,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
//"July 11, 2015, 13:34:09", "Today|Yesterday at 20:04:23"
|
||||
release.PublishDate = DateTimeUtil.FromUnknown(dateStr);
|
||||
var sizeStr = row.Children[5].TextContent;
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
release.Size = ParseUtil.GetBytes(sizeStr);
|
||||
release.Seeders = ParseUtil.CoerceInt(row.Children[7].TextContent);
|
||||
release.Peers = ParseUtil.CoerceInt(row.Children[8].TextContent) + release.Seeders;
|
||||
var grabs = row.QuerySelector("td:nth-child(10)").TextContent;
|
||||
|
||||
@@ -259,7 +259,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var link = new Uri(_settings.BaseUrl + row.Children[4].FirstElementChild.GetAttribute("href"));
|
||||
var description = row.Children[2].QuerySelector("span").TextContent;
|
||||
var size = ReleaseInfo.GetBytes(row.Children[7].TextContent);
|
||||
var size = ParseUtil.GetBytes(row.Children[7].TextContent);
|
||||
|
||||
var dateTag = row.Children[6].FirstElementChild;
|
||||
var dateString = string.Join(" ", dateTag.Attributes.Select(attr => attr.Name));
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace NzbDrone.Core.Indexers.Headphones
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override string[] IndexerUrls => new string[] { "https://indexer.codeshy.com" };
|
||||
public override string Description => "";
|
||||
public override string Description => "A Private Usenet indexer for music";
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
@@ -63,7 +63,7 @@ namespace NzbDrone.Core.Indexers.Headphones
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.ExecuteAsync(request, Definition);
|
||||
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
|
||||
downloadBytes = response.ResponseData;
|
||||
}
|
||||
catch (Exception)
|
||||
|
||||
@@ -304,7 +304,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
// Torrents - Category column == Icons
|
||||
var cat = _categories.MapTrackerCatToNewznab(catIcon.GetAttribute("href").Substring(1));
|
||||
|
||||
var size = ReleaseInfo.GetBytes(row.Children[5].TextContent);
|
||||
var size = ParseUtil.GetBytes(row.Children[5].TextContent);
|
||||
|
||||
var colIndex = 6;
|
||||
int? files = null;
|
||||
|
||||
@@ -280,7 +280,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
release.PublishDate = DateTime.ParseExact(dateString, "yyyy-MM-dd hh:mm tt", CultureInfo.InvariantCulture);
|
||||
|
||||
var sizeStr = row.QuerySelector("td:nth-of-type(5)").TextContent.Trim();
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
release.Size = ParseUtil.GetBytes(sizeStr);
|
||||
|
||||
release.Seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(7)").TextContent.Trim());
|
||||
release.Peers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(8)").TextContent.Trim()) + release.Seeders;
|
||||
|
||||
@@ -71,10 +71,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
};
|
||||
|
||||
// c.f. https://archive.org/services/docs/api/metadata-schema/index.html?highlight=mediatype#mediatype
|
||||
// "Movies" is a catch all category for videos
|
||||
caps.Categories.AddCategoryMapping("texts", NewznabStandardCategory.Books);
|
||||
caps.Categories.AddCategoryMapping("etree", NewznabStandardCategory.Audio);
|
||||
caps.Categories.AddCategoryMapping("audio", NewznabStandardCategory.Audio);
|
||||
caps.Categories.AddCategoryMapping("movies", NewznabStandardCategory.Movies);
|
||||
caps.Categories.AddCategoryMapping("movies", NewznabStandardCategory.TV);
|
||||
caps.Categories.AddCategoryMapping("software", NewznabStandardCategory.PC);
|
||||
caps.Categories.AddCategoryMapping("image", NewznabStandardCategory.OtherMisc);
|
||||
caps.Categories.AddCategoryMapping("data", NewznabStandardCategory.Other);
|
||||
@@ -241,7 +243,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
UploadVolumeFactor = 1
|
||||
};
|
||||
|
||||
if (searchResult.InfoHash != null)
|
||||
if (!_settings.TorrentFileOnly && searchResult.InfoHash != null)
|
||||
{
|
||||
release.MagnetUrl = MagnetLinkBuilder.BuildPublicMagnetLink(searchResult.InfoHash, title);
|
||||
}
|
||||
@@ -292,7 +294,10 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
[FieldDefinition(4, Label = "Title Only", Type = FieldType.Checkbox, Advanced = true, HelpText = "Whether to search in title only.")]
|
||||
public bool TitleOnly { get; set; }
|
||||
|
||||
[FieldDefinition(5)]
|
||||
[FieldDefinition(5, Label = "Torrent File Only", Type = FieldType.Checkbox, Advanced = true, HelpText = "Only use torrent files, not magnet links.")]
|
||||
public bool TorrentFileOnly { get; set; }
|
||||
|
||||
[FieldDefinition(6)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public InternetArchiveSettings()
|
||||
@@ -300,6 +305,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
SortBy = (int)InternetArchiveSort.PublicDate;
|
||||
SortOrder = (int)InternetArchiveSortOrder.Descending;
|
||||
TitleOnly = false;
|
||||
TorrentFileOnly = false;
|
||||
}
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
|
||||
51
src/NzbDrone.Core/Indexers/Definitions/LatTeam.cs
Normal file
51
src/NzbDrone.Core/Indexers/Definitions/LatTeam.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Definitions.UNIT3D;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class LatTeam : Unit3dBase
|
||||
{
|
||||
public override string Name => "Lat-Team";
|
||||
public override string Language => "es";
|
||||
public override string[] IndexerUrls => new[] { "https://lat-team.com/" };
|
||||
public override string Description => "Lat-Team is a Private Torrent Tracker for HD MOVIES / TV";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public LatTeam(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
},
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies, "Peliculas");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.MoviesOther, "Retro Pelicula");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVAnime, "Anime");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TV, "TV Series");
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.TVOther, "Retro Serie TV");
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.TVForeign, "Telenovelas y Teleseries");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio, "Musica");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,14 @@ using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
@@ -269,6 +271,23 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
// Throw common http errors here before we try to parse
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
// Remove cookie cache
|
||||
CookiesUpdater(null, null);
|
||||
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
||||
}
|
||||
|
||||
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
|
||||
{
|
||||
// Remove cookie cache
|
||||
CookiesUpdater(null, null);
|
||||
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
|
||||
}
|
||||
|
||||
var torrentInfos = new List<TorrentInfo>();
|
||||
|
||||
var jsonResponse = JsonConvert.DeserializeObject<MyAnonamouseResponse>(indexerResponse.Content);
|
||||
@@ -341,7 +360,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
release.Seeders = item.Seeders;
|
||||
release.Peers = item.Leechers + release.Seeders;
|
||||
var size = item.Size;
|
||||
release.Size = ReleaseInfo.GetBytes(size);
|
||||
release.Size = ParseUtil.GetBytes(size);
|
||||
|
||||
release.DownloadVolumeFactor = item.Free ? 0 : 1;
|
||||
release.UploadVolumeFactor = 1;
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string[] IndexerUrls => new string[] { "https://nebulance.io/" };
|
||||
private string LoginUrl => Settings.BaseUrl + "login.php";
|
||||
public override string Description => "Nebulance (NBL) is a ratioless Private Torrent Tracker for TV";
|
||||
public override string Language => "en-us";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
@@ -224,7 +224,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var link = _settings.BaseUrl + row.QuerySelector("a[href*='action=download']").GetAttribute("href");
|
||||
|
||||
var qColSize = row.QuerySelector("td:nth-child(3)");
|
||||
var size = ReleaseInfo.GetBytes(qColSize.Children[0].TextContent);
|
||||
var size = ParseUtil.GetBytes(qColSize.Children[0].TextContent);
|
||||
var files = ParseUtil.CoerceInt(qColSize.Children[1].TextContent.Split(':')[1].Trim());
|
||||
|
||||
var qPublishdate = row.QuerySelector("td:nth-child(4) span");
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
|
||||
public override string Name => "Newznab";
|
||||
public override string[] IndexerUrls => GetBaseUrlFromSettings();
|
||||
public override string Description => "";
|
||||
public override string Description => "Newznab is an API search specification for Usenet";
|
||||
public override bool FollowRedirect => true;
|
||||
public override bool SupportsRedirect => true;
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
|
||||
try
|
||||
{
|
||||
response = _httpClient.Execute(request, definition);
|
||||
response = _httpClient.ExecuteProxied(request, definition);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
{
|
||||
public override string Name => "PassThePopcorn";
|
||||
public override string[] IndexerUrls => new string[] { "https://passthepopcorn.me" };
|
||||
public override string Description => "";
|
||||
public override string Description => "PassThePopcorn (PTP) is a Private site for MOVIES / TV";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override bool SupportsRss => true;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string[] IndexerUrls => new string[] { "https://pretome.info/" };
|
||||
public override string Description => "PreToMe is a ratioless 0Day/General tracker.";
|
||||
private string LoginUrl => Settings.BaseUrl + "takelogin.php";
|
||||
public override string Language => "en-us";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.GetEncoding("iso-8859-1");
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
@@ -342,7 +342,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var dateStr = Regex.Replace(row.Children[5].InnerHtml, @"\<br[\s]{0,1}[\/]{0,1}\>", " ");
|
||||
var publishDate = DateTimeUtil.FromTimeAgo(dateStr);
|
||||
var files = ParseUtil.CoerceInt(row.Children[3].TextContent);
|
||||
var size = ReleaseInfo.GetBytes(row.Children[7].TextContent);
|
||||
var size = ParseUtil.GetBytes(row.Children[7].TextContent);
|
||||
var grabs = ParseUtil.CoerceInt(row.Children[8].TextContent);
|
||||
var seeders = ParseUtil.CoerceInt(row.Children[9].TextContent);
|
||||
var leechers = ParseUtil.CoerceInt(row.Children[10].TextContent);
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Name => "Redacted";
|
||||
public override string[] IndexerUrls => new string[] { "https://redacted.ch/" };
|
||||
public override string Description => "REDActed (Aka.PassTheHeadPhones) is one of the most well-known music trackers.";
|
||||
public override string Language => "en-us";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
@@ -269,6 +269,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var details = _settings.BaseUrl + qDetails.GetAttribute("href");
|
||||
var title = qDetails.QuerySelector("b").TextContent;
|
||||
|
||||
// Remove auto-generated [REQ] tag from fulfilled requests
|
||||
if (title.StartsWith("[REQ] "))
|
||||
{
|
||||
title = title.Substring(6);
|
||||
}
|
||||
|
||||
var qLink = row.QuerySelector("td:nth-child(4) > a");
|
||||
if (qLink == null)
|
||||
{
|
||||
@@ -281,7 +287,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var dateString = row.QuerySelector("td:nth-child(6) nobr").TextContent.Trim();
|
||||
var publishDate = DateTime.ParseExact(dateString, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture);
|
||||
|
||||
var size = ReleaseInfo.GetBytes(row.QuerySelector("td:nth-child(7)").InnerHtml.Split('<').First().Trim());
|
||||
var size = ParseUtil.GetBytes(row.QuerySelector("td:nth-child(7)").InnerHtml.Split('<').First().Trim());
|
||||
var files = ParseUtil.GetLongFromString(row.QuerySelector("td:nth-child(7) > a").TextContent);
|
||||
var grabs = ParseUtil.GetLongFromString(row.QuerySelector("td:nth-child(8)").TextContent);
|
||||
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(9)").TextContent);
|
||||
|
||||
@@ -1443,6 +1443,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
queryCollection.Add("nm", searchString);
|
||||
}
|
||||
|
||||
if (categories.Length > 0)
|
||||
{
|
||||
queryCollection.Add("f", string.Join(",", Capabilities.Categories.MapTorznabCapsToTrackers(categories)));
|
||||
}
|
||||
|
||||
searchUrl = searchUrl + "?" + queryCollection.GetQueryString();
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||
@@ -1672,7 +1677,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
private long GetSizeOfRelease(in IElement row)
|
||||
{
|
||||
var qSize = row.QuerySelector("td.tor-size");
|
||||
var size = ReleaseInfo.GetBytes(qSize.GetAttribute("data-ts_text"));
|
||||
var size = ParseUtil.GetBytes(qSize.GetAttribute("data-ts_text"));
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Name => "SceneTime";
|
||||
public override string[] IndexerUrls => new[] { "https://www.scenetime.com/" };
|
||||
public override string Description => "Always on time";
|
||||
public override string Language => "en-us";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.GetEncoding("iso-8859-1");
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
@@ -254,7 +254,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
DownloadUrl = string.Format("{0}/download.php/{1}/download.torrent", _settings.BaseUrl, torrentId),
|
||||
Guid = details,
|
||||
PublishDate = DateTimeUtil.FromTimeAgo(qDescCol.ChildNodes.Last().TextContent),
|
||||
Size = ReleaseInfo.GetBytes(sizeStr),
|
||||
Size = ParseUtil.GetBytes(sizeStr),
|
||||
Seeders = seeders,
|
||||
Peers = ParseUtil.CoerceInt(row.Children[leechersIndex].TextContent.Trim()) + seeders,
|
||||
DownloadVolumeFactor = row.QuerySelector("font > b:contains(Freeleech)") != null ? 0 : 1,
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Name => "ShareIsland";
|
||||
public override string[] IndexerUrls => new string[] { "https://shareisland.org/" };
|
||||
public override string Description => "A general italian tracker.";
|
||||
public override string Language => "it-it";
|
||||
public override string Language => "it-IT";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public ShareIsland(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Name => "ShizaProject";
|
||||
public override string[] IndexerUrls => new string[] { "https://shiza-project.com/" };
|
||||
public override string Description => "Shizaproject is russian anime voiceover group and eponymous anime tracker.";
|
||||
public override string Language => "ru-ru";
|
||||
public override string Language => "ru-RU";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public override string Name => "ShowRSS";
|
||||
public override string[] IndexerUrls => new string[] { "https://showrss.info/" };
|
||||
public override string Language => "en-us";
|
||||
public override string Language => "en-US";
|
||||
public override string Description => "showRSS is a service that allows you to keep track of your favorite TV shows";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user