Compare commits

..

1 Commits

Author SHA1 Message Date
Bakerboy448 5e15054329 Avistaz response improvements 2023-01-05 15:40:22 -06:00
260 changed files with 5308 additions and 9977 deletions
+7 -7
View File
@@ -117,6 +117,7 @@ dotnet_diagnostic.CA1003.severity = suggestion
dotnet_diagnostic.CA1008.severity = suggestion dotnet_diagnostic.CA1008.severity = suggestion
dotnet_diagnostic.CA1010.severity = suggestion dotnet_diagnostic.CA1010.severity = suggestion
dotnet_diagnostic.CA1012.severity = suggestion dotnet_diagnostic.CA1012.severity = suggestion
dotnet_diagnostic.CA1014.severity = suggestion
dotnet_diagnostic.CA1016.severity = suggestion dotnet_diagnostic.CA1016.severity = suggestion
dotnet_diagnostic.CA1017.severity = suggestion dotnet_diagnostic.CA1017.severity = suggestion
dotnet_diagnostic.CA1018.severity = suggestion dotnet_diagnostic.CA1018.severity = suggestion
@@ -162,7 +163,6 @@ dotnet_diagnostic.CA1309.severity = suggestion
dotnet_diagnostic.CA1310.severity = suggestion dotnet_diagnostic.CA1310.severity = suggestion
dotnet_diagnostic.CA1401.severity = suggestion dotnet_diagnostic.CA1401.severity = suggestion
dotnet_diagnostic.CA1416.severity = suggestion dotnet_diagnostic.CA1416.severity = suggestion
dotnet_diagnostic.CA1419.severity = suggestion
dotnet_diagnostic.CA1507.severity = suggestion dotnet_diagnostic.CA1507.severity = suggestion
dotnet_diagnostic.CA1508.severity = suggestion dotnet_diagnostic.CA1508.severity = suggestion
dotnet_diagnostic.CA1707.severity = suggestion dotnet_diagnostic.CA1707.severity = suggestion
@@ -178,6 +178,9 @@ dotnet_diagnostic.CA1720.severity = suggestion
dotnet_diagnostic.CA1721.severity = suggestion dotnet_diagnostic.CA1721.severity = suggestion
dotnet_diagnostic.CA1724.severity = suggestion dotnet_diagnostic.CA1724.severity = suggestion
dotnet_diagnostic.CA1725.severity = suggestion dotnet_diagnostic.CA1725.severity = suggestion
dotnet_diagnostic.CA1801.severity = suggestion
dotnet_diagnostic.CA1802.severity = suggestion
dotnet_diagnostic.CA1805.severity = suggestion
dotnet_diagnostic.CA1806.severity = suggestion dotnet_diagnostic.CA1806.severity = suggestion
dotnet_diagnostic.CA1810.severity = suggestion dotnet_diagnostic.CA1810.severity = suggestion
dotnet_diagnostic.CA1812.severity = suggestion dotnet_diagnostic.CA1812.severity = suggestion
@@ -189,14 +192,13 @@ dotnet_diagnostic.CA1819.severity = suggestion
dotnet_diagnostic.CA1822.severity = suggestion dotnet_diagnostic.CA1822.severity = suggestion
dotnet_diagnostic.CA1823.severity = suggestion dotnet_diagnostic.CA1823.severity = suggestion
dotnet_diagnostic.CA1824.severity = suggestion dotnet_diagnostic.CA1824.severity = suggestion
dotnet_diagnostic.CA1835.severity = suggestion
dotnet_diagnostic.CA1845.severity = suggestion
dotnet_diagnostic.CA1848.severity = suggestion
dotnet_diagnostic.CA1849.severity = suggestion
dotnet_diagnostic.CA2000.severity = suggestion dotnet_diagnostic.CA2000.severity = suggestion
dotnet_diagnostic.CA2002.severity = suggestion dotnet_diagnostic.CA2002.severity = suggestion
dotnet_diagnostic.CA2007.severity = suggestion dotnet_diagnostic.CA2007.severity = suggestion
dotnet_diagnostic.CA2008.severity = suggestion dotnet_diagnostic.CA2008.severity = suggestion
dotnet_diagnostic.CA2009.severity = suggestion
dotnet_diagnostic.CA2010.severity = suggestion
dotnet_diagnostic.CA2011.severity = suggestion
dotnet_diagnostic.CA2012.severity = suggestion dotnet_diagnostic.CA2012.severity = suggestion
dotnet_diagnostic.CA2013.severity = suggestion dotnet_diagnostic.CA2013.severity = suggestion
dotnet_diagnostic.CA2100.severity = suggestion dotnet_diagnostic.CA2100.severity = suggestion
@@ -227,7 +229,6 @@ dotnet_diagnostic.CA2243.severity = suggestion
dotnet_diagnostic.CA2244.severity = suggestion dotnet_diagnostic.CA2244.severity = suggestion
dotnet_diagnostic.CA2245.severity = suggestion dotnet_diagnostic.CA2245.severity = suggestion
dotnet_diagnostic.CA2246.severity = suggestion dotnet_diagnostic.CA2246.severity = suggestion
dotnet_diagnostic.CA2254.severity = suggestion
dotnet_diagnostic.CA3061.severity = suggestion dotnet_diagnostic.CA3061.severity = suggestion
dotnet_diagnostic.CA3075.severity = suggestion dotnet_diagnostic.CA3075.severity = suggestion
dotnet_diagnostic.CA3076.severity = suggestion dotnet_diagnostic.CA3076.severity = suggestion
@@ -254,7 +255,6 @@ dotnet_diagnostic.CA5385.severity = suggestion
dotnet_diagnostic.CA5392.severity = suggestion dotnet_diagnostic.CA5392.severity = suggestion
dotnet_diagnostic.CA5394.severity = suggestion dotnet_diagnostic.CA5394.severity = suggestion
dotnet_diagnostic.CA5397.severity = suggestion dotnet_diagnostic.CA5397.severity = suggestion
dotnet_diagnostic.CA5401.severity = suggestion
dotnet_diagnostic.SYSLIB0014.severity = none dotnet_diagnostic.SYSLIB0014.severity = none
+41
View File
@@ -0,0 +1,41 @@
name: Sync issue to Azure DevOps work item
on:
issues:
types:
[opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
concurrency: azuresync-${{ github.event.issue.number }}
jobs:
alert:
runs-on: ubuntu-latest
steps:
- uses: danhellem/github-actions-issue-to-work-item@master
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == true }}"
env:
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
github_token: "${{ github.token }}"
ado_organization: "Servarr"
ado_project: "Servarr"
ado_area_path: "Servarr\\Prowlarr"
ado_wit: "Bug"
ado_new_state: "New"
ado_active_state: "Active"
ado_close_state: "Closed"
ado_bypassrules: true
log_level: 100
- uses: danhellem/github-actions-issue-to-work-item@master
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == false }}"
env:
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
github_token: "${{ github.token }}"
ado_organization: "Servarr"
ado_project: "Servarr"
ado_area_path: "Servarr\\Prowlarr"
ado_wit: "User Story"
ado_new_state: "New"
ado_active_state: "Active"
ado_close_state: "Closed"
ado_bypassrules: true
log_level: 100
+2 -2
View File
@@ -9,13 +9,13 @@ variables:
testsFolder: './_tests' testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '1.2.2' majorVersion: '1.1.0'
minorVersion: $[counter('minorVersion', 1)] minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)' prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)' buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
sentryOrg: 'servarr' sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com' sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.405' dotnetVersion: '6.0.404'
innoVersion: '6.2.0' innoVersion: '6.2.0'
nodeVersion: '16.x' nodeVersion: '16.x'
windowsImage: 'windows-2022' windowsImage: 'windows-2022'
+2 -2
View File
@@ -142,8 +142,8 @@ module.exports = (env) => {
module: { module: {
rules: [ rules: [
{ {
test: /\.jsx?$/, test: /\.js?$/,
exclude: /[\\/]node_modules[\\/](?!(@sentry\/browser|@sentry\/integrations|chart.js|filesize|normalize.css)[\\/])/, exclude: /(node_modules|JsLibraries)/,
use: [ use: [
{ {
loader: 'babel-loader', loader: 'babel-loader',
@@ -50,7 +50,7 @@ function CustomFiltersModalContent(props) {
<div className={styles.addButtonContainer}> <div className={styles.addButtonContainer}>
<Button onPress={onAddCustomFilter}> <Button onPress={onAddCustomFilter}>
{translate('AddCustomFilter')} Add Custom Filter
</Button> </Button>
</div> </div>
</ModalBody> </ModalBody>
@@ -61,15 +61,15 @@ class TagsModalContent extends Component {
} = this.state; } = this.state;
const applyTagsOptions = [ const applyTagsOptions = [
{ key: 'add', value: translate('Add') }, { key: 'add', value: 'Add' },
{ key: 'remove', value: translate('Remove') }, { key: 'remove', value: 'Remove' },
{ key: 'replace', value: translate('Replace') } { key: 'replace', value: 'Replace' }
]; ];
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
{translate('Tags')} Tags
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
@@ -26,7 +26,7 @@ function IndexerIndexSortMenu(props) {
sortDirection={sortDirection} sortDirection={sortDirection}
onPress={onSortSelect} onPress={onSortSelect}
> >
{translate('Status')} Status
</SortMenuItem> </SortMenuItem>
<SortMenuItem <SortMenuItem
@@ -62,7 +62,7 @@ function IndexerIndexSortMenu(props) {
sortDirection={sortDirection} sortDirection={sortDirection}
onPress={onSortSelect} onPress={onSortSelect}
> >
{translate('Priority')} {'Priority'}
</SortMenuItem> </SortMenuItem>
<SortMenuItem <SortMenuItem
@@ -71,7 +71,7 @@ function IndexerIndexSortMenu(props) {
sortDirection={sortDirection} sortDirection={sortDirection}
onPress={onSortSelect} onPress={onSortSelect}
> >
{translate('Protocol')} {'Protocol'}
</SortMenuItem> </SortMenuItem>
<SortMenuItem <SortMenuItem
@@ -80,7 +80,7 @@ function IndexerIndexSortMenu(props) {
sortDirection={sortDirection} sortDirection={sortDirection}
onPress={onSortSelect} onPress={onSortSelect}
> >
{translate('Privacy')} {'Privacy'}
</SortMenuItem> </SortMenuItem>
</MenuContent> </MenuContent>
</SortMenu> </SortMenu>
@@ -79,7 +79,6 @@ class IndexerIndexRow extends Component {
privacy, privacy,
priority, priority,
status, status,
fields,
appProfile, appProfile,
added, added,
capabilities, capabilities,
@@ -97,8 +96,6 @@ class IndexerIndexRow extends Component {
isIndexerInfoModalOpen isIndexerInfoModalOpen
} = this.state; } = this.state;
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? (Array.isArray(indexerUrls) ? indexerUrls[0] : undefined);
return ( return (
<> <>
{ {
@@ -248,12 +245,12 @@ class IndexerIndexRow extends Component {
/> />
{ {
baseUrl ? indexerUrls ?
<IconButton <IconButton
className={styles.externalLink} className={styles.externalLink}
name={icons.EXTERNAL_LINK} name={icons.EXTERNAL_LINK}
title={translate('Website')} title={translate('Website')}
to={baseUrl.replace('api.', '')} to={indexerUrls[0].replace('api.', '')}
/> : null /> : null
} }
@@ -302,7 +299,6 @@ IndexerIndexRow.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
enable: PropTypes.bool.isRequired, enable: PropTypes.bool.isRequired,
redirect: PropTypes.bool.isRequired, redirect: PropTypes.bool.isRequired,
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
appProfile: PropTypes.object.isRequired, appProfile: PropTypes.object.isRequired,
status: PropTypes.object, status: PropTypes.object,
capabilities: PropTypes.object, capabilities: PropTypes.object,
@@ -20,14 +20,11 @@ function IndexerInfoModalContent(props) {
encoding, encoding,
language, language,
indexerUrls, indexerUrls,
fields,
protocol, protocol,
capabilities, capabilities,
onModalClose onModalClose
} = props; } = props;
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? indexerUrls[0];
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
@@ -60,7 +57,7 @@ function IndexerInfoModalContent(props) {
/> />
<DescriptionListItemTitle>{translate('IndexerSite')}</DescriptionListItemTitle> <DescriptionListItemTitle>{translate('IndexerSite')}</DescriptionListItemTitle>
<DescriptionListItemDescription> <DescriptionListItemDescription>
<Link to={baseUrl}>{baseUrl}</Link> <Link to={indexerUrls[0]}>{indexerUrls[0]}</Link>
</DescriptionListItemDescription> </DescriptionListItemDescription>
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle> <DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
<DescriptionListItemDescription> <DescriptionListItemDescription>
@@ -117,7 +114,6 @@ IndexerInfoModalContent.propTypes = {
encoding: PropTypes.string.isRequired, encoding: PropTypes.string.isRequired,
language: PropTypes.string.isRequired, language: PropTypes.string.isRequired,
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired, indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
protocol: PropTypes.string.isRequired, protocol: PropTypes.string.isRequired,
capabilities: PropTypes.object.isRequired, capabilities: PropTypes.object.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
@@ -28,7 +28,7 @@ class AddApplicationModalContent extends Component {
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
{translate('AddApplication')} Add Application
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
@@ -71,14 +71,14 @@ class Application extends Component {
{ {
syncLevel === 'addOnly' && syncLevel === 'addOnly' &&
<Label kind={kinds.WARNING}> <Label kind={kinds.WARNING}>
{translate('AddRemoveOnly')} Add and Remove Only
</Label> </Label>
} }
{ {
syncLevel === 'fullSync' && syncLevel === 'fullSync' &&
<Label kind={kinds.SUCCESS}> <Label kind={kinds.SUCCESS}>
{translate('FullSync')} Full Sync
</Label> </Label>
} }
@@ -88,7 +88,7 @@ class Application extends Component {
kind={kinds.DISABLED} kind={kinds.DISABLED}
outline={true} outline={true}
> >
{translate('Disabled')} Disabled
</Label> </Label>
} }
@@ -106,7 +106,7 @@ class IndexerProxy extends Component {
kind={kinds.DISABLED} kind={kinds.DISABLED}
outline={true} outline={true}
> >
{translate('Disabled')} Disabled
</Label> : </Label> :
null null
} }
+2 -2
View File
@@ -54,7 +54,7 @@ export const defaultState = {
}, },
{ {
name: 'grabTitle', name: 'grabTitle',
label: translate('GrabTitle'), label: translate('Grab Title'),
isSortable: false, isSortable: false,
isVisible: false isVisible: false
}, },
@@ -78,7 +78,7 @@ export const defaultState = {
}, },
{ {
name: 'elapsedTime', name: 'elapsedTime',
label: translate('ElapsedTime'), label: translate('Elapsed Time'),
isSortable: false, isSortable: false,
isVisible: true isVisible: true
}, },
+1 -1
View File
@@ -25,7 +25,7 @@ const columns = [
}, },
{ {
name: 'size', name: 'size',
label: translate('Size'), label: 'Size',
isVisible: true isVisible: true
}, },
{ {
+1 -1
View File
@@ -113,7 +113,7 @@ class Updates extends Component {
/> />
<div className={styles.message}> <div className={styles.message}>
{translate('TheLatestVersionIsAlreadyInstalled', ['Prowlarr'])} The latest version of Prowlarr is already installed
</div> </div>
{ {
+2 -4
View File
@@ -11,8 +11,7 @@
"lint": "esprint check", "lint": "esprint check",
"lint-fix": "esprint check --fix", "lint-fix": "esprint check --fix",
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc", "stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc", "stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc"
"check-modules": "are-you-es5 check . -r"
}, },
"repository": "https://github.com/Prowlarr/Prowlarr", "repository": "https://github.com/Prowlarr/Prowlarr",
"author": "Team Prowlarr", "author": "Team Prowlarr",
@@ -31,7 +30,7 @@
"@fortawesome/free-regular-svg-icons": "6.2.1", "@fortawesome/free-regular-svg-icons": "6.2.1",
"@fortawesome/free-solid-svg-icons": "6.2.1", "@fortawesome/free-solid-svg-icons": "6.2.1",
"@fortawesome/react-fontawesome": "0.2.0", "@fortawesome/react-fontawesome": "0.2.0",
"@microsoft/signalr": "6.0.13", "@microsoft/signalr": "6.0.11",
"@sentry/browser": "7.28.0", "@sentry/browser": "7.28.0",
"@sentry/integrations": "7.28.0", "@sentry/integrations": "7.28.0",
"chart.js": "4.1.1", "chart.js": "4.1.1",
@@ -94,7 +93,6 @@
"@babel/plugin-syntax-dynamic-import": "7.8.3", "@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.20.2", "@babel/preset-env": "7.20.2",
"@babel/preset-react": "7.18.6", "@babel/preset-react": "7.18.6",
"are-you-es5": "2.1.2",
"autoprefixer": "10.4.13", "autoprefixer": "10.4.13",
"babel-loader": "9.1.0", "babel-loader": "9.1.0",
"babel-plugin-inline-classnames": "2.0.1", "babel-plugin-inline-classnames": "2.0.1",
-3
View File
@@ -1,3 +0,0 @@
is_global = true
dotnet_diagnostic.CA1014.severity = none
-1
View File
@@ -1,7 +1,6 @@
<Project> <Project>
<!-- Common to all Prowlarr Projects --> <!-- Common to all Prowlarr Projects -->
<PropertyGroup> <PropertyGroup>
<AnalysisLevel>6.0-all</AnalysisLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles> <ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
@@ -53,26 +53,6 @@ namespace NzbDrone.Common.Test.Http
newUri.FullUri.Should().Be(expected); newUri.FullUri.Should().Be(expected);
} }
[TestCase("", "./relative", "relative")]
[TestCase("/", "./relative", "/relative")]
[TestCase("/base", "./relative", "/relative")]
[TestCase("/base/sub", "./relative", "/base/relative")]
[TestCase("/base/sub/", "./relative", "/base/sub/relative")]
[TestCase("base/sub", "./relative", "base/relative")]
[TestCase("base/sub/", "./relative", "base/sub/relative")]
[TestCase("", "../relative", "relative")]
[TestCase("/", "../relative", "/relative")]
[TestCase("/base", "../relative", "/relative")]
[TestCase("/base/sub", "../relative", "/base/relative")]
[TestCase("/base/sub/", "../relative", "/base/sub/relative")]
[TestCase("base/sub", "../relative", "base/relative")]
[TestCase("base/sub/", "../relative", "base/sub/relative")]
public void should_combine_uri_with_dot_segment(string basePath, string relativePath, string expected)
{
var newUri = new HttpUri(basePath) + new HttpUri(relativePath);
newUri.FullUri.Should().Be(expected);
}
[TestCase("", "", "")] [TestCase("", "", "")]
[TestCase("/", "", "/")] [TestCase("/", "", "/")]
[TestCase("base", "", "base")] [TestCase("base", "", "base")]
@@ -24,16 +24,12 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"https://beyond-hd.me/api/torrents/2b51db35e1912ffc138825a12b9933d2")] [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")] [TestCase(@"Req: [POST] https://www3.yggtorrent.nz/user/login: id=mySecret&pass=mySecret&ci_csrf_token=2b51db35e1912ffc138825a12b9933d2")]
[TestCase(@"https://torrentseeds.org/api/torrents/filter?api_token=2b51db35e1912ffc138825a12b9933d2&name=&sortField=created_at&sortDirection=desc&perPage=100&page=1")] [TestCase(@"https://torrentseeds.org/api/torrents/filter?api_token=2b51db35e1912ffc138825a12b9933d2&name=&sortField=created_at&sortDirection=desc&perPage=100&page=1")]
[TestCase(@"https://beyond-hd.me/torrent/download/the-next-365-days-2022-2160p-nf-web-dl-dual-ddp-51-dovi-hdr-hevc-apex.225146.2b51db35e1912ffc138825a12b9933d2")]
[TestCase(@"https://anthelion.me/api.php?api_key=2b51db35e1910123321025a12b9933d2&o=json&t=movie&q=&tmdb=&imdb=&cat=&limit=100&offset=0")]
[TestCase(@"https://avistaz.to/api/v1/jackett/auth: username=mySecret&password=mySecret&pid=mySecret")]
// Indexer and Download Client Responses // Indexer and Download Client Responses
// avistaz response // avistaz response
[TestCase(@"""download"":""https://avistaz.to/rss/download/2b51db35e1910123321025a12b9933d2/tb51db35e1910123321025a12b9933d2.torrent"",")] [TestCase(@"""download"":""https://avistaz.to/rss/download/2b51db35e1910123321025a12b9933d2/tb51db35e1910123321025a12b9933d2.torrent"",")]
[TestCase(@",""info_hash"":""2b51db35e1910123321025a12b9933d2"",")] [TestCase(@",""info_hash"":""2b51db35e1910123321025a12b9933d2"",")]
[TestCase(@"""token"":""2b51db35e1910123321025a12b9933d2""")]
// animebytes response // animebytes response
[TestCase(@"""Link"":""https://animebytes.tv/torrent/994064/download/tb51db35e1910123321025a12b9933d2"",")] [TestCase(@"""Link"":""https://animebytes.tv/torrent/994064/download/tb51db35e1910123321025a12b9933d2"",")]
@@ -131,7 +131,7 @@ namespace NzbDrone.Common.Extensions
public static string WrapInQuotes(this string text) public static string WrapInQuotes(this string text)
{ {
if (!text.Contains(' ')) if (!text.Contains(" "))
{ {
return text; return text;
} }
@@ -255,7 +255,7 @@ namespace NzbDrone.Common.Extensions
public static string ToUrlHost(this string input) public static string ToUrlHost(this string input)
{ {
return input.Contains(':') ? $"[{input}]" : input; return input.Contains(":") ? $"[{input}]" : input;
} }
} }
} }
@@ -113,7 +113,7 @@ namespace NzbDrone.Common.Http.Dispatchers
{ {
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK) if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
{ {
await responseMessage.Content.CopyToAsync(request.ResponseStream, null, cts.Token); responseMessage.Content.CopyTo(request.ResponseStream, null, cts.Token);
} }
else else
{ {
-1
View File
@@ -76,7 +76,6 @@ namespace NzbDrone.Common.Http
get get
{ {
var newUrl = Headers["Location"]; var newUrl = Headers["Location"];
if (newUrl == null) if (newUrl == null)
{ {
newUrl = Headers["Refresh"]; newUrl = Headers["Refresh"];
-31
View File
@@ -166,37 +166,6 @@ namespace NzbDrone.Common.Http
return relativePath; return relativePath;
} }
if (relativePath.StartsWith("./"))
{
relativePath = relativePath.TrimStart('.').TrimStart('/');
var lastIndex = basePath.LastIndexOf("/");
if (lastIndex > 0)
{
basePath = basePath.Substring(0, lastIndex) + "/";
}
}
if (relativePath.StartsWith("../"))
{
relativePath = relativePath.TrimStart('.').TrimStart('/');
var lastIndex = basePath.LastIndexOf("/");
if (lastIndex > 0)
{
basePath = basePath.Substring(0, lastIndex) + "/";
}
var secondLastIndex = basePath.LastIndexOf("/");
if (lastIndex > 0)
{
basePath = basePath.Substring(0, secondLastIndex) + "/";
}
}
var baseSlashIndex = basePath.LastIndexOf('/'); var baseSlashIndex = basePath.LastIndexOf('/');
if (baseSlashIndex >= 0) if (baseSlashIndex >= 0)
@@ -11,13 +11,15 @@ namespace NzbDrone.Common.Http
{ {
if (response.Headers.ContainsKey("Retry-After")) if (response.Headers.ContainsKey("Retry-After"))
{ {
var retryAfter = response.Headers["Retry-After"]; var retryAfter = response.Headers["Retry-After"].ToString();
int seconds;
DateTime date;
if (int.TryParse(retryAfter, out var seconds)) if (int.TryParse(retryAfter, out seconds))
{ {
RetryAfter = TimeSpan.FromSeconds(seconds); RetryAfter = TimeSpan.FromSeconds(seconds);
} }
else if (DateTime.TryParse(retryAfter, out var date)) else if (DateTime.TryParse(retryAfter, out date))
{ {
RetryAfter = date.ToUniversalTime() - DateTime.UtcNow; RetryAfter = date.ToUniversalTime() - DateTime.UtcNow;
} }
@@ -1,3 +1,4 @@
using System;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -7,10 +8,10 @@ namespace NzbDrone.Common.Instrumentation
{ {
public class CleanseLogMessage public class CleanseLogMessage
{ {
private static readonly Regex[] CleansingRules = private static readonly Regex[] CleansingRules = new[]
{ {
// Url // Url
new Regex(@"(?<=[?&: ;])(apikey|api_key|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pid|pwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"(?<=[?&: ;])(apikey|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)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)(?<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(@"rss\.torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
@@ -20,7 +21,6 @@ namespace NzbDrone.Common.Instrumentation
new Regex(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=authkey = "")(?<secret>[^&=]+?)(?="")", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"(?<=authkey = "")(?<secret>[^&=]+?)(?="")", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=beyond-hd\.[a-z]+/api/torrents/)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"(?<=beyond-hd\.[a-z]+/api/torrents/)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=beyond-hd\.[a-z]+/torrent/download/[\w\d-]+[.]\d+[.])(?<secret>[a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// UNIT3D // UNIT3D
new Regex(@"(?<=[a-z0-9-]+\.[a-z]+/torrent/download/\d+\.)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"(?<=[a-z0-9-]+\.[a-z]+/torrent/download/\d+\.)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
@@ -58,10 +58,9 @@ namespace NzbDrone.Common.Instrumentation
new Regex(@"(?:avistaz|exoticaz|cinemaz|privatehd)\.[a-z]{2,3}/rss/download/(?<secret>[^&=]+?)/(?<secret>[^&=]+?)\.torrent", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"(?:avistaz|exoticaz|cinemaz|privatehd)\.[a-z]{2,3}/rss/download/(?<secret>[^&=]+?)/(?<secret>[^&=]+?)\.torrent", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?:animebytes)\.[a-z]{2,3}/torrent/[0-9]+/download/(?<secret>[^&=]+?)[""]", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"(?:animebytes)\.[a-z]{2,3}/torrent/[0-9]+/download/(?<secret>[^&=]+?)[""]", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@",""info_hash"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@",""info_hash"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"""token"":""(?<secret>[^&=]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@",""pass[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@",""pass[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@",""rss[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@",""rss[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
}; };
private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled); private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
@@ -8,7 +8,6 @@ using System.Threading;
using NLog; using NLog;
using NLog.Common; using NLog.Common;
using NLog.Targets; using NLog.Targets;
using Npgsql;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using Sentry; using Sentry;
@@ -35,14 +34,6 @@ namespace NzbDrone.Common.Instrumentation.Sentry
SQLiteErrorCode.Auth SQLiteErrorCode.Auth
}; };
private static readonly HashSet<string> FilteredPostgresErrorCodes = new HashSet<string>
{
PostgresErrorCodes.OutOfMemory,
PostgresErrorCodes.TooManyConnections,
PostgresErrorCodes.DiskFull,
PostgresErrorCodes.ProgramLimitExceeded
};
// use string and not Type so we don't need a reference to the project // use string and not Type so we don't need a reference to the project
// where these are defined // where these are defined
private static readonly HashSet<string> FilteredExceptionTypeNames = new HashSet<string> private static readonly HashSet<string> FilteredExceptionTypeNames = new HashSet<string>
@@ -248,19 +239,6 @@ namespace NzbDrone.Common.Instrumentation.Sentry
return false; return false;
} }
var pgEx = logEvent.Exception as PostgresException;
if (pgEx != null && FilteredPostgresErrorCodes.Contains(pgEx.SqlState))
{
return false;
}
// We don't care about transient network and timeout errors
var npgEx = logEvent.Exception as NpgsqlException;
if (npgEx != null && npgEx.IsTransient)
{
return false;
}
if (FilteredExceptionTypeNames.Contains(logEvent.Exception.GetType().Name)) if (FilteredExceptionTypeNames.Contains(logEvent.Exception.GetType().Name))
{ {
return false; return false;
+1 -1
View File
@@ -226,7 +226,7 @@ namespace NzbDrone.Common.OAuth
#if WINRT #if WINRT
return CultureInfo.InvariantCulture.CompareInfo.Compare(left, right, CompareOptions.IgnoreCase) == 0; return CultureInfo.InvariantCulture.CompareInfo.Compare(left, right, CompareOptions.IgnoreCase) == 0;
#else #else
return string.Equals(left, right, StringComparison.InvariantCultureIgnoreCase); return string.Compare(left, right, StringComparison.InvariantCultureIgnoreCase) == 0;
#endif #endif
} }
+1 -2
View File
@@ -4,13 +4,12 @@
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants> <DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DryIoc.dll" Version="5.3.3" /> <PackageReference Include="DryIoc.dll" Version="5.3.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="NLog" Version="5.1.0" /> <PackageReference Include="NLog" Version="5.1.0" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.0" /> <PackageReference Include="NLog.Extensions.Logging" Version="5.2.0" />
<PackageReference Include="Npgsql" Version="5.0.11" />
<PackageReference Include="Sentry" Version="3.24.1" /> <PackageReference Include="Sentry" Version="3.24.1" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" /> <PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="SharpZipLib" Version="1.3.3" /> <PackageReference Include="SharpZipLib" Version="1.3.3" />
+1 -1
View File
@@ -64,7 +64,7 @@ namespace NzbDrone.Common
var args = $"create {serviceName} " + var args = $"create {serviceName} " +
$"DisplayName= \"{serviceName}\" " + $"DisplayName= \"{serviceName}\" " +
$"binpath= \"{Environment.ProcessPath}\" " + $"binpath= \"{Process.GetCurrentProcess().MainModule.FileName}\" " +
"start= auto " + "start= auto " +
"depend= EventLog/Tcpip/http " + "depend= EventLog/Tcpip/http " +
"obj= \"NT AUTHORITY\\LocalService\""; "obj= \"NT AUTHORITY\\LocalService\"";
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@@ -19,7 +19,7 @@ namespace NzbDrone.Common.TPL
private readonly int _maxDegreeOfParallelism; private readonly int _maxDegreeOfParallelism;
/// <summary>Whether the scheduler is currently processing work items.</summary> /// <summary>Whether the scheduler is currently processing work items.</summary>
private int _delegatesQueuedOrRunning; private int _delegatesQueuedOrRunning = 0;
/// <summary> /// <summary>
/// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the /// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://avistaz.to/torrent/187240-japan-sinks-people-of-hope-2021-s01e05-720p-nf-web-dl-ddp20-x264-seikel"); torrentInfo.InfoUrl.Should().Be("https://avistaz.to/torrent/187240-japan-sinks-people-of-hope-2021-s01e05-720p-nf-web-dl-ddp20-x264-seikel");
torrentInfo.CommentUrl.Should().BeNullOrEmpty(); torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name); torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-14 22:26:21")); torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-15 04:26:21"));
torrentInfo.Size.Should().Be(935127615); torrentInfo.Size.Should().Be(935127615);
torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2"); torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
torrentInfo.MagnetUrl.Should().Be(null); torrentInfo.MagnetUrl.Should().Be(null);
@@ -8,7 +8,7 @@ using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions.FileList; using NzbDrone.Core.Indexers.FileList;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
@@ -21,15 +21,10 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
Subject.Definition = new IndexerDefinition Subject.Definition = new IndexerDefinition()
{ {
Name = "FileList", Name = "FileList",
Settings = new FileListSettings Settings = new FileListSettings() { Username = "someuser", Passkey = "somepass" }
{
BaseUrl = "https://filelist.io/",
Username = "someuser",
Passkey = "somepass"
}
}; };
} }
@@ -40,9 +35,9 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
Mocker.GetMock<IIndexerHttpClient>() Mocker.GetMock<IIndexerHttpClient>()
.Setup(o => o.ExecuteProxiedAsync(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))); .Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new[] { 2000 } })).Releases; var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } })).Releases;
releases.Should().HaveCount(4); releases.Should().HaveCount(4);
releases.First().Should().BeOfType<TorrentInfo>(); releases.First().Should().BeOfType<TorrentInfo>();
@@ -55,14 +50,12 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
torrentInfo.InfoUrl.Should().Be("https://filelist.io/details.php?id=665873"); torrentInfo.InfoUrl.Should().Be("https://filelist.io/details.php?id=665873");
torrentInfo.CommentUrl.Should().BeNullOrEmpty(); torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name); torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 20:20:19")); torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 20:20:19").ToUniversalTime());
torrentInfo.Size.Should().Be(8300512414); torrentInfo.Size.Should().Be(8300512414);
torrentInfo.InfoHash.Should().Be(null); torrentInfo.InfoHash.Should().Be(null);
torrentInfo.MagnetUrl.Should().Be(null); torrentInfo.MagnetUrl.Should().Be(null);
torrentInfo.Peers.Should().Be(2 + 12); torrentInfo.Peers.Should().Be(2 + 12);
torrentInfo.Seeders.Should().Be(12); torrentInfo.Seeders.Should().Be(12);
releases.Any(t => t.IndexerFlags.Contains(IndexerFlag.Internal)).Should().Be(true);
} }
} }
} }
@@ -3,7 +3,7 @@ using System.Linq;
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions.FileList; using NzbDrone.Core.Indexers.FileList;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
@@ -16,35 +16,34 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
Subject.Settings = new FileListSettings Subject.Settings = new FileListSettings()
{ {
BaseUrl = "https://filelist.io/",
Passkey = "abcd", Passkey = "abcd",
Username = "somename" Username = "somename",
BaseUrl = "https://filelist.io"
}; };
Subject.Capabilities = new IndexerCapabilities Subject.Capabilities = new IndexerCapabilities
{ {
TvSearchParams = new List<TvSearchParam> TvSearchParams = new List<TvSearchParam>
{ {
TvSearchParam.Q, TvSearchParam.ImdbId, TvSearchParam.Season, TvSearchParam.Ep TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
}, },
MovieSearchParams = new List<MovieSearchParam> MovieSearchParams = new List<MovieSearchParam>
{ {
MovieSearchParam.Q, MovieSearchParam.ImdbId MovieSearchParam.Q, MovieSearchParam.ImdbId
}, },
MusicSearchParams = new List<MusicSearchParam> MusicSearchParams = new List<MusicSearchParam>
{ {
MusicSearchParam.Q MusicSearchParam.Q
}, },
BookSearchParams = new List<BookSearchParam> BookSearchParams = new List<BookSearchParam>
{ {
BookSearchParam.Q BookSearchParam.Q
}, },
Flags = new List<IndexerFlag> Flags = new List<IndexerFlag>
{ {
IndexerFlag.FreeLeech, IndexerFlag.FreeLeech
IndexerFlag.Internal,
} }
}; };
@@ -54,7 +53,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
_movieSearchCriteria = new MovieSearchCriteria _movieSearchCriteria = new MovieSearchCriteria
{ {
SearchTerm = "Star Wars", SearchTerm = "Star Wars",
Categories = new[] { 2000 } Categories = new int[] { 2000 }
}; };
} }
@@ -66,13 +65,13 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
[Test] [Test]
public void should_use_categories_for_feed() public void should_use_categories_for_feed()
{ {
var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new[] { NewznabStandardCategory.MoviesSD.Id, NewznabStandardCategory.MoviesDVD.Id } }); var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new int[] { NewznabStandardCategory.MoviesSD.Id, NewznabStandardCategory.MoviesDVD.Id } });
results.GetAllTiers().Should().HaveCount(1); results.GetAllTiers().Should().HaveCount(1);
var page = results.GetAllTiers().First().First(); var page = results.GetAllTiers().First().First();
page.Url.Query.Should().Contain("&category=1%2C2"); page.Url.Query.Should().Contain("&category=1,2&");
} }
[Test] [Test]
@@ -101,7 +100,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
var page = results.GetAllTiers().First().First(); var page = results.GetAllTiers().First().First();
page.Url.Query.Should().Contain("type=name"); page.Url.Query.Should().Contain("type=name");
page.Url.Query.Should().Contain("query=Star+Wars"); page.Url.Query.Should().Contain("query=Star Wars");
} }
} }
} }
@@ -10,7 +10,7 @@ using NUnit.Framework;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions.HDBits; using NzbDrone.Core.Indexers.HDBits;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
@@ -5,7 +5,7 @@ using Newtonsoft.Json;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions.HDBits; using NzbDrone.Core.Indexers.HDBits;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
@@ -14,13 +14,11 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
public class HDBitsRequestGeneratorFixture : CoreTest<HDBitsRequestGenerator> public class HDBitsRequestGeneratorFixture : CoreTest<HDBitsRequestGenerator>
{ {
private MovieSearchCriteria _movieSearchCriteria; private MovieSearchCriteria _movieSearchCriteria;
private TvSearchCriteria _tvSearchSeasonEpisodeCriteria;
private TvSearchCriteria _tvSearchDailyEpisodeCriteria;
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
Subject.Settings = new HDBitsSettings Subject.Settings = new HDBitsSettings()
{ {
ApiKey = "abcd", ApiKey = "abcd",
Username = "somename" Username = "somename"
@@ -49,25 +47,9 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
_movieSearchCriteria = new MovieSearchCriteria _movieSearchCriteria = new MovieSearchCriteria
{ {
Categories = new[] { 2000, 2010 }, Categories = new int[] { 2000, 2010 },
ImdbId = "0076759" ImdbId = "0076759"
}; };
_tvSearchSeasonEpisodeCriteria = new TvSearchCriteria
{
Categories = new[] { 5000, 5010 },
TvdbId = 392256,
Season = 1,
Episode = "3"
};
_tvSearchDailyEpisodeCriteria = new TvSearchCriteria
{
Categories = new[] { 5000, 5010 },
TvdbId = 289574,
Season = 2023,
Episode = "01/03"
};
} }
[Test] [Test]
@@ -88,49 +70,5 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
query.Category.Should().HaveCount(1); query.Category.Should().HaveCount(1);
query.ImdbInfo.Id.Should().Be(imdbQuery); query.ImdbInfo.Id.Should().Be(imdbQuery);
} }
[Test]
public void should_search_by_tvdbid_season_episode_if_supported()
{
var results = Subject.GetSearchRequests(_tvSearchSeasonEpisodeCriteria);
var tvdbQuery = _tvSearchSeasonEpisodeCriteria.TvdbId;
results.GetAllTiers().Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var encoding = HttpHeader.GetEncodingFromContentType(page.HttpRequest.Headers.ContentType);
var body = encoding.GetString(page.HttpRequest.ContentData);
var query = JsonConvert.DeserializeObject<TorrentQuery>(body);
query.Category.Should().HaveCount(3);
query.TvdbInfo.Id.Should().Be(tvdbQuery);
query.Search.Should().BeNullOrWhiteSpace();
query.TvdbInfo.Season.Should().Be(1);
query.TvdbInfo.Episode.Should().Be("3");
}
[Test]
public void should_search_by_tvdbid_daily_episode_if_supported()
{
var results = Subject.GetSearchRequests(_tvSearchDailyEpisodeCriteria);
var tvdbQuery = _tvSearchDailyEpisodeCriteria.TvdbId;
results.GetAllTiers().Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
var encoding = HttpHeader.GetEncodingFromContentType(page.HttpRequest.Headers.ContentType);
var body = encoding.GetString(page.HttpRequest.ContentData);
var query = JsonConvert.DeserializeObject<TorrentQuery>(body);
query.Category.Should().HaveCount(3);
query.TvdbInfo.Id.Should().Be(tvdbQuery);
query.Search.Should().Be("2023-01-03");
query.TvdbInfo.Season.Should().BeNull();
query.TvdbInfo.Episode.Should().BeNull();
}
} }
} }
@@ -9,7 +9,7 @@ using NUnit.Framework;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions; using NzbDrone.Core.Indexers.Definitions;
using NzbDrone.Core.Indexers.Definitions.Gazelle; using NzbDrone.Core.Indexers.Gazelle;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Test.IndexerTests.OrpheusTests
Subject.Definition = new IndexerDefinition() Subject.Definition = new IndexerDefinition()
{ {
Name = "Orpheus", Name = "Orpheus",
Settings = new OrpheusSettings { Apikey = "somekey" } Settings = new OrpheusSettings() { Apikey = "somekey" }
}; };
} }
@@ -37,14 +37,14 @@ namespace NzbDrone.Core.Test.IndexerTests.OrpheusTests
.Setup(o => o.ExecuteProxiedAsync(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))); .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 BasicSearchCriteria { Categories = new[] { 2000 } })).Releases; var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new int[] { 2000 } })).Releases;
releases.Should().HaveCount(65); releases.Should().HaveCount(65);
releases.First().Should().BeOfType<GazelleInfo>(); releases.First().Should().BeOfType<GazelleInfo>();
var torrentInfo = releases.First() as GazelleInfo; var torrentInfo = releases.First() as GazelleInfo;
torrentInfo.Title.Should().Be("The Beatles - Abbey Road [1969] [Album] [2.0 Mix 2019] [MP3 V2 (VBR)] [BD]"); torrentInfo.Title.Should().Be("The Beatles - Abbey Road (1969) [MP3 V2 (VBR)] [BD]");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent); torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("https://orpheus.network/ajax.php?action=download&id=1902448"); torrentInfo.DownloadUrl.Should().Be("https://orpheus.network/ajax.php?action=download&id=1902448");
torrentInfo.InfoUrl.Should().Be("https://orpheus.network/torrents.php?id=466&torrentid=1902448"); torrentInfo.InfoUrl.Should().Be("https://orpheus.network/torrents.php?id=466&torrentid=1902448");
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
torrentInfo.Title.Should().Be("Sense8.S01E01.WEBRip.x264-FGT"); torrentInfo.Title.Should().Be("Sense8.S01E01.WEBRip.x264-FGT");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent); torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("magnet:?xt=urn:btih:d8bde635f573acb390c7d7e7efc1556965fdc802&dn=Sense8.S01E01.WEBRip.x264-FGT&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce"); torrentInfo.DownloadUrl.Should().Be("magnet:?xt=urn:btih:d8bde635f573acb390c7d7e7efc1556965fdc802&dn=Sense8.S01E01.WEBRip.x264-FGT&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce");
torrentInfo.InfoUrl.Should().Be($"https://torrentapi.org/redirect_to_info.php?token=i5cx7b9agd&p=8_6_4_4_5_6__d8bde635f5&app_id={BuildInfo.AppName}_{BuildInfo.Version}"); torrentInfo.InfoUrl.Should().Be($"https://torrentapi.org/redirect_to_info.php?token=i5cx7b9agd&p=8_6_4_4_5_6__d8bde635f5&app_id={BuildInfo.AppName}");
torrentInfo.Indexer.Should().Be(Subject.Definition.Name); torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2015-06-05 16:58:11 +0000").ToUniversalTime()); torrentInfo.PublishDate.Should().Be(DateTime.Parse("2015-06-05 16:58:11 +0000").ToUniversalTime());
torrentInfo.Size.Should().Be(564198371); torrentInfo.Size.Should().Be(564198371);
@@ -9,7 +9,7 @@ using NUnit.Framework;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions; using NzbDrone.Core.Indexers.Definitions;
using NzbDrone.Core.Indexers.Definitions.Gazelle; using NzbDrone.Core.Indexers.Gazelle;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
@@ -162,7 +162,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
releaseInfo.InfoHash.Should().Be("(removed)"); releaseInfo.InfoHash.Should().Be("(removed)");
releaseInfo.Seeders.Should().Be(3); releaseInfo.Seeders.Should().Be(3);
releaseInfo.Peers.Should().Be(3); releaseInfo.Peers.Should().Be(3);
releaseInfo.Categories.Count.Should().Be(4); releaseInfo.Categories.Count().Should().Be(4);
} }
[Test] [Test]
@@ -6,7 +6,7 @@
<PackageReference Include="Dapper" Version="2.0.123" /> <PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="NBuilder" Version="6.1.0" /> <PackageReference Include="NBuilder" Version="6.1.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" /> <PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="YamlDotNet" Version="13.0.0" /> <PackageReference Include="YamlDotNet" Version="12.3.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" /> <ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />
@@ -147,7 +147,6 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
Categories = string.Join(",", indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())), Categories = string.Join(",", indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())),
Enabled = indexer.Enable, Enabled = indexer.Enable,
Type = schema, Type = schema,
Priority = indexer.Priority
}; };
return lazyLibrarianIndexer; return lazyLibrarianIndexer;
@@ -30,7 +30,6 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
public bool Enabled { get; set; } public bool Enabled { get; set; }
public string Altername { get; set; } public string Altername { get; set; }
public LazyLibrarianProviderType Type { get; set; } public LazyLibrarianProviderType Type { get; set; }
public int Priority { get; set; }
public bool Equals(LazyLibrarianIndexer other) public bool Equals(LazyLibrarianIndexer other)
{ {
@@ -44,8 +43,7 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
other.Name == Name && other.Name == Name &&
other.Categories == Categories && other.Categories == Categories &&
other.Enabled == Enabled && other.Enabled == Enabled &&
other.Altername == Altername && other.Altername == Altername;
other.Priority == Priority;
} }
} }
} }
@@ -21,8 +21,6 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
public class LazyLibrarianV1Proxy : ILazyLibrarianV1Proxy public class LazyLibrarianV1Proxy : ILazyLibrarianV1Proxy
{ {
private const int ProwlarrHighestPriority = 50;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly Logger _logger; private readonly Logger _logger;
@@ -92,8 +90,7 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
{ "host", indexer.Host }, { "host", indexer.Host },
{ "prov_apikey", indexer.Apikey }, { "prov_apikey", indexer.Apikey },
{ "enabled", indexer.Enabled.ToString().ToLower() }, { "enabled", indexer.Enabled.ToString().ToLower() },
{ "categories", indexer.Categories }, { "categories", indexer.Categories }
{ "dlpriority", CalculatePriority(indexer.Priority).ToString() }
}; };
var request = BuildRequest(settings, "/api", "addProvider", HttpMethod.Get, parameters); var request = BuildRequest(settings, "/api", "addProvider", HttpMethod.Get, parameters);
@@ -111,8 +108,7 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
{ "prov_apikey", indexer.Apikey }, { "prov_apikey", indexer.Apikey },
{ "enabled", indexer.Enabled.ToString().ToLower() }, { "enabled", indexer.Enabled.ToString().ToLower() },
{ "categories", indexer.Categories }, { "categories", indexer.Categories },
{ "altername", indexer.Altername }, { "altername", indexer.Altername }
{ "dlpriority", CalculatePriority(indexer.Priority).ToString() }
}; };
var request = BuildRequest(settings, "/api", "changeProvider", HttpMethod.Get, parameters); var request = BuildRequest(settings, "/api", "changeProvider", HttpMethod.Get, parameters);
@@ -195,7 +191,5 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
return results; return results;
} }
private int CalculatePriority(int indexerPriority) => ProwlarrHighestPriority - indexerPriority + 1;
} }
} }
@@ -196,7 +196,7 @@ namespace NzbDrone.Core.Applications.Lidarr
if (indexer.Protocol == DownloadProtocol.Torrent) if (indexer.Protocol == DownloadProtocol.Torrent)
{ {
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders; lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders;
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio; lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime; lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
@@ -196,7 +196,7 @@ namespace NzbDrone.Core.Applications.Radarr
if (indexer.Protocol == DownloadProtocol.Torrent) if (indexer.Protocol == DownloadProtocol.Torrent)
{ {
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders; radarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders;
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio; radarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime; radarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
} }
@@ -192,7 +192,7 @@ namespace NzbDrone.Core.Applications.Readarr
if (indexer.Protocol == DownloadProtocol.Torrent) if (indexer.Protocol == DownloadProtocol.Torrent)
{ {
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders; readarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders;
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio; readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime; readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
@@ -198,7 +198,7 @@ namespace NzbDrone.Core.Applications.Sonarr
if (indexer.Protocol == DownloadProtocol.Torrent) if (indexer.Protocol == DownloadProtocol.Torrent)
{ {
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders; sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio; sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime; sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.PackSeedTime ?? ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime; sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.PackSeedTime ?? ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
@@ -192,7 +192,7 @@ namespace NzbDrone.Core.Applications.Whisparr
if (indexer.Protocol == DownloadProtocol.Torrent) if (indexer.Protocol == DownloadProtocol.Torrent)
{ {
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders; whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders;
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio; whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime; whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
} }
@@ -19,14 +19,14 @@ namespace NzbDrone.Core.Authentication
public class UserService : IUserService public class UserService : IUserService
{ {
private const int ITERATIONS = 10000;
private const int SALT_SIZE = 128 / 8;
private const int NUMBER_OF_BYTES = 256 / 8;
private readonly IUserRepository _repo; private readonly IUserRepository _repo;
private readonly IAppFolderInfo _appFolderInfo; private readonly IAppFolderInfo _appFolderInfo;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private static readonly int ITERATIONS = 10000;
private static readonly int SALT_SIZE = 128 / 8;
private static readonly int NUMBER_OF_BYTES = 256 / 8;
public UserService(IUserRepository repo, IAppFolderInfo appFolderInfo, IDiskProvider diskProvider) public UserService(IUserRepository repo, IAppFolderInfo appFolderInfo, IDiskProvider diskProvider)
{ {
_repo = repo; _repo = repo;
@@ -1,14 +0,0 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(025)]
public class speedcd_userpasssettings_to_speedcdsettings : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Update.Table("Indexers").Set(new { ConfigContract = "SpeedCDSettings" }).Where(new { Implementation = "SpeedCD" });
}
}
}
@@ -1,14 +0,0 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(026)]
public class torrentday_cookiesettings_to_torrentdaysettings : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Update.Table("Indexers").Set(new { ConfigContract = "TorrentDaySettings" }).Where(new { Implementation = "TorrentDay" });
}
}
}
@@ -1,14 +0,0 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration;
[Migration(027)]
public class alpharatio_greatposterwall_config_contract : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Update.Table("Indexers").Set(new { ConfigContract = "AlphaRatioSettings" }).Where(new { Implementation = "AlphaRatio" });
Update.Table("Indexers").Set(new { ConfigContract = "GreatPosterWallSettings" }).Where(new { Implementation = "GreatPosterWall" });
}
}
@@ -1,14 +0,0 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration;
[Migration(028)]
public class remove_notwhatcd : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
// Remove, site dead
Delete.FromTable("Indexers").Row(new { Implementation = "NotWhatCD" });
}
}
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Text; using System.Text;
@@ -250,7 +250,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
} }
Index = end + 1; Index = end + 1;
identifier.Append(Buffer.AsSpan(start, end - start)); identifier.Append(Buffer.Substring(start, end - start));
if (Buffer[Index] != escape) if (Buffer[Index] != escape)
{ {
@@ -15,9 +15,9 @@ namespace NzbDrone.Core.Datastore
private const DbType EnumerableMultiParameter = (DbType)(-1); private const DbType EnumerableMultiParameter = (DbType)(-1);
private readonly string _paramNamePrefix; private readonly string _paramNamePrefix;
private readonly bool _requireConcreteValue; private readonly bool _requireConcreteValue = false;
private int _paramCount; private int _paramCount = 0;
private bool _gotConcreteValue; private bool _gotConcreteValue = false;
public WhereBuilderPostgres(Expression filter, bool requireConcreteValue, int seq) public WhereBuilderPostgres(Expression filter, bool requireConcreteValue, int seq)
{ {
@@ -15,9 +15,9 @@ namespace NzbDrone.Core.Datastore
private const DbType EnumerableMultiParameter = (DbType)(-1); private const DbType EnumerableMultiParameter = (DbType)(-1);
private readonly string _paramNamePrefix; private readonly string _paramNamePrefix;
private readonly bool _requireConcreteValue; private readonly bool _requireConcreteValue = false;
private int _paramCount; private int _paramCount = 0;
private bool _gotConcreteValue; private bool _gotConcreteValue = false;
public WhereBuilderSqlite(Expression filter, bool requireConcreteValue, int seq) public WhereBuilderSqlite(Expression filter, bool requireConcreteValue, int seq)
{ {
+13 -4
View File
@@ -1,6 +1,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using NLog; using NLog;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
@@ -60,6 +61,14 @@ namespace NzbDrone.Core.Download
// Get the seed configuration for this release. // Get the seed configuration for this release.
// remoteMovie.SeedConfiguration = _seedConfigProvider.GetSeedConfiguration(remoteMovie); // remoteMovie.SeedConfiguration = _seedConfigProvider.GetSeedConfiguration(remoteMovie);
// Limit grabs to 2 per second.
if (release.DownloadUrl.IsNotNullOrWhiteSpace() && !release.DownloadUrl.StartsWith("magnet:"))
{
var url = new HttpUri(release.DownloadUrl);
_rateLimitService.WaitAndPulse(url.Host, TimeSpan.FromSeconds(2));
}
var indexer = _indexerFactory.GetInstance(_indexerFactory.Get(release.IndexerId)); var indexer = _indexerFactory.GetInstance(_indexerFactory.Get(release.IndexerId));
string downloadClientId; string downloadClientId;
@@ -83,7 +92,8 @@ namespace NzbDrone.Core.Download
} }
catch (ReleaseDownloadException ex) catch (ReleaseDownloadException ex)
{ {
if (ex.InnerException is TooManyRequestsException http429) var http429 = ex.InnerException as TooManyRequestsException;
if (http429 != null)
{ {
_indexerStatusService.RecordFailure(release.IndexerId, http429.RetryAfter); _indexerStatusService.RecordFailure(release.IndexerId, http429.RetryAfter);
} }
@@ -131,7 +141,8 @@ namespace NzbDrone.Core.Download
} }
catch (ReleaseDownloadException ex) catch (ReleaseDownloadException ex)
{ {
if (ex.InnerException is TooManyRequestsException http429) var http429 = ex.InnerException as TooManyRequestsException;
if (http429 != null)
{ {
_indexerStatusService.RecordFailure(indexerId, http429.RetryAfter); _indexerStatusService.RecordFailure(indexerId, http429.RetryAfter);
} }
@@ -144,9 +155,7 @@ namespace NzbDrone.Core.Download
throw; throw;
} }
_logger.Trace("Downloaded {0} bytes from {1}", downloadedBytes.Length, link);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, host, title, url.AbsoluteUri)); _eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, host, title, url.AbsoluteUri));
return downloadedBytes; return downloadedBytes;
} }
@@ -34,8 +34,8 @@ namespace NzbDrone.Core.HealthCheck
private readonly ICached<HealthCheck> _healthCheckResults; private readonly ICached<HealthCheck> _healthCheckResults;
private bool _hasRunHealthChecksAfterGracePeriod; private bool _hasRunHealthChecksAfterGracePeriod = false;
private bool _isRunningHealthChecksAfterGracePeriod; private bool _isRunningHealthChecksAfterGracePeriod = false;
public HealthCheckService(IEnumerable<IProvideHealthCheck> healthChecks, public HealthCheckService(IEnumerable<IProvideHealthCheck> healthChecks,
IServerSideNotificationService serverSideNotificationService, IServerSideNotificationService serverSideNotificationService,
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.HealthCheck
_startupHealthChecks = _healthChecks.Where(v => v.CheckOnStartup).ToArray(); _startupHealthChecks = _healthChecks.Where(v => v.CheckOnStartup).ToArray();
_scheduledHealthChecks = _healthChecks.Where(v => v.CheckOnSchedule).ToArray(); _scheduledHealthChecks = _healthChecks.Where(v => v.CheckOnSchedule).ToArray();
_eventDrivenHealthChecks = GetEventDrivenHealthChecks(); _eventDrivenHealthChecks = GetEventDrivenHealthChecks();
_startupGracePeriodEndTime = runtimeInfo.StartTime.AddMinutes(15); _startupGracePeriodEndTime = runtimeInfo.StartTime + TimeSpan.FromMinutes(15);
} }
public List<HealthCheck> Results() public List<HealthCheck> Results()
@@ -19,7 +19,6 @@ namespace NzbDrone.Core.History
List<History> Since(DateTime date, HistoryEventType? eventType); List<History> Since(DateTime date, HistoryEventType? eventType);
void Cleanup(int days); void Cleanup(int days);
int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes); int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes);
History FindFirstForIndexerSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes, int limit);
} }
public class HistoryRepository : BasicRepository<History>, IHistoryRepository public class HistoryRepository : BasicRepository<History>, IHistoryRepository
@@ -116,24 +115,5 @@ namespace NzbDrone.Core.History
return conn.ExecuteScalar<int>(sql.RawSql, sql.Parameters); return conn.ExecuteScalar<int>(sql.RawSql, sql.Parameters);
} }
} }
public History FindFirstForIndexerSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes, int limit)
{
var intEvents = eventTypes.Select(t => (int)t).ToList();
var builder = Builder()
.Where<History>(x => x.IndexerId == indexerId)
.Where<History>(x => x.Date >= date)
.Where<History>(x => intEvents.Contains((int)x.EventType));
var query = Query(builder);
if (limit > 0)
{
query = query.OrderByDescending(h => h.Date).Take(limit).ToList();
}
return query.MinBy(h => h.Date);
}
} }
} }
+1 -7
View File
@@ -27,7 +27,6 @@ namespace NzbDrone.Core.History
List<History> Between(DateTime start, DateTime end); List<History> Between(DateTime start, DateTime end);
List<History> Since(DateTime date, HistoryEventType? eventType); List<History> Since(DateTime date, HistoryEventType? eventType);
int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes); int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes);
History FindFirstForIndexerSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes, int limit);
} }
public class HistoryService : IHistoryService, public class HistoryService : IHistoryService,
@@ -174,7 +173,7 @@ namespace NzbDrone.Core.History
history.Data.Add("Categories", string.Join(",", message.Query.Categories) ?? string.Empty); history.Data.Add("Categories", string.Join(",", message.Query.Categories) ?? string.Empty);
history.Data.Add("Source", message.Query.Source ?? string.Empty); history.Data.Add("Source", message.Query.Source ?? string.Empty);
history.Data.Add("Host", message.Query.Host ?? string.Empty); history.Data.Add("Host", message.Query.Host ?? string.Empty);
history.Data.Add("QueryResults", message.QueryResult.Releases?.Count.ToString() ?? string.Empty); history.Data.Add("QueryResults", message.QueryResult.Releases?.Count().ToString() ?? string.Empty);
history.Data.Add("Url", message.QueryResult.Response?.Request.Url.FullUri ?? string.Empty); history.Data.Add("Url", message.QueryResult.Response?.Request.Url.FullUri ?? string.Empty);
_historyRepository.Insert(history); _historyRepository.Insert(history);
@@ -233,10 +232,5 @@ namespace NzbDrone.Core.History
{ {
return _historyRepository.CountSince(indexerId, date, eventTypes); return _historyRepository.CountSince(indexerId, date, eventTypes);
} }
public History FindFirstForIndexerSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes, int limit)
{
return _historyRepository.FindFirstForIndexerSince(indexerId, date, eventTypes, limit);
}
} }
} }
@@ -1,16 +1,15 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text.RegularExpressions;
using NLog; using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
namespace NzbDrone.Core.Http.CloudFlare namespace NzbDrone.Core.Http.CloudFlare
{ {
public class CloudFlareDetectionService public class CloudFlareDetectionService
{ {
private static readonly HashSet<string> CloudflareServerNames = new () { "cloudflare", "cloudflare-nginx", "ddos-guard" }; private static readonly HashSet<string> CloudflareServerNames = new HashSet<string> { "cloudflare", "cloudflare-nginx", "ddos-guard" };
private readonly Logger _logger; private readonly Logger _logger;
public CloudFlareDetectionService(Logger logger) public CloudFlareDetectionService(Logger logger)
@@ -30,11 +29,7 @@ namespace NzbDrone.Core.Http.CloudFlare
response.StatusCode.Equals(HttpStatusCode.Forbidden)) response.StatusCode.Equals(HttpStatusCode.Forbidden))
{ {
var responseHtml = response.Content; var responseHtml = response.Content;
if (responseHtml.Contains("<title>Just a moment...</title>") || if (responseHtml.Contains("<title>Just a moment...") || responseHtml.Contains("<title>DDOS-GUARD"))
responseHtml.Contains("<title>Access denied</title>") ||
responseHtml.Contains("<title>Attention Required! | Cloudflare</title>") ||
responseHtml.Trim().Equals("error code: 1020") ||
responseHtml.Contains("<title>DDOS-GUARD</title>", StringComparison.OrdinalIgnoreCase))
{ {
return true; return true;
} }
@@ -42,7 +37,7 @@ namespace NzbDrone.Core.Http.CloudFlare
// detect Custom CloudFlare for EbookParadijs, Film-Paleis, MuziekFabriek and Puur-Hollands // detect Custom CloudFlare for EbookParadijs, Film-Paleis, MuziekFabriek and Puur-Hollands
if (response.Headers.Vary == "Accept-Encoding,User-Agent" && if (response.Headers.Vary == "Accept-Encoding,User-Agent" &&
response.Headers.ContentEncoding.IsNullOrWhiteSpace() && response.Headers.ContentEncoding == "" &&
response.Content.ToLower().Contains("ddos")) response.Content.ToLower().Contains("ddos"))
{ {
return true; return true;
@@ -170,7 +170,6 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
newRequest.Headers.ContentType = "application/json"; newRequest.Headers.ContentType = "application/json";
newRequest.Method = HttpMethod.Post; newRequest.Method = HttpMethod.Post;
newRequest.LogResponseContent = true; newRequest.LogResponseContent = true;
newRequest.RequestTimeout = TimeSpan.FromSeconds(Settings.RequestTimeout + 5);
newRequest.SetContent(req.ToJson()); newRequest.SetContent(req.ToJson());
_logger.Debug("Cloudflare Detected, Applying FlareSolverr Proxy {0} to request {1}", Name, request.Url); _logger.Debug("Cloudflare Detected, Applying FlareSolverr Proxy {0} to request {1}", Name, request.Url);
@@ -10,6 +10,17 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public int? Year { get; set; } public int? Year { get; set; }
public string Genre { get; set; } public string Genre { get; set; }
public override bool RssSearch => SearchTerm.IsNullOrWhiteSpace() && Author.IsNullOrWhiteSpace() && Title.IsNullOrWhiteSpace(); public override bool RssSearch
{
get
{
if (SearchTerm.IsNullOrWhiteSpace() && Author.IsNullOrWhiteSpace() && Title.IsNullOrWhiteSpace())
{
return true;
}
return false;
}
}
} }
} }
@@ -13,7 +13,18 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public int? Year { get; set; } public int? Year { get; set; }
public string Genre { get; set; } public string Genre { get; set; }
public override bool RssSearch => SearchTerm.IsNullOrWhiteSpace() && ImdbId.IsNullOrWhiteSpace() && !TmdbId.HasValue && !TraktId.HasValue; public override bool RssSearch
{
get
{
if (SearchTerm.IsNullOrWhiteSpace() && ImdbId.IsNullOrWhiteSpace() && !TmdbId.HasValue && !TraktId.HasValue)
{
return true;
}
return false;
}
}
public string FullImdbId => ParseUtil.GetFullImdbId(ImdbId); public string FullImdbId => ParseUtil.GetFullImdbId(ImdbId);
@@ -11,6 +11,17 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public string Track { get; set; } public string Track { get; set; }
public int? Year { get; set; } public int? Year { get; set; }
public override bool RssSearch => SearchTerm.IsNullOrWhiteSpace() && Album.IsNullOrWhiteSpace() && Artist.IsNullOrWhiteSpace() && Label.IsNullOrWhiteSpace(); public override bool RssSearch
{
get
{
if (SearchTerm.IsNullOrWhiteSpace() && Album.IsNullOrWhiteSpace() && Artist.IsNullOrWhiteSpace() && Label.IsNullOrWhiteSpace())
{
return true;
}
return false;
}
}
} }
} }
@@ -7,8 +7,9 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
{ {
public abstract class SearchCriteriaBase public abstract class SearchCriteriaBase
{ {
private static readonly Regex StandardizeDashesRegex = new (@"\p{Pd}+", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex SpecialCharacter = new Regex(@"[`'.]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex StandardizeSingleQuotesRegex = new (@"[\u0060\u00B4\u2018\u2019]", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex NonWord = new Regex(@"[\W]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex BeginningThe = new Regex(@"^the\s", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public virtual bool InteractiveSearch { get; set; } public virtual bool InteractiveSearch { get; set; }
public List<int> IndexerIds { get; set; } public List<int> IndexerIds { get; set; }
@@ -20,24 +21,58 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public string Source { get; set; } public string Source { get; set; }
public string Host { get; set; } public string Host { get; set; }
public override string ToString() => $"{SearchQuery}, Offset: {Offset ?? 0}, Limit: {Limit ?? 0}, Categories: [{string.Join(", ", Categories)}]"; public virtual string SearchQuery
public virtual string SearchQuery => $"Term: [{SearchTerm}]";
public virtual bool RssSearch => SearchTerm.IsNullOrWhiteSpace();
public string SanitizedSearchTerm => GetSanitizedTerm(SearchTerm);
private static string GetSanitizedTerm(string term)
{ {
term ??= ""; get
{
return $"Term: [{SearchTerm}]";
}
}
term = StandardizeDashesRegex.Replace(term, "-"); public override string ToString()
term = StandardizeSingleQuotesRegex.Replace(term, "'"); {
return $"{SearchQuery}, Offset: {Offset ?? 0}, Limit: {Limit ?? 0}, Categories: [{string.Join(", ", Categories)}]";
}
var safeTitle = term.Where(c => char.IsLetterOrDigit(c) || char.IsWhiteSpace(c) || c is '-' or '.' or '_' or '(' or ')' or '@' or '/' or '\'' or '[' or ']' or '+' or '%'); public virtual bool RssSearch
{
get
{
if (SearchTerm.IsNullOrWhiteSpace())
{
return true;
}
return string.Concat(safeTitle); return false;
}
}
public string SanitizedSearchTerm
{
get
{
var term = SearchTerm;
if (SearchTerm == null)
{
term = "";
}
var safeTitle = term.Where(c => (char.IsLetterOrDigit(c)
|| char.IsWhiteSpace(c)
|| c == '-'
|| c == '.'
|| c == '_'
|| c == '('
|| c == ')'
|| c == '@'
|| c == '/'
|| c == '\''
|| c == '['
|| c == ']'
|| c == '+'
|| c == '%'));
return string.Concat(safeTitle);
}
} }
} }
} }
@@ -21,12 +21,23 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public int? Year { get; set; } public int? Year { get; set; }
public string Genre { get; set; } public string Genre { get; set; }
public string SanitizedTvSearchString => $"{SanitizedSearchTerm} {EpisodeSearchString}".Trim(); public string SanitizedTvSearchString => (SanitizedSearchTerm + " " + EpisodeSearchString).Trim();
public string EpisodeSearchString => GetEpisodeSearchString(); public string EpisodeSearchString => GetEpisodeSearchString();
public string FullImdbId => ParseUtil.GetFullImdbId(ImdbId); public string FullImdbId => ParseUtil.GetFullImdbId(ImdbId);
public override bool RssSearch => SearchTerm.IsNullOrWhiteSpace() && ImdbId.IsNullOrWhiteSpace() && !TvdbId.HasValue && !RId.HasValue && !TraktId.HasValue && !TvMazeId.HasValue; public override bool RssSearch
{
get
{
if (SearchTerm.IsNullOrWhiteSpace() && ImdbId.IsNullOrWhiteSpace() && !TvdbId.HasValue && !RId.HasValue && !TraktId.HasValue && !TvMazeId.HasValue)
{
return true;
}
return false;
}
}
public override string SearchQuery public override string SearchQuery
{ {
@@ -59,7 +59,7 @@ namespace NzbDrone.Core.IndexerStats
var elapsedTimeEvents = sortedEvents.Where(h => int.TryParse(h.Data.GetValueOrDefault("elapsedTime"), out temp)) var elapsedTimeEvents = sortedEvents.Where(h => int.TryParse(h.Data.GetValueOrDefault("elapsedTime"), out temp))
.Select(h => temp); .Select(h => temp);
indexerStats.AverageResponseTime = elapsedTimeEvents.Any() ? (int)elapsedTimeEvents.Average() : 0; indexerStats.AverageResponseTime = elapsedTimeEvents.Count() > 0 ? (int)elapsedTimeEvents.Average() : 0;
foreach (var historyEvent in sortedEvents) foreach (var historyEvent in sortedEvents)
{ {
@@ -29,14 +29,13 @@ namespace NzbDrone.Core.IndexerVersions
/* Update Service will fall back if version # does not exist for an indexer per Ta */ /* Update Service will fall back if version # does not exist for an indexer per Ta */
private const string DEFINITION_BRANCH = "master"; private const string DEFINITION_BRANCH = "master";
private const int DEFINITION_VERSION = 8; private const int DEFINITION_VERSION = 7;
// Used when moving yml to C# //Used when moving yml to C#
private readonly List<string> _definitionBlocklist = new () private readonly List<string> _defintionBlocklist = new List<string>()
{ {
"aither", "aither",
"animeworld", "animeworld",
"audiobookbay",
"beyond-hd-oneurl", "beyond-hd-oneurl",
"beyond-hd", "beyond-hd",
"blutopia", "blutopia",
@@ -90,7 +89,7 @@ namespace NzbDrone.Core.IndexerVersions
{ {
var request = new HttpRequest($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}"); var request = new HttpRequest($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}");
var response = _httpClient.Get<List<CardigannMetaDefinition>>(request); var response = _httpClient.Get<List<CardigannMetaDefinition>>(request);
indexerList = response.Resource.Where(i => !_definitionBlocklist.Contains(i.File)).ToList(); indexerList = response.Resource.Where(i => !_defintionBlocklist.Contains(i.File)).ToList();
} }
catch catch
{ {
@@ -126,7 +125,7 @@ namespace NzbDrone.Core.IndexerVersions
public List<string> GetBlocklist() public List<string> GetBlocklist()
{ {
return _definitionBlocklist; return _defintionBlocklist;
} }
private List<CardigannMetaDefinition> ReadDefinitionsFromDisk(List<CardigannMetaDefinition> defs, string path, SearchOption options = SearchOption.TopDirectoryOnly) private List<CardigannMetaDefinition> ReadDefinitionsFromDisk(List<CardigannMetaDefinition> defs, string path, SearchOption options = SearchOption.TopDirectoryOnly)
@@ -228,10 +227,10 @@ namespace NzbDrone.Core.IndexerVersions
if (definition.Settings == null) if (definition.Settings == null)
{ {
definition.Settings = new List<SettingsField> definition.Settings = new List<SettingsField>
{ {
new () { Name = "username", Label = "Username", Type = "text" }, new SettingsField { Name = "username", Label = "Username", Type = "text" },
new () { Name = "password", Label = "Password", Type = "password" } new SettingsField { Name = "password", Label = "Password", Type = "password" }
}; };
} }
if (definition.Encoding == null) if (definition.Encoding == null)
@@ -1,120 +1,85 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized;
using NLog; using NLog;
using NzbDrone.Core.Annotations; using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.Gazelle;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions; namespace NzbDrone.Core.Indexers.Definitions
public class AlphaRatio : GazelleBase<AlphaRatioSettings>
{ {
public override string Name => "AlphaRatio"; public class AlphaRatio : Gazelle.Gazelle
public override string[] IndexerUrls => new[] { "https://alpharatio.cc/" };
public override string Description => "AlphaRatio(AR) is a Private Torrent Tracker for 0DAY / GENERAL";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public AlphaRatio(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{ {
} public override string Name => "AlphaRatio";
public override string[] IndexerUrls => new string[] { "https://alpharatio.cc/" };
public override string Description => "AlphaRatio(AR) is a Private Torrent Tracker for 0DAY / GENERAL";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IIndexerRequestGenerator GetRequestGenerator() public AlphaRatio(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
{ : base(httpClient, eventAggregator, indexerStatusService, configService, logger)
return new AlphaRatioRequestGenerator(Settings, Capabilities, _httpClient, _logger);
}
protected override IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{ {
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVSD, "TvSD");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVHD, "TvHD");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVUHD, "TvUHD");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVSD, "TvDVDRip");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVSD, "TvPackSD");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TVHD, "TvPackHD");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.TVUHD, "TvPackUHD");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.MoviesSD, "MovieSD");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.MoviesHD, "MovieHD");
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.MoviesUHD, "MovieUHD");
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.MoviesSD, "MoviePackSD");
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.MoviesHD, "MoviePackHD");
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.MoviesUHD, "MoviePackUHD");
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.XXX, "MovieXXX");
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.MoviesBluRay, "Bluray");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.TVAnime, "AnimeSD");
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.TVAnime, "AnimeHD");
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.PCGames, "GamesPC");
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.ConsoleXBox, "GamesxBox");
caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.ConsolePS4, "GamesPS");
caps.Categories.AddCategoryMapping(21, NewznabStandardCategory.ConsoleWii, "GamesNin");
caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.PC0day, "AppsWindows");
caps.Categories.AddCategoryMapping(23, NewznabStandardCategory.PCMac, "AppsMAC");
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.PC0day, "AppsLinux");
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.PCMobileOther, "AppsMobile");
caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.XXX, "0dayXXX");
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.Books, "eBook");
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.AudioAudiobook, "AudioBook");
caps.Categories.AddCategoryMapping(29, NewznabStandardCategory.AudioOther, "Music");
caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.Other, "Misc");
return caps;
}
}
public class AlphaRatioRequestGenerator : GazelleRequestGenerator
{
protected override bool ImdbInTags => true;
private readonly AlphaRatioSettings _settings;
public AlphaRatioRequestGenerator(AlphaRatioSettings settings,
IndexerCapabilities capabilities,
IIndexerHttpClient httpClient,
Logger logger)
: base(settings, capabilities, httpClient, logger)
{
_settings = settings;
}
protected override NameValueCollection GetBasicSearchParameters(string term, int[] categories)
{
var parameters = base.GetBasicSearchParameters(term, categories);
if (_settings.FreeleechOnly)
{
parameters.Set("freetorrent", "1");
} }
if (_settings.ExcludeScene) public override IIndexerRequestGenerator GetRequestGenerator()
{ {
parameters.Set("scene", "0"); return new AlphaRatioRequestGenerator()
{
Settings = Settings,
HttpClient = _httpClient,
Logger = _logger,
Capabilities = Capabilities
};
} }
return parameters; protected override IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVSD, "TvSD");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVHD, "TvHD");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVUHD, "TvUHD");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVSD, "TvDVDRip");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVSD, "TvPackSD");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TVHD, "TvPackHD");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.TVUHD, "TvPackUHD");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.MoviesSD, "MovieSD");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.MoviesHD, "MovieHD");
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.MoviesUHD, "MovieUHD");
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.MoviesSD, "MoviePackSD");
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.MoviesHD, "MoviePackHD");
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.MoviesUHD, "MoviePackUHD");
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.XXX, "MovieXXX");
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.MoviesBluRay, "Bluray");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.TVAnime, "AnimeSD");
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.TVAnime, "AnimeHD");
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.PCGames, "GamesPC");
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.ConsoleXBox, "GamesxBox");
caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.ConsolePS4, "GamesPS");
caps.Categories.AddCategoryMapping(21, NewznabStandardCategory.ConsoleWii, "GamesNin");
caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.PC0day, "AppsWindows");
caps.Categories.AddCategoryMapping(23, NewznabStandardCategory.PCMac, "AppsMAC");
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.PC0day, "AppsLinux");
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.PCMobileOther, "AppsMobile");
caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.XXX, "0dayXXX");
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.Books, "eBook");
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.AudioAudiobook, "AudioBook");
caps.Categories.AddCategoryMapping(29, NewznabStandardCategory.AudioOther, "Music");
caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.Other, "Misc");
return caps;
}
}
public class AlphaRatioRequestGenerator : Gazelle.GazelleRequestGenerator
{
protected override bool ImdbInTags => true;
} }
} }
public class AlphaRatioSettings : GazelleSettings
{
[FieldDefinition(6, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Search freeleech torrents only")]
public bool FreeleechOnly { get; set; }
[FieldDefinition(7, Label = "Exclude Scene", Type = FieldType.Checkbox, HelpText = "Exclude Scene torrents from results")]
public bool ExcludeScene { get; set; }
}
@@ -1,365 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using AngleSharp.Html.Parser;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Indexers.Settings;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Definitions
{
public class Anidex : TorrentIndexerBase<AnidexSettings>
{
public override string Name => "Anidex";
public override string[] IndexerUrls => new[] { "https://anidex.info/" };
public override string Description => "Anidex is a Public torrent tracker and indexer, primarily for English fansub groups of anime";
public override string Language => "en-US";
public override Encoding Encoding => Encoding.UTF8;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
public override IndexerCapabilities Capabilities => SetCapabilities();
public Anidex(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AnidexRequestGenerator(Settings, Capabilities);
}
public override IParseIndexerResponse GetParser()
{
return new AnidexParser(Settings, Capabilities.Categories);
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q,
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q,
}
};
caps.Categories.AddCategoryMapping("1", NewznabStandardCategory.TVAnime, "Anime - Sub");
caps.Categories.AddCategoryMapping("2", NewznabStandardCategory.TVAnime, "Anime - Raw");
caps.Categories.AddCategoryMapping("3", NewznabStandardCategory.TVAnime, "Anime - Dub");
caps.Categories.AddCategoryMapping("4", NewznabStandardCategory.TVAnime, "LA - Sub");
caps.Categories.AddCategoryMapping("5", NewznabStandardCategory.TVAnime, "LA - Raw");
caps.Categories.AddCategoryMapping("6", NewznabStandardCategory.BooksEBook, "Light Novel");
caps.Categories.AddCategoryMapping("7", NewznabStandardCategory.BooksComics, "Manga - TLed");
caps.Categories.AddCategoryMapping("8", NewznabStandardCategory.BooksComics, "Manga - Raw");
caps.Categories.AddCategoryMapping("9", NewznabStandardCategory.AudioMP3, "♫ - Lossy");
caps.Categories.AddCategoryMapping("10", NewznabStandardCategory.AudioLossless, "♫ - Lossless");
caps.Categories.AddCategoryMapping("11", NewznabStandardCategory.AudioVideo, "♫ - Video");
caps.Categories.AddCategoryMapping("12", NewznabStandardCategory.PCGames, "Games");
caps.Categories.AddCategoryMapping("13", NewznabStandardCategory.PC0day, "Applications");
caps.Categories.AddCategoryMapping("14", NewznabStandardCategory.XXXImageSet, "Pictures");
caps.Categories.AddCategoryMapping("15", NewznabStandardCategory.XXX, "Adult Video");
caps.Categories.AddCategoryMapping("16", NewznabStandardCategory.Other, "Other");
return caps;
}
}
public class AnidexRequestGenerator : IIndexerRequestGenerator
{
private readonly AnidexSettings _settings;
private readonly IndexerCapabilities _capabilities;
public AnidexRequestGenerator(AnidexSettings settings, IndexerCapabilities capabilities)
{
_settings = settings;
_capabilities = capabilities;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
return pageableRequests;
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{
var parameters = new NameValueCollection
{
{ "page", "search" },
{ "s", "upload_timestamp" },
{ "o", "desc" },
{ "group_id", "0" }, // All groups
{ "q", term ?? string.Empty }
};
if (_settings.AuthorisedOnly)
{
parameters.Add("a", "1");
}
var searchUrl = $"{_settings.BaseUrl}?{parameters.GetQueryString()}";
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(categories);
if (queryCats.Any())
{
searchUrl += "&id=" + string.Join(",", queryCats);
}
if (_settings.LanguagesOnly.Any())
{
searchUrl += "&lang_id=" + string.Join(",", _settings.LanguagesOnly);
}
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
yield return request;
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class AnidexParser : IParseIndexerResponse
{
private readonly AnidexSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public AnidexParser(AnidexSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse, $"Anidex search returned unexpected result. Expected 200 OK but got {indexerResponse.HttpResponse.StatusCode}.");
}
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll("div#content table > tbody > tr");
foreach (var row in rows)
{
var downloadUrl = _settings.BaseUrl + row.QuerySelector("a[href^=\"/dl/\"]")?.GetAttribute("href");
var infoUrl = _settings.BaseUrl + row.QuerySelector("td:nth-child(3) a")?.GetAttribute("href");
var title = row.QuerySelector("td:nth-child(3) span")?.GetAttribute("title")?.Trim();
var language = row.QuerySelector("td:nth-child(1) img")?.GetAttribute("title")?.Trim();
if (language.IsNotNullOrWhiteSpace())
{
title += $" [{language}]";
}
var categoryLink = row.QuerySelector("td:nth-child(1) a").GetAttribute("href");
var cat = ParseUtil.GetArgumentFromQueryString(categoryLink, "id");
var categories = _categories.MapTrackerCatToNewznab(cat);
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(9)")?.TextContent);
var peers = seeders + ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(10)")?.TextContent.Trim());
var added = row.QuerySelector("td:nth-child(8)").GetAttribute("title").Trim();
var release = new TorrentInfo
{
Guid = infoUrl,
InfoUrl = infoUrl,
DownloadUrl = downloadUrl,
MagnetUrl = row.QuerySelector("a[href^=\"magnet:?\"]")?.GetAttribute("href"),
Title = title,
Categories = categories,
Seeders = seeders,
Peers = peers,
Size = ParseUtil.GetBytes(row.QuerySelector("td:nth-child(7)")?.TextContent.Trim()),
Grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(11)")?.TextContent),
PublishDate = DateTime.ParseExact(added, "yyyy-MM-dd HH:mm:ss UTC", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal),
DownloadVolumeFactor = 0,
UploadVolumeFactor = 1
};
releaseInfos.Add(release);
}
return releaseInfos.ToArray();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class AnidexSettings : NoAuthTorrentBaseSettings
{
public AnidexSettings()
{
AuthorisedOnly = false;
LanguagesOnly = Array.Empty<int>();
}
[FieldDefinition(2, Label = "Authorised Only", Type = FieldType.Checkbox, HelpText = "Search authorised torrents only")]
public bool AuthorisedOnly { get; set; }
[FieldDefinition(3, Label = "Languages Only", Type = FieldType.Select, SelectOptions = typeof(AnidexLanguage), HelpText = "Search selected languages only. None ticked = ALL.")]
public IEnumerable<int> LanguagesOnly { get; set; }
}
public enum AnidexLanguage
{
[FieldOption(Hint = "English")]
GB = 1,
[FieldOption(Hint = "Japanese")]
JP = 2,
[FieldOption(Hint = "Polish")]
PL = 3,
[FieldOption(Hint = "Serbo-Croatian")]
RS = 4,
[FieldOption(Hint = "Dutch")]
NL = 5,
[FieldOption(Hint = "Italian")]
IT = 6,
[FieldOption(Hint = "Russian")]
RU = 7,
[FieldOption(Hint = "German")]
DE = 8,
[FieldOption(Hint = "Hungarian")]
HU = 9,
[FieldOption(Hint = "French")]
FR = 10,
[FieldOption(Hint = "Finnish")]
FI = 11,
[FieldOption(Hint = "Vietnamese")]
VN = 12,
[FieldOption(Hint = "Greek")]
GR = 13,
[FieldOption(Hint = "Bulgarian")]
BG = 14,
[FieldOption(Hint = "Spanish (Spain)")]
ES = 15,
[FieldOption(Hint = "Portuguese (Brazil)")]
BR = 16,
[FieldOption(Hint = "Portuguese (Portugal)")]
PT = 17,
[FieldOption(Hint = "Swedish")]
SE = 18,
[FieldOption(Hint = "Arabic")]
SA = 19,
[FieldOption(Hint = "Danish")]
DK = 20,
[FieldOption(Hint = "Chinese (Simplified)")]
CN = 21,
[FieldOption(Hint = "Bengali")]
BD = 22,
[FieldOption(Hint = "Romanian")]
RO = 23,
[FieldOption(Hint = "Czech")]
CZ = 24,
[FieldOption(Hint = "Mongolian")]
MN = 25,
[FieldOption(Hint = "Turkish")]
TR = 26,
[FieldOption(Hint = "Indonesian")]
ID = 27,
[FieldOption(Hint = "Korean")]
KR = 28,
[FieldOption(Hint = "Spanish (LATAM)")]
MX = 29,
[FieldOption(Hint = "Persian")]
IR = 30,
[FieldOption(Hint = "Malaysian")]
MY = 31,
}
}
@@ -9,8 +9,10 @@ using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using AngleSharp.Html.Parser; using AngleSharp.Html.Parser;
using FluentValidation;
using NLog; using NLog;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions; using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Indexers.Settings; using NzbDrone.Core.Indexers.Settings;
@@ -18,13 +20,14 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions namespace NzbDrone.Core.Indexers.Definitions
{ {
public class Anidub : TorrentIndexerBase<UserPassTorrentBaseSettings> public class Anidub : TorrentIndexerBase<UserPassTorrentBaseSettings>
{ {
public override string Name => "Anidub"; public override string Name => "Anidub";
public override string[] IndexerUrls => new[] { "https://tr.anidub.com/" }; 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 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 Encoding Encoding => Encoding.UTF8;
@@ -39,29 +42,31 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator() public override IIndexerRequestGenerator GetRequestGenerator()
{ {
return new AnidubRequestGenerator(Settings); return new AnidubRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
} }
public override IParseIndexerResponse GetParser() public override IParseIndexerResponse GetParser()
{ {
return new AnidubParser(Settings, Capabilities.Categories, RateLimit, _httpClient, _logger); return new AnidubParser(Settings, Capabilities.Categories) { HttpClient = _httpClient, Logger = _logger };
} }
protected override async Task DoLogin() protected override async Task DoLogin()
{ {
UpdateCookies(null, null); UpdateCookies(null, null);
var mainPage = await ExecuteAuth(new HttpRequest(Settings.BaseUrl));
var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl + "index.php") var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl + "index.php")
{ {
LogResponseContent = true, LogResponseContent = true,
AllowAutoRedirect = true, AllowAutoRedirect = true
Method = HttpMethod.Post
}; };
var mainPage = await ExecuteAuth(new HttpRequest(Settings.BaseUrl));
requestBuilder.Method = HttpMethod.Post;
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
requestBuilder.SetCookies(mainPage.GetCookies());
var authLoginRequest = requestBuilder var authLoginRequest = requestBuilder
.SetCookies(mainPage.GetCookies())
.AddFormParameter("login_name", Settings.Username) .AddFormParameter("login_name", Settings.Username)
.AddFormParameter("login_password", Settings.Password) .AddFormParameter("login_password", Settings.Password)
.AddFormParameter("login", "submit") .AddFormParameter("login", "submit")
@@ -72,22 +77,27 @@ namespace NzbDrone.Core.Indexers.Definitions
if (response.Content != null && !CheckIfLoginNeeded(response)) if (response.Content != null && !CheckIfLoginNeeded(response))
{ {
UpdateCookies(response.GetCookies(), DateTime.Now.AddDays(30)); UpdateCookies(response.GetCookies(), DateTime.Now + TimeSpan.FromDays(30));
_logger.Debug("Anidub authentication succeeded"); _logger.Debug("Anidub authentication succeeded");
} }
else else
{ {
const string ErrorSelector = "#content .berror .berror_c";
var parser = new HtmlParser(); var parser = new HtmlParser();
var document = await parser.ParseDocumentAsync(response.Content); var document = await parser.ParseDocumentAsync(response.Content);
var errorMessage = document.QuerySelector("#content .berror .berror_c")?.TextContent.Trim(); var errorMessage = document.QuerySelector(ErrorSelector).TextContent.Trim();
throw new IndexerAuthException("Anidub authentication failed. Error: " + errorMessage);
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
} }
} }
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse) protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{ {
return !httpResponse.Content.Contains("index.php?action=logout"); if (httpResponse.Content.Contains("index.php?action=logout"))
{
return false;
}
return true;
} }
private IndexerCapabilities SetCapabilities() private IndexerCapabilities SetCapabilities()
@@ -128,32 +138,31 @@ namespace NzbDrone.Core.Indexers.Definitions
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.BooksComics, "Манга"); caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.BooksComics, "Манга");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.Audio, "OST"); caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.Audio, "OST");
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.Audio, "Подкасты"); caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.Audio, "Подкасты");
return caps; return caps;
} }
} }
public class AnidubRequestGenerator : IIndexerRequestGenerator public class AnidubRequestGenerator : IIndexerRequestGenerator
{ {
private readonly UserPassTorrentBaseSettings _settings; public UserPassTorrentBaseSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public AnidubRequestGenerator(UserPassTorrentBaseSettings settings) public AnidubRequestGenerator()
{ {
_settings = settings;
} }
private IEnumerable<IndexerRequest> GetPagedRequests(string term) private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{ {
string requestUrl; var requestUrl = string.Empty;
var isSearch = !string.IsNullOrWhiteSpace(term); var isSearch = !string.IsNullOrWhiteSpace(term);
if (isSearch) if (isSearch)
{ {
requestUrl = $"{_settings.BaseUrl.TrimEnd('/')}/index.php?do=search"; requestUrl = string.Format("{0}/index.php?do=search", Settings.BaseUrl.TrimEnd('/'));
} }
else else
{ {
requestUrl = _settings.BaseUrl; requestUrl = Settings.BaseUrl;
} }
var request = new IndexerRequest(requestUrl, HttpAccept.Html); var request = new IndexerRequest(requestUrl, HttpAccept.Html);
@@ -198,7 +207,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}")); pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests; return pageableRequests;
} }
@@ -207,7 +216,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedTvSearchString}")); pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
return pageableRequests; return pageableRequests;
} }
@@ -216,7 +225,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}")); pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests; return pageableRequests;
} }
@@ -225,7 +234,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}")); pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests; return pageableRequests;
} }
@@ -234,7 +243,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}")); pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests; return pageableRequests;
} }
@@ -247,37 +256,33 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
private readonly UserPassTorrentBaseSettings _settings; private readonly UserPassTorrentBaseSettings _settings;
private readonly IndexerCapabilitiesCategories _categories; private readonly IndexerCapabilitiesCategories _categories;
private readonly TimeSpan _rateLimit; public IIndexerHttpClient HttpClient { get; set; }
private readonly IIndexerHttpClient _httpClient; public Logger Logger { get; set; }
private readonly Logger _logger;
private static Dictionary<string, string> CategoriesMap => new () private static Dictionary<string, string> CategoriesMap => new Dictionary<string, string>
{ {
{ "/anime_tv/full", "14" }, { "/anime_tv/full", "14" },
{ "/anime_tv/anime_ongoing", "10" }, { "/anime_tv/anime_ongoing", "10" },
{ "/anime_tv/shonen", "11" }, { "/anime_tv/shonen", "11" },
{ "/anime_tv", "2" }, { "/anime_tv", "2" },
{ "/xxx", "13" }, { "/xxx", "13" },
{ "/manga", "15" }, { "/manga", "15" },
{ "/ost", "16" }, { "/ost", "16" },
{ "/podcast", "17" }, { "/podcast", "17" },
{ "/anime_movie", "3" }, { "/anime_movie", "3" },
{ "/anime_ova/anime_ona", "5" }, { "/anime_ova/anime_ona", "5" },
{ "/anime_ova", "4" }, { "/anime_ova", "4" },
{ "/dorama/japan_dorama", "6" }, { "/dorama/japan_dorama", "6" },
{ "/dorama/korea_dorama", "7" }, { "/dorama/korea_dorama", "7" },
{ "/dorama/china_dorama", "8" }, { "/dorama/china_dorama", "8" },
{ "/dorama", "9" }, { "/dorama", "9" },
{ "/anons_ongoing", "12" } { "/anons_ongoing", "12" }
}; };
public AnidubParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories, TimeSpan rateLimit, IIndexerHttpClient httpClient, Logger logger) public AnidubParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
{ {
_settings = settings; _settings = settings;
_categories = categories; _categories = categories;
_rateLimit = rateLimit;
_httpClient = httpClient;
_logger = logger;
} }
private static string GetTitle(AngleSharp.Html.Dom.IHtmlDocument content, AngleSharp.Dom.IElement tabNode) private static string GetTitle(AngleSharp.Html.Dom.IHtmlDocument content, AngleSharp.Dom.IElement tabNode)
@@ -322,9 +327,9 @@ namespace NzbDrone.Core.Indexers.Definitions
private static int GetReleaseLeechers(AngleSharp.Dom.IElement tabNode) private static int GetReleaseLeechers(AngleSharp.Dom.IElement tabNode)
{ {
const string leechersSelector = ".list.down > .li_swing_m"; const string LeechersSelector = ".list.down > .li_swing_m";
var leechersStr = tabNode.QuerySelector(leechersSelector).TextContent; var leechersStr = tabNode.QuerySelector(LeechersSelector).TextContent;
int.TryParse(leechersStr, out var leechers); int.TryParse(leechersStr, out var leechers);
return leechers; return leechers;
} }
@@ -340,18 +345,18 @@ namespace NzbDrone.Core.Indexers.Definitions
private static int GetReleaseGrabs(AngleSharp.Dom.IElement tabNode) private static int GetReleaseGrabs(AngleSharp.Dom.IElement tabNode)
{ {
const string grabsSelector = ".list.down > .li_download_m"; const string GrabsSelector = ".list.down > .li_download_m";
var grabsStr = tabNode.QuerySelector(grabsSelector).TextContent; var grabsStr = tabNode.QuerySelector(GrabsSelector).TextContent;
int.TryParse(grabsStr, out var grabs); int.TryParse(grabsStr, out var grabs);
return grabs; return grabs;
} }
private static string GetDateFromDocument(AngleSharp.Html.Dom.IHtmlDocument content) private static string GetDateFromDocument(AngleSharp.Html.Dom.IHtmlDocument content)
{ {
const string dateSelector = ".story_inf > li:nth-child(2)"; const string DateSelector = ".story_inf > li:nth-child(2)";
var domDate = content.QuerySelector(dateSelector).LastChild; var domDate = content.QuerySelector(DateSelector).LastChild;
if (domDate?.NodeName != "#text") if (domDate?.NodeName != "#text")
{ {
@@ -392,16 +397,16 @@ namespace NzbDrone.Core.Indexers.Definitions
return utcDate.AddHours(-russianStandardTimeDiff); return utcDate.AddHours(-russianStandardTimeDiff);
} }
_logger.Warn($"[AniDub] Date time couldn't be parsed on. Date text: {dateText}"); Logger.Warn($"[AniDub] Date time couldn't be parsed on. Date text: {dateText}");
return DateTime.UtcNow; return DateTime.UtcNow;
} }
private static long GetReleaseSize(AngleSharp.Dom.IElement tabNode) private static long GetReleaseSize(AngleSharp.Dom.IElement tabNode)
{ {
const string sizeSelector = ".list.down > .red"; const string SizeSelector = ".list.down > .red";
var sizeStr = tabNode.QuerySelector(sizeSelector).TextContent; var sizeStr = tabNode.QuerySelector(SizeSelector).TextContent;
return ParseUtil.GetBytes(sizeStr); return ParseUtil.GetBytes(sizeStr);
} }
@@ -441,11 +446,11 @@ namespace NzbDrone.Core.Indexers.Definitions
var release = new TorrentInfo var release = new TorrentInfo
{ {
Title = GetTitle(dom, t), Title = GetTitle(dom, t),
InfoUrl = indexerResponse.Request.Url.FullUri, InfoUrl = indexerResponse.Request.Url.ToString(),
DownloadVolumeFactor = 0, DownloadVolumeFactor = 0,
UploadVolumeFactor = 1, UploadVolumeFactor = 1,
Guid = indexerResponse.Request.Url.FullUri + t.Id, Guid = indexerResponse.Request.Url.ToString() + t.Id,
Seeders = GetReleaseSeeders(t), Seeders = GetReleaseSeeders(t),
Peers = GetReleaseSeeders(t) + GetReleaseLeechers(t), Peers = GetReleaseSeeders(t) + GetReleaseLeechers(t),
Grabs = GetReleaseGrabs(t), Grabs = GetReleaseGrabs(t),
@@ -467,30 +472,36 @@ namespace NzbDrone.Core.Indexers.Definitions
var parser = new HtmlParser(); var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content); var dom = parser.ParseDocument(indexerResponse.Content);
var domQuery = string.Empty;
var links = dom.QuerySelectorAll(".searchitem > h3 > a[href], #dle-content > .story > .story_h > .lcol > h2 > a[href]"); if (indexerResponse.Request.Url.Query.Contains("do=search"))
{
domQuery = ".searchitem > h3 > a";
}
else
{
domQuery = "#dle-content > .story > .story_h > .lcol > h2 > a";
}
var links = dom.QuerySelectorAll(domQuery);
foreach (var link in links) foreach (var link in links)
{ {
var url = link.GetAttribute("href"); var url = link.GetAttribute("href");
var releaseRequest = new HttpRequestBuilder(url) var releaseRequest = new IndexerRequest(url, HttpAccept.Html);
.WithRateLimit(_rateLimit.TotalSeconds) var releaseResponse = new IndexerResponse(releaseRequest, HttpClient.Execute(releaseRequest.HttpRequest));
.SetHeader("Referer", _settings.BaseUrl)
.Accept(HttpAccept.Html)
.Build();
var releaseIndexerRequest = new IndexerRequest(releaseRequest);
var releaseResponse = new IndexerResponse(releaseIndexerRequest, _httpClient.Execute(releaseIndexerRequest.HttpRequest));
// Throw common http errors here before we try to parse // Throw common http errors here before we try to parse
if (releaseResponse.HttpResponse.HasHttpError) if (releaseResponse.HttpResponse.HasHttpError)
{ {
if (releaseResponse.HttpResponse.StatusCode == HttpStatusCode.TooManyRequests) if (releaseResponse.HttpResponse.StatusCode == HttpStatusCode.TooManyRequests)
{ {
throw new TooManyRequestsException(releaseResponse.HttpRequest, releaseResponse.HttpResponse); throw new TooManyRequestsException(releaseRequest.HttpRequest, releaseResponse.HttpResponse);
}
else
{
throw new IndexerException(releaseResponse, "Http error code: " + releaseResponse.HttpResponse.StatusCode);
} }
throw new IndexerException(releaseResponse, $"HTTP Error - {releaseResponse.HttpResponse.StatusCode}. {url}");
} }
torrentInfos.AddRange(ParseRelease(releaseResponse)); torrentInfos.AddRange(ParseRelease(releaseResponse));
@@ -425,7 +425,7 @@ namespace NzbDrone.Core.Indexers.Definitions
} }
var releaseGroup = releaseTags.LastOrDefault(); var releaseGroup = releaseTags.LastOrDefault();
if (releaseGroup != null && releaseGroup.Contains('(') && releaseGroup.Contains(')')) if (releaseGroup != null && releaseGroup.Contains("(") && releaseGroup.Contains(")"))
{ {
//// Skip raws if set //// Skip raws if set
//if (releaseGroup.ToLowerInvariant().StartsWith("raw") && !AllowRaws) //if (releaseGroup.ToLowerInvariant().StartsWith("raw") && !AllowRaws)
@@ -521,7 +521,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; } public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
} }
public class AnimeBytesSettingsValidator : NoAuthSettingsValidator<AnimeBytesSettings> public class AnimeBytesSettingsValidator : AbstractValidator<AnimeBytesSettings>
{ {
public AnimeBytesSettingsValidator() public AnimeBytesSettingsValidator()
{ {
@@ -535,7 +535,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class AnimeBytesSettings : NoAuthTorrentBaseSettings public class AnimeBytesSettings : NoAuthTorrentBaseSettings
{ {
private static readonly AnimeBytesSettingsValidator Validator = new (); private static readonly AnimeBytesSettingsValidator Validator = new AnimeBytesSettingsValidator();
public AnimeBytesSettings() public AnimeBytesSettings()
{ {
@@ -7,8 +7,10 @@ using System.Net.Http;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using AngleSharp.Html.Parser; using AngleSharp.Html.Parser;
using FluentValidation;
using NLog; using NLog;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions; using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Indexers.Settings; using NzbDrone.Core.Indexers.Settings;
@@ -16,6 +18,7 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions namespace NzbDrone.Core.Indexers.Definitions
{ {
@@ -23,7 +26,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
public override string Name => "AnimeTorrents"; public override string Name => "AnimeTorrents";
public override string[] IndexerUrls => new[] { "https://animetorrents.me/" }; public override string[] IndexerUrls => new string[] { "https://animetorrents.me/" };
public override string Description => "Definitive source for anime and manga"; public override string Description => "Definitive source for anime and manga";
private string LoginUrl => Settings.BaseUrl + "login.php"; private string LoginUrl => Settings.BaseUrl + "login.php";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent; public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
@@ -37,7 +40,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator() public override IIndexerRequestGenerator GetRequestGenerator()
{ {
return new AnimeTorrentsRequestGenerator { Settings = Settings, Capabilities = Capabilities }; return new AnimeTorrentsRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
} }
public override IParseIndexerResponse GetParser() public override IParseIndexerResponse GetParser()
@@ -49,29 +52,30 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
UpdateCookies(null, null); UpdateCookies(null, null);
var loginPage = await ExecuteAuth(new HttpRequest(LoginUrl));
var requestBuilder = new HttpRequestBuilder(LoginUrl) var requestBuilder = new HttpRequestBuilder(LoginUrl)
{ {
LogResponseContent = true, LogResponseContent = true,
AllowAutoRedirect = true, AllowAutoRedirect = true
Method = HttpMethod.Post
}; };
var loginPage = await ExecuteAuth(new HttpRequest(LoginUrl));
requestBuilder.Method = HttpMethod.Post;
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
requestBuilder.SetCookies(loginPage.GetCookies());
var authLoginRequest = requestBuilder var authLoginRequest = requestBuilder
.SetCookies(loginPage.GetCookies())
.AddFormParameter("username", Settings.Username) .AddFormParameter("username", Settings.Username)
.AddFormParameter("password", Settings.Password) .AddFormParameter("password", Settings.Password)
.AddFormParameter("form", "login") .AddFormParameter("form", "login")
.AddFormParameter("rememberme[]", "1") .AddFormParameter("rememberme[]", "1")
.SetHeader("Content-Type", "application/x-www-form-urlencoded") .SetHeader("Content-Type", "multipart/form-data")
.Build(); .Build();
var response = await ExecuteAuth(authLoginRequest); var response = await ExecuteAuth(authLoginRequest);
if (response.Content != null && response.Content.Contains("logout.php")) if (response.Content != null && response.Content.Contains("logout.php"))
{ {
UpdateCookies(response.GetCookies(), DateTime.Now.AddDays(30)); UpdateCookies(response.GetCookies(), DateTime.Now + TimeSpan.FromDays(30));
_logger.Debug("AnimeTorrents authentication succeeded"); _logger.Debug("AnimeTorrents authentication succeeded");
} }
@@ -83,7 +87,12 @@ namespace NzbDrone.Core.Indexers.Definitions
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse) protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{ {
return httpResponse.Content.Contains("Access Denied!") || httpResponse.Content.Contains("login.php"); if (httpResponse.Content.Contains("Access Denied!") || httpResponse.Content.Contains("login.php"))
{
return true;
}
return false;
} }
private IndexerCapabilities SetCapabilities() private IndexerCapabilities SetCapabilities()
@@ -91,13 +100,13 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities var caps = new IndexerCapabilities
{ {
TvSearchParams = new List<TvSearchParam> TvSearchParams = new List<TvSearchParam>
{ {
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
}, },
MovieSearchParams = new List<MovieSearchParam> MovieSearchParams = new List<MovieSearchParam>
{ {
MovieSearchParam.Q MovieSearchParam.Q
} }
}; };
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesSD, "Anime Movie"); caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesSD, "Anime Movie");
@@ -129,6 +138,10 @@ namespace NzbDrone.Core.Indexers.Definitions
public UserPassTorrentBaseSettings Settings { get; set; } public UserPassTorrentBaseSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; } public IndexerCapabilities Capabilities { get; set; }
public AnimeTorrentsRequestGenerator()
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories) private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{ {
var searchString = term; var searchString = term;
@@ -5,8 +5,10 @@ using System.Net;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using AngleSharp.Html.Parser; using AngleSharp.Html.Parser;
using FluentValidation;
using NLog; using NLog;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions; using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Indexers.Settings; using NzbDrone.Core.Indexers.Settings;
@@ -14,13 +16,14 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions namespace NzbDrone.Core.Indexers.Definitions
{ {
public class Animedia : TorrentIndexerBase<NoAuthTorrentBaseSettings> public class Animedia : TorrentIndexerBase<NoAuthTorrentBaseSettings>
{ {
public override string Name => "Animedia"; public override string Name => "Animedia";
public override string[] IndexerUrls => new[] { "https://tt.animedia.tv/" }; 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 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 Encoding Encoding => Encoding.UTF8;
@@ -35,12 +38,12 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator() public override IIndexerRequestGenerator GetRequestGenerator()
{ {
return new AnimediaRequestGenerator(Settings); return new AnimediaRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
} }
public override IParseIndexerResponse GetParser() public override IParseIndexerResponse GetParser()
{ {
return new AnimediaParser(Settings, Capabilities.Categories, RateLimit, _httpClient); return new AnimediaParser(Settings, Capabilities.Categories) { HttpClient = _httpClient, Logger = _logger };
} }
private IndexerCapabilities SetCapabilities() private IndexerCapabilities SetCapabilities()
@@ -48,40 +51,38 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities var caps = new IndexerCapabilities
{ {
TvSearchParams = new List<TvSearchParam> TvSearchParams = new List<TvSearchParam>
{ {
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
}, },
MovieSearchParams = new List<MovieSearchParam> MovieSearchParams = new List<MovieSearchParam>
{ {
MovieSearchParam.Q MovieSearchParam.Q
} }
}; };
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "TV Anime"); caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "TV Anime");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "OVA/ONA/Special"); caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "OVA/ONA/Special");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TV, "Dorama"); caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TV, "Dorama");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.Movies, "Movies"); caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.Movies, "Movies");
return caps; return caps;
} }
} }
public class AnimediaRequestGenerator : IIndexerRequestGenerator public class AnimediaRequestGenerator : IIndexerRequestGenerator
{ {
private readonly NoAuthTorrentBaseSettings _settings; public NoAuthTorrentBaseSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public AnimediaRequestGenerator(NoAuthTorrentBaseSettings settings) public AnimediaRequestGenerator()
{ {
_settings = settings;
} }
private IEnumerable<IndexerRequest> GetPagedRequests(string term) private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{ {
string requestUrl; var requestUrl = string.Empty;
if (string.IsNullOrWhiteSpace(term)) if (string.IsNullOrWhiteSpace(term))
{ {
requestUrl = _settings.BaseUrl; requestUrl = Settings.BaseUrl;
} }
else else
{ {
@@ -93,17 +94,18 @@ namespace NzbDrone.Core.Indexers.Definitions
{ "orderby_sort", "entry_date|desc" } { "orderby_sort", "entry_date|desc" }
}; };
requestUrl = $"{_settings.BaseUrl.TrimEnd('/')}/ajax/search_result/P0?{queryCollection.GetQueryString()}"; requestUrl = string.Format("{0}/ajax/search_result/P0?{1}", Settings.BaseUrl.TrimEnd('/'), queryCollection.GetQueryString());
} }
yield return new IndexerRequest(requestUrl, HttpAccept.Html); var request = new IndexerRequest(requestUrl, HttpAccept.Html);
yield return request;
} }
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria) public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}")); pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests; return pageableRequests;
} }
@@ -112,7 +114,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedTvSearchString}")); pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
return pageableRequests; return pageableRequests;
} }
@@ -121,7 +123,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}")); pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests; return pageableRequests;
} }
@@ -146,9 +148,6 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
private readonly NoAuthTorrentBaseSettings _settings; private readonly NoAuthTorrentBaseSettings _settings;
private readonly IndexerCapabilitiesCategories _categories; private readonly IndexerCapabilitiesCategories _categories;
private readonly TimeSpan _rateLimit;
private readonly IIndexerHttpClient _httpClient;
private static readonly Regex EpisodesInfoQueryRegex = new Regex(@"сери[ия] (\d+)(?:-(\d+))? из.*", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex EpisodesInfoQueryRegex = new Regex(@"сери[ия] (\d+)(?:-(\d+))? из.*", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ResolutionInfoQueryRegex = new Regex(@"качество (\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex ResolutionInfoQueryRegex = new Regex(@"качество (\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex SizeInfoQueryRegex = new Regex(@"размер:(.*)\n", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex SizeInfoQueryRegex = new Regex(@"размер:(.*)\n", RegexOptions.Compiled | RegexOptions.IgnoreCase);
@@ -156,25 +155,25 @@ namespace NzbDrone.Core.Indexers.Definitions
private static readonly Regex CategorieMovieRegex = new Regex(@"Фильм", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex CategorieMovieRegex = new Regex(@"Фильм", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex CategorieOVARegex = new Regex(@"ОВА|OVA|ОНА|ONA|Special", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex CategorieOVARegex = new Regex(@"ОВА|OVA|ОНА|ONA|Special", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex CategorieDoramaRegex = new Regex(@"Дорама", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex CategorieDoramaRegex = new Regex(@"Дорама", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public IIndexerHttpClient HttpClient { get; set; }
public Logger Logger { get; set; }
public AnimediaParser(NoAuthTorrentBaseSettings settings, IndexerCapabilitiesCategories categories, TimeSpan rateLimit, IIndexerHttpClient httpClient) public AnimediaParser(NoAuthTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
{ {
_settings = settings; _settings = settings;
_categories = categories; _categories = categories;
_rateLimit = rateLimit;
_httpClient = httpClient;
} }
private string ComposeTitle(AngleSharp.Html.Dom.IHtmlDocument dom, AngleSharp.Dom.IElement t, AngleSharp.Dom.IElement tr) private string composeTitle(AngleSharp.Html.Dom.IHtmlDocument dom, AngleSharp.Dom.IElement t, AngleSharp.Dom.IElement tr)
{ {
var nameRu = dom.QuerySelector("div.media__post__header > h1")?.TextContent.Trim() ?? string.Empty; var name_ru = dom.QuerySelector("div.media__post__header > h1").TextContent.Trim();
var nameEn = dom.QuerySelector("div.media__panel > div:nth-of-type(1) > div.col-l:nth-of-type(1) > div > span")?.TextContent.Trim() ?? string.Empty; var name_en = dom.QuerySelector("div.media__panel > div:nth-of-type(1) > div.col-l:nth-of-type(1) > div > span").TextContent.Trim();
var nameOrig = dom.QuerySelector("div.media__panel > div:nth-of-type(1) > div.col-l:nth-of-type(2) > div > span")?.TextContent.Trim() ?? string.Empty; var name_orig = dom.QuerySelector("div.media__panel > div:nth-of-type(1) > div.col-l:nth-of-type(2) > div > span").TextContent.Trim();
var title = nameRu + " / " + nameEn; var title = name_ru + " / " + name_en;
if (nameEn != nameOrig) if (name_en != name_orig)
{ {
title += " / " + nameOrig; title += " / " + name_orig;
} }
var tabName = t.TextContent; var tabName = t.TextContent;
@@ -184,7 +183,7 @@ namespace NzbDrone.Core.Indexers.Definitions
tabName = ""; tabName = "";
} }
var heading = tr.QuerySelector("h3.tracker_info_bold")?.TextContent.Trim() ?? string.Empty; var heading = tr.QuerySelector("h3.tracker_info_bold").TextContent;
// Parse episodes info from heading if episods info present // Parse episodes info from heading if episods info present
var match = EpisodesInfoQueryRegex.Match(heading); var match = EpisodesInfoQueryRegex.Match(heading);
@@ -193,40 +192,40 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
if (string.IsNullOrEmpty(match.Groups[2].Value)) if (string.IsNullOrEmpty(match.Groups[2].Value))
{ {
heading += $" E{match.Groups[1].Value}"; heading += " E" + match.Groups[1].Value;
} }
else else
{ {
heading += $" E{match.Groups[1].Value}-{match.Groups[2].Value}"; heading += string.Format(" E{0}-{1}", match.Groups[1].Value, match.Groups[2].Value);
} }
} }
return title + " - " + heading + " [" + GetResolution(tr) + "p]"; return title + " - " + heading + " [" + getResolution(tr) + "p]";
} }
private string GetResolution(AngleSharp.Dom.IElement tr) private string getResolution(AngleSharp.Dom.IElement tr)
{ {
var resolution = tr.QuerySelector("div.tracker_info_left")?.TextContent.Trim() ?? string.Empty; var resolution = tr.QuerySelector("div.tracker_info_left").TextContent;
return ResolutionInfoQueryRegex.Match(resolution).Groups[1].Value; return ResolutionInfoQueryRegex.Match(resolution).Groups[1].Value;
} }
private long GetReleaseSize(AngleSharp.Dom.IElement tr) private long getReleaseSize(AngleSharp.Dom.IElement tr)
{ {
var sizeStr = tr.QuerySelector("div.tracker_info_left")?.TextContent.Trim() ?? string.Empty; var sizeStr = tr.QuerySelector("div.tracker_info_left").TextContent;
return ParseUtil.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) private DateTime getReleaseDate(AngleSharp.Dom.IElement tr)
{ {
var sizeStr = tr.QuerySelector("div.tracker_info_left")?.TextContent.Trim() ?? string.Empty; var sizeStr = tr.QuerySelector("div.tracker_info_left").TextContent;
return DateTime.Parse(ReleaseDateInfoQueryRegex.Match(sizeStr).Groups[1].Value.Trim()); return DateTime.Parse(ReleaseDateInfoQueryRegex.Match(sizeStr).Groups[1].Value.Trim());
} }
private ICollection<IndexerCategory> MapCategories(AngleSharp.Html.Dom.IHtmlDocument dom, AngleSharp.Dom.IElement t, AngleSharp.Dom.IElement tr) private ICollection<IndexerCategory> MapCategories(AngleSharp.Html.Dom.IHtmlDocument dom, AngleSharp.Dom.IElement t, AngleSharp.Dom.IElement tr)
{ {
var rName = t.TextContent; var rName = t.TextContent;
var rDesc = tr.QuerySelector("h3.tracker_info_bold")?.TextContent.Trim() ?? string.Empty; var rDesc = tr.QuerySelector("h3.tracker_info_bold").TextContent;
var type = dom.QuerySelector("div.releases-date:contains('Тип:')")?.TextContent.Trim() ?? string.Empty; var type = dom.QuerySelector("div.releases-date:contains('Тип:')").TextContent;
// Check OVA first cause OVA looks like anime with OVA in release name or description // Check OVA first cause OVA looks like anime with OVA in release name or description
if (CategorieOVARegex.IsMatch(rName) || CategorieOVARegex.IsMatch(rDesc)) if (CategorieOVARegex.IsMatch(rName) || CategorieOVARegex.IsMatch(rDesc))
@@ -257,28 +256,28 @@ namespace NzbDrone.Core.Indexers.Definitions
foreach (var t in dom.QuerySelectorAll("ul.media__tabs__nav > li > a")) foreach (var t in dom.QuerySelectorAll("ul.media__tabs__nav > li > a"))
{ {
var trId = t.GetAttribute("href"); var tr_id = t.Attributes["href"].Value;
var tr = dom.QuerySelector("div" + trId); var tr = dom.QuerySelector("div" + tr_id);
var seeders = int.Parse(tr.QuerySelector("div.circle_green_text_top").TextContent); var seeders = int.Parse(tr.QuerySelector("div.circle_green_text_top").TextContent);
var url = indexerResponse.HttpRequest.Url.FullUri; var url = indexerResponse.HttpRequest.Url.ToString();
var release = new TorrentInfo var release = new TorrentInfo
{ {
Title = ComposeTitle(dom, t, tr), Title = composeTitle(dom, t, tr),
InfoUrl = url, InfoUrl = url,
DownloadVolumeFactor = 0, DownloadVolumeFactor = 0,
UploadVolumeFactor = 1, UploadVolumeFactor = 1,
Guid = url + trId, Guid = url + tr_id,
Seeders = seeders, Seeders = seeders,
Peers = seeders + int.Parse(tr.QuerySelector("div.circle_red_text_top").TextContent), Peers = seeders + int.Parse(tr.QuerySelector("div.circle_red_text_top").TextContent),
Grabs = int.Parse(tr.QuerySelector("div.circle_grey_text_top").TextContent), Grabs = int.Parse(tr.QuerySelector("div.circle_grey_text_top").TextContent),
Categories = MapCategories(dom, t, tr), Categories = MapCategories(dom, t, tr),
PublishDate = GetReleaseDate(tr), PublishDate = getReleaseDate(tr),
DownloadUrl = tr.QuerySelector("div.download_tracker > a.btn__green").GetAttribute("href"), DownloadUrl = tr.QuerySelector("div.download_tracker > a.btn__green").Attributes["href"].Value,
MagnetUrl = tr.QuerySelector("div.download_tracker > a.btn__d-gray").GetAttribute("href"), MagnetUrl = tr.QuerySelector("div.download_tracker > a.btn__d-gray").Attributes["href"].Value,
Size = GetReleaseSize(tr), Size = getReleaseSize(tr),
Resolution = GetResolution(tr) Resolution = getResolution(tr)
}; };
torrentInfos.Add(release); torrentInfos.Add(release);
} }
@@ -292,7 +291,6 @@ namespace NzbDrone.Core.Indexers.Definitions
var parser = new HtmlParser(); var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content); var dom = parser.ParseDocument(indexerResponse.Content);
var links = dom.QuerySelectorAll("a.ads-list__item__title"); var links = dom.QuerySelectorAll("a.ads-list__item__title");
foreach (var link in links) foreach (var link in links)
{ {
@@ -304,24 +302,20 @@ namespace NzbDrone.Core.Indexers.Definitions
url = "https:" + url; url = "https:" + url;
} }
var releaseRequest = new HttpRequestBuilder(url) var releaseRequest = new IndexerRequest(url, HttpAccept.Html);
.WithRateLimit(_rateLimit.TotalSeconds) var releaseResponse = new IndexerResponse(releaseRequest, HttpClient.Execute(releaseRequest.HttpRequest));
.SetHeader("Referer", _settings.BaseUrl)
.Accept(HttpAccept.Html)
.Build();
var releaseIndexerRequest = new IndexerRequest(releaseRequest);
var releaseResponse = new IndexerResponse(releaseIndexerRequest, _httpClient.Execute(releaseIndexerRequest.HttpRequest));
// Throw common http errors here before we try to parse // Throw common http errors here before we try to parse
if (releaseResponse.HttpResponse.HasHttpError) if (releaseResponse.HttpResponse.HasHttpError)
{ {
if (releaseResponse.HttpResponse.StatusCode == HttpStatusCode.TooManyRequests) if (releaseResponse.HttpResponse.StatusCode == HttpStatusCode.TooManyRequests)
{ {
throw new TooManyRequestsException(releaseResponse.HttpRequest, releaseResponse.HttpResponse); throw new TooManyRequestsException(releaseRequest.HttpRequest, releaseResponse.HttpResponse);
}
else
{
throw new IndexerException(releaseResponse, "Http error code: " + releaseResponse.HttpResponse.StatusCode);
} }
throw new IndexerException(releaseResponse, $"HTTP Error - {releaseResponse.HttpResponse.StatusCode}. {url}");
} }
torrentInfos.AddRange(ParseRelease(releaseResponse)); torrentInfos.AddRange(ParseRelease(releaseResponse));
@@ -52,42 +52,55 @@ namespace NzbDrone.Core.Indexers.Definitions
var requestBuilder = new HttpRequestBuilder(LoginUrl) var requestBuilder = new HttpRequestBuilder(LoginUrl)
{ {
LogResponseContent = true, LogResponseContent = true,
AllowAutoRedirect = true, AllowAutoRedirect = true
Method = HttpMethod.Post
}; };
var cookies = Cookies; requestBuilder.Method = HttpMethod.Post;
Cookies = null; requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
var cookies = Cookies;
Cookies = null;
var authLoginRequest = requestBuilder var authLoginRequest = requestBuilder
.AddFormParameter("username", Settings.Username) .AddFormParameter("username", Settings.Username)
.AddFormParameter("password", Settings.Password) .AddFormParameter("password", Settings.Password)
.AddFormParameter("keeplogged", "1") .AddFormParameter("keeplogged", "1")
.AddFormParameter("login", "Log+In!") .AddFormParameter("login", "Log+In!")
.SetHeader("Content-Type", "application/x-www-form-urlencoded") .SetHeader("Content-Type", "multipart/form-data")
.SetHeader("Referer", LoginUrl)
.Build(); .Build();
var headers = new NameValueCollection
{
{ "Referer", LoginUrl }
};
authLoginRequest.Headers.Add(headers);
var response = await ExecuteAuth(authLoginRequest); var response = await ExecuteAuth(authLoginRequest);
if (CheckIfLoginNeeded(response)) if (CheckIfLoginNeeded(response))
{ {
var parser = new HtmlParser(); var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content); var dom = parser.ParseDocument(response.Content);
var errorMessage = dom.QuerySelector("form#loginform")?.TextContent.Trim(); var errorMessage = dom.QuerySelector("form#loginform").TextContent.Trim();
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report."); throw new IndexerAuthException(errorMessage);
} }
cookies = response.GetCookies(); cookies = response.GetCookies();
UpdateCookies(cookies, DateTime.Now.AddDays(30)); UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
_logger.Debug("Anthelion authentication succeeded."); _logger.Debug("Anthelion authentication succeeded.");
} }
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse) protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{ {
return !httpResponse.Content.Contains("logout.php"); if (!httpResponse.Content.Contains("logout.php"))
{
return true;
}
return false;
} }
private IndexerCapabilities SetCapabilities() private IndexerCapabilities SetCapabilities()
@@ -95,13 +108,13 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities var caps = new IndexerCapabilities
{ {
TvSearchParams = new List<TvSearchParam> TvSearchParams = new List<TvSearchParam>
{ {
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
}, },
MovieSearchParams = new List<MovieSearchParam> MovieSearchParams = new List<MovieSearchParam>
{ {
MovieSearchParam.Q MovieSearchParam.Q
} }
}; };
caps.Categories.AddCategoryMapping("1", NewznabStandardCategory.Movies, "Film/Feature"); caps.Categories.AddCategoryMapping("1", NewznabStandardCategory.Movies, "Film/Feature");
@@ -118,6 +131,10 @@ namespace NzbDrone.Core.Indexers.Definitions
public UserPassTorrentBaseSettings Settings { get; set; } public UserPassTorrentBaseSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; } public IndexerCapabilities Capabilities { get; set; }
public AnthelionRequestGenerator()
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null) private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
{ {
var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/')); var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/'));
@@ -1,81 +0,0 @@
using System.Collections.Generic;
using AngleSharp.Html.Parser;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.Gazelle;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions;
public class AroLol : GazelleBase<AroLolSettings>
{
public override string Name => "aro.lol";
public override string[] IndexerUrls => new[] { "https://aro.lol/" };
public override string Description => "aro.lol is a SERBIAN/ENGLISH Private Torrent Tracker for ANIME";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public AroLol(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
protected override HttpRequestBuilder AuthLoginRequestBuilder()
{
return base.AuthLoginRequestBuilder()
.AddFormParameter("twofa", Settings.TwoFactorAuthCode?.Trim() ?? "");
}
protected override bool CheckForLoginError(HttpResponse response)
{
if (response.Content.Contains("loginform"))
{
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
var errorMessage = dom.QuerySelector("#loginform > .warning")?.TextContent.Trim();
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
}
return true;
}
protected override IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
}
};
caps.Categories.AddCategoryMapping("1", NewznabStandardCategory.Movies, "Movies");
caps.Categories.AddCategoryMapping("2", NewznabStandardCategory.TVAnime, "Anime");
caps.Categories.AddCategoryMapping("3", NewznabStandardCategory.Books, "Manga");
caps.Categories.AddCategoryMapping("4", NewznabStandardCategory.Console, "Games");
caps.Categories.AddCategoryMapping("5", NewznabStandardCategory.Other, "Other");
return caps;
}
}
public class AroLolSettings : GazelleSettings
{
[FieldDefinition(4, Label = "2FA code", Type = FieldType.Textbox, HelpText = "Only fill in the <b>2FA code</b> box if you have enabled <b>2FA</b> on the aro.lol Web Site. Otherwise just leave it empty.")]
public string TwoFactorAuthCode { get; set; }
}
@@ -1,358 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Settings;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Definitions;
public class AudioBookBay : TorrentIndexerBase<NoAuthTorrentBaseSettings>
{
public override string Name => "AudioBook Bay";
public override string[] IndexerUrls => new[]
{
"https://audiobookbay.li/",
"https://audiobookbay.se/"
};
public override string[] LegacyUrls => new[]
{
"https://audiobookbay.la/",
"http://audiobookbay.net/",
"https://audiobookbay.unblockit.tv/",
"http://audiobookbay.nl/",
"http://audiobookbay.ws/",
"https://audiobookbay.unblockit.how/",
"https://audiobookbay.unblockit.cam/",
"https://audiobookbay.unblockit.biz/",
"https://audiobookbay.unblockit.day/",
"https://audiobookbay.unblockit.llc/",
"https://audiobookbay.unblockit.blue/",
"https://audiobookbay.unblockit.name/",
"http://audiobookbay.fi/",
"http://audiobookbay.se/",
"http://audiobookbayabb.com/",
"https://audiobookbay.unblockit.ist/",
"https://audiobookbay.unblockit.bet/",
"https://audiobookbay.unblockit.cat/",
"https://audiobookbay.unblockit.nz/",
"https://audiobookbay.fi/",
"https://audiobookbay.unblockit.page/",
"https://audiobookbay.unblockit.pet/",
"https://audiobookbay.unblockit.ink/",
"https://audiobookbay.unblockit.bio/" // error 502
};
public override string Description => "AudioBook Bay (ABB) is a public Torrent Tracker for AUDIOBOOKS";
public override string Language => "en-US";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
public override int PageSize => 15;
public override IndexerCapabilities Capabilities => SetCapabilities();
public AudioBookBay(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AudioBookBayRequestGenerator(Settings, Capabilities);
}
public override IParseIndexerResponse GetParser()
{
return new AudioBookBayParser(Settings, Capabilities.Categories);
}
public override async Task<byte[]> Download(Uri link)
{
var request = new HttpRequestBuilder(link.ToString())
.SetCookies(GetCookies() ?? new Dictionary<string, string>())
.Accept(HttpAccept.Html)
.Build();
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
var hash = dom.QuerySelector("td:contains(\"Info Hash:\") ~ td")?.TextContent.Trim();
if (hash == null)
{
throw new Exception($"Failed to fetch hash from {link}");
}
var title = dom.QuerySelector("div.postTitle h1")?.TextContent.Trim();
if (title == null)
{
throw new Exception($"Failed to fetch title from {link}");
}
title = StringUtil.MakeValidFileName(title, '_', false);
var magnet = MagnetLinkBuilder.BuildPublicMagnetLink(hash, title);
return await base.Download(new Uri(magnet));
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
}
};
// Age
caps.Categories.AddCategoryMapping("children", NewznabStandardCategory.AudioAudiobook, "Children");
caps.Categories.AddCategoryMapping("teen-young-adult", NewznabStandardCategory.AudioAudiobook, "Teen & Young Adult");
caps.Categories.AddCategoryMapping("adults", NewznabStandardCategory.AudioAudiobook, "Adults");
// Category
caps.Categories.AddCategoryMapping("postapocalyptic", NewznabStandardCategory.AudioAudiobook, "(Post)apocalyptic");
caps.Categories.AddCategoryMapping("action", NewznabStandardCategory.AudioAudiobook, "Action");
caps.Categories.AddCategoryMapping("adventure", NewznabStandardCategory.AudioAudiobook, "Adventure");
caps.Categories.AddCategoryMapping("art", NewznabStandardCategory.AudioAudiobook, "Art");
caps.Categories.AddCategoryMapping("autobiography-biographies", NewznabStandardCategory.AudioAudiobook, "Autobiography & Biographies");
caps.Categories.AddCategoryMapping("business", NewznabStandardCategory.AudioAudiobook, "Business");
caps.Categories.AddCategoryMapping("computer", NewznabStandardCategory.AudioAudiobook, "Computer");
caps.Categories.AddCategoryMapping("contemporary", NewznabStandardCategory.AudioAudiobook, "Contemporary");
caps.Categories.AddCategoryMapping("crime", NewznabStandardCategory.AudioAudiobook, "Crime");
caps.Categories.AddCategoryMapping("detective", NewznabStandardCategory.AudioAudiobook, "Detective");
caps.Categories.AddCategoryMapping("doctor-who-sci-fi", NewznabStandardCategory.AudioAudiobook, "Doctor Who");
caps.Categories.AddCategoryMapping("education", NewznabStandardCategory.AudioAudiobook, "Education");
caps.Categories.AddCategoryMapping("fantasy", NewznabStandardCategory.AudioAudiobook, "Fantasy");
caps.Categories.AddCategoryMapping("general-fiction", NewznabStandardCategory.AudioAudiobook, "General Fiction");
caps.Categories.AddCategoryMapping("historical-fiction", NewznabStandardCategory.AudioAudiobook, "Historical Fiction");
caps.Categories.AddCategoryMapping("history", NewznabStandardCategory.AudioAudiobook, "History");
caps.Categories.AddCategoryMapping("horror", NewznabStandardCategory.AudioAudiobook, "Horror");
caps.Categories.AddCategoryMapping("humor", NewznabStandardCategory.AudioAudiobook, "Humor");
caps.Categories.AddCategoryMapping("lecture", NewznabStandardCategory.AudioAudiobook, "Lecture");
caps.Categories.AddCategoryMapping("lgbt", NewznabStandardCategory.AudioAudiobook, "LGBT");
caps.Categories.AddCategoryMapping("literature", NewznabStandardCategory.AudioAudiobook, "Literature");
caps.Categories.AddCategoryMapping("litrpg", NewznabStandardCategory.AudioAudiobook, "LitRPG");
caps.Categories.AddCategoryMapping("general-non-fiction", NewznabStandardCategory.AudioAudiobook, "Misc. Non-fiction");
caps.Categories.AddCategoryMapping("mystery", NewznabStandardCategory.AudioAudiobook, "Mystery");
caps.Categories.AddCategoryMapping("paranormal", NewznabStandardCategory.AudioAudiobook, "Paranormal");
caps.Categories.AddCategoryMapping("plays-theater", NewznabStandardCategory.AudioAudiobook, "Plays & Theater");
caps.Categories.AddCategoryMapping("poetry", NewznabStandardCategory.AudioAudiobook, "Poetry");
caps.Categories.AddCategoryMapping("political", NewznabStandardCategory.AudioAudiobook, "Political");
caps.Categories.AddCategoryMapping("radio-productions", NewznabStandardCategory.AudioAudiobook, "Radio Productions");
caps.Categories.AddCategoryMapping("romance", NewznabStandardCategory.AudioAudiobook, "Romance");
caps.Categories.AddCategoryMapping("sci-fi", NewznabStandardCategory.AudioAudiobook, "Sci-Fi");
caps.Categories.AddCategoryMapping("science", NewznabStandardCategory.AudioAudiobook, "Science");
caps.Categories.AddCategoryMapping("self-help", NewznabStandardCategory.AudioAudiobook, "Self-help");
caps.Categories.AddCategoryMapping("spiritual", NewznabStandardCategory.AudioAudiobook, "Spiritual & Religious");
caps.Categories.AddCategoryMapping("sports", NewznabStandardCategory.AudioAudiobook, "Sport & Recreation");
caps.Categories.AddCategoryMapping("suspense", NewznabStandardCategory.AudioAudiobook, "Suspense");
caps.Categories.AddCategoryMapping("thriller", NewznabStandardCategory.AudioAudiobook, "Thriller");
caps.Categories.AddCategoryMapping("true-crime", NewznabStandardCategory.AudioAudiobook, "True Crime");
caps.Categories.AddCategoryMapping("tutorial", NewznabStandardCategory.AudioAudiobook, "Tutorial");
caps.Categories.AddCategoryMapping("westerns", NewznabStandardCategory.AudioAudiobook, "Westerns");
caps.Categories.AddCategoryMapping("zombies", NewznabStandardCategory.AudioAudiobook, "Zombies");
// Category Modifiers
caps.Categories.AddCategoryMapping("anthology", NewznabStandardCategory.AudioAudiobook, "Anthology");
caps.Categories.AddCategoryMapping("bestsellers", NewznabStandardCategory.AudioAudiobook, "Bestsellers");
caps.Categories.AddCategoryMapping("classic", NewznabStandardCategory.AudioAudiobook, "Classic");
caps.Categories.AddCategoryMapping("documentary", NewznabStandardCategory.AudioAudiobook, "Documentary");
caps.Categories.AddCategoryMapping("full-cast", NewznabStandardCategory.AudioAudiobook, "Full Cast");
caps.Categories.AddCategoryMapping("libertarian", NewznabStandardCategory.AudioAudiobook, "Libertarian");
caps.Categories.AddCategoryMapping("military", NewznabStandardCategory.AudioAudiobook, "Military");
caps.Categories.AddCategoryMapping("novel", NewznabStandardCategory.AudioAudiobook, "Novel");
caps.Categories.AddCategoryMapping("short-story", NewznabStandardCategory.AudioAudiobook, "Short Story");
return caps;
}
}
public class AudioBookBayRequestGenerator : IIndexerRequestGenerator
{
private readonly NoAuthTorrentBaseSettings _settings;
private readonly IndexerCapabilities _capabilities;
public AudioBookBayRequestGenerator(NoAuthTorrentBaseSettings settings, IndexerCapabilities capabilities)
{
_settings = settings;
_capabilities = capabilities;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
return pageableRequests;
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term)
{
var searchUrl = _settings.BaseUrl;
var parameters = new NameValueCollection();
term = Regex.Replace(term, @"[\W]+", " ").Trim();
if (term.IsNotNullOrWhiteSpace())
{
parameters.Set("s", term);
parameters.Set("tt", "1");
}
if (parameters.Count > 0)
{
searchUrl += $"?{parameters.GetQueryString()}";
}
yield return new IndexerRequest(new UriBuilder(searchUrl) { Path = "/" }.Uri.AbsoluteUri, HttpAccept.Html);
yield return new IndexerRequest(new UriBuilder(searchUrl) { Path = "/page/2/" }.Uri.AbsoluteUri, HttpAccept.Html);
yield return new IndexerRequest(new UriBuilder(searchUrl) { Path = "/page/3/" }.Uri.AbsoluteUri, HttpAccept.Html);
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class AudioBookBayParser : IParseIndexerResponse
{
private readonly NoAuthTorrentBaseSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public AudioBookBayParser(NoAuthTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var releaseInfos = new List<ReleaseInfo>();
var doc = ParseHtmlDocument(indexerResponse.Content);
var rows = doc.QuerySelectorAll("div.post:has(div[class=\"postTitle\"])");
foreach (var row in rows)
{
var infoUrl = _settings.BaseUrl + row.QuerySelector("div.postTitle h2 a")?.GetAttribute("href")?.Trim().TrimStart('/');
var title = row.QuerySelector("div.postTitle")?.TextContent.Trim();
var infoString = row.QuerySelector("div.postContent")?.TextContent.Trim() ?? string.Empty;
var matchFormat = Regex.Match(infoString, @"Format: (.+) \/", RegexOptions.IgnoreCase);
if (matchFormat.Groups[1].Success && matchFormat.Groups[1].Value.Length > 0 && matchFormat.Groups[1].Value != "?")
{
title += $" [{matchFormat.Groups[1].Value.Trim()}]";
}
var matchBitrate = Regex.Match(infoString, @"Bitrate: (.+)File", RegexOptions.IgnoreCase);
if (matchBitrate.Groups[1].Success && matchBitrate.Groups[1].Value.Length > 0 && matchBitrate.Groups[1].Value != "?")
{
title += $" [{matchBitrate.Groups[1].Value.Trim()}]";
}
var matchSize = Regex.Match(infoString, @"File Size: (.+?)s?$", RegexOptions.IgnoreCase);
var size = matchSize.Groups[1].Success ? ParseUtil.GetBytes(matchSize.Groups[1].Value) : 0;
var matchDateAdded = Regex.Match(infoString, @"Posted: (\d{1,2} \D{3} \d{4})", RegexOptions.IgnoreCase);
var publishDate = matchDateAdded.Groups[1].Success && DateTime.TryParseExact(matchDateAdded.Groups[1].Value, "d MMM yyyy", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsedDate) ? parsedDate : DateTime.Now;
var postInfo = row.QuerySelector("div.postInfo")?.FirstChild?.TextContent.Trim().Replace("\xA0", ";") ?? string.Empty;
var matchCategory = Regex.Match(postInfo, @"Category: (.+)$", RegexOptions.IgnoreCase);
var category = matchCategory.Groups[1].Success ? matchCategory.Groups[1].Value.Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList() : new List<string>();
var categories = category.SelectMany(_categories.MapTrackerCatDescToNewznab).Distinct().ToList();
var release = new TorrentInfo
{
Guid = infoUrl,
InfoUrl = infoUrl,
DownloadUrl = infoUrl,
Title = CleanTitle(title),
Categories = categories,
Size = size,
Seeders = 1,
Peers = 1,
PublishDate = publishDate,
DownloadVolumeFactor = 0,
UploadVolumeFactor = 1
};
var cover = row.QuerySelector("img[src]")?.GetAttribute("src")?.Trim();
if (!string.IsNullOrEmpty(cover))
{
release.PosterUrl = cover.StartsWith("http") ? cover : _settings.BaseUrl + cover;
}
releaseInfos.Add(release);
}
return releaseInfos;
}
private static IHtmlDocument ParseHtmlDocument(string response)
{
var parser = new HtmlParser();
var doc = parser.ParseDocument(response);
var hidden = doc.QuerySelectorAll("div.post.re-ab");
foreach (var element in hidden)
{
var body = doc.CreateElement("div");
body.ClassList.Add("post");
body.InnerHtml = Encoding.UTF8.GetString(Convert.FromBase64String(element.TextContent));
element.Parent.ReplaceChild(body, element);
}
return doc;
}
private static string CleanTitle(string title)
{
title = Regex.Replace(title, @"[\u0000-\u0008\u000A-\u001F\u0100-\uFFFF]", string.Empty, RegexOptions.Compiled);
title = Regex.Replace(title, @"\s+", " ", RegexOptions.Compiled | RegexOptions.IgnoreCase);
return title.Trim();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using NLog; using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.Avistaz; using NzbDrone.Core.Indexers.Definitions.Avistaz;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
@@ -9,23 +10,18 @@ namespace NzbDrone.Core.Indexers.Definitions
public class AvistaZ : AvistazBase public class AvistaZ : AvistazBase
{ {
public override string Name => "AvistaZ"; public override string Name => "AvistaZ";
public override string[] IndexerUrls => new[] { "https://avistaz.to/" }; public override string[] IndexerUrls => new string[] { "https://avistaz.to/" };
public override string Description => "Aka AsiaTorrents"; public override string Description => "Aka AsiaTorrents";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private; public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public AvistaZ(IIndexerRepository indexerRepository, public AvistaZ(IIndexerRepository indexerRepository, IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(indexerRepository, httpClient, eventAggregator, indexerStatusService, configService, logger) : base(indexerRepository, httpClient, eventAggregator, indexerStatusService, configService, logger)
{ {
} }
public override IIndexerRequestGenerator GetRequestGenerator() public override IIndexerRequestGenerator GetRequestGenerator()
{ {
return new AvistazRequestGenerator return new AvistazRequestGenerator()
{ {
Settings = Settings, Settings = Settings,
HttpClient = _httpClient, HttpClient = _httpClient,
@@ -34,23 +30,18 @@ namespace NzbDrone.Core.Indexers.Definitions
}; };
} }
public override IParseIndexerResponse GetParser()
{
return new AvistaZParser();
}
protected override IndexerCapabilities SetCapabilities() protected override IndexerCapabilities SetCapabilities()
{ {
var caps = new IndexerCapabilities var caps = new IndexerCapabilities
{ {
TvSearchParams = new List<TvSearchParam> TvSearchParams = new List<TvSearchParam>
{ {
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId, TvSearchParam.Genre TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId, TvSearchParam.Genre
}, },
MovieSearchParams = new List<MovieSearchParam> MovieSearchParams = new List<MovieSearchParam>
{ {
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId, MovieSearchParam.Genre MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId, MovieSearchParam.Genre
} }
}; };
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies); caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies);
@@ -61,13 +52,9 @@ namespace NzbDrone.Core.Indexers.Definitions
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVUHD); caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVUHD);
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVHD); caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVHD);
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVSD); caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVSD);
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio);
return caps; return caps;
} }
} }
public class AvistaZParser : AvistazParserBase
{
protected override string TimezoneOffset => "+01:00";
}
} }
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -13,11 +13,12 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public abstract class AvistazBase : TorrentIndexerBase<AvistazSettings> public abstract class AvistazBase : TorrentIndexerBase<AvistazSettings>
{ {
public override DownloadProtocol Protocol => DownloadProtocol.Torrent; public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override string[] IndexerUrls => new string[] { "" };
protected virtual string LoginUrl => Settings.BaseUrl + "api/v1/jackett/auth";
public override bool SupportsRss => true; public override bool SupportsRss => true;
public override bool SupportsSearch => true; public override bool SupportsSearch => true;
public override int PageSize => 50; public override int PageSize => 50;
public override IndexerCapabilities Capabilities => SetCapabilities(); public override IndexerCapabilities Capabilities => SetCapabilities();
protected virtual string LoginUrl => Settings.BaseUrl + "api/v1/jackett/auth";
private IIndexerRepository _indexerRepository; private IIndexerRepository _indexerRepository;
public AvistazBase(IIndexerRepository indexerRepository, public AvistazBase(IIndexerRepository indexerRepository,
@@ -33,7 +34,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public override IIndexerRequestGenerator GetRequestGenerator() public override IIndexerRequestGenerator GetRequestGenerator()
{ {
return new AvistazRequestGenerator return new AvistazRequestGenerator()
{ {
Settings = Settings, Settings = Settings,
HttpClient = _httpClient, HttpClient = _httpClient,
@@ -44,12 +45,14 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public override IParseIndexerResponse GetParser() public override IParseIndexerResponse GetParser()
{ {
return new AvistazParserBase(); return new AvistazParser();
} }
protected virtual IndexerCapabilities SetCapabilities() protected virtual IndexerCapabilities SetCapabilities()
{ {
return new IndexerCapabilities(); var caps = new IndexerCapabilities();
return caps;
} }
protected override async Task DoLogin() protected override async Task DoLogin()
@@ -66,7 +69,12 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
protected override bool CheckIfLoginNeeded(HttpResponse response) protected override bool CheckIfLoginNeeded(HttpResponse response)
{ {
return response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.PreconditionFailed; if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.PreconditionFailed)
{
return true;
}
return false;
} }
protected override void ModifyRequest(IndexerRequest request) protected override void ModifyRequest(IndexerRequest request)
@@ -90,18 +98,24 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
var jsonResponse = new HttpResponse<AvistazErrorResponse>(ex.Response); var jsonResponse = new HttpResponse<AvistazErrorResponse>(ex.Response);
return new ValidationFailure(string.Empty, jsonResponse.Resource?.Message ?? "Unauthorized request to indexer"); return new ValidationFailure(string.Empty, jsonResponse.Resource?.Message ?? "Unauthorized request to indexer");
} }
else if (ex.Response.StatusCode == HttpStatusCode.TooManyRequests)
{
_logger.Warn(ex, "Too Many Requests");
return new ValidationFailure(string.Empty, "Too Many Requests");
}
else else
{ {
_logger.Warn(ex, "Unable to connect to indexer"); _logger.Warn(ex, "Unable to connect to indexer");
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details. " + ex.Message); return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Warn(ex, "Unable to connect to indexer"); _logger.Warn(ex, "Unable to connect to indexer");
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details"); return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
} }
return null; return null;
@@ -111,10 +125,12 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{ {
var requestBuilder = new HttpRequestBuilder(LoginUrl) var requestBuilder = new HttpRequestBuilder(LoginUrl)
{ {
LogResponseContent = true, LogResponseContent = true
Method = HttpMethod.Post
}; };
requestBuilder.Method = HttpMethod.Post;
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
var authLoginRequest = requestBuilder var authLoginRequest = requestBuilder
.AddFormParameter("username", Settings.Username) .AddFormParameter("username", Settings.Username)
.AddFormParameter("password", Settings.Password) .AddFormParameter("password", Settings.Password)
@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@@ -11,10 +10,13 @@ using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Definitions.Avistaz namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{ {
public class AvistazParserBase : IParseIndexerResponse public class AvistazParser : IParseIndexerResponse
{ {
protected virtual string TimezoneOffset => "-05:00"; // Avistaz does not specify a timezone & returns server time private readonly HashSet<string> _hdResolutions = new HashSet<string> { "1080p", "1080i", "720p" };
private readonly HashSet<string> _hdResolutions = new () { "1080p", "1080i", "720p" };
public AvistazParser()
{
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; } public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
@@ -22,24 +24,26 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{ {
var torrentInfos = new List<TorrentInfo>(); var torrentInfos = new List<TorrentInfo>();
if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.NotFound) if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
{ {
return torrentInfos.ToArray(); throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
}
if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.TooManyRequests)
{
throw new RequestLimitReachedException(indexerResponse, "API Request Limit Reached");
} }
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK) if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{ {
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request"); if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.NotFound)
} {
// No results found
return torrentInfos.ToArray();
}
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value)) HttpResponse<AvistazErrorResponse> jsonErrorResponse = new HttpResponse<AvistazErrorResponse>(indexerResponse.HttpResponse);
{ if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.Unauthorized)
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}"); {
throw new IndexerAuthException(string.Empty, jsonErrorResponse.Resource?.Message ?? "Unauthorized request to indexer");
}
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
} }
var jsonResponse = new HttpResponse<AvistazResponse>(indexerResponse.HttpResponse); var jsonResponse = new HttpResponse<AvistazResponse>(indexerResponse.HttpResponse);
@@ -59,7 +63,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
InfoUrl = details, InfoUrl = details,
Guid = details, Guid = details,
Categories = cats, Categories = cats,
PublishDate = DateTime.Parse($"{row.CreatedAt} {TimezoneOffset}", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal), PublishDate = DateTime.Parse(row.CreatedAt + "-05:00").ToUniversalTime(), // Avistaz does not specify a timezone & returns server time
Size = row.FileSize, Size = row.FileSize,
Files = row.FileCount, Files = row.FileCount,
Grabs = row.Completed, Grabs = row.Completed,
@@ -12,12 +12,17 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public class AvistazRequestGenerator : IIndexerRequestGenerator public class AvistazRequestGenerator : IIndexerRequestGenerator
{ {
public AvistazSettings Settings { get; set; } public AvistazSettings Settings { get; set; }
public IDictionary<string, string> AuthCookieCache { get; set; }
public IIndexerHttpClient HttpClient { get; set; } public IIndexerHttpClient HttpClient { get; set; }
public IndexerCapabilities Capabilities { get; set; } public IndexerCapabilities Capabilities { get; set; }
public Logger Logger { get; set; } public Logger Logger { get; set; }
protected virtual string SearchUrl => Settings.BaseUrl + "api/v1/jackett/torrents";
protected virtual bool ImdbInTags => false;
public Func<IDictionary<string, string>> GetCookies { get; set; } public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; } public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
protected virtual string SearchUrl => Settings.BaseUrl + "api/v1/jackett/torrents";
// hook to adjust the search category // hook to adjust the search category
protected virtual List<KeyValuePair<string, string>> GetBasicSearchParameters(int[] categories, string genre) protected virtual List<KeyValuePair<string, string>> GetBasicSearchParameters(int[] categories, string genre)
@@ -40,8 +45,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
} }
// resolution filter to improve the search // resolution filter to improve the search
if (!categories.Contains(NewznabStandardCategory.Movies.Id) && if (!categories.Contains(NewznabStandardCategory.Movies.Id) && !categories.Contains(NewznabStandardCategory.TV.Id) &&
!categories.Contains(NewznabStandardCategory.TV.Id) &&
!categories.Contains(NewznabStandardCategory.Audio.Id)) !categories.Contains(NewznabStandardCategory.Audio.Id))
{ {
if (categories.Contains(NewznabStandardCategory.MoviesUHD.Id) || categories.Contains(NewznabStandardCategory.TVUHD.Id)) if (categories.Contains(NewznabStandardCategory.MoviesUHD.Id) || categories.Contains(NewznabStandardCategory.TVUHD.Id))
@@ -5,7 +5,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions.Avistaz namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{ {
public class AvistazSettingsValidator : NoAuthSettingsValidator<AvistazSettings> public class AvistazSettingsValidator : AbstractValidator<AvistazSettings>
{ {
public AvistazSettingsValidator() public AvistazSettingsValidator()
{ {
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public class AvistazSettings : NoAuthTorrentBaseSettings public class AvistazSettings : NoAuthTorrentBaseSettings
{ {
private static readonly AvistazSettingsValidator Validator = new (); private static readonly AvistazSettingsValidator Validator = new AvistazSettingsValidator();
public AvistazSettings() public AvistazSettings()
{ {
+42 -22
View File
@@ -7,8 +7,10 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using AngleSharp.Dom; using AngleSharp.Dom;
using AngleSharp.Html.Parser; using AngleSharp.Html.Parser;
using FluentValidation;
using NLog; using NLog;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions; using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Indexers.Settings; using NzbDrone.Core.Indexers.Settings;
@@ -16,13 +18,14 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions namespace NzbDrone.Core.Indexers.Definitions
{ {
public class BB : TorrentIndexerBase<UserPassTorrentBaseSettings> public class BB : TorrentIndexerBase<UserPassTorrentBaseSettings>
{ {
public override string Name => "BB"; public override string Name => "BB";
public override string[] IndexerUrls => new[] { StringUtil.FromBase64("aHR0cHM6Ly9iYWNvbmJpdHMub3JnLw==") }; public override string[] IndexerUrls => new string[] { StringUtil.FromBase64("aHR0cHM6Ly9iYWNvbmJpdHMub3JnLw==") };
private string LoginUrl => Settings.BaseUrl + "login.php"; private string LoginUrl => Settings.BaseUrl + "login.php";
public override string Description => "BB is a Private Torrent Tracker for 0DAY / GENERAL"; public override string Description => "BB is a Private Torrent Tracker for 0DAY / GENERAL";
public override string Language => "en-US"; public override string Language => "en-US";
@@ -38,7 +41,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator() public override IIndexerRequestGenerator GetRequestGenerator()
{ {
return new BBRequestGenerator { Settings = Settings, Capabilities = Capabilities }; return new BBRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
} }
public override IParseIndexerResponse GetParser() public override IParseIndexerResponse GetParser()
@@ -51,22 +54,30 @@ namespace NzbDrone.Core.Indexers.Definitions
var requestBuilder = new HttpRequestBuilder(LoginUrl) var requestBuilder = new HttpRequestBuilder(LoginUrl)
{ {
LogResponseContent = true, LogResponseContent = true,
AllowAutoRedirect = true, AllowAutoRedirect = true
Method = HttpMethod.Post
}; };
var cookies = Cookies; requestBuilder.Method = HttpMethod.Post;
Cookies = null; requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
var cookies = Cookies;
Cookies = null;
var authLoginRequest = requestBuilder var authLoginRequest = requestBuilder
.AddFormParameter("username", Settings.Username) .AddFormParameter("username", Settings.Username)
.AddFormParameter("password", Settings.Password) .AddFormParameter("password", Settings.Password)
.AddFormParameter("keeplogged", "1") .AddFormParameter("keeplogged", "1")
.AddFormParameter("login", "Log+In!") .AddFormParameter("login", "Log+In!")
.SetHeader("Content-Type", "application/x-www-form-urlencoded") .SetHeader("Content-Type", "multipart/form-data")
.SetHeader("Referer", LoginUrl)
.Build(); .Build();
var headers = new NameValueCollection
{
{ "Referer", LoginUrl }
};
authLoginRequest.Headers.Add(headers);
var response = await ExecuteAuth(authLoginRequest); var response = await ExecuteAuth(authLoginRequest);
if (CheckIfLoginNeeded(response)) if (CheckIfLoginNeeded(response))
@@ -87,14 +98,19 @@ namespace NzbDrone.Core.Indexers.Definitions
} }
cookies = response.GetCookies(); cookies = response.GetCookies();
UpdateCookies(cookies, DateTime.Now.AddDays(30)); UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
_logger.Debug("BB authentication succeeded."); _logger.Debug("BB authentication succeeded.");
} }
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse) protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{ {
return !httpResponse.Content.Contains("logout.php"); if (!httpResponse.Content.Contains("logout.php"))
{
return true;
}
return false;
} }
private IndexerCapabilities SetCapabilities() private IndexerCapabilities SetCapabilities()
@@ -102,21 +118,21 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities var caps = new IndexerCapabilities
{ {
TvSearchParams = new List<TvSearchParam> TvSearchParams = new List<TvSearchParam>
{ {
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
}, },
MovieSearchParams = new List<MovieSearchParam> MovieSearchParams = new List<MovieSearchParam>
{ {
MovieSearchParam.Q MovieSearchParam.Q
}, },
MusicSearchParams = new List<MusicSearchParam> MusicSearchParams = new List<MusicSearchParam>
{ {
MusicSearchParam.Q MusicSearchParam.Q
}, },
BookSearchParams = new List<BookSearchParam> BookSearchParams = new List<BookSearchParam>
{ {
BookSearchParam.Q BookSearchParam.Q
} }
}; };
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Audio); caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Audio);
@@ -147,6 +163,10 @@ namespace NzbDrone.Core.Indexers.Definitions
public UserPassTorrentBaseSettings Settings { get; set; } public UserPassTorrentBaseSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; } public IndexerCapabilities Capabilities { get; set; }
public BBRequestGenerator()
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories) private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{ {
var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/')); var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/'));
@@ -8,6 +8,7 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using AngleSharp.Dom; using AngleSharp.Dom;
using AngleSharp.Html.Parser; using AngleSharp.Html.Parser;
using FluentValidation;
using NLog; using NLog;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
@@ -18,6 +19,7 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions namespace NzbDrone.Core.Indexers.Definitions
{ {
@@ -25,7 +27,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
public override string Name => "BakaBT"; public override string Name => "BakaBT";
public override string[] IndexerUrls => new[] { "https://bakabt.me/" }; public override string[] IndexerUrls => new string[] { "https://bakabt.me/" };
public override string Description => "Anime Community"; public override string Description => "Anime Community";
private string LoginUrl => Settings.BaseUrl + "login.php"; private string LoginUrl => Settings.BaseUrl + "login.php";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent; public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
@@ -39,7 +41,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator() public override IIndexerRequestGenerator GetRequestGenerator()
{ {
return new BakaBTRequestGenerator { Settings = Settings, Capabilities = Capabilities }; return new BakaBTRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
} }
public override IParseIndexerResponse GetParser() public override IParseIndexerResponse GetParser()
@@ -50,14 +52,14 @@ namespace NzbDrone.Core.Indexers.Definitions
public override async Task<byte[]> Download(Uri link) public override async Task<byte[]> Download(Uri link)
{ {
var request = new HttpRequestBuilder(link.ToString()) var request = new HttpRequestBuilder(link.ToString())
.SetCookies(GetCookies() ?? new Dictionary<string, string>()) .SetCookies(GetCookies() ?? new Dictionary<string, string>())
.Build(); .Build();
var response = await _httpClient.ExecuteProxiedAsync(request, Definition); var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
var parser = new HtmlParser(); var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content); var dom = parser.ParseDocument(response.Content);
var downloadLink = dom.QuerySelector(".download_link")?.GetAttribute("href"); var downloadLink = dom.QuerySelectorAll(".download_link").First().GetAttribute("href");
if (string.IsNullOrWhiteSpace(downloadLink)) if (string.IsNullOrWhiteSpace(downloadLink))
{ {
@@ -74,12 +76,19 @@ namespace NzbDrone.Core.Indexers.Definitions
var requestBuilder = new HttpRequestBuilder(LoginUrl) var requestBuilder = new HttpRequestBuilder(LoginUrl)
{ {
LogResponseContent = true, LogResponseContent = true,
AllowAutoRedirect = true, AllowAutoRedirect = true
Method = HttpMethod.Post
}; };
var loginPage = await ExecuteAuth(new HttpRequest(LoginUrl)); var loginPage = await ExecuteAuth(new HttpRequest(LoginUrl));
requestBuilder.Method = HttpMethod.Post;
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
requestBuilder.SetCookies(loginPage.GetCookies());
requestBuilder.AddFormParameter("username", Settings.Username);
requestBuilder.AddFormParameter("password", Settings.Password);
requestBuilder.AddFormParameter("returnto", "/index.php");
var parser = new HtmlParser(); var parser = new HtmlParser();
var dom = parser.ParseDocument(loginPage.Content); var dom = parser.ParseDocument(loginPage.Content);
var loginKey = dom.QuerySelector("input[name=\"loginKey\"]"); var loginKey = dom.QuerySelector("input[name=\"loginKey\"]");
@@ -89,18 +98,14 @@ namespace NzbDrone.Core.Indexers.Definitions
} }
var authLoginRequest = requestBuilder var authLoginRequest = requestBuilder
.SetCookies(loginPage.GetCookies()) .SetHeader("Content-Type", "multipart/form-data")
.AddFormParameter("username", Settings.Username)
.AddFormParameter("password", Settings.Password)
.AddFormParameter("returnto", "/index.php")
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
.Build(); .Build();
var response = await ExecuteAuth(authLoginRequest); var response = await ExecuteAuth(authLoginRequest);
if (response.Content != null && response.Content.Contains("<a href=\"logout.php\">Logout</a>")) if (response.Content != null && response.Content.Contains("<a href=\"logout.php\">Logout</a>"))
{ {
UpdateCookies(response.GetCookies(), DateTime.Now.AddDays(30)); UpdateCookies(response.GetCookies(), DateTime.Now + TimeSpan.FromDays(30));
_logger.Debug("BakaBT authentication succeeded"); _logger.Debug("BakaBT authentication succeeded");
} }
@@ -112,7 +117,12 @@ namespace NzbDrone.Core.Indexers.Definitions
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse) protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{ {
return !httpResponse.Content.Contains("<a href=\"logout.php\">Logout</a>"); if (!httpResponse.Content.Contains("<a href=\"logout.php\">Logout</a>"))
{
return true;
}
return false;
} }
private IndexerCapabilities SetCapabilities() private IndexerCapabilities SetCapabilities()
@@ -120,21 +130,21 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities var caps = new IndexerCapabilities
{ {
TvSearchParams = new List<TvSearchParam> TvSearchParams = new List<TvSearchParam>
{ {
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
}, },
MovieSearchParams = new List<MovieSearchParam> MovieSearchParams = new List<MovieSearchParam>
{ {
MovieSearchParam.Q MovieSearchParam.Q
}, },
MusicSearchParams = new List<MusicSearchParam> MusicSearchParams = new List<MusicSearchParam>
{ {
MusicSearchParam.Q MusicSearchParam.Q
}, },
BookSearchParams = new List<BookSearchParam> BookSearchParams = new List<BookSearchParam>
{ {
BookSearchParam.Q BookSearchParam.Q
} }
}; };
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "Anime Series"); caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "Anime Series");
@@ -156,6 +166,10 @@ namespace NzbDrone.Core.Indexers.Definitions
public BakaBTSettings Settings { get; set; } public BakaBTSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; } public IndexerCapabilities Capabilities { get; set; }
public BakaBTRequestGenerator()
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term) private IEnumerable<IndexerRequest> GetPagedRequests(string term)
{ {
var searchString = term; var searchString = term;
@@ -231,7 +245,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
private readonly BakaBTSettings _settings; private readonly BakaBTSettings _settings;
private readonly IndexerCapabilitiesCategories _categories; private readonly IndexerCapabilitiesCategories _categories;
private readonly List<IndexerCategory> _defaultCategories = new () { NewznabStandardCategory.TVAnime }; private readonly List<IndexerCategory> _defaultCategories = new List<IndexerCategory> { NewznabStandardCategory.TVAnime };
public BakaBTParser(BakaBTSettings settings, IndexerCapabilitiesCategories categories) public BakaBTParser(BakaBTSettings settings, IndexerCapabilitiesCategories categories)
{ {
@@ -281,7 +295,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var stringSeparator = new[] { " | " }; var stringSeparator = new[] { " | " };
var titles = titleSeries.Split(stringSeparator, StringSplitOptions.RemoveEmptyEntries); var titles = titleSeries.Split(stringSeparator, StringSplitOptions.RemoveEmptyEntries);
if (titles.Length > 1 && !_settings.AddRomajiTitle) if (titles.Count() > 1 && !_settings.AddRomajiTitle)
{ {
titles = titles.Skip(1).ToArray(); titles = titles.Skip(1).ToArray();
} }
@@ -293,7 +307,7 @@ namespace NzbDrone.Core.Indexers.Definitions
release.Title = (name + releaseInfo).Trim(); release.Title = (name + releaseInfo).Trim();
// Ensure the season is defined as this tracker only deals with full seasons // Ensure the season is defined as this tracker only deals with full seasons
if (!release.Title.Contains("Season", StringComparison.CurrentCulture) && _settings.AppendSeason) if (release.Title.IndexOf("Season") == -1 && _settings.AppendSeason)
{ {
// Insert before the release info // Insert before the release info
var aidx = release.Title.IndexOf('('); var aidx = release.Title.IndexOf('(');
@@ -401,6 +415,10 @@ namespace NzbDrone.Core.Indexers.Definitions
public class BakaBTSettings : UserPassTorrentBaseSettings public class BakaBTSettings : UserPassTorrentBaseSettings
{ {
public BakaBTSettings()
{
}
[FieldDefinition(4, Label = "Add Romaji Title", Type = FieldType.Checkbox, HelpText = "Add releases for Romaji Title")] [FieldDefinition(4, Label = "Add Romaji Title", Type = FieldType.Checkbox, HelpText = "Add releases for Romaji Title")]
public bool AddRomajiTitle { get; set; } public bool AddRomajiTitle { get; set; }
@@ -246,7 +246,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; } public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
} }
public class BeyondHDSettingsValidator : NoAuthSettingsValidator<BeyondHDSettings> public class BeyondHDSettingsValidator : AbstractValidator<BeyondHDSettings>
{ {
public BeyondHDSettingsValidator() public BeyondHDSettingsValidator()
{ {
@@ -257,7 +257,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class BeyondHDSettings : NoAuthTorrentBaseSettings public class BeyondHDSettings : NoAuthTorrentBaseSettings
{ {
private static readonly BeyondHDSettingsValidator Validator = new (); private static readonly BeyondHDSettingsValidator Validator = new BeyondHDSettingsValidator();
public BeyondHDSettings() public BeyondHDSettings()
{ {
@@ -219,7 +219,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public string BaseUrl { get; set; } public string BaseUrl { get; set; }
[FieldDefinition(2)] [FieldDefinition(2)]
public IndexerBaseSettings BaseSettings { get; set; } = new (); public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
public NzbDroneValidationResult Validate() public NzbDroneValidationResult Validate()
{ {
@@ -5,7 +5,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.BroadcastheNet namespace NzbDrone.Core.Indexers.BroadcastheNet
{ {
public class BroadcastheNetSettingsValidator : NoAuthSettingsValidator<BroadcastheNetSettings> public class BroadcastheNetSettingsValidator : AbstractValidator<BroadcastheNetSettings>
{ {
public BroadcastheNetSettingsValidator() public BroadcastheNetSettingsValidator()
{ {
@@ -15,7 +15,11 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
public class BroadcastheNetSettings : NoAuthTorrentBaseSettings public class BroadcastheNetSettings : NoAuthTorrentBaseSettings
{ {
private static readonly BroadcastheNetSettingsValidator Validator = new (); private static readonly BroadcastheNetSettingsValidator Validator = new BroadcastheNetSettingsValidator();
public BroadcastheNetSettings()
{
}
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey)] [FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; } public string ApiKey { get; set; }
@@ -1,39 +1,38 @@
using NLog; using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.Gazelle;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions; namespace NzbDrone.Core.Indexers.Definitions
public class BrokenStones : GazelleBase<GazelleSettings>
{ {
public override string Name => "BrokenStones"; public class BrokenStones : Gazelle.Gazelle
public override string[] IndexerUrls => new[] { "https://brokenstones.club/" };
public override string Description => "Broken Stones is a Private site for MacOS and iOS APPS / GAMES";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public BrokenStones(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{ {
} public override string Name => "BrokenStones";
public override string[] IndexerUrls => new string[] { "https://brokenstones.club/" };
public override string Description => "Broken Stones is a Private site for MacOS and iOS APPS / GAMES";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
protected override IndexerCapabilities SetCapabilities() public BrokenStones(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
{ : base(httpClient, eventAggregator, indexerStatusService, configService, logger)
var caps = new IndexerCapabilities(); {
}
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.PCMac, "MacOS Apps"); protected override IndexerCapabilities SetCapabilities()
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.PCMac, "MacOS Games"); {
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.PCMobileiOS, "iOS Apps"); var caps = new IndexerCapabilities
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.PCMobileiOS, "iOS Games"); {
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Other, "Graphics"); };
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.Audio, "Audio");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.Other, "Tutorials");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.Other, "Other");
return caps; caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.PCMac, "MacOS Apps");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.PCMac, "MacOS Games");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.PCMobileiOS, "iOS Apps");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.PCMobileiOS, "iOS Games");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Other, "Graphics");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.Audio, "Audio");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.Other, "Tutorials");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.Other, "Other");
return caps;
}
} }
} }
@@ -1,38 +1,37 @@
using NLog; using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.Gazelle;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions; namespace NzbDrone.Core.Indexers.Definitions
public class CGPeers : GazelleBase<GazelleSettings>
{ {
public override string Name => "CGPeers"; public class CGPeers : Gazelle.Gazelle
public override string[] IndexerUrls => new[] { "https://cgpeers.to/" };
public override string Description => "CGPeers is a Private Torrent Tracker for GRAPHICS SOFTWARE / TUTORIALS / ETC";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public CGPeers(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{ {
} public override string Name => "CGPeers";
public override string[] IndexerUrls => new string[] { "https://cgpeers.to/" };
public override string Description => "CGPeers is a Private Torrent Tracker for GRAPHICS SOFTWARE / TUTORIALS / ETC";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
protected override IndexerCapabilities SetCapabilities() public CGPeers(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
{ : base(httpClient, eventAggregator, indexerStatusService, configService, logger)
var caps = new IndexerCapabilities(); {
}
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.PCISO, "Full Applications"); protected override IndexerCapabilities SetCapabilities()
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.PC0day, "Plugins"); {
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Other, "Tutorials"); var caps = new IndexerCapabilities
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.Other, "Models"); {
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Other, "Materials"); };
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.OtherMisc, "Misc");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.Other, "GameDev");
return caps; caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.PCISO, "Full Applications");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.PC0day, "Plugins");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Other, "Tutorials");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.Other, "Models");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Other, "Materials");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.OtherMisc, "Misc");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.Other, "GameDev");
return caps;
}
} }
} }
@@ -1,12 +1,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentValidation.Results; using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.IndexerVersions; using NzbDrone.Core.IndexerVersions;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
@@ -27,31 +30,15 @@ namespace NzbDrone.Core.Indexers.Cardigann
public override IndexerPrivacy Privacy => IndexerPrivacy.Private; public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
// Page size is different per indexer, setting to 1 ensures we don't break out of paging logic // Page size is different per indexer, setting to 1 ensures we don't break out of paging logic
// thinking its a partial page and instead all search_path requests are run for each indexer // thinking its a partial page and insteaad all search_path requests are run for each indexer
public override int PageSize => 1; public override int PageSize => 1;
public override TimeSpan RateLimit
{
get
{
var definition = _definitionService.GetCachedDefinition(Settings.DefinitionFile);
if (definition.RequestDelay.HasValue && definition.RequestDelay.Value > base.RateLimit.TotalSeconds)
{
return TimeSpan.FromSeconds(definition.RequestDelay.Value);
}
return base.RateLimit;
}
}
public override IIndexerRequestGenerator GetRequestGenerator() public override IIndexerRequestGenerator GetRequestGenerator()
{ {
var generator = _generatorCache.Get(Settings.DefinitionFile, () => var generator = _generatorCache.Get(Settings.DefinitionFile, () =>
new CardigannRequestGenerator(_configService, new CardigannRequestGenerator(_configService,
_definitionService.GetCachedDefinition(Settings.DefinitionFile), _definitionService.GetCachedDefinition(Settings.DefinitionFile),
_logger, _logger)
RateLimit)
{ {
HttpClient = _httpClient, HttpClient = _httpClient,
Definition = Definition, Definition = Definition,
@@ -178,15 +165,60 @@ namespace NzbDrone.Core.Indexers.Cardigann
await generator.DoLogin(); await generator.DoLogin();
} }
protected override async Task<HttpRequest> GetDownloadRequest(Uri link) public override async Task<byte[]> Download(Uri link)
{ {
var generator = (CardigannRequestGenerator)GetRequestGenerator(); var generator = (CardigannRequestGenerator)GetRequestGenerator();
var request = await generator.DownloadRequest(link); var request = await generator.DownloadRequest(link);
if (request.Url.Scheme == "magnet")
{
ValidateMagnet(request.Url.FullUri);
return Encoding.UTF8.GetBytes(request.Url.FullUri);
}
request.AllowAutoRedirect = true; request.AllowAutoRedirect = true;
return request; var downloadBytes = Array.Empty<byte>();
try
{
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
downloadBytes = response.ResponseData;
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
{
_logger.Error(ex, "Downloading torrent file for release failed since it no longer exists ({0})", request.Url.FullUri);
throw new ReleaseUnavailableException("Downloading torrent failed", ex);
}
if (ex.Response.StatusCode == HttpStatusCode.TooManyRequests)
{
_logger.Error("API Grab Limit reached for {0}", request.Url.FullUri);
}
else
{
_logger.Error(ex, "Downloading torrent file for release failed ({0})", request.Url.FullUri);
}
throw new ReleaseDownloadException("Downloading torrent failed", ex);
}
catch (WebException ex)
{
_logger.Error(ex, "Downloading torrent file for release failed ({0})", request.Url.FullUri);
throw new ReleaseDownloadException("Downloading torrent failed", ex);
}
catch (Exception)
{
_indexerStatusService.RecordFailure(Definition.Id);
_logger.Error("Downloading torrent failed");
throw;
}
return downloadBytes;
} }
protected override async Task Test(List<ValidationFailure> failures) protected override async Task Test(List<ValidationFailure> failures)
@@ -2,7 +2,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using AngleSharp.Dom; using AngleSharp.Dom;
@@ -300,12 +300,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
} }
else if (setting.Type == "checkbox") else if (setting.Type == "checkbox")
{ {
if (value is string stringValue && bool.TryParse(stringValue, out var result)) variables[name] = ((bool)value) ? ".True" : null;
{
value = result;
}
variables[name] = (bool)value ? ".True" : null;
} }
else if (setting.Type == "select") else if (setting.Type == "select")
{ {
@@ -333,12 +328,12 @@ namespace NzbDrone.Core.Indexers.Cardigann
} }
else else
{ {
throw new NotSupportedException($"Type {setting.Type} is not supported."); throw new NotSupportedException();
} }
if (setting.Type != "password" && setting.Name != "apikey" && setting.Name != "rsskey" && indexerLogging && variables.ContainsKey(name)) if (setting.Type != "password" && setting.Name != "apikey" && setting.Name != "rsskey" && indexerLogging)
{ {
_logger.Debug($"Setting {setting.Name} to {variables[name].ToJson()}"); _logger.Debug($"Setting {setting.Name} to {variables[name]}");
} }
} }
@@ -685,12 +680,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
case "urlencode": case "urlencode":
data = data.UrlEncode(_encoding); data = data.UrlEncode(_encoding);
break; break;
case "htmldecode":
data = WebUtility.HtmlDecode(data);
break;
case "htmlencode":
data = WebUtility.HtmlEncode(data);
break;
case "timeago": case "timeago":
case "reltime": case "reltime":
data = DateTimeUtil.FromTimeAgo(data).ToString(DateTimeUtil.Rfc1123ZPattern); data = DateTimeUtil.FromTimeAgo(data).ToString(DateTimeUtil.Rfc1123ZPattern);
@@ -41,10 +41,9 @@ namespace NzbDrone.Core.Indexers.Cardigann
public string Type { get; set; } public string Type { get; set; }
public string Language { get; set; } public string Language { get; set; }
public string Encoding { get; set; } public string Encoding { get; set; }
public double? RequestDelay { get; set; }
public List<string> Links { get; set; } public List<string> Links { get; set; }
public List<string> Legacylinks { get; set; } public List<string> Legacylinks { get; set; }
public bool Followredirect { get; set; } public bool Followredirect { get; set; } = false;
public bool TestLinkTorrent { get; set; } = true; public bool TestLinkTorrent { get; set; } = true;
public List<string> Certificates { get; set; } public List<string> Certificates { get; set; }
public CapabilitiesBlock Caps { get; set; } public CapabilitiesBlock Caps { get; set; }
@@ -95,7 +94,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
public List<string> Cookies { get; set; } public List<string> Cookies { get; set; }
public string Method { get; set; } public string Method { get; set; }
public string Form { get; set; } public string Form { get; set; }
public bool Selectors { get; set; } public bool Selectors { get; set; } = false;
public Dictionary<string, string> Inputs { get; set; } public Dictionary<string, string> Inputs { get; set; }
public Dictionary<string, SelectorBlock> Selectorinputs { get; set; } public Dictionary<string, SelectorBlock> Selectorinputs { get; set; }
public Dictionary<string, SelectorBlock> Getselectorinputs { get; set; } public Dictionary<string, SelectorBlock> Getselectorinputs { get; set; }
@@ -114,7 +113,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
public class SelectorBlock public class SelectorBlock
{ {
public string Selector { get; set; } public string Selector { get; set; }
public bool Optional { get; set; } public bool Optional { get; set; } = false;
public string Text { get; set; } public string Text { get; set; }
public string Attribute { get; set; } public string Attribute { get; set; }
public string Remove { get; set; } public string Remove { get; set; }
@@ -157,7 +156,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
public int After { get; set; } public int After { get; set; }
public SelectorBlock Dateheaders { get; set; } public SelectorBlock Dateheaders { get; set; }
public SelectorBlock Count { get; set; } public SelectorBlock Count { get; set; }
public bool Multiple { get; set; } public bool Multiple { get; set; } = false;
} }
public class SearchPathBlock : RequestBlock public class SearchPathBlock : RequestBlock
@@ -40,16 +40,12 @@ namespace NzbDrone.Core.Indexers.Cardigann
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK) if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{ {
if (indexerResponse.HttpResponse.HasHttpRedirect) // Remove cookie cache
if (indexerResponse.HttpResponse.HasHttpRedirect && indexerResponse.HttpResponse.RedirectUrl
.ContainsIgnoreCase("login.php"))
{ {
if (indexerResponse.HttpResponse.RedirectUrl.ContainsIgnoreCase("login.php")) CookiesUpdater(null, null);
{ throw new IndexerException(indexerResponse, "We are being redirected to the login page. Most likely your session expired or was killed. Recheck your cookie or credentials and try testing the indexer.");
// Remove cookie cache
CookiesUpdater(null, null);
throw new IndexerException(indexerResponse, "We are being redirected to the login page. Most likely your session expired or was killed. Recheck your cookie or credentials and try testing the indexer.");
}
throw new IndexerException(indexerResponse, $"Redirected to {indexerResponse.HttpResponse.RedirectUrl} from API request");
} }
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request"); throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
@@ -66,7 +62,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
{ {
if (request.SearchPath.Response != null && if (request.SearchPath.Response != null &&
request.SearchPath.Response.NoResultsMessage != null && request.SearchPath.Response.NoResultsMessage != null &&
((request.SearchPath.Response.NoResultsMessage.IsNotNullOrWhiteSpace() && results.Contains(request.SearchPath.Response.NoResultsMessage)) || (request.SearchPath.Response.NoResultsMessage.IsNullOrWhiteSpace() && results.IsNullOrWhiteSpace()))) ((request.SearchPath.Response.NoResultsMessage != string.Empty && results.Contains(request.SearchPath.Response.NoResultsMessage)) || (request.SearchPath.Response.NoResultsMessage == string.Empty && results == string.Empty)))
{ {
return releases; return releases;
} }
@@ -4,6 +4,7 @@ using System.Collections.Specialized;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using AngleSharp.Html.Dom; using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser; using AngleSharp.Html.Parser;
@@ -28,15 +29,11 @@ namespace NzbDrone.Core.Indexers.Cardigann
protected IHtmlDocument landingResultDocument; protected IHtmlDocument landingResultDocument;
protected override string SiteLink => ResolveSiteLink(); protected override string SiteLink => ResolveSiteLink();
private readonly TimeSpan _rateLimit;
public CardigannRequestGenerator(IConfigService configService, public CardigannRequestGenerator(IConfigService configService,
CardigannDefinition definition, CardigannDefinition definition,
Logger logger, Logger logger)
TimeSpan rateLimit)
: base(configService, definition, logger) : base(configService, definition, logger)
{ {
_rateLimit = rateLimit;
} }
public Func<IDictionary<string, string>> GetCookies { get; set; } public Func<IDictionary<string, string>> GetCookies { get; set; }
@@ -221,18 +218,15 @@ namespace NzbDrone.Core.Indexers.Cardigann
requestBuilder.AddFormParameter(pair.Key, pair.Value); requestBuilder.AddFormParameter(pair.Key, pair.Value);
} }
var request = requestBuilder requestBuilder.Headers.Add("Referer", SiteLink);
.SetHeader("Referer", SiteLink)
.WithRateLimit(_rateLimit.TotalSeconds)
.Build();
var response = await HttpClient.ExecuteProxiedAsync(request, Definition); var response = await HttpClient.ExecuteProxiedAsync(requestBuilder.Build(), Definition);
Cookies = response.GetCookies(); Cookies = response.GetCookies();
CheckForError(response, login.Error); CheckForError(response, login.Error);
CookiesUpdater(Cookies, DateTime.Now.AddDays(30)); CookiesUpdater(Cookies, DateTime.Now + TimeSpan.FromDays(30));
} }
else if (login.Method == "form") else if (login.Method == "form")
{ {
@@ -362,13 +356,11 @@ namespace NzbDrone.Core.Indexers.Cardigann
Encoding = _encoding Encoding = _encoding
}; };
var request = requestBuilder requestBuilder.SetCookies(Cookies);
.SetCookies(Cookies)
.SetHeader("Referer", loginUrl)
.WithRateLimit(_rateLimit.TotalSeconds)
.Build();
var simpleCaptchaResult = await HttpClient.ExecuteProxiedAsync(request, Definition); requestBuilder.Headers.Add("Referer", loginUrl);
var simpleCaptchaResult = await HttpClient.ExecuteProxiedAsync(requestBuilder.Build(), Definition);
var simpleCaptchaJSON = JObject.Parse(simpleCaptchaResult.Content); var simpleCaptchaJSON = JObject.Parse(simpleCaptchaResult.Content);
var captchaSelection = simpleCaptchaJSON["images"][0]["hash"].ToString(); var captchaSelection = simpleCaptchaJSON["images"][0]["hash"].ToString();
@@ -432,6 +424,10 @@ namespace NzbDrone.Core.Indexers.Cardigann
Encoding = _encoding Encoding = _encoding
}; };
requestBuilder.Headers.Add("Referer", SiteLink);
requestBuilder.SetCookies(Cookies);
foreach (var pair in pairs) foreach (var pair in pairs)
{ {
requestBuilder.AddFormParameter(pair.Key, pair.Value); requestBuilder.AddFormParameter(pair.Key, pair.Value);
@@ -442,12 +438,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
requestBuilder.SetHeader(header.Key, header.Value); requestBuilder.SetHeader(header.Key, header.Value);
} }
var request = requestBuilder var request = requestBuilder.Build();
.SetCookies(Cookies)
.SetHeader("Referer", SiteLink)
.WithRateLimit(_rateLimit.TotalSeconds)
.Build();
request.SetContent(body); request.SetContent(body);
loginResult = await HttpClient.ExecuteProxiedAsync(request, Definition); loginResult = await HttpClient.ExecuteProxiedAsync(request, Definition);
@@ -463,29 +454,26 @@ namespace NzbDrone.Core.Indexers.Cardigann
Encoding = _encoding Encoding = _encoding
}; };
requestBuilder.SetCookies(Cookies);
requestBuilder.Headers.Add("Referer", loginUrl);
foreach (var pair in pairs) foreach (var pair in pairs)
{ {
requestBuilder.AddFormParameter(pair.Key, pair.Value); requestBuilder.AddFormParameter(pair.Key, pair.Value);
} }
var request = requestBuilder loginResult = await HttpClient.ExecuteProxiedAsync(requestBuilder.Build(), Definition);
.SetCookies(Cookies)
.SetHeader("Referer", loginUrl)
.WithRateLimit(_rateLimit.TotalSeconds)
.Build();
loginResult = await HttpClient.ExecuteProxiedAsync(request, Definition);
} }
Cookies = loginResult.GetCookies(); Cookies = loginResult.GetCookies();
CheckForError(loginResult, login.Error); CheckForError(loginResult, login.Error);
CookiesUpdater(Cookies, DateTime.Now.AddDays(30)); CookiesUpdater(Cookies, DateTime.Now + TimeSpan.FromDays(30));
} }
else if (login.Method == "cookie") else if (login.Method == "cookie")
{ {
CookiesUpdater(null, null); CookiesUpdater(null, null);
Settings.ExtraFieldData.TryGetValue("cookie", out var cookies); Settings.ExtraFieldData.TryGetValue("cookie", out var cookies);
CookiesUpdater(CookieUtil.CookieHeaderToDictionary((string)cookies), DateTime.Now.AddDays(30)); CookiesUpdater(CookieUtil.CookieHeaderToDictionary((string)cookies), DateTime.Now + TimeSpan.FromDays(30));
} }
else if (login.Method == "get") else if (login.Method == "get")
{ {
@@ -508,18 +496,15 @@ namespace NzbDrone.Core.Indexers.Cardigann
Encoding = _encoding Encoding = _encoding
}; };
var request = requestBuilder requestBuilder.Headers.Add("Referer", SiteLink);
.SetHeader("Referer", SiteLink)
.WithRateLimit(_rateLimit.TotalSeconds)
.Build();
var response = await HttpClient.ExecuteProxiedAsync(request, Definition); var response = await HttpClient.ExecuteProxiedAsync(requestBuilder.Build(), Definition);
Cookies = response.GetCookies(); Cookies = response.GetCookies();
CheckForError(response, login.Error); CheckForError(response, login.Error);
CookiesUpdater(Cookies, DateTime.Now.AddDays(30)); CookiesUpdater(Cookies, DateTime.Now + TimeSpan.FromDays(30));
} }
else if (login.Method == "oneurl") else if (login.Method == "oneurl")
{ {
@@ -536,18 +521,15 @@ namespace NzbDrone.Core.Indexers.Cardigann
Encoding = _encoding Encoding = _encoding
}; };
var request = requestBuilder requestBuilder.Headers.Add("Referer", SiteLink);
.SetHeader("Referer", SiteLink)
.WithRateLimit(_rateLimit.TotalSeconds)
.Build();
var response = await HttpClient.ExecuteProxiedAsync(request, Definition); var response = await HttpClient.ExecuteProxiedAsync(requestBuilder.Build(), Definition);
Cookies = response.GetCookies(); Cookies = response.GetCookies();
CheckForError(response, login.Error); CheckForError(response, login.Error);
CookiesUpdater(Cookies, DateTime.Now.AddDays(30)); CookiesUpdater(Cookies, DateTime.Now + TimeSpan.FromDays(30));
} }
else else
{ {
@@ -612,15 +594,14 @@ namespace NzbDrone.Core.Indexers.Cardigann
Encoding = _encoding Encoding = _encoding
}; };
requestBuilder.Headers.Add("Referer", SiteLink);
if (Cookies != null) if (Cookies != null)
{ {
requestBuilder.SetCookies(Cookies); requestBuilder.SetCookies(Cookies);
} }
var request = requestBuilder var request = requestBuilder.Build();
.SetHeader("Referer", SiteLink)
.WithRateLimit(_rateLimit.TotalSeconds)
.Build();
landingResult = await HttpClient.ExecuteProxiedAsync(request, Definition); landingResult = await HttpClient.ExecuteProxiedAsync(request, Definition);
@@ -665,7 +646,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
.SetCookies(landingResult.GetCookies()) .SetCookies(landingResult.GetCookies())
.SetHeader("Referer", loginUrl.AbsoluteUri) .SetHeader("Referer", loginUrl.AbsoluteUri)
.SetEncoding(_encoding) .SetEncoding(_encoding)
.WithRateLimit(_rateLimit.TotalSeconds)
.Build(); .Build();
var response = await HttpClient.ExecuteProxiedAsync(request, Definition); var response = await HttpClient.ExecuteProxiedAsync(request, Definition);
@@ -676,8 +656,10 @@ namespace NzbDrone.Core.Indexers.Cardigann
ImageData = response.ResponseData ImageData = response.ResponseData
}; };
} }
else
_logger.Debug("CardigannIndexer ({0}): No captcha image found", _definition.Id); {
_logger.Debug("CardigannIndexer ({0}): No captcha image found", _definition.Id);
}
} }
else else
{ {
@@ -742,28 +724,23 @@ namespace NzbDrone.Core.Indexers.Cardigann
requestLinkStr += queryCollection.GetQueryString(_encoding, separator: request.Queryseparator); requestLinkStr += queryCollection.GetQueryString(_encoding, separator: request.Queryseparator);
} }
var httpRequestBuilder = new HttpRequestBuilder(requestLinkStr) var httpRequest = new HttpRequestBuilder(requestLinkStr)
{ .SetCookies(Cookies ?? new Dictionary<string, string>())
Method = method, .SetEncoding(_encoding)
Encoding = _encoding .SetHeader("Referer", referer);
};
httpRequest.Method = method;
// Add form data for POST requests // Add form data for POST requests
if (method == HttpMethod.Post) if (method == HttpMethod.Post)
{ {
foreach (var param in pairs) foreach (var param in pairs)
{ {
httpRequestBuilder.AddFormParameter(param.Key, param.Value); httpRequest.AddFormParameter(param.Key, param.Value);
} }
} }
var httpRequest = httpRequestBuilder var response = await HttpClient.ExecuteProxiedAsync(httpRequest.Build(), Definition);
.SetCookies(Cookies ?? new Dictionary<string, string>())
.SetHeader("Referer", referer)
.WithRateLimit(_rateLimit.TotalSeconds)
.Build();
var response = await HttpClient.ExecuteProxiedAsync(httpRequest, Definition);
_logger.Debug("CardigannIndexer ({0}): handleRequest() remote server returned {1}", _definition.Id, response.StatusCode); _logger.Debug("CardigannIndexer ({0}): handleRequest() remote server returned {1}", _definition.Id, response.StatusCode);
return response; return response;
@@ -789,7 +766,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
.SetCookies(Cookies ?? new Dictionary<string, string>()) .SetCookies(Cookies ?? new Dictionary<string, string>())
.SetHeaders(headers ?? new Dictionary<string, string>()) .SetHeaders(headers ?? new Dictionary<string, string>())
.SetEncoding(_encoding) .SetEncoding(_encoding)
.WithRateLimit(_rateLimit.TotalSeconds)
.Build(); .Build();
request.AllowAutoRedirect = true; request.AllowAutoRedirect = true;
@@ -880,7 +856,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
.SetCookies(Cookies ?? new Dictionary<string, string>()) .SetCookies(Cookies ?? new Dictionary<string, string>())
.SetHeaders(headers ?? new Dictionary<string, string>()) .SetHeaders(headers ?? new Dictionary<string, string>())
.SetEncoding(_encoding) .SetEncoding(_encoding)
.WithRateLimit(_rateLimit.TotalSeconds)
.Build(); .Build();
response = await HttpClient.ExecuteProxiedAsync(testLinkRequest, Definition); response = await HttpClient.ExecuteProxiedAsync(testLinkRequest, Definition);
@@ -900,7 +875,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
.SetCookies(Cookies ?? new Dictionary<string, string>()) .SetCookies(Cookies ?? new Dictionary<string, string>())
.SetHeaders(headers ?? new Dictionary<string, string>()) .SetHeaders(headers ?? new Dictionary<string, string>())
.SetEncoding(_encoding) .SetEncoding(_encoding)
.WithRateLimit(_rateLimit.TotalSeconds)
.Build(); .Build();
selectorDownloadRequest.Method = method; selectorDownloadRequest.Method = method;
@@ -921,7 +895,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
.SetCookies(Cookies ?? new Dictionary<string, string>()) .SetCookies(Cookies ?? new Dictionary<string, string>())
.SetHeaders(headers ?? new Dictionary<string, string>()) .SetHeaders(headers ?? new Dictionary<string, string>())
.SetEncoding(_encoding) .SetEncoding(_encoding)
.WithRateLimit(_rateLimit.TotalSeconds)
.Build(); .Build();
downloadRequest.Method = method; downloadRequest.Method = method;
@@ -1123,18 +1096,16 @@ namespace NzbDrone.Core.Indexers.Cardigann
_logger.Info($"Adding request: {searchUrl}"); _logger.Info($"Adding request: {searchUrl}");
var requestBuilder = new HttpRequestBuilder(searchUrl) var requestbuilder = new HttpRequestBuilder(searchUrl);
{
Method = method, requestbuilder.Method = method;
Encoding = _encoding
};
// Add FormData for searchs that POST // Add FormData for searchs that POST
if (method == HttpMethod.Post) if (method == HttpMethod.Post)
{ {
foreach (var param in queryCollection) foreach (var param in queryCollection)
{ {
requestBuilder.AddFormParameter(param.Key, param.Value); requestbuilder.AddFormParameter(param.Key, param.Value);
} }
} }
@@ -1142,22 +1113,14 @@ namespace NzbDrone.Core.Indexers.Cardigann
if (search.Headers != null) if (search.Headers != null)
{ {
var headers = ParseCustomHeaders(search.Headers, variables); var headers = ParseCustomHeaders(search.Headers, variables);
requestBuilder.SetHeaders(headers ?? new Dictionary<string, string>()); requestbuilder.SetHeaders(headers ?? new Dictionary<string, string>());
} }
var request = requestBuilder var request = new CardigannRequest(requestbuilder.SetEncoding(_encoding).Build(), variables, searchPath);
.WithRateLimit(_rateLimit.TotalSeconds)
.Build();
var cardigannRequest = new CardigannRequest(request, variables, searchPath) request.HttpRequest.AllowAutoRedirect = searchPath.Followredirect;
{
HttpRequest =
{
AllowAutoRedirect = searchPath.Followredirect
}
};
yield return cardigannRequest; yield return request;
} }
} }
} }
@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using NLog; using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.Avistaz; using NzbDrone.Core.Indexers.Definitions.Avistaz;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
@@ -9,23 +10,18 @@ namespace NzbDrone.Core.Indexers.Definitions
public class CinemaZ : AvistazBase public class CinemaZ : AvistazBase
{ {
public override string Name => "CinemaZ"; public override string Name => "CinemaZ";
public override string[] IndexerUrls => new[] { "https://cinemaz.to/" }; public override string[] IndexerUrls => new string[] { "https://cinemaz.to/" };
public override string Description => "CinemaZ (EuTorrents) is a Private Torrent Tracker for FOREIGN NON-ASIAN MOVIES."; public override string Description => "CinemaZ (EuTorrents) is a Private Torrent Tracker for FOREIGN NON-ASIAN MOVIES.";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private; public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public CinemaZ(IIndexerRepository indexerRepository, public CinemaZ(IIndexerRepository indexerRepository, IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(indexerRepository, httpClient, eventAggregator, indexerStatusService, configService, logger) : base(indexerRepository, httpClient, eventAggregator, indexerStatusService, configService, logger)
{ {
} }
public override IIndexerRequestGenerator GetRequestGenerator() public override IIndexerRequestGenerator GetRequestGenerator()
{ {
return new AvistazRequestGenerator return new AvistazRequestGenerator()
{ {
Settings = Settings, Settings = Settings,
HttpClient = _httpClient, HttpClient = _httpClient,
@@ -56,6 +52,7 @@ namespace NzbDrone.Core.Indexers.Definitions
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVUHD); caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVUHD);
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVHD); caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVHD);
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVSD); caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVSD);
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio);
return caps; return caps;
} }
@@ -10,23 +10,18 @@ namespace NzbDrone.Core.Indexers.Definitions
public class ExoticaZ : AvistazBase public class ExoticaZ : AvistazBase
{ {
public override string Name => "ExoticaZ"; public override string Name => "ExoticaZ";
public override string[] IndexerUrls => new[] { "https://exoticaz.to/" }; public override string[] IndexerUrls => new string[] { "https://exoticaz.to/" };
public override string Description => "ExoticaZ (YourExotic) is a Private Torrent Tracker for 3X"; public override string Description => "ExoticaZ (YourExotic) is a Private Torrent Tracker for 3X";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private; public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public ExoticaZ(IIndexerRepository indexerRepository, public ExoticaZ(IIndexerRepository indexerRepository, IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(indexerRepository, httpClient, eventAggregator, indexerStatusService, configService, logger) : base(indexerRepository, httpClient, eventAggregator, indexerStatusService, configService, logger)
{ {
} }
public override IIndexerRequestGenerator GetRequestGenerator() public override IIndexerRequestGenerator GetRequestGenerator()
{ {
return new AvistazRequestGenerator return new AvistazRequestGenerator()
{ {
Settings = Settings, Settings = Settings,
HttpClient = _httpClient, HttpClient = _httpClient,
@@ -57,7 +52,7 @@ namespace NzbDrone.Core.Indexers.Definitions
} }
} }
public class ExoticaZParser : AvistazParserBase public class ExoticaZParser : AvistazParser
{ {
private readonly IndexerCapabilitiesCategories _categories; private readonly IndexerCapabilitiesCategories _categories;
@@ -1,100 +1,93 @@
using System.Collections.Generic; using System.Collections.Generic;
using NLog; using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions.FileList; namespace NzbDrone.Core.Indexers.FileList
public class FileList : TorrentIndexerBase<FileListSettings>
{ {
public override string Name => "FileList.io"; public class FileList : TorrentIndexerBase<FileListSettings>
public override string[] IndexerUrls => new[]
{ {
"https://filelist.io/", public override string Name => "FileList.io";
"https://flro.org/" public override string[] IndexerUrls => new string[] { "https://filelist.io" };
}; public override string Description => "FileList (FL) is a ROMANIAN Private Torrent Tracker for 0DAY / GENERAL";
public override string[] LegacyUrls => new[] { "https://filelist.io" }; public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override string Description => "FileList (FL) is a ROMANIAN Private Torrent Tracker for 0DAY / GENERAL"; public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent; public override bool SupportsRss => true;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private; public override bool SupportsSearch => true;
public override bool SupportsRss => true; public override bool SupportsRedirect => true;
public override bool SupportsSearch => true; public override IndexerCapabilities Capabilities => SetCapabilities();
public override bool SupportsRedirect => true;
public override IndexerCapabilities Capabilities => SetCapabilities();
public FileList(IIndexerHttpClient httpClient, public FileList(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
IEventAggregator eventAggregator, : base(httpClient, eventAggregator, indexerStatusService, configService, logger)
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new FileListRequestGenerator { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new FileListParser(Settings, Capabilities.Categories);
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{ {
TvSearchParams = new List<TvSearchParam> }
{
TvSearchParam.Q, TvSearchParam.ImdbId, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
},
Flags = new List<IndexerFlag>
{
IndexerFlag.Internal,
IndexerFlag.FreeLeech
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesSD, "Filme SD"); public override IIndexerRequestGenerator GetRequestGenerator()
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.MoviesDVD, "Filme DVD"); {
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.MoviesForeign, "Filme DVD-RO"); return new FileListRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.MoviesHD, "Filme HD"); }
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.AudioLossless, "FLAC");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.MoviesUHD, "Filme 4K");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.XXX, "XXX");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.PC, "Programe");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.PCGames, "Jocuri PC");
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.Console, "Jocuri Console");
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.Audio, "Audio");
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.AudioVideo, "Videoclip");
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.TVSport, "Sport");
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.TV, "Desene");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.Books, "Docs");
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.PC, "Linux");
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.Other, "Diverse");
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.MoviesForeign, "Filme HD-RO");
caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.MoviesBluRay, "Filme Blu-Ray");
caps.Categories.AddCategoryMapping(21, NewznabStandardCategory.TVHD, "Seriale HD");
caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.PCMobileOther, "Mobile");
caps.Categories.AddCategoryMapping(23, NewznabStandardCategory.TVSD, "Seriale SD");
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.TVAnime, "Anime");
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.Movies3D, "Filme 3D");
caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.MoviesBluRay, "Filme 4K Blu-Ray");
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.TVUHD, "Seriale 4K");
return caps; public override IParseIndexerResponse GetParser()
{
return new FileListParser(Settings, Capabilities.Categories);
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.ImdbId, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
},
Flags = new List<IndexerFlag>
{
IndexerFlag.Internal,
IndexerFlag.FreeLeech
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesSD, "Filme SD");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.MoviesDVD, "Filme DVD");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.MoviesForeign, "Filme DVD-RO");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.MoviesHD, "Filme HD");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.AudioLossless, "FLAC");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.MoviesUHD, "Filme 4K");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.XXX, "XXX");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.PC, "Programe");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.PCGames, "Jocuri PC");
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.Console, "Jocuri Console");
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.Audio, "Audio");
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.AudioVideo, "Videoclip");
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.TVSport, "Sport");
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.TV, "Desene");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.Books, "Docs");
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.PC, "Linux");
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.Other, "Diverse");
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.MoviesForeign, "Filme HD-RO");
caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.MoviesBluRay, "Filme Blu-Ray");
caps.Categories.AddCategoryMapping(21, NewznabStandardCategory.TVHD, "Seriale HD");
caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.PCMobileOther, "Mobile");
caps.Categories.AddCategoryMapping(23, NewznabStandardCategory.TVSD, "Seriale SD");
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.TVAnime, "Anime");
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.Movies3D, "Filme 3D");
caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.MoviesBluRay, "Filme 4K Blu-Ray");
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.TVUHD, "Seriale 4K");
return caps;
}
} }
} }

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