mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-04-18 21:55:12 -04:00
Compare commits
173 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 99bc56efb6 | |||
| 04276eb587 | |||
| 34c560fd3a | |||
| caa8bb05a7 | |||
| 773e8ff1f4 | |||
| 0984976378 | |||
| fcb3c96455 | |||
| acf7a425b5 | |||
| da898fe958 | |||
| 5bb3ea0806 | |||
| b41cb80e33 | |||
| a39341be4b | |||
| 27b3d8618a | |||
| 550b9b58df | |||
| 035ad33b72 | |||
| 85f8e0c451 | |||
| ea2061a7d3 | |||
| ea6d01a49b | |||
| 252cd97e35 | |||
| a8ea05af07 | |||
| 24d6a0cb06 | |||
| 8e1771b5a9 | |||
| d767a82e84 | |||
| 76bfd29f23 | |||
| c923982711 | |||
| f03a64f9ac | |||
| e713e58e83 | |||
| 4fb5d3432b | |||
| a31b107a90 | |||
| f91ffb8328 | |||
| a3ba070296 | |||
| bccb0bd5c8 | |||
| 4517f271c4 | |||
| 2ae2a0b184 | |||
| b5e43e7a1a | |||
| 3a52048dc2 | |||
| 8b898733ab | |||
| f99a2e1164 | |||
| 306209fcc2 | |||
| 5d09c2b5fa | |||
| 41a9d2d732 | |||
| 49b120ba55 | |||
| a88fc34a78 | |||
| c46b7c5e4b | |||
| 94c45541ae | |||
| f8082047a5 | |||
| 011fd57f7d | |||
| 6c35c3fc6f | |||
| 5da02c49eb | |||
| 1a339b9ab2 | |||
| 94edd7538e | |||
| 9b2274805e | |||
| dbf86efb0a | |||
| 529fbfd9bd | |||
| 0ed5bfe0d0 | |||
| 6a43eb0031 | |||
| a12001a5ef | |||
| b57014762d | |||
| a51a8bf921 | |||
| e8dc5b3206 | |||
| d4f22f3596 | |||
| b6018a4cd7 | |||
| ec389987df | |||
| 6b62504916 | |||
| 626d777d3c | |||
| 234707b291 | |||
| 15734ca0da | |||
| 19913e5b01 | |||
| 156f6505be | |||
| e383287972 | |||
| 0c0cbdac2f | |||
| 0685c2eb04 | |||
| e8c132e908 | |||
| bea9bd39ff | |||
| 077e4727f2 | |||
| 5f7bc82eb5 | |||
| 0dd5c56175 | |||
| 409a218379 | |||
| 07cc1e03c8 | |||
| 560cda8ba0 | |||
| 934f566359 | |||
| 89ae5ceaa6 | |||
| c7d5889e59 | |||
| bea3c051b9 | |||
| c0b1675627 | |||
| 906d09e162 | |||
| 8cd9ad01c2 | |||
| ce2f322478 | |||
| 0487309ee8 | |||
| 9862584611 | |||
| 6a00e0db90 | |||
| c93831dd8b | |||
| 6546ba773c | |||
| 4c3484a898 | |||
| 8561b862f9 | |||
| e1032fb0f5 | |||
| 4063219430 | |||
| e008be8581 | |||
| d6b379df64 | |||
| 27094ccf62 | |||
| edf9473e9a | |||
| a0d11e7e33 | |||
| 7729eb398a | |||
| 989564dbce | |||
| c1f917f1ac | |||
| 4b7e47c397 | |||
| 1529527af9 | |||
| a11bd1c3c7 | |||
| 915b320a4a | |||
| 155f72cc45 | |||
| 3f73fec5c3 | |||
| 8515623ceb | |||
| 963cddb582 | |||
| ede323b8ed | |||
| 07d7fc98b0 | |||
| 1b78fd38db | |||
| 5a9d4d6280 | |||
| 70685de5d2 | |||
| 9860183433 | |||
| 50331c61ae | |||
| bd3408f170 | |||
| c043bf8da9 | |||
| ea3fa6f28d | |||
| 8917347c0b | |||
| 2cebdf4a06 | |||
| 985110cfb9 | |||
| de876247a3 | |||
| bad6c301f8 | |||
| fc3b23394a | |||
| 92c3656bad | |||
| 1acbee2a57 | |||
| c28f9b6bcd | |||
| aa8048968c | |||
| 6646734510 | |||
| 71dd8b6d04 | |||
| 6d87bd9f8c | |||
| 551d969680 | |||
| 57dac6afdd | |||
| 3dfbfd07dd | |||
| 842df6913c | |||
| 599eeb4c61 | |||
| da371dd921 | |||
| fc25ba7ac0 | |||
| 6e1bef13e2 | |||
| 72ee413411 | |||
| e87b45b47e | |||
| cc841fe3d1 | |||
| 264ffdcc26 | |||
| 5cc044aa8f | |||
| de2fd92b6f | |||
| eff09c1f72 | |||
| 9db888c9a3 | |||
| bf78396164 | |||
| 0e7eaa9221 | |||
| 5b82decc31 | |||
| 38ab533272 | |||
| 4914fcd5df | |||
| 858415b037 | |||
| 43f4899324 | |||
| c60a94adfb | |||
| f386ddb806 | |||
| 4175c2577e | |||
| 6ce9e5ceb9 | |||
| c15643be39 | |||
| a58380031d | |||
| 73af5c9a72 | |||
| d556545e7f | |||
| affde5d7b7 | |||
| 518c85dee2 | |||
| ba3a240707 | |||
| 587a73f3d6 | |||
| ae8f017ca8 | |||
| d9098b612e |
+7
-7
@@ -117,7 +117,6 @@ 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
|
||||||
@@ -163,6 +162,7 @@ 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,9 +178,6 @@ 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
|
||||||
@@ -192,13 +189,14 @@ 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
|
||||||
@@ -229,6 +227,7 @@ 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
|
||||||
@@ -255,6 +254,7 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
name: Sync issue to Azure DevOps work item
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types:
|
|
||||||
[opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
|
|
||||||
|
|
||||||
concurrency: azuresync-${{ github.event.issue.number }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
alert:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: danhellem/github-actions-issue-to-work-item@master
|
|
||||||
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == true }}"
|
|
||||||
env:
|
|
||||||
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
|
|
||||||
github_token: "${{ github.token }}"
|
|
||||||
ado_organization: "Servarr"
|
|
||||||
ado_project: "Servarr"
|
|
||||||
ado_area_path: "Servarr\\Prowlarr"
|
|
||||||
ado_wit: "Bug"
|
|
||||||
ado_new_state: "New"
|
|
||||||
ado_active_state: "Active"
|
|
||||||
ado_close_state: "Closed"
|
|
||||||
ado_bypassrules: true
|
|
||||||
log_level: 100
|
|
||||||
- uses: danhellem/github-actions-issue-to-work-item@master
|
|
||||||
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == false }}"
|
|
||||||
env:
|
|
||||||
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
|
|
||||||
github_token: "${{ github.token }}"
|
|
||||||
ado_organization: "Servarr"
|
|
||||||
ado_project: "Servarr"
|
|
||||||
ado_area_path: "Servarr\\Prowlarr"
|
|
||||||
ado_wit: "User Story"
|
|
||||||
ado_new_state: "New"
|
|
||||||
ado_active_state: "Active"
|
|
||||||
ado_close_state: "Closed"
|
|
||||||
ado_bypassrules: true
|
|
||||||
log_level: 100
|
|
||||||
+2
-2
@@ -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.0.0'
|
majorVersion: '1.2.2'
|
||||||
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.404'
|
dotnetVersion: '6.0.405'
|
||||||
innoVersion: '6.2.0'
|
innoVersion: '6.2.0'
|
||||||
nodeVersion: '16.x'
|
nodeVersion: '16.x'
|
||||||
windowsImage: 'windows-2022'
|
windowsImage: 'windows-2022'
|
||||||
|
|||||||
@@ -142,8 +142,8 @@ module.exports = (env) => {
|
|||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.js?$/,
|
test: /\.jsx?$/,
|
||||||
exclude: /(node_modules|JsLibraries)/,
|
exclude: /[\\/]node_modules[\\/](?!(@sentry\/browser|@sentry\/integrations|chart.js|filesize|normalize.css)[\\/])/,
|
||||||
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}>
|
||||||
Add Custom Filter
|
{translate('AddCustomFilter')}
|
||||||
</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: 'Add' },
|
{ key: 'add', value: translate('Add') },
|
||||||
{ key: 'remove', value: 'Remove' },
|
{ key: 'remove', value: translate('Remove') },
|
||||||
{ key: 'replace', value: 'Replace' }
|
{ key: 'replace', value: translate('Replace') }
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Tags
|
{translate('Tags')}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function IndexerIndexSortMenu(props) {
|
|||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onPress={onSortSelect}
|
onPress={onSortSelect}
|
||||||
>
|
>
|
||||||
Status
|
{translate('Status')}
|
||||||
</SortMenuItem>
|
</SortMenuItem>
|
||||||
|
|
||||||
<SortMenuItem
|
<SortMenuItem
|
||||||
@@ -62,7 +62,7 @@ function IndexerIndexSortMenu(props) {
|
|||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onPress={onSortSelect}
|
onPress={onSortSelect}
|
||||||
>
|
>
|
||||||
{'Priority'}
|
{translate('Priority')}
|
||||||
</SortMenuItem>
|
</SortMenuItem>
|
||||||
|
|
||||||
<SortMenuItem
|
<SortMenuItem
|
||||||
@@ -71,7 +71,7 @@ function IndexerIndexSortMenu(props) {
|
|||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onPress={onSortSelect}
|
onPress={onSortSelect}
|
||||||
>
|
>
|
||||||
{'Protocol'}
|
{translate('Protocol')}
|
||||||
</SortMenuItem>
|
</SortMenuItem>
|
||||||
|
|
||||||
<SortMenuItem
|
<SortMenuItem
|
||||||
@@ -80,7 +80,7 @@ function IndexerIndexSortMenu(props) {
|
|||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onPress={onSortSelect}
|
onPress={onSortSelect}
|
||||||
>
|
>
|
||||||
{'Privacy'}
|
{translate('Privacy')}
|
||||||
</SortMenuItem>
|
</SortMenuItem>
|
||||||
</MenuContent>
|
</MenuContent>
|
||||||
</SortMenu>
|
</SortMenu>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ function CapabilitiesLabel(props) {
|
|||||||
let filteredList = categories.filter((item) => item.id < 100000);
|
let filteredList = categories.filter((item) => item.id < 100000);
|
||||||
|
|
||||||
if (categoryFilter.length > 0) {
|
if (categoryFilter.length > 0) {
|
||||||
filteredList = filteredList.filter((item) => categoryFilter.includes(item.id));
|
filteredList = filteredList.filter((item) => categoryFilter.includes(item.id) || (item.subCategories && item.subCategories.some((r) => categoryFilter.includes(r.id))));
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameList = filteredList.map((item) => item.name).sort();
|
const nameList = filteredList.map((item) => item.name).sort();
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ class IndexerIndexRow extends Component {
|
|||||||
privacy,
|
privacy,
|
||||||
priority,
|
priority,
|
||||||
status,
|
status,
|
||||||
|
fields,
|
||||||
appProfile,
|
appProfile,
|
||||||
added,
|
added,
|
||||||
capabilities,
|
capabilities,
|
||||||
@@ -96,6 +97,8 @@ 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 (
|
||||||
<>
|
<>
|
||||||
{
|
{
|
||||||
@@ -245,12 +248,12 @@ class IndexerIndexRow extends Component {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
indexerUrls ?
|
baseUrl ?
|
||||||
<IconButton
|
<IconButton
|
||||||
className={styles.externalLink}
|
className={styles.externalLink}
|
||||||
name={icons.EXTERNAL_LINK}
|
name={icons.EXTERNAL_LINK}
|
||||||
title={translate('Website')}
|
title={translate('Website')}
|
||||||
to={indexerUrls[0].replace('api.', '')}
|
to={baseUrl.replace('api.', '')}
|
||||||
/> : null
|
/> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +302,7 @@ 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,11 +20,14 @@ 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>
|
||||||
@@ -57,7 +60,7 @@ function IndexerInfoModalContent(props) {
|
|||||||
/>
|
/>
|
||||||
<DescriptionListItemTitle>{translate('IndexerSite')}</DescriptionListItemTitle>
|
<DescriptionListItemTitle>{translate('IndexerSite')}</DescriptionListItemTitle>
|
||||||
<DescriptionListItemDescription>
|
<DescriptionListItemDescription>
|
||||||
<Link to={indexerUrls[0]}>{indexerUrls[0]}</Link>
|
<Link to={baseUrl}>{baseUrl}</Link>
|
||||||
</DescriptionListItemDescription>
|
</DescriptionListItemDescription>
|
||||||
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
|
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
|
||||||
<DescriptionListItemDescription>
|
<DescriptionListItemDescription>
|
||||||
@@ -114,6 +117,7 @@ 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
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ $hoverScale: 1.05;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
overflow: hidden;
|
||||||
width: 85%;
|
width: 85%;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React, { Component } from 'react';
|
|||||||
import TextTruncate from 'react-text-truncate';
|
import TextTruncate from 'react-text-truncate';
|
||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
|
import Link from 'Components/Link/Link';
|
||||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
import CategoryLabel from 'Search/Table/CategoryLabel';
|
import CategoryLabel from 'Search/Table/CategoryLabel';
|
||||||
@@ -52,12 +53,17 @@ class SearchIndexOverview extends Component {
|
|||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onEditSeriesPress = () => {
|
onGrabPress = () => {
|
||||||
this.setState({ isEditSeriesModalOpen: true });
|
const {
|
||||||
};
|
guid,
|
||||||
|
indexerId,
|
||||||
|
onGrabPress
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
onEditSeriesModalClose = () => {
|
onGrabPress({
|
||||||
this.setState({ isEditSeriesModalOpen: false });
|
guid,
|
||||||
|
indexerId
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -66,6 +72,7 @@ class SearchIndexOverview extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
|
infoUrl,
|
||||||
protocol,
|
protocol,
|
||||||
downloadUrl,
|
downloadUrl,
|
||||||
categories,
|
categories,
|
||||||
@@ -91,10 +98,16 @@ class SearchIndexOverview extends Component {
|
|||||||
<div className={styles.info} style={{ height: contentHeight }}>
|
<div className={styles.info} style={{ height: contentHeight }}>
|
||||||
<div className={styles.titleRow}>
|
<div className={styles.titleRow}>
|
||||||
<div className={styles.title}>
|
<div className={styles.title}>
|
||||||
<TextTruncate
|
<Link
|
||||||
line={2}
|
to={infoUrl}
|
||||||
text={title}
|
title={title}
|
||||||
/>
|
>
|
||||||
|
<TextTruncate
|
||||||
|
line={2}
|
||||||
|
text={title}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class SearchIndexOverviews extends Component {
|
|||||||
this._grid &&
|
this._grid &&
|
||||||
(prevState.width !== width ||
|
(prevState.width !== width ||
|
||||||
prevState.rowHeight !== rowHeight ||
|
prevState.rowHeight !== rowHeight ||
|
||||||
hasDifferentItemsOrOrder(prevProps.items, items)
|
hasDifferentItemsOrOrder(prevProps.items, items, 'guid')
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
||||||
@@ -93,7 +93,6 @@ class SearchIndexOverviews extends Component {
|
|||||||
cellRenderer = ({ key, rowIndex, style }) => {
|
cellRenderer = ({ key, rowIndex, style }) => {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
sortKey,
|
|
||||||
showRelativeDates,
|
showRelativeDates,
|
||||||
shortDateFormat,
|
shortDateFormat,
|
||||||
longDateFormat,
|
longDateFormat,
|
||||||
@@ -117,7 +116,6 @@ class SearchIndexOverviews extends Component {
|
|||||||
<SearchIndexItemConnector
|
<SearchIndexItemConnector
|
||||||
key={release.guid}
|
key={release.guid}
|
||||||
component={SearchIndexOverview}
|
component={SearchIndexOverview}
|
||||||
sortKey={sortKey}
|
|
||||||
rowHeight={rowHeight}
|
rowHeight={rowHeight}
|
||||||
showRelativeDates={showRelativeDates}
|
showRelativeDates={showRelativeDates}
|
||||||
shortDateFormat={shortDateFormat}
|
shortDateFormat={shortDateFormat}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const mapDispatchToProps = {
|
|||||||
dispatchExecuteCommand: executeCommand
|
dispatchExecuteCommand: executeCommand
|
||||||
};
|
};
|
||||||
|
|
||||||
class MovieIndexItemConnector extends Component {
|
class SearchIndexItemConnector extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
@@ -66,9 +66,9 @@ class MovieIndexItemConnector extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MovieIndexItemConnector.propTypes = {
|
SearchIndexItemConnector.propTypes = {
|
||||||
guid: PropTypes.string,
|
guid: PropTypes.string,
|
||||||
component: PropTypes.elementType.isRequired
|
component: PropTypes.elementType.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieIndexItemConnector);
|
export default connect(createMapStateToProps, mapDispatchToProps)(SearchIndexItemConnector);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class AddApplicationModalContent extends Component {
|
|||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Add Application
|
{translate('AddApplication')}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
|
|||||||
@@ -71,14 +71,14 @@ class Application extends Component {
|
|||||||
{
|
{
|
||||||
syncLevel === 'addOnly' &&
|
syncLevel === 'addOnly' &&
|
||||||
<Label kind={kinds.WARNING}>
|
<Label kind={kinds.WARNING}>
|
||||||
Add and Remove Only
|
{translate('AddRemoveOnly')}
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
syncLevel === 'fullSync' &&
|
syncLevel === 'fullSync' &&
|
||||||
<Label kind={kinds.SUCCESS}>
|
<Label kind={kinds.SUCCESS}>
|
||||||
Full Sync
|
{translate('FullSync')}
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ class Application extends Component {
|
|||||||
kind={kinds.DISABLED}
|
kind={kinds.DISABLED}
|
||||||
outline={true}
|
outline={true}
|
||||||
>
|
>
|
||||||
Disabled
|
{translate('Disabled')}
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class IndexerProxy extends Component {
|
|||||||
kind={kinds.DISABLED}
|
kind={kinds.DISABLED}
|
||||||
outline={true}
|
outline={true}
|
||||||
>
|
>
|
||||||
Disabled
|
{translate('Disabled')}
|
||||||
</Label> :
|
</Label> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const defaultState = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'grabTitle',
|
name: 'grabTitle',
|
||||||
label: translate('Grab Title'),
|
label: translate('GrabTitle'),
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
@@ -78,7 +78,7 @@ export const defaultState = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'elapsedTime',
|
name: 'elapsedTime',
|
||||||
label: translate('Elapsed Time'),
|
label: translate('ElapsedTime'),
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'size',
|
name: 'size',
|
||||||
label: 'Size',
|
label: translate('Size'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class Updates extends Component {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={styles.message}>
|
<div className={styles.message}>
|
||||||
The latest version of Prowlarr is already installed
|
{translate('TheLatestVersionIsAlreadyInstalled', ['Prowlarr'])}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<!-- Windows Phone -->
|
<!-- Windows Phone -->
|
||||||
<meta name="msapplication-navbutton-color" content="#3a3f51" />
|
<meta name="msapplication-navbutton-color" content="#3a3f51" />
|
||||||
|
|
||||||
<meta name="description" content="Prowlarr (Preview)" />
|
<meta name="description" content="Prowlarr" />
|
||||||
|
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-icon"
|
rel="apple-touch-icon"
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
<link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css">
|
<link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css">
|
||||||
<!-- webpack bundles head -->
|
<!-- webpack bundles head -->
|
||||||
|
|
||||||
<title>Prowlarr (Preview)</title>
|
<title>Prowlarr</title>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
The super basic styling for .root will live here,
|
The super basic styling for .root will live here,
|
||||||
|
|||||||
+4
-2
@@ -11,7 +11,8 @@
|
|||||||
"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",
|
||||||
@@ -30,7 +31,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.11",
|
"@microsoft/signalr": "6.0.13",
|
||||||
"@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",
|
||||||
@@ -93,6 +94,7 @@
|
|||||||
"@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",
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
is_global = true
|
||||||
|
|
||||||
|
dotnet_diagnostic.CA1014.severity = none
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<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,6 +53,26 @@ 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,12 +24,16 @@ 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)
|
||||||
{
|
{
|
||||||
responseMessage.Content.CopyTo(request.ResponseStream, null, cts.Token);
|
await responseMessage.Content.CopyToAsync(request.ResponseStream, null, cts.Token);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ 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"];
|
||||||
|
|||||||
@@ -166,6 +166,37 @@ 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)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace NzbDrone.Common.Http
|
namespace NzbDrone.Common.Http
|
||||||
{
|
{
|
||||||
@@ -11,19 +11,23 @@ namespace NzbDrone.Common.Http
|
|||||||
{
|
{
|
||||||
if (response.Headers.ContainsKey("Retry-After"))
|
if (response.Headers.ContainsKey("Retry-After"))
|
||||||
{
|
{
|
||||||
var retryAfter = response.Headers["Retry-After"].ToString();
|
var retryAfter = response.Headers["Retry-After"];
|
||||||
int seconds;
|
|
||||||
DateTime date;
|
|
||||||
|
|
||||||
if (int.TryParse(retryAfter, out seconds))
|
if (int.TryParse(retryAfter, out var seconds))
|
||||||
{
|
{
|
||||||
RetryAfter = TimeSpan.FromSeconds(seconds);
|
RetryAfter = TimeSpan.FromSeconds(seconds);
|
||||||
}
|
}
|
||||||
else if (DateTime.TryParse(retryAfter, out date))
|
else if (DateTime.TryParse(retryAfter, out var date))
|
||||||
{
|
{
|
||||||
RetryAfter = date.ToUniversalTime() - DateTime.UtcNow;
|
RetryAfter = date.ToUniversalTime() - DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TooManyRequestsException(HttpRequest request, HttpResponse response, TimeSpan retryWait)
|
||||||
|
: base(request, response)
|
||||||
|
{
|
||||||
|
RetryAfter = retryWait;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
@@ -8,10 +7,10 @@ namespace NzbDrone.Common.Instrumentation
|
|||||||
{
|
{
|
||||||
public class CleanseLogMessage
|
public class CleanseLogMessage
|
||||||
{
|
{
|
||||||
private static readonly Regex[] CleansingRules = new[]
|
private static readonly Regex[] CleansingRules =
|
||||||
{
|
{
|
||||||
// Url
|
// Url
|
||||||
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(@"(?<=[?&: ;])(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(@"(?<=[?& ;])[^=]*?(_?(?<!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),
|
||||||
@@ -21,6 +20,7 @@ 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,9 +58,10 @@ 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,6 +8,7 @@ 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;
|
||||||
@@ -34,6 +35,14 @@ 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>
|
||||||
@@ -239,6 +248,19 @@ 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;
|
||||||
|
|||||||
@@ -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.Compare(left, right, StringComparison.InvariantCultureIgnoreCase) == 0;
|
return string.Equals(left, right, StringComparison.InvariantCultureIgnoreCase);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,13 @@
|
|||||||
<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.1" />
|
<PackageReference Include="DryIoc.dll" Version="5.3.3" />
|
||||||
<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" />
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ namespace NzbDrone.Common
|
|||||||
|
|
||||||
var args = $"create {serviceName} " +
|
var args = $"create {serviceName} " +
|
||||||
$"DisplayName= \"{serviceName}\" " +
|
$"DisplayName= \"{serviceName}\" " +
|
||||||
$"binpath= \"{Process.GetCurrentProcess().MainModule.FileName}\" " +
|
$"binpath= \"{Environment.ProcessPath}\" " +
|
||||||
"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 = 0;
|
private int _delegatesQueuedOrRunning;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the
|
/// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the
|
||||||
|
|||||||
@@ -28,15 +28,15 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
|||||||
[Test]
|
[Test]
|
||||||
public void should_return_warning_when_branch_not_valid()
|
public void should_return_warning_when_branch_not_valid()
|
||||||
{
|
{
|
||||||
GivenValidBranch("master");
|
GivenValidBranch("test");
|
||||||
|
|
||||||
Subject.Check().ShouldBeWarning();
|
Subject.Check().ShouldBeWarning();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("Develop")]
|
|
||||||
[TestCase("develop")]
|
|
||||||
[TestCase("nightly")]
|
[TestCase("nightly")]
|
||||||
[TestCase("Nightly")]
|
[TestCase("Nightly")]
|
||||||
|
[TestCase("develop")]
|
||||||
|
[TestCase("master")]
|
||||||
public void should_return_no_warning_when_branch_valid(string branch)
|
public void should_return_no_warning_when_branch_valid(string branch)
|
||||||
{
|
{
|
||||||
GivenValidBranch(branch);
|
GivenValidBranch(branch);
|
||||||
|
|||||||
@@ -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-15 04:26:21"));
|
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-14 22: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);
|
||||||
|
|||||||
@@ -71,12 +71,12 @@ namespace NzbDrone.Core.Test.IndexerTests.CardigannTests
|
|||||||
result.Should().Be(expected);
|
result.Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("{{ .Today.Year }}", "2022")]
|
[TestCase("{{ .Today.Year }}")]
|
||||||
public void should_handle_variables_statements(string template, string expected)
|
public void should_handle_variables_statements(string template)
|
||||||
{
|
{
|
||||||
var result = Subject.ApplyGoTemplateText(template, _variables);
|
var result = Subject.ApplyGoTemplateText(template, _variables);
|
||||||
|
|
||||||
result.Should().Be(expected);
|
result.Should().Be(DateTime.Now.Year.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("{{if .False }}0{{else}}1{{end}}", "1")]
|
[TestCase("{{if .False }}0{{else}}1{{end}}", "1")]
|
||||||
|
|||||||
@@ -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.FileList;
|
using NzbDrone.Core.Indexers.Definitions.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,10 +21,15 @@ 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() { Username = "someuser", Passkey = "somepass" }
|
Settings = new FileListSettings
|
||||||
|
{
|
||||||
|
BaseUrl = "https://filelist.io/",
|
||||||
|
Username = "someuser",
|
||||||
|
Passkey = "somepass"
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,9 +40,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(), 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 MovieSearchCriteria { Categories = new int[] { 2000 } })).Releases;
|
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new[] { 2000 } })).Releases;
|
||||||
|
|
||||||
releases.Should().HaveCount(4);
|
releases.Should().HaveCount(4);
|
||||||
releases.First().Should().BeOfType<TorrentInfo>();
|
releases.First().Should().BeOfType<TorrentInfo>();
|
||||||
@@ -50,12 +55,14 @@ 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 22:20:19"));
|
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 20:20:19"));
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-21
@@ -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.FileList;
|
using NzbDrone.Core.Indexers.Definitions.FileList;
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
@@ -16,34 +16,35 @@ 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.Season, TvSearchParam.Ep
|
TvSearchParam.Q, TvSearchParam.ImdbId, 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,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
|
|||||||
_movieSearchCriteria = new MovieSearchCriteria
|
_movieSearchCriteria = new MovieSearchCriteria
|
||||||
{
|
{
|
||||||
SearchTerm = "Star Wars",
|
SearchTerm = "Star Wars",
|
||||||
Categories = new int[] { 2000 }
|
Categories = new[] { 2000 }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,13 +66,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 int[] { NewznabStandardCategory.MoviesSD.Id, NewznabStandardCategory.MoviesDVD.Id } });
|
var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new[] { 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,2&");
|
page.Url.Query.Should().Contain("&category=1%2C2");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -100,7 +101,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.HDBits;
|
using NzbDrone.Core.Indexers.Definitions.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.HDBits;
|
using NzbDrone.Core.Indexers.Definitions.HDBits;
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
@@ -14,11 +14,13 @@ 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"
|
||||||
@@ -47,9 +49,25 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
|
|||||||
|
|
||||||
_movieSearchCriteria = new MovieSearchCriteria
|
_movieSearchCriteria = new MovieSearchCriteria
|
||||||
{
|
{
|
||||||
Categories = new int[] { 2000, 2010 },
|
Categories = new[] { 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]
|
||||||
@@ -70,5 +88,49 @@ 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.Gazelle;
|
using NzbDrone.Core.Indexers.Definitions.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 int[] { 2000 } })).Releases;
|
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new[] { 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) [MP3 V2 (VBR)] [BD]");
|
torrentInfo.Title.Should().Be("The Beatles - Abbey Road [1969] [Album] [2.0 Mix 2019] [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}");
|
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.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.Gazelle;
|
using NzbDrone.Core.Indexers.Definitions.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="12.3.1" />
|
<PackageReference Include="YamlDotNet" Version="13.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />
|
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ namespace NzbDrone.Core.Test.ThingiProviderTests
|
|||||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||||
status.Should().NotBeNull();
|
status.Should().NotBeNull();
|
||||||
status.DisabledTill.Should().HaveValue();
|
status.DisabledTill.Should().HaveValue();
|
||||||
status.DisabledTill.Value.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(5), 500);
|
status.DisabledTill.Value.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(1), 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -133,7 +133,7 @@ namespace NzbDrone.Core.Test.ThingiProviderTests
|
|||||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||||
status.Should().NotBeNull();
|
status.Should().NotBeNull();
|
||||||
status.DisabledTill.Should().HaveValue();
|
status.DisabledTill.Should().HaveValue();
|
||||||
status.DisabledTill.Value.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(15), 500);
|
status.DisabledTill.Value.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(5), 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -160,7 +160,7 @@ namespace NzbDrone.Core.Test.ThingiProviderTests
|
|||||||
status.Should().NotBeNull();
|
status.Should().NotBeNull();
|
||||||
|
|
||||||
origStatus.EscalationLevel.Should().Be(3);
|
origStatus.EscalationLevel.Should().Be(3);
|
||||||
status.DisabledTill.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(5), 500);
|
status.DisabledTill.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(1), 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ 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,6 +30,7 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -43,7 +44,8 @@ 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,6 +21,8 @@ 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;
|
||||||
|
|
||||||
@@ -90,7 +92,8 @@ 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);
|
||||||
@@ -108,7 +111,8 @@ 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);
|
||||||
@@ -191,5 +195,7 @@ 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 = indexer.AppProfile.Value.MinimumSeeders;
|
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? 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 = indexer.AppProfile.Value.MinimumSeeders;
|
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? 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 = indexer.AppProfile.Value.MinimumSeeders;
|
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? 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 = indexer.AppProfile.Value.MinimumSeeders;
|
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? 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 = indexer.AppProfile.Value.MinimumSeeders;
|
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? 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;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Configuration
|
|||||||
var releaseInfoPath = Path.Combine(bin, "release_info");
|
var releaseInfoPath = Path.Combine(bin, "release_info");
|
||||||
|
|
||||||
PackageUpdateMechanism = UpdateMechanism.BuiltIn;
|
PackageUpdateMechanism = UpdateMechanism.BuiltIn;
|
||||||
DefaultBranch = "develop";
|
DefaultBranch = "master";
|
||||||
|
|
||||||
if (Path.GetFileName(bin) == "bin" && diskProvider.FileExists(packageInfoPath))
|
if (Path.GetFileName(bin) == "bin" && diskProvider.FileExists(packageInfoPath))
|
||||||
{
|
{
|
||||||
|
|||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
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.Substring(start, end - start));
|
identifier.Append(Buffer.AsSpan(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 = false;
|
private readonly bool _requireConcreteValue;
|
||||||
private int _paramCount = 0;
|
private int _paramCount;
|
||||||
private bool _gotConcreteValue = false;
|
private bool _gotConcreteValue;
|
||||||
|
|
||||||
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 = false;
|
private readonly bool _requireConcreteValue;
|
||||||
private int _paramCount = 0;
|
private int _paramCount;
|
||||||
private bool _gotConcreteValue = false;
|
private bool _gotConcreteValue;
|
||||||
|
|
||||||
public WhereBuilderSqlite(Expression filter, bool requireConcreteValue, int seq)
|
public WhereBuilderSqlite(Expression filter, bool requireConcreteValue, int seq)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||||
|
{
|
||||||
|
public static class EncodingForBase64
|
||||||
|
{
|
||||||
|
public static string EncodeBase64(this string text)
|
||||||
|
{
|
||||||
|
if (text == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] textAsBytes = System.Text.Encoding.UTF8.GetBytes(text);
|
||||||
|
return System.Convert.ToBase64String(textAsBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string DecodeBase64(this string encodedText)
|
||||||
|
{
|
||||||
|
if (encodedText == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] textAsBytes = System.Convert.FromBase64String(encodedText);
|
||||||
|
return System.Text.Encoding.UTF8.GetString(textAsBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||||
|
{
|
||||||
|
public class FreeboxDownloadException : DownloadClientException
|
||||||
|
{
|
||||||
|
public FreeboxDownloadException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||||
|
{
|
||||||
|
public enum FreeboxDownloadPriority
|
||||||
|
{
|
||||||
|
Last = 0,
|
||||||
|
First = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Cache;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
|
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||||
|
{
|
||||||
|
public interface IFreeboxDownloadProxy
|
||||||
|
{
|
||||||
|
void Authenticate(FreeboxDownloadSettings settings);
|
||||||
|
string AddTaskFromUrl(string url, string directory, bool addPaused, bool addFirst, FreeboxDownloadSettings settings);
|
||||||
|
string AddTaskFromFile(string fileName, byte[] fileContent, string directory, bool addPaused, bool addFirst, FreeboxDownloadSettings settings);
|
||||||
|
void DeleteTask(string id, bool deleteData, FreeboxDownloadSettings settings);
|
||||||
|
FreeboxDownloadConfiguration GetDownloadConfiguration(FreeboxDownloadSettings settings);
|
||||||
|
List<FreeboxDownloadTask> GetTasks(FreeboxDownloadSettings settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FreeboxDownloadProxy : IFreeboxDownloadProxy
|
||||||
|
{
|
||||||
|
private readonly IHttpClient _httpClient;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
private ICached<string> _authSessionTokenCache;
|
||||||
|
|
||||||
|
public FreeboxDownloadProxy(ICacheManager cacheManager, IHttpClient httpClient, Logger logger)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
|
_authSessionTokenCache = cacheManager.GetCache<string>(GetType(), "authSessionToken");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Authenticate(FreeboxDownloadSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest(settings).Resource("/login").Build();
|
||||||
|
|
||||||
|
var response = ProcessRequest<FreeboxLogin>(request, settings);
|
||||||
|
|
||||||
|
if (response.Result.LoggedIn == false)
|
||||||
|
{
|
||||||
|
throw new DownloadClientAuthenticationException("Not logged");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string AddTaskFromUrl(string url, string directory, bool addPaused, bool addFirst, FreeboxDownloadSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest(settings).Resource("/downloads/add").Post();
|
||||||
|
request.Headers.ContentType = "application/x-www-form-urlencoded";
|
||||||
|
|
||||||
|
request.AddFormParameter("download_url", System.Web.HttpUtility.UrlPathEncode(url));
|
||||||
|
|
||||||
|
if (!directory.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
request.AddFormParameter("download_dir", directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = ProcessRequest<FreeboxDownloadTask>(request.Build(), settings);
|
||||||
|
|
||||||
|
SetTorrentSettings(response.Result.Id, addPaused, addFirst, settings);
|
||||||
|
|
||||||
|
return response.Result.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string AddTaskFromFile(string fileName, byte[] fileContent, string directory, bool addPaused, bool addFirst, FreeboxDownloadSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest(settings).Resource("/downloads/add").Post();
|
||||||
|
|
||||||
|
request.AddFormUpload("download_file", fileName, fileContent, "multipart/form-data");
|
||||||
|
|
||||||
|
if (directory.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
request.AddFormParameter("download_dir", directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = ProcessRequest<FreeboxDownloadTask>(request.Build(), settings);
|
||||||
|
|
||||||
|
SetTorrentSettings(response.Result.Id, addPaused, addFirst, settings);
|
||||||
|
|
||||||
|
return response.Result.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteTask(string id, bool deleteData, FreeboxDownloadSettings settings)
|
||||||
|
{
|
||||||
|
var uri = "/downloads/" + id;
|
||||||
|
|
||||||
|
if (deleteData == true)
|
||||||
|
{
|
||||||
|
uri += "/erase";
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = BuildRequest(settings).Resource(uri).Build();
|
||||||
|
|
||||||
|
request.Method = HttpMethod.Delete;
|
||||||
|
|
||||||
|
ProcessRequest<string>(request, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FreeboxDownloadConfiguration GetDownloadConfiguration(FreeboxDownloadSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest(settings).Resource("/downloads/config/").Build();
|
||||||
|
|
||||||
|
return ProcessRequest<FreeboxDownloadConfiguration>(request, settings).Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FreeboxDownloadTask> GetTasks(FreeboxDownloadSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest(settings).Resource("/downloads/").Build();
|
||||||
|
|
||||||
|
return ProcessRequest<List<FreeboxDownloadTask>>(request, settings).Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildCachedHeaderKey(FreeboxDownloadSettings settings)
|
||||||
|
{
|
||||||
|
return $"{settings.Host}:{settings.AppId}:{settings.AppToken}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetTorrentSettings(string id, bool addPaused, bool addFirst, FreeboxDownloadSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest(settings).Resource("/downloads/" + id).Build();
|
||||||
|
|
||||||
|
request.Method = HttpMethod.Put;
|
||||||
|
|
||||||
|
var body = new Dictionary<string, object> { };
|
||||||
|
|
||||||
|
if (addPaused)
|
||||||
|
{
|
||||||
|
body.Add("status", FreeboxDownloadTaskStatus.Stopped.ToString().ToLower());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addFirst)
|
||||||
|
{
|
||||||
|
body.Add("queue_pos", "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.SetContent(body.ToJson());
|
||||||
|
|
||||||
|
ProcessRequest<FreeboxDownloadTask>(request, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetSessionToken(HttpRequestBuilder requestBuilder, FreeboxDownloadSettings settings, bool force = false)
|
||||||
|
{
|
||||||
|
var sessionToken = _authSessionTokenCache.Find(BuildCachedHeaderKey(settings));
|
||||||
|
|
||||||
|
if (sessionToken == null || force)
|
||||||
|
{
|
||||||
|
_authSessionTokenCache.Remove(BuildCachedHeaderKey(settings));
|
||||||
|
|
||||||
|
_logger.Debug($"Client needs a new Session Token to reach the API with App ID '{settings.AppId}'");
|
||||||
|
|
||||||
|
// Obtaining a Session Token (from official documentation):
|
||||||
|
// To protect the app_token secret, it will never be used directly to authenticate the
|
||||||
|
// application, instead the API will provide a challenge the app will combine to its
|
||||||
|
// app_token to open a session and get a session_token.
|
||||||
|
// The validity of the session_token is limited in time and the app will have to renew
|
||||||
|
// this session_token once in a while.
|
||||||
|
|
||||||
|
// Retrieving the 'challenge' value (it changes frequently and have a limited time validity)
|
||||||
|
// needed to build password
|
||||||
|
var challengeRequest = requestBuilder.Resource("/login").Build();
|
||||||
|
challengeRequest.Method = HttpMethod.Get;
|
||||||
|
|
||||||
|
var challenge = ProcessRequest<FreeboxLogin>(challengeRequest, settings).Result.Challenge;
|
||||||
|
|
||||||
|
// The password is computed using the 'challenge' value and the 'app_token' ('App Token' setting)
|
||||||
|
var enc = System.Text.Encoding.ASCII;
|
||||||
|
var hmac = new HMACSHA1(enc.GetBytes(settings.AppToken));
|
||||||
|
hmac.Initialize();
|
||||||
|
var buffer = enc.GetBytes(challenge);
|
||||||
|
var password = System.BitConverter.ToString(hmac.ComputeHash(buffer)).Replace("-", "").ToLower();
|
||||||
|
|
||||||
|
// Both 'app_id' ('App ID' setting) and computed password are set to get a Session Token
|
||||||
|
var sessionRequest = requestBuilder.Resource("/login/session").Post().Build();
|
||||||
|
var body = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "app_id", settings.AppId },
|
||||||
|
{ "password", password }
|
||||||
|
};
|
||||||
|
sessionRequest.SetContent(body.ToJson());
|
||||||
|
|
||||||
|
sessionToken = ProcessRequest<FreeboxLogin>(sessionRequest, settings).Result.SessionToken;
|
||||||
|
|
||||||
|
_authSessionTokenCache.Set(BuildCachedHeaderKey(settings), sessionToken);
|
||||||
|
|
||||||
|
_logger.Debug($"New Session Token stored in cache for App ID '{settings.AppId}', ready to reach API");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpRequestBuilder BuildRequest(FreeboxDownloadSettings settings, bool authentication = true)
|
||||||
|
{
|
||||||
|
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.ApiUrl)
|
||||||
|
{
|
||||||
|
LogResponseContent = true
|
||||||
|
};
|
||||||
|
|
||||||
|
requestBuilder.Headers.ContentType = "application/json";
|
||||||
|
|
||||||
|
if (authentication == true)
|
||||||
|
{
|
||||||
|
requestBuilder.SetHeader("X-Fbx-App-Auth", GetSessionToken(requestBuilder, settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FreeboxResponse<T> ProcessRequest<T>(HttpRequest request, FreeboxDownloadSettings settings)
|
||||||
|
{
|
||||||
|
request.LogResponseContent = true;
|
||||||
|
request.SuppressHttpError = true;
|
||||||
|
|
||||||
|
HttpResponse response;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response = _httpClient.Execute(request);
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
throw new DownloadClientUnavailableException($"Unable to reach Freebox API. Verify 'Host', 'Port' or 'Use SSL' settings. (Error: {ex.Message})", ex);
|
||||||
|
}
|
||||||
|
catch (WebException ex)
|
||||||
|
{
|
||||||
|
throw new DownloadClientUnavailableException("Unable to connect to Freebox API, please check your settings", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.StatusCode == HttpStatusCode.Forbidden || response.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
_authSessionTokenCache.Remove(BuildCachedHeaderKey(settings));
|
||||||
|
|
||||||
|
var responseContent = Json.Deserialize<FreeboxResponse<FreeboxLogin>>(response.Content);
|
||||||
|
|
||||||
|
var msg = $"Authentication to Freebox API failed. Reason: {responseContent.GetErrorDescription()}";
|
||||||
|
_logger.Error(msg);
|
||||||
|
throw new DownloadClientAuthenticationException(msg);
|
||||||
|
}
|
||||||
|
else if (response.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
throw new FreeboxDownloadException("Unable to reach Freebox API. Verify 'API URL' setting for base URL and version.");
|
||||||
|
}
|
||||||
|
else if (response.StatusCode == HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
var responseContent = Json.Deserialize<FreeboxResponse<T>>(response.Content);
|
||||||
|
|
||||||
|
if (responseContent.Success)
|
||||||
|
{
|
||||||
|
return responseContent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var msg = $"Freebox API returned error: {responseContent.GetErrorDescription()}";
|
||||||
|
_logger.Error(msg);
|
||||||
|
throw new DownloadClientException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new DownloadClientException("Unable to connect to Freebox, please check your settings.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
using NzbDrone.Core.Validation.Paths;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||||
|
{
|
||||||
|
public class FreeboxDownloadSettingsValidator : AbstractValidator<FreeboxDownloadSettings>
|
||||||
|
{
|
||||||
|
public FreeboxDownloadSettingsValidator()
|
||||||
|
{
|
||||||
|
RuleFor(c => c.Host).ValidHost();
|
||||||
|
RuleFor(c => c.Port).InclusiveBetween(1, 65535);
|
||||||
|
RuleFor(c => c.ApiUrl).NotEmpty()
|
||||||
|
.WithMessage("'API URL' must not be empty.");
|
||||||
|
RuleFor(c => c.ApiUrl).ValidUrlBase();
|
||||||
|
RuleFor(c => c.AppId).NotEmpty()
|
||||||
|
.WithMessage("'App ID' must not be empty.");
|
||||||
|
RuleFor(c => c.AppToken).NotEmpty()
|
||||||
|
.WithMessage("'App Token' must not be empty.");
|
||||||
|
RuleFor(c => c.Category).Matches(@"^\.?[-a-z]*$", RegexOptions.IgnoreCase)
|
||||||
|
.WithMessage("Allowed characters a-z and -");
|
||||||
|
RuleFor(c => c.DestinationDirectory).IsValidPath()
|
||||||
|
.When(c => c.DestinationDirectory.IsNotNullOrWhiteSpace());
|
||||||
|
RuleFor(c => c.DestinationDirectory).Empty()
|
||||||
|
.When(c => c.Category.IsNotNullOrWhiteSpace())
|
||||||
|
.WithMessage("Cannot use 'Category' and 'Destination Directory' at the same time.");
|
||||||
|
RuleFor(c => c.Category).Empty()
|
||||||
|
.When(c => c.DestinationDirectory.IsNotNullOrWhiteSpace())
|
||||||
|
.WithMessage("Cannot use 'Category' and 'Destination Directory' at the same time.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FreeboxDownloadSettings : IProviderConfig
|
||||||
|
{
|
||||||
|
private static readonly FreeboxDownloadSettingsValidator Validator = new FreeboxDownloadSettingsValidator();
|
||||||
|
|
||||||
|
public FreeboxDownloadSettings()
|
||||||
|
{
|
||||||
|
Host = "mafreebox.freebox.fr";
|
||||||
|
Port = 443;
|
||||||
|
UseSsl = true;
|
||||||
|
ApiUrl = "/api/v1/";
|
||||||
|
}
|
||||||
|
|
||||||
|
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox, HelpText = "Hostname or host IP address of the Freebox, defaults to 'mafreebox.freebox.fr' (will only work if on same network)")]
|
||||||
|
public string Host { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox, HelpText = "Port used to access Freebox interface, defaults to '443'")]
|
||||||
|
public int Port { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secured connection when connecting to Freebox API")]
|
||||||
|
public bool UseSsl { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(3, Label = "API URL", Type = FieldType.Textbox, Advanced = true, HelpText = "Define Freebox API base URL with API version, eg http://[host]:[port]/[api_base_url]/[api_version]/, defaults to '/api/v1/'")]
|
||||||
|
public string ApiUrl { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(4, Label = "App ID", Type = FieldType.Textbox, HelpText = "App ID given when creating access to Freebox API (ie 'app_id')")]
|
||||||
|
public string AppId { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(5, Label = "App Token", Type = FieldType.Password, Privacy = PrivacyLevel.Password, HelpText = "App token retrieved when creating access to Freebox API (ie 'app_token')")]
|
||||||
|
public string AppToken { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(6, Label = "Destination Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Freebox download location")]
|
||||||
|
public string DestinationDirectory { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(7, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated non-Prowlarr downloads (will create a [category] subdirectory in the output directory)")]
|
||||||
|
public string Category { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(8, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "Priority to use when grabbing")]
|
||||||
|
public int Priority { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(10, Label = "Add Paused", Type = FieldType.Checkbox)]
|
||||||
|
public bool AddPaused { get; set; }
|
||||||
|
|
||||||
|
public NzbDroneValidationResult Validate()
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
|
||||||
|
{
|
||||||
|
public class FreeboxDownloadConfiguration
|
||||||
|
{
|
||||||
|
[JsonProperty(PropertyName = "download_dir")]
|
||||||
|
public string DownloadDirectory { get; set; }
|
||||||
|
public string DecodedDownloadDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return DownloadDirectory.DecodeBase64();
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
DownloadDirectory = value.EncodeBase64();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
|
||||||
|
{
|
||||||
|
public enum FreeboxDownloadTaskType
|
||||||
|
{
|
||||||
|
Bt,
|
||||||
|
Nzb,
|
||||||
|
Http,
|
||||||
|
Ftp
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FreeboxDownloadTaskStatus
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
Stopped,
|
||||||
|
Queued,
|
||||||
|
Starting,
|
||||||
|
Downloading,
|
||||||
|
Stopping,
|
||||||
|
Error,
|
||||||
|
Done,
|
||||||
|
Checking,
|
||||||
|
Repairing,
|
||||||
|
Extracting,
|
||||||
|
Seeding,
|
||||||
|
Retry
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FreeboxDownloadTaskIoPriority
|
||||||
|
{
|
||||||
|
Low,
|
||||||
|
Normal,
|
||||||
|
High
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FreeboxDownloadTask
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<string, string> Descriptions;
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "download_dir")]
|
||||||
|
public string DownloadDirectory { get; set; }
|
||||||
|
public string DecodedDownloadDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return DownloadDirectory.DecodeBase64();
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
DownloadDirectory = value.EncodeBase64();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "info_hash")]
|
||||||
|
public string InfoHash { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "queue_pos")]
|
||||||
|
public int QueuePosition { get; set; }
|
||||||
|
[JsonConverter(typeof(UnderscoreStringEnumConverter), FreeboxDownloadTaskStatus.Unknown)]
|
||||||
|
public FreeboxDownloadTaskStatus Status { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "eta")]
|
||||||
|
public long Eta { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "error")]
|
||||||
|
public string Error { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "type")]
|
||||||
|
public string Type { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "io_priority")]
|
||||||
|
public string IoPriority { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "stop_ratio")]
|
||||||
|
public long StopRatio { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "piece_length")]
|
||||||
|
public long PieceLength { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "created_ts")]
|
||||||
|
public long CreatedTimestamp { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "size")]
|
||||||
|
public long Size { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "rx_pct")]
|
||||||
|
public long ReceivedPrct { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "rx_bytes")]
|
||||||
|
public long ReceivedBytes { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "rx_rate")]
|
||||||
|
public long ReceivedRate { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "tx_pct")]
|
||||||
|
public long TransmittedPrct { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "tx_bytes")]
|
||||||
|
public long TransmittedBytes { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "tx_rate")]
|
||||||
|
public long TransmittedRate { get; set; }
|
||||||
|
|
||||||
|
static FreeboxDownloadTask()
|
||||||
|
{
|
||||||
|
Descriptions = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "internal", "Internal error." },
|
||||||
|
{ "disk_full", "The disk is full." },
|
||||||
|
{ "unknown", "Unknown error." },
|
||||||
|
{ "parse_error", "Parse error." },
|
||||||
|
{ "unknown_host", "Unknown host." },
|
||||||
|
{ "timeout", "Timeout." },
|
||||||
|
{ "bad_authentication", "Invalid credentials." },
|
||||||
|
{ "connection_refused", "Remote host refused connection." },
|
||||||
|
{ "bt_tracker_error", "Unable to announce on tracker." },
|
||||||
|
{ "bt_missing_files", "Missing torrent files." },
|
||||||
|
{ "bt_file_error", "Error accessing torrent files." },
|
||||||
|
{ "missing_ctx_file", "Error accessing task context file." },
|
||||||
|
{ "nzb_no_group", "Cannot find the requested group on server." },
|
||||||
|
{ "nzb_not_found", "Article not fount on the server." },
|
||||||
|
{ "nzb_invalid_crc", "Invalid article CRC." },
|
||||||
|
{ "nzb_invalid_size", "Invalid article size." },
|
||||||
|
{ "nzb_invalid_filename", "Invalid filename." },
|
||||||
|
{ "nzb_open_failed", "Error opening." },
|
||||||
|
{ "nzb_write_failed", "Error writing." },
|
||||||
|
{ "nzb_missing_size", "Missing article size." },
|
||||||
|
{ "nzb_decode_error", "Article decoding error." },
|
||||||
|
{ "nzb_missing_segments", "Missing article segments." },
|
||||||
|
{ "nzb_error", "Other nzb error." },
|
||||||
|
{ "nzb_authentication_required", "Nzb server need authentication." }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetErrorDescription()
|
||||||
|
{
|
||||||
|
if (Descriptions.ContainsKey(Error))
|
||||||
|
{
|
||||||
|
return Descriptions[Error];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{Error} - Unknown error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
|
||||||
|
{
|
||||||
|
public class FreeboxLogin
|
||||||
|
{
|
||||||
|
[JsonProperty(PropertyName = "logged_in")]
|
||||||
|
public bool LoggedIn { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "challenge")]
|
||||||
|
public string Challenge { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "password_salt")]
|
||||||
|
public string PasswordSalt { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "password_set")]
|
||||||
|
public bool PasswordSet { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "session_token")]
|
||||||
|
public string SessionToken { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
|
||||||
|
{
|
||||||
|
public class FreeboxResponse<T>
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<string, string> Descriptions;
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "success")]
|
||||||
|
public bool Success { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "msg")]
|
||||||
|
public string Message { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "error_code")]
|
||||||
|
public string ErrorCode { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "result")]
|
||||||
|
public T Result { get; set; }
|
||||||
|
|
||||||
|
static FreeboxResponse()
|
||||||
|
{
|
||||||
|
Descriptions = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
// Common errors
|
||||||
|
{ "invalid_request", "Your request is invalid." },
|
||||||
|
{ "invalid_api_version", "Invalid API base url or unknown API version." },
|
||||||
|
{ "internal_error", "Internal error." },
|
||||||
|
|
||||||
|
// Login API errors
|
||||||
|
{ "auth_required", "Invalid session token, or no session token sent." },
|
||||||
|
{ "invalid_token", "The app token you are trying to use is invalid or has been revoked." },
|
||||||
|
{ "pending_token", "The app token you are trying to use has not been validated by user yet." },
|
||||||
|
{ "insufficient_rights", "Your app permissions does not allow accessing this API." },
|
||||||
|
{ "denied_from_external_ip", "You are trying to get an app_token from a remote IP." },
|
||||||
|
{ "ratelimited", "Too many auth error have been made from your IP." },
|
||||||
|
{ "new_apps_denied", "New application token request has been disabled." },
|
||||||
|
{ "apps_denied", "API access from apps has been disabled." },
|
||||||
|
|
||||||
|
// Download API errors
|
||||||
|
{ "task_not_found", "No task was found with the given id." },
|
||||||
|
{ "invalid_operation", "Attempt to perform an invalid operation." },
|
||||||
|
{ "invalid_file", "Error with the download file (invalid format ?)." },
|
||||||
|
{ "invalid_url", "URL is invalid." },
|
||||||
|
{ "not_implemented", "Method not implemented." },
|
||||||
|
{ "out_of_memory", "No more memory available to perform the requested action." },
|
||||||
|
{ "invalid_task_type", "The task type is invalid." },
|
||||||
|
{ "hibernating", "The downloader is hibernating." },
|
||||||
|
{ "need_bt_stopped_done", "This action is only valid for Bittorrent task in stopped or done state." },
|
||||||
|
{ "bt_tracker_not_found", "Attempt to access an invalid tracker object." },
|
||||||
|
{ "too_many_tasks", "Too many tasks." },
|
||||||
|
{ "invalid_address", "Invalid peer address." },
|
||||||
|
{ "port_conflict", "Port conflict when setting config." },
|
||||||
|
{ "invalid_priority", "Invalid priority." },
|
||||||
|
{ "ctx_file_error", "Failed to initialize task context file (need to check disk)." },
|
||||||
|
{ "exists", "Same task already exists." },
|
||||||
|
{ "port_outside_range", "Incoming port is not available for this customer." }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetErrorDescription()
|
||||||
|
{
|
||||||
|
if (Descriptions.ContainsKey(ErrorCode))
|
||||||
|
{
|
||||||
|
return Descriptions[ErrorCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{ErrorCode} - Unknown error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||||
|
{
|
||||||
|
public class TorrentFreeboxDownload : TorrentClientBase<FreeboxDownloadSettings>
|
||||||
|
{
|
||||||
|
private readonly IFreeboxDownloadProxy _proxy;
|
||||||
|
|
||||||
|
public TorrentFreeboxDownload(IFreeboxDownloadProxy proxy,
|
||||||
|
ITorrentFileInfoReader torrentFileInfoReader,
|
||||||
|
IHttpClient httpClient,
|
||||||
|
IConfigService configService,
|
||||||
|
IDiskProvider diskProvider,
|
||||||
|
Logger logger)
|
||||||
|
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||||
|
{
|
||||||
|
_proxy = proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name => "Freebox Download";
|
||||||
|
|
||||||
|
public override bool SupportsCategories => true;
|
||||||
|
|
||||||
|
protected IEnumerable<FreeboxDownloadTask> GetTorrents()
|
||||||
|
{
|
||||||
|
return _proxy.GetTasks(Settings).Where(v => v.Type.ToLower() == FreeboxDownloadTaskType.Bt.ToString().ToLower());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
|
||||||
|
{
|
||||||
|
return _proxy.AddTaskFromUrl(magnetLink,
|
||||||
|
GetDownloadDirectory(release).EncodeBase64(),
|
||||||
|
ToBePaused(),
|
||||||
|
ToBeQueuedFirst(),
|
||||||
|
Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
|
||||||
|
{
|
||||||
|
return _proxy.AddTaskFromFile(filename,
|
||||||
|
fileContent,
|
||||||
|
GetDownloadDirectory(release).EncodeBase64(),
|
||||||
|
ToBePaused(),
|
||||||
|
ToBeQueuedFirst(),
|
||||||
|
Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink)
|
||||||
|
{
|
||||||
|
return _proxy.AddTaskFromUrl(torrentLink,
|
||||||
|
GetDownloadDirectory(release).EncodeBase64(),
|
||||||
|
ToBePaused(),
|
||||||
|
ToBeQueuedFirst(),
|
||||||
|
Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Test(List<ValidationFailure> failures)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_proxy.Authenticate(Settings);
|
||||||
|
}
|
||||||
|
catch (DownloadClientUnavailableException ex)
|
||||||
|
{
|
||||||
|
failures.Add(new ValidationFailure("Host", ex.Message));
|
||||||
|
failures.Add(new ValidationFailure("Port", ex.Message));
|
||||||
|
}
|
||||||
|
catch (DownloadClientAuthenticationException ex)
|
||||||
|
{
|
||||||
|
failures.Add(new ValidationFailure("AppId", ex.Message));
|
||||||
|
failures.Add(new ValidationFailure("AppToken", ex.Message));
|
||||||
|
}
|
||||||
|
catch (FreeboxDownloadException ex)
|
||||||
|
{
|
||||||
|
failures.Add(new ValidationFailure("ApiUrl", ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ValidateCategories(List<ValidationFailure> failures)
|
||||||
|
{
|
||||||
|
base.ValidateCategories(failures);
|
||||||
|
|
||||||
|
foreach (var label in Categories)
|
||||||
|
{
|
||||||
|
if (!Regex.IsMatch(label.ClientCategory, "^\\.?[-a-z]*$"))
|
||||||
|
{
|
||||||
|
failures.AddIfNotNull(new ValidationFailure(string.Empty, "Mapped Categories allowed characters a-z and -"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetDownloadDirectory(ReleaseInfo release)
|
||||||
|
{
|
||||||
|
if (Settings.DestinationDirectory.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return Settings.DestinationDirectory.TrimEnd('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
var destDir = _proxy.GetDownloadConfiguration(Settings).DecodedDownloadDirectory.TrimEnd('/');
|
||||||
|
|
||||||
|
if (Settings.Category.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||||
|
|
||||||
|
destDir = $"{destDir}/{category}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return destDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ToBeQueuedFirst()
|
||||||
|
{
|
||||||
|
if (Settings.Priority == (int)FreeboxDownloadPriority.First)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ToBePaused()
|
||||||
|
{
|
||||||
|
return Settings.AddPaused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
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;
|
||||||
@@ -61,14 +60,6 @@ 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;
|
||||||
@@ -92,8 +83,7 @@ namespace NzbDrone.Core.Download
|
|||||||
}
|
}
|
||||||
catch (ReleaseDownloadException ex)
|
catch (ReleaseDownloadException ex)
|
||||||
{
|
{
|
||||||
var http429 = ex.InnerException as TooManyRequestsException;
|
if (ex.InnerException is TooManyRequestsException http429)
|
||||||
if (http429 != null)
|
|
||||||
{
|
{
|
||||||
_indexerStatusService.RecordFailure(release.IndexerId, http429.RetryAfter);
|
_indexerStatusService.RecordFailure(release.IndexerId, http429.RetryAfter);
|
||||||
}
|
}
|
||||||
@@ -141,8 +131,7 @@ namespace NzbDrone.Core.Download
|
|||||||
}
|
}
|
||||||
catch (ReleaseDownloadException ex)
|
catch (ReleaseDownloadException ex)
|
||||||
{
|
{
|
||||||
var http429 = ex.InnerException as TooManyRequestsException;
|
if (ex.InnerException is TooManyRequestsException http429)
|
||||||
if (http429 != null)
|
|
||||||
{
|
{
|
||||||
_indexerStatusService.RecordFailure(indexerId, http429.RetryAfter);
|
_indexerStatusService.RecordFailure(indexerId, http429.RetryAfter);
|
||||||
}
|
}
|
||||||
@@ -155,7 +144,9 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
|||||||
|
|
||||||
public enum ReleaseBranches
|
public enum ReleaseBranches
|
||||||
{
|
{
|
||||||
|
Master,
|
||||||
Develop,
|
Develop,
|
||||||
Nightly
|
Nightly
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ namespace NzbDrone.Core.HealthCheck
|
|||||||
|
|
||||||
private readonly ICached<HealthCheck> _healthCheckResults;
|
private readonly ICached<HealthCheck> _healthCheckResults;
|
||||||
|
|
||||||
private bool _hasRunHealthChecksAfterGracePeriod = false;
|
private bool _hasRunHealthChecksAfterGracePeriod;
|
||||||
private bool _isRunningHealthChecksAfterGracePeriod = false;
|
private bool _isRunningHealthChecksAfterGracePeriod;
|
||||||
|
|
||||||
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 + TimeSpan.FromMinutes(15);
|
_startupGracePeriodEndTime = runtimeInfo.StartTime.AddMinutes(15);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<HealthCheck> Results()
|
public List<HealthCheck> Results()
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ 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
|
||||||
@@ -115,5 +116,24 @@ 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ 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,
|
||||||
@@ -173,7 +174,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);
|
||||||
@@ -232,5 +233,10 @@ 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,15 +1,16 @@
|
|||||||
|
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 HashSet<string> { "cloudflare", "cloudflare-nginx", "ddos-guard" };
|
private static readonly HashSet<string> CloudflareServerNames = new () { "cloudflare", "cloudflare-nginx", "ddos-guard" };
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public CloudFlareDetectionService(Logger logger)
|
public CloudFlareDetectionService(Logger logger)
|
||||||
@@ -28,12 +29,20 @@ namespace NzbDrone.Core.Http.CloudFlare
|
|||||||
if (response.StatusCode.Equals(HttpStatusCode.ServiceUnavailable) ||
|
if (response.StatusCode.Equals(HttpStatusCode.ServiceUnavailable) ||
|
||||||
response.StatusCode.Equals(HttpStatusCode.Forbidden))
|
response.StatusCode.Equals(HttpStatusCode.Forbidden))
|
||||||
{
|
{
|
||||||
return true; // Defected CloudFlare and DDoS-GUARD
|
var responseHtml = response.Content;
|
||||||
|
if (responseHtml.Contains("<title>Just a moment...</title>") ||
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 == "" &&
|
response.Headers.ContentEncoding.IsNullOrWhiteSpace() &&
|
||||||
response.Content.ToLower().Contains("ddos"))
|
response.Content.ToLower().Contains("ddos"))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ 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,17 +10,6 @@ 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
|
public override bool RssSearch => SearchTerm.IsNullOrWhiteSpace() && Author.IsNullOrWhiteSpace() && Title.IsNullOrWhiteSpace();
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (SearchTerm.IsNullOrWhiteSpace() && Author.IsNullOrWhiteSpace() && Title.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,18 +13,7 @@ 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
|
public override bool RssSearch => SearchTerm.IsNullOrWhiteSpace() && ImdbId.IsNullOrWhiteSpace() && !TmdbId.HasValue && !TraktId.HasValue;
|
||||||
{
|
|
||||||
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,17 +11,6 @@ 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
|
public override bool RssSearch => SearchTerm.IsNullOrWhiteSpace() && Album.IsNullOrWhiteSpace() && Artist.IsNullOrWhiteSpace() && Label.IsNullOrWhiteSpace();
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (SearchTerm.IsNullOrWhiteSpace() && Album.IsNullOrWhiteSpace() && Artist.IsNullOrWhiteSpace() && Label.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
|||||||
{
|
{
|
||||||
public abstract class SearchCriteriaBase
|
public abstract class SearchCriteriaBase
|
||||||
{
|
{
|
||||||
private static readonly Regex SpecialCharacter = new Regex(@"[`'.]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
private static readonly Regex StandardizeDashesRegex = new (@"\p{Pd}+", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
private static readonly Regex NonWord = new Regex(@"[\W]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
private static readonly Regex StandardizeSingleQuotesRegex = new (@"[\u0060\u00B4\u2018\u2019]", 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; }
|
||||||
@@ -21,58 +20,24 @@ 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 virtual string SearchQuery
|
public override string ToString() => $"{SearchQuery}, Offset: {Offset ?? 0}, Limit: {Limit ?? 0}, Categories: [{string.Join(", ", Categories)}]";
|
||||||
|
|
||||||
|
public virtual string SearchQuery => $"Term: [{SearchTerm}]";
|
||||||
|
|
||||||
|
public virtual bool RssSearch => SearchTerm.IsNullOrWhiteSpace();
|
||||||
|
|
||||||
|
public string SanitizedSearchTerm => GetSanitizedTerm(SearchTerm);
|
||||||
|
|
||||||
|
private static string GetSanitizedTerm(string term)
|
||||||
{
|
{
|
||||||
get
|
term ??= "";
|
||||||
{
|
|
||||||
return $"Term: [{SearchTerm}]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
term = StandardizeDashesRegex.Replace(term, "-");
|
||||||
{
|
term = StandardizeSingleQuotesRegex.Replace(term, "'");
|
||||||
return $"{SearchQuery}, Offset: {Offset ?? 0}, Limit: {Limit ?? 0}, Categories: [{string.Join(", ", Categories)}]";
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual bool RssSearch
|
var safeTitle = term.Where(c => char.IsLetterOrDigit(c) || char.IsWhiteSpace(c) || c is '-' or '.' or '_' or '(' or ')' or '@' or '/' or '\'' or '[' or ']' or '+' or '%');
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (SearchTerm.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return string.Concat(safeTitle);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,23 +21,12 @@ 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
|
public override bool RssSearch => SearchTerm.IsNullOrWhiteSpace() && ImdbId.IsNullOrWhiteSpace() && !TvdbId.HasValue && !RId.HasValue && !TraktId.HasValue && !TvMazeId.HasValue;
|
||||||
{
|
|
||||||
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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -93,12 +93,13 @@ namespace NzbDrone.Core.IndexerSearch
|
|||||||
r.Languages == null ? null : from c in r.Languages select GetNabElement("language", c, protocol),
|
r.Languages == null ? null : from c in r.Languages select GetNabElement("language", c, protocol),
|
||||||
r.Subs == null ? null : from c in r.Subs select GetNabElement("subs", c, protocol),
|
r.Subs == null ? null : from c in r.Subs select GetNabElement("subs", c, protocol),
|
||||||
r.Genres == null ? null : GetNabElement("genre", string.Join(", ", r.Genres), protocol),
|
r.Genres == null ? null : GetNabElement("genre", string.Join(", ", r.Genres), protocol),
|
||||||
GetNabElement("rageid", r.TvRageId, protocol),
|
r.TvRageId == 0 ? null : GetNabElement("rageid", r.TvRageId, protocol),
|
||||||
GetNabElement("tvdbid", r.TvdbId, protocol),
|
r.TvdbId == 0 ? null : GetNabElement("tvdbid", r.TvdbId, protocol),
|
||||||
GetNabElement("imdb", r.ImdbId.ToString("D7"), protocol),
|
r.ImdbId == 0 ? null : GetNabElement("imdb", r.ImdbId.ToString("D7"), protocol),
|
||||||
GetNabElement("tmdbid", r.TmdbId, protocol),
|
r.TmdbId == 0 ? null : GetNabElement("tmdbid", r.TmdbId, protocol),
|
||||||
GetNabElement("traktid", r.TraktId, protocol),
|
r.TraktId == 0 ? null : GetNabElement("traktid", r.TraktId, protocol),
|
||||||
GetNabElement("doubanid", r.DoubanId, protocol),
|
r.TvMazeId == 0 ? null : GetNabElement("tvmazeid", r.TvMazeId, protocol),
|
||||||
|
r.DoubanId == 0 ? null : GetNabElement("doubanid", r.DoubanId, protocol),
|
||||||
GetNabElement("seeders", t.Seeders, protocol),
|
GetNabElement("seeders", t.Seeders, protocol),
|
||||||
GetNabElement("files", r.Files, protocol),
|
GetNabElement("files", r.Files, protocol),
|
||||||
GetNabElement("grabs", r.Grabs, protocol),
|
GetNabElement("grabs", r.Grabs, protocol),
|
||||||
|
|||||||
@@ -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.Count() > 0 ? (int)elapsedTimeEvents.Average() : 0;
|
indexerStats.AverageResponseTime = elapsedTimeEvents.Any() ? (int)elapsedTimeEvents.Average() : 0;
|
||||||
|
|
||||||
foreach (var historyEvent in sortedEvents)
|
foreach (var historyEvent in sortedEvents)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,13 +29,14 @@ 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 = 7;
|
private const int DEFINITION_VERSION = 8;
|
||||||
|
|
||||||
//Used when moving yml to C#
|
// Used when moving yml to C#
|
||||||
private readonly List<string> _defintionBlocklist = new List<string>()
|
private readonly List<string> _definitionBlocklist = new ()
|
||||||
{
|
{
|
||||||
"aither",
|
"aither",
|
||||||
"animeworld",
|
"animeworld",
|
||||||
|
"audiobookbay",
|
||||||
"beyond-hd-oneurl",
|
"beyond-hd-oneurl",
|
||||||
"beyond-hd",
|
"beyond-hd",
|
||||||
"blutopia",
|
"blutopia",
|
||||||
@@ -89,7 +90,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 => !_defintionBlocklist.Contains(i.File)).ToList();
|
indexerList = response.Resource.Where(i => !_definitionBlocklist.Contains(i.File)).ToList();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -125,7 +126,7 @@ namespace NzbDrone.Core.IndexerVersions
|
|||||||
|
|
||||||
public List<string> GetBlocklist()
|
public List<string> GetBlocklist()
|
||||||
{
|
{
|
||||||
return _defintionBlocklist;
|
return _definitionBlocklist;
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@@ -227,10 +228,10 @@ namespace NzbDrone.Core.IndexerVersions
|
|||||||
if (definition.Settings == null)
|
if (definition.Settings == null)
|
||||||
{
|
{
|
||||||
definition.Settings = new List<SettingsField>
|
definition.Settings = new List<SettingsField>
|
||||||
{
|
{
|
||||||
new SettingsField { Name = "username", Label = "Username", Type = "text" },
|
new () { Name = "username", Label = "Username", Type = "text" },
|
||||||
new SettingsField { Name = "password", Label = "Password", Type = "password" }
|
new () { Name = "password", Label = "Password", Type = "password" }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (definition.Encoding == null)
|
if (definition.Encoding == null)
|
||||||
|
|||||||
@@ -1,85 +1,120 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Core.Annotations;
|
||||||
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 class AlphaRatio : Gazelle.Gazelle
|
public override string Name => "AlphaRatio";
|
||||||
|
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 AlphaRatio(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
|
||||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
|
||||||
{
|
|
||||||
return new AlphaRatioRequestGenerator()
|
|
||||||
{
|
|
||||||
Settings = Settings,
|
|
||||||
HttpClient = _httpClient,
|
|
||||||
Logger = _logger,
|
|
||||||
Capabilities = Capabilities
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||||
{
|
{
|
||||||
protected override bool ImdbInTags => true;
|
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)
|
||||||
|
{
|
||||||
|
parameters.Set("scene", "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,365 @@
|
|||||||
|
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,10 +9,8 @@ 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;
|
||||||
@@ -20,14 +18,13 @@ 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 string[] { "https://tr.anidub.com/" };
|
public override string[] IndexerUrls => new[] { "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;
|
||||||
@@ -42,31 +39,29 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
|
|
||||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||||
{
|
{
|
||||||
return new AnidubRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
return new AnidubRequestGenerator(Settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IParseIndexerResponse GetParser()
|
public override IParseIndexerResponse GetParser()
|
||||||
{
|
{
|
||||||
return new AnidubParser(Settings, Capabilities.Categories) { HttpClient = _httpClient, Logger = _logger };
|
return new AnidubParser(Settings, Capabilities.Categories, RateLimit, _httpClient, _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")
|
||||||
@@ -77,27 +72,22 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
|
|
||||||
if (response.Content != null && !CheckIfLoginNeeded(response))
|
if (response.Content != null && !CheckIfLoginNeeded(response))
|
||||||
{
|
{
|
||||||
UpdateCookies(response.GetCookies(), DateTime.Now + TimeSpan.FromDays(30));
|
UpdateCookies(response.GetCookies(), DateTime.Now.AddDays(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(ErrorSelector).TextContent.Trim();
|
var errorMessage = document.QuerySelector("#content .berror .berror_c")?.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)
|
||||||
{
|
{
|
||||||
if (httpResponse.Content.Contains("index.php?action=logout"))
|
return !httpResponse.Content.Contains("index.php?action=logout");
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IndexerCapabilities SetCapabilities()
|
private IndexerCapabilities SetCapabilities()
|
||||||
@@ -138,31 +128,32 @@ 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
|
||||||
{
|
{
|
||||||
public UserPassTorrentBaseSettings Settings { get; set; }
|
private readonly UserPassTorrentBaseSettings _settings;
|
||||||
public IndexerCapabilities Capabilities { get; set; }
|
|
||||||
|
|
||||||
public AnidubRequestGenerator()
|
public AnidubRequestGenerator(UserPassTorrentBaseSettings settings)
|
||||||
{
|
{
|
||||||
|
_settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
|
private IEnumerable<IndexerRequest> GetPagedRequests(string term)
|
||||||
{
|
{
|
||||||
var requestUrl = string.Empty;
|
string requestUrl;
|
||||||
var isSearch = !string.IsNullOrWhiteSpace(term);
|
var isSearch = !string.IsNullOrWhiteSpace(term);
|
||||||
|
|
||||||
if (isSearch)
|
if (isSearch)
|
||||||
{
|
{
|
||||||
requestUrl = string.Format("{0}/index.php?do=search", Settings.BaseUrl.TrimEnd('/'));
|
requestUrl = $"{_settings.BaseUrl.TrimEnd('/')}/index.php?do=search";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
requestUrl = Settings.BaseUrl;
|
requestUrl = _settings.BaseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = new IndexerRequest(requestUrl, HttpAccept.Html);
|
var request = new IndexerRequest(requestUrl, HttpAccept.Html);
|
||||||
@@ -207,7 +198,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
{
|
{
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
||||||
|
|
||||||
return pageableRequests;
|
return pageableRequests;
|
||||||
}
|
}
|
||||||
@@ -216,7 +207,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
{
|
{
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
|
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedTvSearchString}"));
|
||||||
|
|
||||||
return pageableRequests;
|
return pageableRequests;
|
||||||
}
|
}
|
||||||
@@ -225,7 +216,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
{
|
{
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
||||||
|
|
||||||
return pageableRequests;
|
return pageableRequests;
|
||||||
}
|
}
|
||||||
@@ -234,7 +225,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
{
|
{
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
||||||
|
|
||||||
return pageableRequests;
|
return pageableRequests;
|
||||||
}
|
}
|
||||||
@@ -243,7 +234,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
{
|
{
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
||||||
|
|
||||||
return pageableRequests;
|
return pageableRequests;
|
||||||
}
|
}
|
||||||
@@ -256,33 +247,37 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
{
|
{
|
||||||
private readonly UserPassTorrentBaseSettings _settings;
|
private readonly UserPassTorrentBaseSettings _settings;
|
||||||
private readonly IndexerCapabilitiesCategories _categories;
|
private readonly IndexerCapabilitiesCategories _categories;
|
||||||
public IIndexerHttpClient HttpClient { get; set; }
|
private readonly TimeSpan _rateLimit;
|
||||||
public Logger Logger { get; set; }
|
private readonly IIndexerHttpClient _httpClient;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
private static Dictionary<string, string> CategoriesMap => new Dictionary<string, string>
|
private static Dictionary<string, string> CategoriesMap => new ()
|
||||||
{
|
{
|
||||||
{ "/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)
|
public AnidubParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories, TimeSpan rateLimit, IIndexerHttpClient httpClient, Logger logger)
|
||||||
{
|
{
|
||||||
_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)
|
||||||
@@ -327,9 +322,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;
|
||||||
}
|
}
|
||||||
@@ -345,18 +340,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")
|
||||||
{
|
{
|
||||||
@@ -397,16 +392,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,11 +441,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.ToString(),
|
InfoUrl = indexerResponse.Request.Url.FullUri,
|
||||||
DownloadVolumeFactor = 0,
|
DownloadVolumeFactor = 0,
|
||||||
UploadVolumeFactor = 1,
|
UploadVolumeFactor = 1,
|
||||||
|
|
||||||
Guid = indexerResponse.Request.Url.ToString() + t.Id,
|
Guid = indexerResponse.Request.Url.FullUri + t.Id,
|
||||||
Seeders = GetReleaseSeeders(t),
|
Seeders = GetReleaseSeeders(t),
|
||||||
Peers = GetReleaseSeeders(t) + GetReleaseLeechers(t),
|
Peers = GetReleaseSeeders(t) + GetReleaseLeechers(t),
|
||||||
Grabs = GetReleaseGrabs(t),
|
Grabs = GetReleaseGrabs(t),
|
||||||
@@ -472,36 +467,30 @@ 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;
|
|
||||||
|
|
||||||
if (indexerResponse.Request.Url.Query.Contains("do=search"))
|
var links = dom.QuerySelectorAll(".searchitem > h3 > a[href], #dle-content > .story > .story_h > .lcol > h2 > a[href]");
|
||||||
{
|
|
||||||
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 IndexerRequest(url, HttpAccept.Html);
|
var releaseRequest = new HttpRequestBuilder(url)
|
||||||
var releaseResponse = new IndexerResponse(releaseRequest, HttpClient.Execute(releaseRequest.HttpRequest));
|
.WithRateLimit(_rateLimit.TotalSeconds)
|
||||||
|
.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(releaseRequest.HttpRequest, releaseResponse.HttpResponse);
|
throw new TooManyRequestsException(releaseResponse.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 : AbstractValidator<AnimeBytesSettings>
|
public class AnimeBytesSettingsValidator : NoAuthSettingsValidator<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 AnimeBytesSettingsValidator();
|
private static readonly AnimeBytesSettingsValidator Validator = new ();
|
||||||
|
|
||||||
public AnimeBytesSettings()
|
public AnimeBytesSettings()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,10 +7,8 @@ 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;
|
||||||
@@ -18,7 +16,6 @@ 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
|
||||||
{
|
{
|
||||||
@@ -26,7 +23,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
{
|
{
|
||||||
public override string Name => "AnimeTorrents";
|
public override string Name => "AnimeTorrents";
|
||||||
|
|
||||||
public override string[] IndexerUrls => new string[] { "https://animetorrents.me/" };
|
public override string[] IndexerUrls => new[] { "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;
|
||||||
@@ -40,7 +37,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()
|
||||||
@@ -52,30 +49,29 @@ 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", "multipart/form-data")
|
.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("logout.php"))
|
if (response.Content != null && response.Content.Contains("logout.php"))
|
||||||
{
|
{
|
||||||
UpdateCookies(response.GetCookies(), DateTime.Now + TimeSpan.FromDays(30));
|
UpdateCookies(response.GetCookies(), DateTime.Now.AddDays(30));
|
||||||
|
|
||||||
_logger.Debug("AnimeTorrents authentication succeeded");
|
_logger.Debug("AnimeTorrents authentication succeeded");
|
||||||
}
|
}
|
||||||
@@ -87,12 +83,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
|
|
||||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||||
{
|
{
|
||||||
if (httpResponse.Content.Contains("Access Denied!") || httpResponse.Content.Contains("login.php"))
|
return httpResponse.Content.Contains("Access Denied!") || httpResponse.Content.Contains("login.php");
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IndexerCapabilities SetCapabilities()
|
private IndexerCapabilities SetCapabilities()
|
||||||
@@ -100,13 +91,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");
|
||||||
@@ -138,10 +129,6 @@ 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,10 +5,8 @@ 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;
|
||||||
@@ -16,14 +14,13 @@ 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 string[] { "https://tt.animedia.tv/" };
|
public override string[] IndexerUrls => new[] { "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;
|
||||||
@@ -38,12 +35,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
|
|
||||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||||
{
|
{
|
||||||
return new AnimediaRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
return new AnimediaRequestGenerator(Settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IParseIndexerResponse GetParser()
|
public override IParseIndexerResponse GetParser()
|
||||||
{
|
{
|
||||||
return new AnimediaParser(Settings, Capabilities.Categories) { HttpClient = _httpClient, Logger = _logger };
|
return new AnimediaParser(Settings, Capabilities.Categories, RateLimit, _httpClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IndexerCapabilities SetCapabilities()
|
private IndexerCapabilities SetCapabilities()
|
||||||
@@ -51,38 +48,40 @@ 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
|
||||||
{
|
{
|
||||||
public NoAuthTorrentBaseSettings Settings { get; set; }
|
private readonly NoAuthTorrentBaseSettings _settings;
|
||||||
public IndexerCapabilities Capabilities { get; set; }
|
|
||||||
|
|
||||||
public AnimediaRequestGenerator()
|
public AnimediaRequestGenerator(NoAuthTorrentBaseSettings settings)
|
||||||
{
|
{
|
||||||
|
_settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
|
private IEnumerable<IndexerRequest> GetPagedRequests(string term)
|
||||||
{
|
{
|
||||||
var requestUrl = string.Empty;
|
string requestUrl;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(term))
|
if (string.IsNullOrWhiteSpace(term))
|
||||||
{
|
{
|
||||||
requestUrl = Settings.BaseUrl;
|
requestUrl = _settings.BaseUrl;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -94,18 +93,17 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
{ "orderby_sort", "entry_date|desc" }
|
{ "orderby_sort", "entry_date|desc" }
|
||||||
};
|
};
|
||||||
|
|
||||||
requestUrl = string.Format("{0}/ajax/search_result/P0?{1}", Settings.BaseUrl.TrimEnd('/'), queryCollection.GetQueryString());
|
requestUrl = $"{_settings.BaseUrl.TrimEnd('/')}/ajax/search_result/P0?{queryCollection.GetQueryString()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = new IndexerRequest(requestUrl, HttpAccept.Html);
|
yield return 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(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
||||||
|
|
||||||
return pageableRequests;
|
return pageableRequests;
|
||||||
}
|
}
|
||||||
@@ -114,7 +112,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
{
|
{
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
|
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedTvSearchString}"));
|
||||||
|
|
||||||
return pageableRequests;
|
return pageableRequests;
|
||||||
}
|
}
|
||||||
@@ -123,7 +121,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
{
|
{
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
||||||
|
|
||||||
return pageableRequests;
|
return pageableRequests;
|
||||||
}
|
}
|
||||||
@@ -148,6 +146,9 @@ 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);
|
||||||
@@ -155,25 +156,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)
|
public AnimediaParser(NoAuthTorrentBaseSettings settings, IndexerCapabilitiesCategories categories, TimeSpan rateLimit, IIndexerHttpClient httpClient)
|
||||||
{
|
{
|
||||||
_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 name_ru = dom.QuerySelector("div.media__post__header > h1").TextContent.Trim();
|
var nameRu = dom.QuerySelector("div.media__post__header > h1")?.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 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_orig = dom.QuerySelector("div.media__panel > div:nth-of-type(1) > div.col-l:nth-of-type(2) > 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 title = name_ru + " / " + name_en;
|
var title = nameRu + " / " + nameEn;
|
||||||
if (name_en != name_orig)
|
if (nameEn != nameOrig)
|
||||||
{
|
{
|
||||||
title += " / " + name_orig;
|
title += " / " + nameOrig;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tabName = t.TextContent;
|
var tabName = t.TextContent;
|
||||||
@@ -183,7 +184,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
tabName = "";
|
tabName = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
var heading = tr.QuerySelector("h3.tracker_info_bold").TextContent;
|
var heading = tr.QuerySelector("h3.tracker_info_bold")?.TextContent.Trim() ?? string.Empty;
|
||||||
|
|
||||||
// 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);
|
||||||
@@ -192,40 +193,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 += string.Format(" E{0}-{1}", match.Groups[1].Value, match.Groups[2].Value);
|
heading += $" E{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;
|
var resolution = tr.QuerySelector("div.tracker_info_left")?.TextContent.Trim() ?? string.Empty;
|
||||||
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;
|
var sizeStr = tr.QuerySelector("div.tracker_info_left")?.TextContent.Trim() ?? string.Empty;
|
||||||
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;
|
var sizeStr = tr.QuerySelector("div.tracker_info_left")?.TextContent.Trim() ?? string.Empty;
|
||||||
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;
|
var rDesc = tr.QuerySelector("h3.tracker_info_bold")?.TextContent.Trim() ?? string.Empty;
|
||||||
var type = dom.QuerySelector("div.releases-date:contains('Тип:')").TextContent;
|
var type = dom.QuerySelector("div.releases-date:contains('Тип:')")?.TextContent.Trim() ?? string.Empty;
|
||||||
|
|
||||||
// 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))
|
||||||
@@ -256,28 +257,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 tr_id = t.Attributes["href"].Value;
|
var trId = t.GetAttribute("href");
|
||||||
var tr = dom.QuerySelector("div" + tr_id);
|
var tr = dom.QuerySelector("div" + trId);
|
||||||
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.ToString();
|
var url = indexerResponse.HttpRequest.Url.FullUri;
|
||||||
|
|
||||||
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 + tr_id,
|
Guid = url + trId,
|
||||||
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").Attributes["href"].Value,
|
DownloadUrl = tr.QuerySelector("div.download_tracker > a.btn__green").GetAttribute("href"),
|
||||||
MagnetUrl = tr.QuerySelector("div.download_tracker > a.btn__d-gray").Attributes["href"].Value,
|
MagnetUrl = tr.QuerySelector("div.download_tracker > a.btn__d-gray").GetAttribute("href"),
|
||||||
Size = getReleaseSize(tr),
|
Size = GetReleaseSize(tr),
|
||||||
Resolution = getResolution(tr)
|
Resolution = GetResolution(tr)
|
||||||
};
|
};
|
||||||
torrentInfos.Add(release);
|
torrentInfos.Add(release);
|
||||||
}
|
}
|
||||||
@@ -291,6 +292,7 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -302,20 +304,24 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
url = "https:" + url;
|
url = "https:" + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
var releaseRequest = new IndexerRequest(url, HttpAccept.Html);
|
var releaseRequest = new HttpRequestBuilder(url)
|
||||||
var releaseResponse = new IndexerResponse(releaseRequest, HttpClient.Execute(releaseRequest.HttpRequest));
|
.WithRateLimit(_rateLimit.TotalSeconds)
|
||||||
|
.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(releaseRequest.HttpRequest, releaseResponse.HttpResponse);
|
throw new TooManyRequestsException(releaseResponse.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,55 +52,42 @@ 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
|
||||||
};
|
};
|
||||||
|
|
||||||
requestBuilder.Method = HttpMethod.Post;
|
|
||||||
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
|
|
||||||
|
|
||||||
var cookies = Cookies;
|
var cookies = Cookies;
|
||||||
|
|
||||||
Cookies = null;
|
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", "multipart/form-data")
|
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
.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);
|
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||||
}
|
}
|
||||||
|
|
||||||
cookies = response.GetCookies();
|
cookies = response.GetCookies();
|
||||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||||
|
|
||||||
_logger.Debug("Anthelion authentication succeeded.");
|
_logger.Debug("Anthelion authentication succeeded.");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||||
{
|
{
|
||||||
if (!httpResponse.Content.Contains("logout.php"))
|
return !httpResponse.Content.Contains("logout.php");
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IndexerCapabilities SetCapabilities()
|
private IndexerCapabilities SetCapabilities()
|
||||||
@@ -108,13 +95,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");
|
||||||
@@ -131,10 +118,6 @@ 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('/'));
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user