mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-03-05 13:40:08 -05:00
Compare commits
35 Commits
v0.4.2.187
...
def-rework
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77f98c832a | ||
|
|
0f61e424e4 | ||
|
|
e9205a850a | ||
|
|
700a72b524 | ||
|
|
cb35a3948e | ||
|
|
8c314439cd | ||
|
|
ee6467073f | ||
|
|
6412048eb9 | ||
|
|
efffeebe7c | ||
|
|
1d25a643f9 | ||
|
|
60f48e3a94 | ||
|
|
60f8778305 | ||
|
|
d5088cf472 | ||
|
|
215c87a099 | ||
|
|
32ca2d1720 | ||
|
|
8baf1b533b | ||
|
|
970f80b155 | ||
|
|
b8dd8b1880 | ||
|
|
f607347bd7 | ||
|
|
9959a1b5ed | ||
|
|
8c10f8b55c | ||
|
|
cad4f3740b | ||
|
|
f26b0474f5 | ||
|
|
8b8d0b24ae | ||
|
|
4dee1d65d1 | ||
|
|
09ed132fe6 | ||
|
|
e85ccd5808 | ||
|
|
37914fb90e | ||
|
|
f2f6a82cf0 | ||
|
|
812cf8135a | ||
|
|
e4284d47b0 | ||
|
|
c53e0054ee | ||
|
|
ddcef3a99c | ||
|
|
b7b5a6e7e1 | ||
|
|
593a649045 |
@@ -27,10 +27,7 @@ Prowlarr is an indexer manager/proxy built on the popular \*arr .net/reactjs bas
|
||||
|
||||
## Support
|
||||
|
||||
Note: Prowlarr is currently early in life, thus bugs should be expected
|
||||
|
||||
[](https://wiki.servarr.com/prowlarr)
|
||||
|
||||
[](https://prowlarr.com/discord)
|
||||
[](https://www.reddit.com/r/Prowlarr)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '0.4.2'
|
||||
majorVersion: '0.4.4'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||
@@ -97,15 +97,14 @@ stages:
|
||||
- bash: |
|
||||
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
|
||||
echo $BUNDLEDVERSIONS
|
||||
grep osx-x64 $BUNDLEDVERSIONS
|
||||
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||
echo "BSD already enabled"
|
||||
echo "Extra platforms already enabled"
|
||||
else
|
||||
echo "Enabling BSD support"
|
||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' $BUNDLEDVERSIONS
|
||||
echo "Enabling extra platform support"
|
||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||
fi
|
||||
displayName: Enable FreeBSD Support
|
||||
- bash: ./build.sh --backend --enable-bsd
|
||||
displayName: Enable Extra Platform Support
|
||||
- bash: ./build.sh --backend --enable-extra-platforms
|
||||
displayName: Build Prowlarr Backend
|
||||
- bash: |
|
||||
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
|
||||
@@ -119,24 +118,28 @@ stages:
|
||||
displayName: Publish Backend
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
||||
artifact: WindowsCoreTests
|
||||
displayName: Publish Windows Test Package
|
||||
artifact: win-x64-tests
|
||||
displayName: Publish win-x64 Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
||||
artifact: LinuxCoreTests
|
||||
displayName: Publish Linux Test Package
|
||||
artifact: linux-x64-tests
|
||||
displayName: Publish linux-x64 Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/linux-x86/publish'
|
||||
artifact: linux-x86-tests
|
||||
displayName: Publish linux-x86 Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
||||
artifact: LinuxMuslCoreTests
|
||||
displayName: Publish Linux Musl Test Package
|
||||
artifact: linux-musl-x64-tests
|
||||
displayName: Publish linux-musl-x64 Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
||||
artifact: FreebsdCoreTests
|
||||
displayName: Publish FreeBSD Test Package
|
||||
artifact: freebsd-x64-tests
|
||||
displayName: Publish freebsd-x64 Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
||||
artifact: MacCoreTests
|
||||
displayName: Publish MacOS Test Package
|
||||
artifact: osx-x64-tests
|
||||
displayName: Publish osx-x64 Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
|
||||
- stage: Build_Frontend
|
||||
@@ -239,35 +242,35 @@ stages:
|
||||
artifactName: WindowsFrontend
|
||||
targetPath: _output
|
||||
displayName: Fetch Frontend
|
||||
- bash: ./build.sh --packages --enable-bsd
|
||||
- bash: ./build.sh --packages --enable-extra-platforms
|
||||
displayName: Create Packages
|
||||
- bash: |
|
||||
find . -name "Prowlarr" -exec chmod a+x {} \;
|
||||
find . -name "Prowlarr.Update" -exec chmod a+x {} \;
|
||||
displayName: Set executable bits
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create Windows Core zip
|
||||
displayName: Create win-x64 zip
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).windows-core-x64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create Windows x86 Core zip
|
||||
displayName: Create win-x86 zip
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).windows-core-x86.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create MacOS x64 Core app
|
||||
displayName: Create osx-x64 app
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-app-core-x64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create MacOS x64 Core tar
|
||||
displayName: Create osx-x64 tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-core-x64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -275,14 +278,14 @@ stages:
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create MacOS arm64 Core app
|
||||
displayName: Create osx-arm64 app
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-app-core-arm64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create MacOS arm64 Core tar
|
||||
displayName: Create osx-arm64 tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-core-arm64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -290,7 +293,7 @@ stages:
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create Linux Core tar
|
||||
displayName: Create linux-x64 tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-x64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -298,7 +301,7 @@ stages:
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create Linux Musl Core tar
|
||||
displayName: Create linux-musl-x64 tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-musl-core-x64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -306,7 +309,15 @@ stages:
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM32 Linux Core tar
|
||||
displayName: Create linux-x86 tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-x86.tar.gz'
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-x86/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create linux-arm tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-arm.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -314,7 +325,7 @@ stages:
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM32 Linux Musl Core tar
|
||||
displayName: Create linux-musl-arm tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-musl-core-arm.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -322,7 +333,7 @@ stages:
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM64 Linux Core tar
|
||||
displayName: Create linux-arm64 tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-arm64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -330,7 +341,7 @@ stages:
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM64 Linux Musl Core tar
|
||||
displayName: Create linux-musl-arm64 tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-musl-core-arm64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -405,22 +416,22 @@ stages:
|
||||
matrix:
|
||||
MacCore:
|
||||
osName: 'Mac'
|
||||
testName: 'MacCore'
|
||||
testName: 'osx-x64'
|
||||
poolName: 'Azure Pipelines'
|
||||
imageName: ${{ variables.macImage }}
|
||||
WindowsCore:
|
||||
osName: 'Windows'
|
||||
testName: 'WindowsCore'
|
||||
testName: 'win-x64'
|
||||
poolName: 'Azure Pipelines'
|
||||
imageName: ${{ variables.windowsImage }}
|
||||
LinuxCore:
|
||||
osName: 'Linux'
|
||||
testName: 'LinuxCore'
|
||||
testName: 'linux-x64'
|
||||
poolName: 'Azure Pipelines'
|
||||
imageName: ${{ variables.linuxImage }}
|
||||
FreebsdCore:
|
||||
osName: 'Linux'
|
||||
testName: 'FreebsdCore'
|
||||
testName: 'freebsd-x64'
|
||||
poolName: 'FreeBSD'
|
||||
imageName:
|
||||
|
||||
@@ -439,7 +450,7 @@ stages:
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: '$(testName)Tests'
|
||||
artifactName: '$(testName)-tests'
|
||||
targetPath: $(testsFolder)
|
||||
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||
displayName: Enable Windows Test Service
|
||||
@@ -469,8 +480,12 @@ stages:
|
||||
matrix:
|
||||
alpine:
|
||||
testName: 'Musl Net Core'
|
||||
artifactName: LinuxMuslCoreTests
|
||||
artifactName: linux-musl-x64-tests
|
||||
containerImage: ghcr.io/servarr/testimages:alpine
|
||||
linux-x86:
|
||||
testName: 'linux-x86'
|
||||
artifactName: linux-x86-tests
|
||||
containerImage: ghcr.io/servarr/testimages:linux-x86
|
||||
|
||||
pool:
|
||||
vmImage: ${{ variables.linuxImage }}
|
||||
@@ -481,9 +496,15 @@ stages:
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core'
|
||||
displayName: 'Install .NET'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
|
||||
- bash: |
|
||||
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
|
||||
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
|
||||
displayName: 'Install .NET'
|
||||
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Test Artifact
|
||||
@@ -513,7 +534,7 @@ stages:
|
||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||
variables:
|
||||
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
||||
artifactName: LinuxCoreTests
|
||||
artifactName: linux-x64-tests
|
||||
Prowlarr__Postgres__Host: 'localhost'
|
||||
Prowlarr__Postgres__Port: '5432'
|
||||
Prowlarr__Postgres__User: 'prowlarr'
|
||||
@@ -585,17 +606,17 @@ stages:
|
||||
matrix:
|
||||
MacCore:
|
||||
osName: 'Mac'
|
||||
testName: 'MacCore'
|
||||
testName: 'osx-x64'
|
||||
imageName: ${{ variables.macImage }}
|
||||
pattern: 'Prowlarr.*.osx-core-x64.tar.gz'
|
||||
WindowsCore:
|
||||
osName: 'Windows'
|
||||
testName: 'WindowsCore'
|
||||
testName: 'win-x64'
|
||||
imageName: ${{ variables.windowsImage }}
|
||||
pattern: 'Prowlarr.*.windows-core-x64.zip'
|
||||
LinuxCore:
|
||||
osName: 'Linux'
|
||||
testName: 'LinuxCore'
|
||||
testName: 'linux-x64'
|
||||
imageName: ${{ variables.linuxImage }}
|
||||
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
||||
|
||||
@@ -612,7 +633,7 @@ stages:
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: '$(testName)Tests'
|
||||
artifactName: '$(testName)-tests'
|
||||
targetPath: $(testsFolder)
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Build Artifact
|
||||
@@ -666,7 +687,7 @@ stages:
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'LinuxCoreTests'
|
||||
artifactName: 'linux-x64-tests'
|
||||
targetPath: $(testsFolder)
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Build Artifact
|
||||
@@ -720,7 +741,7 @@ stages:
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'FreebsdCoreTests'
|
||||
artifactName: 'freebsd-x64-tests'
|
||||
targetPath: $(testsFolder)
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Build Artifact
|
||||
@@ -756,11 +777,15 @@ stages:
|
||||
strategy:
|
||||
matrix:
|
||||
alpine:
|
||||
testName: 'Musl Net Core'
|
||||
artifactName: LinuxMuslCoreTests
|
||||
testName: 'linux-musl-x64'
|
||||
artifactName: linux-musl-x64-tests
|
||||
containerImage: ghcr.io/servarr/testimages:alpine
|
||||
pattern: 'Prowlarr.*.linux-musl-core-x64.tar.gz'
|
||||
|
||||
linux-x86:
|
||||
testName: 'linux-x86'
|
||||
artifactName: linux-x86-tests
|
||||
containerImage: ghcr.io/servarr/testimages:linux-x86
|
||||
pattern: 'Prowlarr.*.linux-core-x86.tar.gz'
|
||||
pool:
|
||||
vmImage: ${{ variables.linuxImage }}
|
||||
|
||||
@@ -770,9 +795,15 @@ stages:
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core'
|
||||
displayName: 'Install .NET'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
|
||||
- bash: |
|
||||
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
|
||||
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
|
||||
displayName: 'Install .NET'
|
||||
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Test Artifact
|
||||
@@ -818,16 +849,19 @@ stages:
|
||||
matrix:
|
||||
Linux:
|
||||
osName: 'Linux'
|
||||
artifactName: 'linux-x64'
|
||||
imageName: ${{ variables.linuxImage }}
|
||||
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
||||
failBuild: true
|
||||
Mac:
|
||||
osName: 'Mac'
|
||||
artifactName: 'osx-x64'
|
||||
imageName: ${{ variables.macImage }}
|
||||
pattern: 'Prowlarr.*.osx-core-x64.tar.gz'
|
||||
failBuild: true
|
||||
Windows:
|
||||
osName: 'Windows'
|
||||
artifactName: 'win-x64'
|
||||
imageName: ${{ variables.windowsImage }}
|
||||
pattern: 'Prowlarr.*.windows-core-x64.zip'
|
||||
failBuild: true
|
||||
@@ -845,7 +879,7 @@ stages:
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: '$(osName)CoreTests'
|
||||
artifactName: '$(artifactName)-tests'
|
||||
targetPath: $(testsFolder)
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Build Artifact
|
||||
|
||||
45
build.sh
45
build.sh
@@ -25,15 +25,22 @@ UpdateVersionNumber()
|
||||
fi
|
||||
}
|
||||
|
||||
EnableBsdSupport()
|
||||
EnableExtraPlatformsInSDK()
|
||||
{
|
||||
#todo enable sdk with
|
||||
#SDK_PATH=$(dotnet --list-sdks | grep -P '5\.\d\.\d+' | head -1 | sed 's/\(5\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
|
||||
# BUNDLED_VERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
||||
SDK_PATH=$(dotnet --list-sdks | grep -P '6\.\d\.\d+' | head -1 | sed 's/\(6\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
|
||||
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
||||
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||
echo "Extra platforms already enabled"
|
||||
else
|
||||
echo "Enabling extra platform support"
|
||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||
fi
|
||||
}
|
||||
|
||||
EnableExtraPlatforms()
|
||||
{
|
||||
if grep -qv freebsd-x64 src/Directory.Build.props; then
|
||||
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64</RuntimeIdentifiers>^g" src/Directory.Build.props
|
||||
sed -i'' -e "s^<ExcludedRuntimeFrameworkPairs>\(.*\)</ExcludedRuntimeFrameworkPairs>^<ExcludedRuntimeFrameworkPairs>\1;freebsd-x64:net472</ExcludedRuntimeFrameworkPairs>^g" src/Directory.Build.props
|
||||
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64;linux-x86</RuntimeIdentifiers>^g" src/Directory.Build.props
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -293,7 +300,8 @@ if [ $# -eq 0 ]; then
|
||||
PACKAGES=YES
|
||||
INSTALLER=NO
|
||||
LINT=YES
|
||||
ENABLE_BSD=NO
|
||||
ENABLE_EXTRA_PLATFORMS=NO
|
||||
ENABLE_EXTRA_PLATFORMS_IN_SDK=NO
|
||||
fi
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
@@ -305,8 +313,12 @@ case $key in
|
||||
BACKEND=YES
|
||||
shift # past argument
|
||||
;;
|
||||
--enable-bsd)
|
||||
ENABLE_BSD=YES
|
||||
--enable-bsd|--enable-extra-platforms)
|
||||
ENABLE_EXTRA_PLATFORMS=YES
|
||||
shift # past argument
|
||||
;;
|
||||
--enable-extra-platforms-in-sdk)
|
||||
ENABLE_EXTRA_PLATFORMS_IN_SDK=YES
|
||||
shift # past argument
|
||||
;;
|
||||
-r|--runtime)
|
||||
@@ -350,12 +362,17 @@ esac
|
||||
done
|
||||
set -- "${POSITIONAL[@]}" # restore positional parameters
|
||||
|
||||
if [ "$ENABLE_EXTRA_PLATFORMS_IN_SDK" = "YES" ];
|
||||
then
|
||||
EnableExtraPlatformsInSDK
|
||||
fi
|
||||
|
||||
if [ "$BACKEND" = "YES" ];
|
||||
then
|
||||
UpdateVersionNumber
|
||||
if [ "$ENABLE_BSD" = "YES" ];
|
||||
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||
then
|
||||
EnableBsdSupport
|
||||
EnableExtraPlatforms
|
||||
fi
|
||||
Build
|
||||
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
||||
@@ -365,9 +382,10 @@ then
|
||||
PackageTests "net6.0" "linux-x64"
|
||||
PackageTests "net6.0" "linux-musl-x64"
|
||||
PackageTests "net6.0" "osx-x64"
|
||||
if [ "$ENABLE_BSD" = "YES" ];
|
||||
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||
then
|
||||
PackageTests "net6.0" "freebsd-x64"
|
||||
PackageTests "net6.0" "linux-x86"
|
||||
fi
|
||||
else
|
||||
PackageTests "$FRAMEWORK" "$RID"
|
||||
@@ -406,9 +424,10 @@ then
|
||||
Package "net6.0" "linux-musl-arm"
|
||||
Package "net6.0" "osx-x64"
|
||||
Package "net6.0" "osx-arm64"
|
||||
if [ "$ENABLE_BSD" = "YES" ];
|
||||
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||
then
|
||||
Package "net6.0" "freebsd-x64"
|
||||
Package "net6.0" "linux-x86"
|
||||
fi
|
||||
else
|
||||
Package "$FRAMEWORK" "$RID"
|
||||
|
||||
@@ -7,7 +7,7 @@ import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
|
||||
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
|
||||
import { fetchIndexers } from 'Store/Actions/indexerActions';
|
||||
import { fetchIndexerStatus } from 'Store/Actions/indexerStatusActions';
|
||||
import { fetchAppProfiles, fetchGeneralSettings, fetchIndexerCategories, fetchLanguages, fetchUISettings } from 'Store/Actions/settingsActions';
|
||||
import { fetchAppProfiles, fetchGeneralSettings, fetchIndexerCategories, fetchUISettings } from 'Store/Actions/settingsActions';
|
||||
import { fetchStatus } from 'Store/Actions/systemActions';
|
||||
import { fetchTags } from 'Store/Actions/tagActions';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
@@ -48,7 +48,6 @@ const selectIsPopulated = createSelector(
|
||||
(state) => state.tags.isPopulated,
|
||||
(state) => state.settings.ui.isPopulated,
|
||||
(state) => state.settings.general.isPopulated,
|
||||
(state) => state.settings.languages.isPopulated,
|
||||
(state) => state.settings.appProfiles.isPopulated,
|
||||
(state) => state.indexers.isPopulated,
|
||||
(state) => state.indexerStatus.isPopulated,
|
||||
@@ -59,7 +58,6 @@ const selectIsPopulated = createSelector(
|
||||
tagsIsPopulated,
|
||||
uiSettingsIsPopulated,
|
||||
generalSettingsIsPopulated,
|
||||
languagesIsPopulated,
|
||||
appProfilesIsPopulated,
|
||||
indexersIsPopulated,
|
||||
indexerStatusIsPopulated,
|
||||
@@ -71,7 +69,6 @@ const selectIsPopulated = createSelector(
|
||||
tagsIsPopulated &&
|
||||
uiSettingsIsPopulated &&
|
||||
generalSettingsIsPopulated &&
|
||||
languagesIsPopulated &&
|
||||
appProfilesIsPopulated &&
|
||||
indexersIsPopulated &&
|
||||
indexerStatusIsPopulated &&
|
||||
@@ -86,7 +83,6 @@ const selectErrors = createSelector(
|
||||
(state) => state.tags.error,
|
||||
(state) => state.settings.ui.error,
|
||||
(state) => state.settings.general.error,
|
||||
(state) => state.settings.languages.error,
|
||||
(state) => state.settings.appProfiles.error,
|
||||
(state) => state.indexers.error,
|
||||
(state) => state.indexerStatus.error,
|
||||
@@ -97,7 +93,6 @@ const selectErrors = createSelector(
|
||||
tagsError,
|
||||
uiSettingsError,
|
||||
generalSettingsError,
|
||||
languagesError,
|
||||
appProfilesError,
|
||||
indexersError,
|
||||
indexerStatusError,
|
||||
@@ -109,7 +104,6 @@ const selectErrors = createSelector(
|
||||
tagsError ||
|
||||
uiSettingsError ||
|
||||
generalSettingsError ||
|
||||
languagesError ||
|
||||
appProfilesError ||
|
||||
indexersError ||
|
||||
indexerStatusError ||
|
||||
@@ -123,7 +117,6 @@ const selectErrors = createSelector(
|
||||
tagsError,
|
||||
uiSettingsError,
|
||||
generalSettingsError,
|
||||
languagesError,
|
||||
appProfilesError,
|
||||
indexersError,
|
||||
indexerStatusError,
|
||||
@@ -166,9 +159,6 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
dispatchFetchTags() {
|
||||
dispatch(fetchTags());
|
||||
},
|
||||
dispatchFetchLanguages() {
|
||||
dispatch(fetchLanguages());
|
||||
},
|
||||
dispatchFetchIndexers() {
|
||||
dispatch(fetchIndexers());
|
||||
},
|
||||
@@ -216,7 +206,6 @@ class PageConnector extends Component {
|
||||
if (!this.props.isPopulated) {
|
||||
this.props.dispatchFetchCustomFilters();
|
||||
this.props.dispatchFetchTags();
|
||||
this.props.dispatchFetchLanguages();
|
||||
this.props.dispatchFetchAppProfiles();
|
||||
this.props.dispatchFetchIndexers();
|
||||
this.props.dispatchFetchIndexerStatus();
|
||||
@@ -242,7 +231,6 @@ class PageConnector extends Component {
|
||||
isPopulated,
|
||||
hasError,
|
||||
dispatchFetchTags,
|
||||
dispatchFetchLanguages,
|
||||
dispatchFetchAppProfiles,
|
||||
dispatchFetchIndexers,
|
||||
dispatchFetchIndexerStatus,
|
||||
@@ -283,7 +271,6 @@ PageConnector.propTypes = {
|
||||
isSidebarVisible: PropTypes.bool.isRequired,
|
||||
dispatchFetchCustomFilters: PropTypes.func.isRequired,
|
||||
dispatchFetchTags: PropTypes.func.isRequired,
|
||||
dispatchFetchLanguages: PropTypes.func.isRequired,
|
||||
dispatchFetchAppProfiles: PropTypes.func.isRequired,
|
||||
dispatchFetchIndexers: PropTypes.func.isRequired,
|
||||
dispatchFetchIndexerStatus: PropTypes.func.isRequired,
|
||||
|
||||
@@ -20,9 +20,9 @@ const SIDEBAR_WIDTH = parseInt(dimensions.sidebarWidth);
|
||||
const links = [
|
||||
{
|
||||
iconName: icons.MOVIE_CONTINUING,
|
||||
title: 'Indexers',
|
||||
title: translate('Indexers'),
|
||||
to: '/',
|
||||
alias: '/movies',
|
||||
alias: '/indexers',
|
||||
children: [
|
||||
{
|
||||
title: translate('Stats'),
|
||||
@@ -33,13 +33,13 @@ const links = [
|
||||
|
||||
{
|
||||
iconName: icons.SEARCH,
|
||||
title: 'Search',
|
||||
title: translate('Search'),
|
||||
to: '/search'
|
||||
},
|
||||
|
||||
{
|
||||
iconName: icons.ACTIVITY,
|
||||
title: 'History',
|
||||
title: translate('History'),
|
||||
to: '/history'
|
||||
},
|
||||
|
||||
|
||||
@@ -35,21 +35,21 @@ class IndexerIndexFooter extends PureComponent {
|
||||
<div className={styles.legendItem}>
|
||||
<div className={styles.enabled} />
|
||||
<div>
|
||||
Enabled
|
||||
{translate('Enabled')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.legendItem}>
|
||||
<div className={styles.redirected} />
|
||||
<div>
|
||||
Enabled, Redirected
|
||||
{translate('EnabledRedirected')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.legendItem}>
|
||||
<div className={styles.disabled} />
|
||||
<div>
|
||||
Disabled
|
||||
{translate('Disabled')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -60,7 +60,7 @@ class IndexerIndexFooter extends PureComponent {
|
||||
)}
|
||||
/>
|
||||
<div>
|
||||
Error
|
||||
{translate('Error')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -62,8 +62,6 @@ class UISettings extends Component {
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const uiLanguages = languages.filter((item) => item.value !== 'Original');
|
||||
|
||||
const themeOptions = Object.keys(themes)
|
||||
.map((theme) => ({ key: theme, value: titleCase(theme) }));
|
||||
|
||||
@@ -172,7 +170,7 @@ class UISettings extends Component {
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="uiLanguage"
|
||||
values={uiLanguages}
|
||||
values={languages}
|
||||
helpText={translate('UILanguageHelpText')}
|
||||
helpTextWarning={translate('UILanguageHelpTextWarning')}
|
||||
onChange={onInputChange}
|
||||
|
||||
@@ -3,6 +3,7 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import { fetchLocalizationOptions } from 'Store/Actions/localizationActions';
|
||||
import { fetchUISettings, saveUISettings, setUISettingsValue } from 'Store/Actions/settingsActions';
|
||||
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||
import UISettings from './UISettings';
|
||||
@@ -11,18 +12,19 @@ const SECTION = 'ui';
|
||||
|
||||
function createLanguagesSelector() {
|
||||
return createSelector(
|
||||
(state) => state.settings.languages,
|
||||
(languages) => {
|
||||
const items = languages.items;
|
||||
const filterItems = ['Any', 'Unknown'];
|
||||
(state) => state.localization,
|
||||
(localization) => {
|
||||
console.log(localization);
|
||||
|
||||
const items = localization.items;
|
||||
|
||||
if (!items) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const newItems = items.filter((lang) => !filterItems.includes(lang.name)).map((item) => {
|
||||
const newItems = items.filter((lang) => !items.includes(lang.name)).map((item) => {
|
||||
return {
|
||||
key: item.id,
|
||||
key: item.value,
|
||||
value: item.name
|
||||
};
|
||||
});
|
||||
@@ -51,6 +53,7 @@ const mapDispatchToProps = {
|
||||
setUISettingsValue,
|
||||
saveUISettings,
|
||||
fetchUISettings,
|
||||
fetchLocalizationOptions,
|
||||
clearPendingChanges
|
||||
};
|
||||
|
||||
@@ -61,6 +64,7 @@ class UISettingsConnector extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchUISettings();
|
||||
this.props.fetchLocalizationOptions();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -96,6 +100,7 @@ UISettingsConnector.propTypes = {
|
||||
setUISettingsValue: PropTypes.func.isRequired,
|
||||
saveUISettings: PropTypes.func.isRequired,
|
||||
fetchUISettings: PropTypes.func.isRequired,
|
||||
fetchLocalizationOptions: PropTypes.func.isRequired,
|
||||
clearPendingChanges: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||
import { createThunk } from 'Store/thunks';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
||||
const section = 'settings.languages';
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
export const FETCH_LANGUAGES = 'settings/languages/fetchLanguages';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const fetchLanguages = createThunk(FETCH_LANGUAGES);
|
||||
|
||||
//
|
||||
// Details
|
||||
|
||||
export default {
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
defaultState: {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
items: []
|
||||
},
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
actionHandlers: {
|
||||
[FETCH_LANGUAGES]: createFetchHandler(section, '/language')
|
||||
},
|
||||
|
||||
//
|
||||
// Reducers
|
||||
|
||||
reducers: {
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
@@ -36,31 +36,31 @@ export const defaultState = {
|
||||
},
|
||||
{
|
||||
name: 'indexer',
|
||||
label: 'Indexer',
|
||||
label: translate('Indexer'),
|
||||
isSortable: false,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'query',
|
||||
label: 'Query',
|
||||
label: translate('Query'),
|
||||
isSortable: false,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'parameters',
|
||||
label: 'Parameters',
|
||||
label: translate('Parameters'),
|
||||
isSortable: false,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'grabTitle',
|
||||
label: 'Grab Title',
|
||||
label: translate('Grab Title'),
|
||||
isSortable: false,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'categories',
|
||||
label: 'Categories',
|
||||
label: translate('Categories'),
|
||||
isSortable: false,
|
||||
isVisible: true
|
||||
},
|
||||
@@ -72,13 +72,13 @@ export const defaultState = {
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
label: 'Source',
|
||||
label: translate('Source'),
|
||||
isSortable: false,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'elapsedTime',
|
||||
label: 'Elapsed Time',
|
||||
label: translate('Elapsed Time'),
|
||||
isSortable: false,
|
||||
isVisible: true
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as indexers from './indexerActions';
|
||||
import * as indexerIndex from './indexerIndexActions';
|
||||
import * as indexerStats from './indexerStatsActions';
|
||||
import * as indexerStatus from './indexerStatusActions';
|
||||
import * as localization from './localizationActions';
|
||||
import * as oAuth from './oAuthActions';
|
||||
import * as paths from './pathActions';
|
||||
import * as providerOptions from './providerOptionActions';
|
||||
@@ -25,6 +26,7 @@ export default [
|
||||
paths,
|
||||
providerOptions,
|
||||
releases,
|
||||
localization,
|
||||
indexers,
|
||||
indexerIndex,
|
||||
indexerStats,
|
||||
|
||||
@@ -36,7 +36,7 @@ export const defaultState = {
|
||||
columns: [
|
||||
{
|
||||
name: 'select',
|
||||
columnLabel: 'Select',
|
||||
columnLabel: translate('Select'),
|
||||
isSortable: false,
|
||||
isVisible: true,
|
||||
isModifiable: false,
|
||||
@@ -51,7 +51,7 @@ export const defaultState = {
|
||||
},
|
||||
{
|
||||
name: 'sortName',
|
||||
label: 'Indexer Name',
|
||||
label: translate('IndexerName'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
isModifiable: false
|
||||
@@ -88,7 +88,7 @@ export const defaultState = {
|
||||
},
|
||||
{
|
||||
name: 'capabilities',
|
||||
label: 'Categories',
|
||||
label: translate('Categories'),
|
||||
isSortable: false,
|
||||
isVisible: true
|
||||
},
|
||||
|
||||
39
frontend/src/Store/Actions/localizationActions.js
Normal file
39
frontend/src/Store/Actions/localizationActions.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
||||
export const section = 'localization';
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
export const defaultState = {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
items: []
|
||||
};
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
export const FETCH_LOCALIZATION_OPTIONS = 'localization/fetchLocalizationOptions';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const fetchLocalizationOptions = createThunk(FETCH_LOCALIZATION_OPTIONS);
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
export const actionHandlers = handleThunks({
|
||||
|
||||
[FETCH_LOCALIZATION_OPTIONS]: createFetchHandler(section, '/localization/options')
|
||||
});
|
||||
|
||||
//
|
||||
// Reducers
|
||||
export const reducers = createHandleActions({}, defaultState, section);
|
||||
@@ -8,7 +8,6 @@ import downloadClients from './Settings/downloadClients';
|
||||
import general from './Settings/general';
|
||||
import indexerCategories from './Settings/indexerCategories';
|
||||
import indexerProxies from './Settings/indexerProxies';
|
||||
import languages from './Settings/languages';
|
||||
import notifications from './Settings/notifications';
|
||||
import ui from './Settings/ui';
|
||||
|
||||
@@ -16,7 +15,6 @@ export * from './Settings/downloadClients';
|
||||
export * from './Settings/general';
|
||||
export * from './Settings/indexerCategories';
|
||||
export * from './Settings/indexerProxies';
|
||||
export * from './Settings/languages';
|
||||
export * from './Settings/notifications';
|
||||
export * from './Settings/applications';
|
||||
export * from './Settings/appProfiles';
|
||||
@@ -38,7 +36,6 @@ export const defaultState = {
|
||||
general: general.defaultState,
|
||||
indexerCategories: indexerCategories.defaultState,
|
||||
indexerProxies: indexerProxies.defaultState,
|
||||
languages: languages.defaultState,
|
||||
notifications: notifications.defaultState,
|
||||
applications: applications.defaultState,
|
||||
appProfiles: appProfiles.defaultState,
|
||||
@@ -68,7 +65,6 @@ export const actionHandlers = handleThunks({
|
||||
...general.actionHandlers,
|
||||
...indexerCategories.actionHandlers,
|
||||
...indexerProxies.actionHandlers,
|
||||
...languages.actionHandlers,
|
||||
...notifications.actionHandlers,
|
||||
...applications.actionHandlers,
|
||||
...appProfiles.actionHandlers,
|
||||
@@ -89,7 +85,6 @@ export const reducers = createHandleActions({
|
||||
...general.reducers,
|
||||
...indexerCategories.reducers,
|
||||
...indexerProxies.reducers,
|
||||
...languages.reducers,
|
||||
...notifications.reducers,
|
||||
...applications.reducers,
|
||||
...appProfiles.reducers,
|
||||
|
||||
@@ -15,27 +15,27 @@ const columns = [
|
||||
},
|
||||
{
|
||||
name: 'commandName',
|
||||
label: 'Name',
|
||||
label: translate('Name'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'queued',
|
||||
label: 'Queued',
|
||||
label: translate('Queued'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'started',
|
||||
label: 'Started',
|
||||
label: translate('Started'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'ended',
|
||||
label: 'Ended',
|
||||
label: translate('Ended'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'duration',
|
||||
label: 'Duration',
|
||||
label: translate('Duration'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,27 +10,27 @@ import ScheduledTaskRowConnector from './ScheduledTaskRowConnector';
|
||||
const columns = [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
label: translate('Name'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'interval',
|
||||
label: 'Interval',
|
||||
label: translate('Interval'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'lastExecution',
|
||||
label: 'Last Execution',
|
||||
label: translate('LastExecution'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'lastDuration',
|
||||
label: 'Last Duration',
|
||||
label: translate('LastDuration'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'nextExecution',
|
||||
label: 'Next Execution',
|
||||
label: translate('NextExecution'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
@@ -15,8 +16,11 @@ using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Dispatchers;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Common.TPL;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Security;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Test.Common.Categories;
|
||||
using HttpClient = NzbDrone.Common.Http.HttpClient;
|
||||
|
||||
namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
@@ -31,6 +35,8 @@ namespace NzbDrone.Common.Test.Http
|
||||
private string _httpBinHost;
|
||||
private string _httpBinHost2;
|
||||
|
||||
private System.Net.Http.HttpClient _httpClient = new ();
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void FixtureSetUp()
|
||||
{
|
||||
@@ -38,7 +44,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
var mainHost = "httpbin.servarr.com";
|
||||
|
||||
// Use mirrors for tests that use two hosts
|
||||
var candidates = new[] { "eu.httpbin.org", /* "httpbin.org", */ "www.httpbin.org" };
|
||||
var candidates = new[] { "httpbin1.servarr.com" };
|
||||
|
||||
// httpbin.org is broken right now, occassionally redirecting to https if it's unavailable.
|
||||
_httpBinHost = mainHost;
|
||||
@@ -46,31 +52,22 @@ namespace NzbDrone.Common.Test.Http
|
||||
|
||||
TestLogger.Info($"{candidates.Length} TestSites available.");
|
||||
|
||||
_httpBinSleep = _httpBinHosts.Length < 2 ? 100 : 10;
|
||||
_httpBinSleep = 10;
|
||||
}
|
||||
|
||||
private bool IsTestSiteAvailable(string site)
|
||||
{
|
||||
try
|
||||
{
|
||||
var req = WebRequest.Create($"https://{site}/get") as HttpWebRequest;
|
||||
var res = req.GetResponse() as HttpWebResponse;
|
||||
var res = _httpClient.GetAsync($"https://{site}/get").GetAwaiter().GetResult();
|
||||
if (res.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
req = WebRequest.Create($"https://{site}/status/429") as HttpWebRequest;
|
||||
res = req.GetResponse() as HttpWebResponse;
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
res = ex.Response as HttpWebResponse;
|
||||
}
|
||||
res = _httpClient.GetAsync($"https://{site}/status/429").GetAwaiter().GetResult();
|
||||
|
||||
if (res == null || res.StatusCode != (HttpStatusCode)429)
|
||||
if (res == null || res.StatusCode != HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -95,10 +92,13 @@ namespace NzbDrone.Common.Test.Http
|
||||
Mocker.GetMock<IOsInfo>().Setup(c => c.Name).Returns("TestOS");
|
||||
Mocker.GetMock<IOsInfo>().Setup(c => c.Version).Returns("9.0.0");
|
||||
|
||||
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(CertificateValidationType.Enabled);
|
||||
|
||||
Mocker.SetConstant<IUserAgentBuilder>(Mocker.Resolve<UserAgentBuilder>());
|
||||
|
||||
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
|
||||
Mocker.SetConstant<ICreateManagedWebProxy>(Mocker.Resolve<ManagedWebProxyFactory>());
|
||||
Mocker.SetConstant<ICertificateValidationService>(new X509CertificateValidationService(Mocker.GetMock<IConfigService>().Object, TestLogger));
|
||||
Mocker.SetConstant<IRateLimitService>(Mocker.Resolve<RateLimitService>());
|
||||
Mocker.SetConstant<IEnumerable<IHttpRequestInterceptor>>(Array.Empty<IHttpRequestInterceptor>());
|
||||
Mocker.SetConstant<IHttpDispatcher>(Mocker.Resolve<TDispatcher>());
|
||||
@@ -138,6 +138,28 @@ namespace NzbDrone.Common.Test.Http
|
||||
response.Content.Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
|
||||
[TestCase(CertificateValidationType.Enabled)]
|
||||
[TestCase(CertificateValidationType.DisabledForLocalAddresses)]
|
||||
public void bad_ssl_should_fail_when_remote_validation_enabled(CertificateValidationType validationType)
|
||||
{
|
||||
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(validationType);
|
||||
var request = new HttpRequest($"https://expired.badssl.com");
|
||||
|
||||
Assert.Throws<HttpRequestException>(() => Subject.Execute(request));
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void bad_ssl_should_pass_if_remote_validation_disabled()
|
||||
{
|
||||
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(CertificateValidationType.Disabled);
|
||||
|
||||
var request = new HttpRequest($"https://expired.badssl.com");
|
||||
|
||||
Subject.Execute(request);
|
||||
ExceptionVerification.ExpectedErrors(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_execute_typed_get()
|
||||
{
|
||||
@@ -162,15 +184,44 @@ namespace NzbDrone.Common.Test.Http
|
||||
response.Resource.Data.Should().Be(message);
|
||||
}
|
||||
|
||||
[TestCase("gzip")]
|
||||
public void should_execute_get_using_gzip(string compression)
|
||||
[Test]
|
||||
public void should_execute_post_with_content_type()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/{compression}");
|
||||
var message = "{ my: 1 }";
|
||||
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/post");
|
||||
request.SetContent(message);
|
||||
request.Headers.ContentType = "application/json";
|
||||
|
||||
var response = Subject.Post<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Data.Should().Be(message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_execute_get_using_gzip()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/gzip");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Headers["Accept-Encoding"].ToString().Should().Be(compression);
|
||||
response.Resource.Headers["Accept-Encoding"].ToString().Should().Contain("gzip");
|
||||
|
||||
response.Resource.Gzipped.Should().BeTrue();
|
||||
response.Resource.Brotli.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_execute_get_using_brotli()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/brotli");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Headers["Accept-Encoding"].ToString().Should().Contain("br");
|
||||
|
||||
response.Resource.Gzipped.Should().BeFalse();
|
||||
response.Resource.Brotli.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase(HttpStatusCode.Unauthorized)]
|
||||
@@ -190,6 +241,28 @@ namespace NzbDrone.Common.Test.Http
|
||||
ExceptionVerification.IgnoreWarns();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_throw_on_suppressed_status_codes()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/status/{HttpStatusCode.NotFound}");
|
||||
request.SuppressHttpErrorStatusCodes = new[] { HttpStatusCode.NotFound };
|
||||
|
||||
Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
||||
|
||||
ExceptionVerification.IgnoreWarns();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_log_unsuccessful_status_codes()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/status/{HttpStatusCode.NotFound}");
|
||||
request.LogHttpError = false;
|
||||
|
||||
Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
||||
|
||||
ExceptionVerification.ExpectedWarns(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_follow_redirects_when_not_in_production()
|
||||
{
|
||||
@@ -315,13 +388,38 @@ namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
var file = GetTempFilePath();
|
||||
|
||||
Assert.Throws<WebException>(() => Subject.DownloadFile("https://download.sonarr.tv/wrongpath", file));
|
||||
Assert.Throws<HttpException>(() => Subject.DownloadFile("https://download.sonarr.tv/wrongpath", file));
|
||||
|
||||
File.Exists(file).Should().BeFalse();
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_write_redirect_content_to_stream()
|
||||
{
|
||||
var file = GetTempFilePath();
|
||||
|
||||
using (var fileStream = new FileStream(file, FileMode.Create))
|
||||
{
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/redirect/1");
|
||||
request.AllowAutoRedirect = false;
|
||||
request.ResponseStream = fileStream;
|
||||
|
||||
var response = Subject.Get(request);
|
||||
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Moved);
|
||||
}
|
||||
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
|
||||
File.Exists(file).Should().BeTrue();
|
||||
|
||||
var fileInfo = new FileInfo(file);
|
||||
|
||||
fileInfo.Length.Should().Be(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_send_cookie()
|
||||
{
|
||||
@@ -753,6 +851,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
public string Url { get; set; }
|
||||
public string Data { get; set; }
|
||||
public bool Gzipped { get; set; }
|
||||
public bool Brotli { get; set; }
|
||||
}
|
||||
|
||||
public class HttpCookieResource
|
||||
|
||||
@@ -210,5 +210,26 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
return result.TrimStart(' ', '.').TrimEnd(' ');
|
||||
}
|
||||
|
||||
public static string EncodeRFC3986(this string value)
|
||||
{
|
||||
// From Twitterizer http://www.twitterizer.net/
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var encoded = Uri.EscapeDataString(value);
|
||||
|
||||
return Regex
|
||||
.Replace(encoded, "(%[0-9a-f][0-9a-f])", c => c.Value.ToUpper())
|
||||
.Replace("(", "%28")
|
||||
.Replace(")", "%29")
|
||||
.Replace("$", "%24")
|
||||
.Replace("!", "%21")
|
||||
.Replace("*", "%2A")
|
||||
.Replace("'", "%27")
|
||||
.Replace("%7E", "~");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
src/NzbDrone.Common/Http/BasicNetworkCredential.cs
Normal file
12
src/NzbDrone.Common/Http/BasicNetworkCredential.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Net;
|
||||
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
public class BasicNetworkCredential : NetworkCredential
|
||||
{
|
||||
public BasicNetworkCredential(string user, string pass)
|
||||
: base(user, pass)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ namespace NzbDrone.Common.Http
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
|
||||
// NOTE: we are not checking non-ascii characters and we should
|
||||
private static readonly Regex _CookieRegex = new Regex(@"([^\(\)<>@,;:\\""/\[\]\?=\{\}\s]+)=([^,;\\""\s]+)");
|
||||
private static readonly string[] FilterProps = { "COMMENT", "COMMENTURL", "DISCORD", "DOMAIN", "EXPIRES", "MAX-AGE", "PATH", "PORT", "SECURE", "VERSION", "HTTPONLY", "SAMESITE" };
|
||||
private static readonly char[] InvalidKeyChars = { '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t', '\n' };
|
||||
private static readonly char[] InvalidValueChars = { '"', ',', ';', '\\', ' ', '\t', '\n' };
|
||||
|
||||
@@ -24,7 +25,7 @@ namespace NzbDrone.Common.Http
|
||||
var matches = _CookieRegex.Match(cookieHeader);
|
||||
while (matches.Success)
|
||||
{
|
||||
if (matches.Groups.Count > 2)
|
||||
if (matches.Groups.Count > 2 && !FilterProps.Contains(matches.Groups[1].Value.ToUpperInvariant()))
|
||||
{
|
||||
cookieDictionary[matches.Groups[1].Value] = matches.Groups[2].Value;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Net.Http;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace NzbDrone.Common.Http.Dispatchers
|
||||
{
|
||||
public interface ICertificateValidationService
|
||||
{
|
||||
bool ShouldByPassValidationError(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,5 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
public interface IHttpDispatcher
|
||||
{
|
||||
Task<HttpResponse> GetResponseAsync(HttpRequest request, CookieContainer cookies);
|
||||
Task DownloadFileAsync(string url, string fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Net.Http;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NLog.Fluent;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
|
||||
@@ -15,221 +18,223 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
{
|
||||
public class ManagedHttpDispatcher : IHttpDispatcher
|
||||
{
|
||||
private const string NO_PROXY_KEY = "no-proxy";
|
||||
|
||||
private const int connection_establish_timeout = 2000;
|
||||
private static bool useIPv6 = Socket.OSSupportsIPv6;
|
||||
private static bool hasResolvedIPv6Availability;
|
||||
|
||||
private readonly IHttpProxySettingsProvider _proxySettingsProvider;
|
||||
private readonly ICreateManagedWebProxy _createManagedWebProxy;
|
||||
private readonly ICertificateValidationService _certificateValidationService;
|
||||
private readonly IUserAgentBuilder _userAgentBuilder;
|
||||
private readonly IPlatformInfo _platformInfo;
|
||||
private readonly Logger _logger;
|
||||
private readonly ICached<System.Net.Http.HttpClient> _httpClientCache;
|
||||
private readonly ICached<CredentialCache> _credentialCache;
|
||||
|
||||
public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, ICreateManagedWebProxy createManagedWebProxy, IUserAgentBuilder userAgentBuilder, IPlatformInfo platformInfo, Logger logger)
|
||||
public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider,
|
||||
ICreateManagedWebProxy createManagedWebProxy,
|
||||
ICertificateValidationService certificateValidationService,
|
||||
IUserAgentBuilder userAgentBuilder,
|
||||
ICacheManager cacheManager)
|
||||
{
|
||||
_proxySettingsProvider = proxySettingsProvider;
|
||||
_createManagedWebProxy = createManagedWebProxy;
|
||||
_certificateValidationService = certificateValidationService;
|
||||
_userAgentBuilder = userAgentBuilder;
|
||||
_platformInfo = platformInfo;
|
||||
_logger = logger;
|
||||
|
||||
_httpClientCache = cacheManager.GetCache<System.Net.Http.HttpClient>(typeof(ManagedHttpDispatcher));
|
||||
_credentialCache = cacheManager.GetCache<CredentialCache>(typeof(ManagedHttpDispatcher), "credentialcache");
|
||||
}
|
||||
|
||||
public async Task<HttpResponse> GetResponseAsync(HttpRequest request, CookieContainer cookies)
|
||||
{
|
||||
var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url);
|
||||
var requestMessage = new HttpRequestMessage(request.Method, (Uri)request.Url);
|
||||
requestMessage.Headers.UserAgent.ParseAdd(_userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent));
|
||||
requestMessage.Headers.ConnectionClose = !request.ConnectionKeepAlive;
|
||||
|
||||
// Deflate is not a standard and could break depending on implementation.
|
||||
// we should just stick with the more compatible Gzip
|
||||
//http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net
|
||||
webRequest.AutomaticDecompression = DecompressionMethods.GZip;
|
||||
|
||||
webRequest.Method = request.Method.ToString();
|
||||
webRequest.UserAgent = _userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent);
|
||||
webRequest.KeepAlive = request.ConnectionKeepAlive;
|
||||
webRequest.AllowAutoRedirect = false;
|
||||
webRequest.CookieContainer = cookies;
|
||||
|
||||
if (request.RequestTimeout != TimeSpan.Zero)
|
||||
var cookieHeader = cookies.GetCookieHeader((Uri)request.Url);
|
||||
if (cookieHeader.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
webRequest.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalMilliseconds);
|
||||
requestMessage.Headers.Add("Cookie", cookieHeader);
|
||||
}
|
||||
|
||||
webRequest.Proxy = request.Proxy ?? GetProxy(request.Url);
|
||||
using var cts = new CancellationTokenSource();
|
||||
if (request.RequestTimeout != TimeSpan.Zero)
|
||||
{
|
||||
cts.CancelAfter(request.RequestTimeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The default for System.Net.Http.HttpClient
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(100));
|
||||
}
|
||||
|
||||
if (request.Credentials != null)
|
||||
{
|
||||
if (request.Credentials is BasicNetworkCredential bc)
|
||||
{
|
||||
// Manually set header to avoid initial challenge response
|
||||
var authInfo = bc.UserName + ":" + bc.Password;
|
||||
authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo));
|
||||
requestMessage.Headers.Add("Authorization", "Basic " + authInfo);
|
||||
}
|
||||
else if (request.Credentials is NetworkCredential nc)
|
||||
{
|
||||
var creds = GetCredentialCache();
|
||||
foreach (var authtype in new[] { "Basic", "Digest" })
|
||||
{
|
||||
creds.Remove((Uri)request.Url, authtype);
|
||||
creds.Add((Uri)request.Url, authtype, nc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (request.ContentData != null)
|
||||
{
|
||||
requestMessage.Content = new ByteArrayContent(request.ContentData);
|
||||
}
|
||||
|
||||
if (request.Headers != null)
|
||||
{
|
||||
AddRequestHeaders(webRequest, request.Headers);
|
||||
AddRequestHeaders(requestMessage, request.Headers);
|
||||
}
|
||||
|
||||
HttpWebResponse httpWebResponse;
|
||||
var httpClient = GetClient(request.Url);
|
||||
|
||||
var sw = new Stopwatch();
|
||||
|
||||
sw.Start();
|
||||
|
||||
try
|
||||
using var responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cts.Token);
|
||||
{
|
||||
if (request.ContentData != null)
|
||||
{
|
||||
webRequest.ContentLength = request.ContentData.Length;
|
||||
using (var writeStream = webRequest.GetRequestStream())
|
||||
{
|
||||
writeStream.Write(request.ContentData, 0, request.ContentData.Length);
|
||||
}
|
||||
}
|
||||
byte[] data = null;
|
||||
|
||||
httpWebResponse = (HttpWebResponse)await webRequest.GetResponseAsync();
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
httpWebResponse = (HttpWebResponse)e.Response;
|
||||
|
||||
if (httpWebResponse == null)
|
||||
try
|
||||
{
|
||||
// The default messages for WebException on mono are pretty horrible.
|
||||
if (e.Status == WebExceptionStatus.NameResolutionFailure)
|
||||
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
throw new WebException($"DNS Name Resolution Failure: '{webRequest.RequestUri.Host}'", e.Status);
|
||||
}
|
||||
else if (e.ToString().Contains("TLS Support not"))
|
||||
{
|
||||
throw new TlsFailureException(webRequest, e);
|
||||
}
|
||||
else if (e.ToString().Contains("The authentication or decryption has failed."))
|
||||
{
|
||||
throw new TlsFailureException(webRequest, e);
|
||||
}
|
||||
else if (OsInfo.IsNotWindows)
|
||||
{
|
||||
throw new WebException($"{e.Message}: '{webRequest.RequestUri}'", e, e.Status, e.Response);
|
||||
responseMessage.Content.CopyTo(request.ResponseStream, null, cts.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
data = responseMessage.Content.ReadAsByteArrayAsync(cts.Token).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte[] data = null;
|
||||
|
||||
using (var responseStream = httpWebResponse.GetResponseStream())
|
||||
{
|
||||
if (responseStream != null && responseStream != Stream.Null)
|
||||
catch (Exception ex)
|
||||
{
|
||||
try
|
||||
throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, null);
|
||||
}
|
||||
|
||||
var headers = responseMessage.Headers.ToNameValueCollection();
|
||||
|
||||
headers.Add(responseMessage.Content.Headers.ToNameValueCollection());
|
||||
|
||||
CookieContainer responseCookies = new CookieContainer();
|
||||
|
||||
if (responseMessage.Headers.TryGetValues("Set-Cookie", out var cookieHeaders))
|
||||
{
|
||||
foreach (var responseCookieHeader in cookieHeaders)
|
||||
{
|
||||
data = await responseStream.ToBytes();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, httpWebResponse);
|
||||
try
|
||||
{
|
||||
cookies.SetCookies(responseMessage.RequestMessage.RequestUri, responseCookieHeader);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore invalid cookies
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
var cookieCollection = cookies.GetCookies(responseMessage.RequestMessage.RequestUri);
|
||||
|
||||
return new HttpResponse(request, new HttpHeader(httpWebResponse.Headers), httpWebResponse.Cookies, data, sw.ElapsedMilliseconds, httpWebResponse.StatusCode);
|
||||
}
|
||||
sw.Stop();
|
||||
|
||||
public async Task DownloadFileAsync(string url, string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(fileName);
|
||||
if (fileInfo.Directory != null && !fileInfo.Directory.Exists)
|
||||
{
|
||||
fileInfo.Directory.Create();
|
||||
}
|
||||
|
||||
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
|
||||
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
var uri = new HttpUri(url);
|
||||
|
||||
using (var webClient = new GZipWebClient())
|
||||
{
|
||||
webClient.Headers.Add(HttpRequestHeader.UserAgent, _userAgentBuilder.GetUserAgent());
|
||||
webClient.Proxy = GetProxy(uri);
|
||||
await webClient.DownloadFileTaskAsync(url, fileName);
|
||||
stopWatch.Stop();
|
||||
_logger.Debug("Downloading Completed. took {0:0}s", stopWatch.Elapsed.Seconds);
|
||||
}
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
_logger.Warn("Failed to get response from: {0} {1}", url, e.Message);
|
||||
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
File.Delete(fileName);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warn(e, "Failed to get response from: " + url);
|
||||
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
File.Delete(fileName);
|
||||
}
|
||||
|
||||
throw;
|
||||
return new HttpResponse(request, new HttpHeader(headers), cookieCollection, data, sw.ElapsedMilliseconds, responseMessage.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IWebProxy GetProxy(HttpUri uri)
|
||||
protected virtual System.Net.Http.HttpClient GetClient(HttpUri uri)
|
||||
{
|
||||
IWebProxy proxy = null;
|
||||
|
||||
var proxySettings = _proxySettingsProvider.GetProxySettings(uri);
|
||||
|
||||
var key = proxySettings?.Key ?? NO_PROXY_KEY;
|
||||
|
||||
return _httpClientCache.Get(key, () => CreateHttpClient(proxySettings));
|
||||
}
|
||||
|
||||
protected virtual System.Net.Http.HttpClient CreateHttpClient(HttpProxySettings proxySettings)
|
||||
{
|
||||
var handler = new SocketsHttpHandler()
|
||||
{
|
||||
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Brotli,
|
||||
UseCookies = false, // sic - we don't want to use a shared cookie container
|
||||
AllowAutoRedirect = false,
|
||||
Credentials = GetCredentialCache(),
|
||||
PreAuthenticate = true,
|
||||
MaxConnectionsPerServer = 12,
|
||||
ConnectCallback = onConnect,
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = _certificateValidationService.ShouldByPassValidationError
|
||||
}
|
||||
};
|
||||
|
||||
if (proxySettings != null)
|
||||
{
|
||||
proxy = _createManagedWebProxy.GetWebProxy(proxySettings);
|
||||
handler.Proxy = _createManagedWebProxy.GetWebProxy(proxySettings);
|
||||
}
|
||||
|
||||
return proxy;
|
||||
var client = new System.Net.Http.HttpClient(handler)
|
||||
{
|
||||
Timeout = Timeout.InfiniteTimeSpan
|
||||
};
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
protected virtual void AddRequestHeaders(HttpWebRequest webRequest, HttpHeader headers)
|
||||
protected virtual void AddRequestHeaders(HttpRequestMessage webRequest, HttpHeader headers)
|
||||
{
|
||||
foreach (var header in headers)
|
||||
{
|
||||
switch (header.Key)
|
||||
{
|
||||
case "Accept":
|
||||
webRequest.Accept = header.Value;
|
||||
webRequest.Headers.Accept.ParseAdd(header.Value);
|
||||
break;
|
||||
case "Connection":
|
||||
webRequest.Connection = header.Value;
|
||||
webRequest.Headers.Connection.Clear();
|
||||
webRequest.Headers.Connection.Add(header.Value);
|
||||
break;
|
||||
case "Content-Length":
|
||||
webRequest.ContentLength = Convert.ToInt64(header.Value);
|
||||
AddContentHeader(webRequest, "Content-Length", header.Value);
|
||||
break;
|
||||
case "Content-Type":
|
||||
webRequest.ContentType = header.Value;
|
||||
AddContentHeader(webRequest, "Content-Type", header.Value);
|
||||
break;
|
||||
case "Date":
|
||||
webRequest.Date = HttpHeader.ParseDateTime(header.Value);
|
||||
webRequest.Headers.Remove("Date");
|
||||
webRequest.Headers.Date = HttpHeader.ParseDateTime(header.Value);
|
||||
break;
|
||||
case "Expect":
|
||||
webRequest.Expect = header.Value;
|
||||
webRequest.Headers.Expect.ParseAdd(header.Value);
|
||||
break;
|
||||
case "Host":
|
||||
webRequest.Host = header.Value;
|
||||
webRequest.Headers.Host = header.Value;
|
||||
break;
|
||||
case "If-Modified-Since":
|
||||
webRequest.IfModifiedSince = HttpHeader.ParseDateTime(header.Value);
|
||||
webRequest.Headers.IfModifiedSince = HttpHeader.ParseDateTime(header.Value);
|
||||
break;
|
||||
case "Range":
|
||||
throw new NotImplementedException();
|
||||
case "Referer":
|
||||
webRequest.Referer = header.Value;
|
||||
webRequest.Headers.Add("Referer", header.Value);
|
||||
break;
|
||||
case "Transfer-Encoding":
|
||||
webRequest.TransferEncoding = header.Value;
|
||||
webRequest.Headers.TransferEncoding.ParseAdd(header.Value);
|
||||
break;
|
||||
case "User-Agent":
|
||||
webRequest.UserAgent = header.Value;
|
||||
webRequest.Headers.UserAgent.ParseAdd(header.Value);
|
||||
break;
|
||||
case "Proxy-Connection":
|
||||
throw new NotImplementedException();
|
||||
@@ -239,5 +244,84 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddContentHeader(HttpRequestMessage request, string header, string value)
|
||||
{
|
||||
var headers = request.Content?.Headers;
|
||||
if (headers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
headers.Remove(header);
|
||||
headers.Add(header, value);
|
||||
}
|
||||
|
||||
private CredentialCache GetCredentialCache()
|
||||
{
|
||||
return _credentialCache.Get("credentialCache", () => new CredentialCache());
|
||||
}
|
||||
|
||||
private static async ValueTask<Stream> onConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
// Until .NET supports an implementation of Happy Eyeballs (https://tools.ietf.org/html/rfc8305#section-2), let's make IPv4 fallback work in a simple way.
|
||||
// This issue is being tracked at https://github.com/dotnet/runtime/issues/26177 and expected to be fixed in .NET 6.
|
||||
if (useIPv6)
|
||||
{
|
||||
try
|
||||
{
|
||||
var localToken = cancellationToken;
|
||||
|
||||
if (!hasResolvedIPv6Availability)
|
||||
{
|
||||
// to make things move fast, use a very low timeout for the initial ipv6 attempt.
|
||||
var quickFailCts = new CancellationTokenSource(connection_establish_timeout);
|
||||
var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, quickFailCts.Token);
|
||||
|
||||
localToken = linkedTokenSource.Token;
|
||||
}
|
||||
|
||||
return await attemptConnection(AddressFamily.InterNetworkV6, context, localToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// very naively fallback to ipv4 permanently for this execution based on the response of the first connection attempt.
|
||||
// note that this may cause users to eventually get switched to ipv4 (on a random failure when they are switching networks, for instance)
|
||||
// but in the interest of keeping this implementation simple, this is acceptable.
|
||||
useIPv6 = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
hasResolvedIPv6Availability = true;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to IPv4.
|
||||
return await attemptConnection(AddressFamily.InterNetwork, context, cancellationToken);
|
||||
}
|
||||
|
||||
private static async ValueTask<Stream> attemptConnection(AddressFamily addressFamily, SocketsHttpConnectionContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
// The following socket constructor will create a dual-mode socket on systems where IPV6 is available.
|
||||
var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp)
|
||||
{
|
||||
// Turn off Nagle's algorithm since it degrades performance in most HttpClient scenarios.
|
||||
NoDelay = true
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// The stream should take the ownership of the underlying socket,
|
||||
// closing it when it's disposed.
|
||||
return new NetworkStream(socket, ownsSocket: true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
socket.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
public class GZipWebClient : WebClient
|
||||
{
|
||||
protected override WebRequest GetWebRequest(Uri address)
|
||||
{
|
||||
var request = (HttpWebRequest)base.GetWebRequest(address);
|
||||
request.AutomaticDecompression = DecompressionMethods.GZip;
|
||||
return request;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
@@ -86,13 +87,21 @@ namespace NzbDrone.Common.Http
|
||||
}
|
||||
|
||||
// 302 or 303 should default to GET on redirect even if POST on original
|
||||
if (response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.RedirectMethod)
|
||||
if (RequestRequiresForceGet(response.StatusCode, response.Request.Method))
|
||||
{
|
||||
request.Method = HttpMethod.Get;
|
||||
request.ContentData = null;
|
||||
}
|
||||
|
||||
response = await ExecuteRequestAsync(request, cookieContainer);
|
||||
// Save to add to final response
|
||||
var responseCookies = response.Cookies;
|
||||
|
||||
// Update cookiecontainer for next request with any cookies recieved on last request
|
||||
var responseContainer = HandleRedirectCookies(request, response);
|
||||
|
||||
response = await ExecuteRequestAsync(request, responseContainer);
|
||||
|
||||
response.Cookies.Add(responseCookies);
|
||||
}
|
||||
while (response.HasHttpRedirect);
|
||||
}
|
||||
@@ -102,11 +111,14 @@ namespace NzbDrone.Common.Http
|
||||
_logger.Error("Server requested a redirect to [{0}] while in developer mode. Update the request URL to avoid this redirect.", response.Headers["Location"]);
|
||||
}
|
||||
|
||||
if (!request.SuppressHttpError && response.HasHttpError)
|
||||
if (!request.SuppressHttpError && response.HasHttpError && (request.SuppressHttpErrorStatusCodes == null || !request.SuppressHttpErrorStatusCodes.Contains(response.StatusCode)))
|
||||
{
|
||||
_logger.Warn("HTTP Error - {0}", response);
|
||||
if (request.LogHttpError)
|
||||
{
|
||||
_logger.Warn("HTTP Error - {0}", response);
|
||||
}
|
||||
|
||||
if ((int)response.StatusCode == 429)
|
||||
if (response.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
throw new TooManyRequestsException(request, response);
|
||||
}
|
||||
@@ -124,6 +136,21 @@ namespace NzbDrone.Common.Http
|
||||
return ExecuteAsync(request).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private static bool RequestRequiresForceGet(HttpStatusCode statusCode, HttpMethod requestMethod)
|
||||
{
|
||||
switch (statusCode)
|
||||
{
|
||||
case HttpStatusCode.Moved:
|
||||
case HttpStatusCode.Found:
|
||||
case HttpStatusCode.MultipleChoices:
|
||||
return requestMethod == HttpMethod.Post;
|
||||
case HttpStatusCode.SeeOther:
|
||||
return requestMethod != HttpMethod.Get && requestMethod != HttpMethod.Head;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<HttpResponse> ExecuteRequestAsync(HttpRequest request, CookieContainer cookieContainer)
|
||||
{
|
||||
foreach (var interceptor in _requestInterceptors)
|
||||
@@ -140,8 +167,6 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
|
||||
PrepareRequestCookies(request, cookieContainer);
|
||||
|
||||
var response = await _httpDispatcher.GetResponseAsync(request, cookieContainer);
|
||||
|
||||
HandleResponseCookies(response, cookieContainer);
|
||||
@@ -208,52 +233,125 @@ namespace NzbDrone.Common.Http
|
||||
}
|
||||
}
|
||||
|
||||
private void PrepareRequestCookies(HttpRequest request, CookieContainer cookieContainer)
|
||||
private CookieContainer HandleRedirectCookies(HttpRequest request, HttpResponse response)
|
||||
{
|
||||
// Don't collect persistnet cookies for intermediate/redirected urls.
|
||||
/*lock (_cookieContainerCache)
|
||||
var sourceContainer = new CookieContainer();
|
||||
var responseCookies = response.GetCookies();
|
||||
if (responseCookies.Count != 0)
|
||||
{
|
||||
var presistentContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
|
||||
var persistentCookies = presistentContainer.GetCookies((Uri)request.Url);
|
||||
var existingCookies = cookieContainer.GetCookies((Uri)request.Url);
|
||||
foreach (var pair in responseCookies)
|
||||
{
|
||||
Cookie cookie;
|
||||
if (pair.Value == null)
|
||||
{
|
||||
cookie = new Cookie(pair.Key, "", "/")
|
||||
{
|
||||
Expires = DateTime.Now.AddDays(-1)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
cookie = new Cookie(pair.Key, pair.Value, "/")
|
||||
{
|
||||
// Use Now rather than UtcNow to work around Mono cookie expiry bug.
|
||||
// See https://gist.github.com/ta264/7822b1424f72e5b4c961
|
||||
Expires = DateTime.Now.AddHours(1)
|
||||
};
|
||||
}
|
||||
|
||||
cookieContainer.Add(persistentCookies);
|
||||
cookieContainer.Add(existingCookies);
|
||||
}*/
|
||||
sourceContainer.Add((Uri)request.Url, cookie);
|
||||
}
|
||||
}
|
||||
|
||||
return sourceContainer;
|
||||
}
|
||||
|
||||
private void HandleResponseCookies(HttpResponse response, CookieContainer cookieContainer)
|
||||
private void HandleResponseCookies(HttpResponse response, CookieContainer container)
|
||||
{
|
||||
foreach (Cookie cookie in container.GetAllCookies())
|
||||
{
|
||||
cookie.Expired = true;
|
||||
}
|
||||
|
||||
var cookieHeaders = response.GetCookieHeaders();
|
||||
if (cookieHeaders.Empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddCookiesToContainer(response.Request.Url, cookieHeaders, container);
|
||||
|
||||
if (response.Request.StoreResponseCookie)
|
||||
{
|
||||
lock (_cookieContainerCache)
|
||||
{
|
||||
var persistentCookieContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
|
||||
|
||||
foreach (var cookieHeader in cookieHeaders)
|
||||
{
|
||||
try
|
||||
{
|
||||
persistentCookieContainer.SetCookies((Uri)response.Request.Url, cookieHeader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Debug(ex, "Invalid cookie in {0}", response.Request.Url);
|
||||
}
|
||||
}
|
||||
AddCookiesToContainer(response.Request.Url, cookieHeaders, persistentCookieContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddCookiesToContainer(HttpUri url, string[] cookieHeaders, CookieContainer container)
|
||||
{
|
||||
foreach (var cookieHeader in cookieHeaders)
|
||||
{
|
||||
try
|
||||
{
|
||||
container.SetCookies((Uri)url, cookieHeader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Debug(ex, "Invalid cookie in {0}", url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DownloadFileAsync(string url, string fileName)
|
||||
{
|
||||
await _httpDispatcher.DownloadFileAsync(url, fileName);
|
||||
var fileNamePart = fileName + ".part";
|
||||
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(fileName);
|
||||
if (fileInfo.Directory != null && !fileInfo.Directory.Exists)
|
||||
{
|
||||
fileInfo.Directory.Create();
|
||||
}
|
||||
|
||||
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
|
||||
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite))
|
||||
{
|
||||
var request = new HttpRequest(url);
|
||||
request.AllowAutoRedirect = true;
|
||||
request.ResponseStream = fileStream;
|
||||
var response = await GetAsync(request);
|
||||
|
||||
if (response.Headers.ContentType != null && response.Headers.ContentType.Contains("text/html"))
|
||||
{
|
||||
throw new HttpException(request, response, "Site responded with html content.");
|
||||
}
|
||||
}
|
||||
|
||||
stopWatch.Stop();
|
||||
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
File.Delete(fileName);
|
||||
}
|
||||
|
||||
File.Move(fileNamePart, fileName);
|
||||
_logger.Debug("Downloading Completed. took {0:0}s", stopWatch.Elapsed.Seconds);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(fileNamePart))
|
||||
{
|
||||
File.Delete(fileNamePart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DownloadFile(string url, string fileName)
|
||||
|
||||
@@ -4,11 +4,27 @@ using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
public static class WebHeaderCollectionExtensions
|
||||
{
|
||||
public static NameValueCollection ToNameValueCollection(this HttpHeaders headers)
|
||||
{
|
||||
var result = new NameValueCollection();
|
||||
foreach (var header in headers)
|
||||
{
|
||||
result.Add(header.Key, header.Value.ConcatToString(";"));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public class HttpHeader : NameValueCollection, IEnumerable<KeyValuePair<string, string>>, IEnumerable
|
||||
{
|
||||
public HttpHeader(NameValueCollection headers)
|
||||
@@ -16,6 +32,11 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
}
|
||||
|
||||
public HttpHeader(HttpHeaders headers)
|
||||
: base(headers.ToNameValueCollection())
|
||||
{
|
||||
}
|
||||
|
||||
public HttpHeader()
|
||||
{
|
||||
}
|
||||
@@ -107,6 +128,30 @@ namespace NzbDrone.Common.Http
|
||||
}
|
||||
}
|
||||
|
||||
public string ContentEncoding
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetSingleValue("Content-Encoding");
|
||||
}
|
||||
set
|
||||
{
|
||||
SetSingleValue("Content-Encoding", value);
|
||||
}
|
||||
}
|
||||
|
||||
public string Vary
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetSingleValue("Vary");
|
||||
}
|
||||
set
|
||||
{
|
||||
SetSingleValue("Vary", value);
|
||||
}
|
||||
}
|
||||
|
||||
public string UserAgent
|
||||
{
|
||||
get
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
@@ -12,11 +13,13 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
public HttpRequest(string url, HttpAccept httpAccept = null)
|
||||
{
|
||||
Method = HttpMethod.Get;
|
||||
Url = new HttpUri(url);
|
||||
Headers = new HttpHeader();
|
||||
Method = HttpMethod.Get;
|
||||
ConnectionKeepAlive = true;
|
||||
AllowAutoRedirect = true;
|
||||
LogHttpError = true;
|
||||
Cookies = new Dictionary<string, string>();
|
||||
|
||||
if (!RuntimeInfo.IsProduction)
|
||||
@@ -37,16 +40,20 @@ namespace NzbDrone.Common.Http
|
||||
public IWebProxy Proxy { get; set; }
|
||||
public byte[] ContentData { get; set; }
|
||||
public string ContentSummary { get; set; }
|
||||
public ICredentials Credentials { get; set; }
|
||||
public bool SuppressHttpError { get; set; }
|
||||
public IEnumerable<HttpStatusCode> SuppressHttpErrorStatusCodes { get; set; }
|
||||
public bool UseSimplifiedUserAgent { get; set; }
|
||||
public bool AllowAutoRedirect { get; set; }
|
||||
public bool ConnectionKeepAlive { get; set; }
|
||||
public bool LogResponseContent { get; set; }
|
||||
public bool LogHttpError { get; set; }
|
||||
public Dictionary<string, string> Cookies { get; private set; }
|
||||
public bool StoreRequestCookie { get; set; }
|
||||
public bool StoreResponseCookie { get; set; }
|
||||
public TimeSpan RequestTimeout { get; set; }
|
||||
public TimeSpan RateLimit { get; set; }
|
||||
public Stream ResponseStream { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
@@ -103,12 +110,5 @@ namespace NzbDrone.Common.Http
|
||||
return encoding.GetString(ContentData);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddBasicAuthentication(string username, string password)
|
||||
{
|
||||
var authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes($"{username}:{password}"));
|
||||
|
||||
Headers.Set("Authorization", "Basic " + authInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,13 @@ namespace NzbDrone.Common.Http
|
||||
public Dictionary<string, string> Segments { get; private set; }
|
||||
public HttpHeader Headers { get; private set; }
|
||||
public bool SuppressHttpError { get; set; }
|
||||
public bool LogHttpError { get; set; }
|
||||
public bool UseSimplifiedUserAgent { get; set; }
|
||||
public bool AllowAutoRedirect { get; set; }
|
||||
public bool ConnectionKeepAlive { get; set; }
|
||||
public TimeSpan RateLimit { get; set; }
|
||||
public bool LogResponseContent { get; set; }
|
||||
public NetworkCredential NetworkCredential { get; set; }
|
||||
public ICredentials NetworkCredential { get; set; }
|
||||
public Dictionary<string, string> Cookies { get; private set; }
|
||||
public bool StoreRequestCookie { get; set; }
|
||||
public bool StoreResponseCookie { get; set; }
|
||||
@@ -46,6 +47,7 @@ namespace NzbDrone.Common.Http
|
||||
Headers = new HttpHeader();
|
||||
Cookies = new Dictionary<string, string>();
|
||||
FormData = new List<HttpFormData>();
|
||||
LogHttpError = true;
|
||||
}
|
||||
|
||||
public HttpRequestBuilder(bool useHttps, string host, int port, string urlBase = null)
|
||||
@@ -106,6 +108,7 @@ namespace NzbDrone.Common.Http
|
||||
request.Method = Method;
|
||||
request.Encoding = Encoding;
|
||||
request.SuppressHttpError = SuppressHttpError;
|
||||
request.LogHttpError = LogHttpError;
|
||||
request.UseSimplifiedUserAgent = UseSimplifiedUserAgent;
|
||||
request.AllowAutoRedirect = AllowAutoRedirect;
|
||||
request.StoreRequestCookie = StoreRequestCookie;
|
||||
@@ -113,13 +116,7 @@ namespace NzbDrone.Common.Http
|
||||
request.ConnectionKeepAlive = ConnectionKeepAlive;
|
||||
request.RateLimit = RateLimit;
|
||||
request.LogResponseContent = LogResponseContent;
|
||||
|
||||
if (NetworkCredential != null)
|
||||
{
|
||||
var authInfo = NetworkCredential.UserName + ":" + NetworkCredential.Password;
|
||||
authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo));
|
||||
request.Headers.Set("Authorization", "Basic " + authInfo);
|
||||
}
|
||||
request.Credentials = NetworkCredential;
|
||||
|
||||
foreach (var header in Headers)
|
||||
{
|
||||
@@ -212,7 +209,7 @@ namespace NzbDrone.Common.Http
|
||||
}
|
||||
else
|
||||
{
|
||||
summary.AppendFormat("\r\n{0}={1}", formData.Name, Encoding.UTF8.GetString(formData.ContentData));
|
||||
summary.AppendFormat("\r\n{0}={1}", formData.Name, Encoding.GetString(formData.ContentData));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,9 +229,9 @@ namespace NzbDrone.Common.Http
|
||||
}
|
||||
else
|
||||
{
|
||||
var parameters = FormData.Select(v => string.Format("{0}={1}", v.Name, Uri.EscapeDataString(Encoding.UTF8.GetString(v.ContentData))));
|
||||
var parameters = FormData.Select(v => string.Format("{0}={1}", v.Name, Uri.EscapeDataString(Encoding.GetString(v.ContentData))));
|
||||
var urlencoded = string.Join("&", parameters);
|
||||
var body = Encoding.UTF8.GetBytes(urlencoded);
|
||||
var body = Encoding.GetBytes(urlencoded);
|
||||
|
||||
request.Headers.ContentType = "application/x-www-form-urlencoded";
|
||||
request.SetContent(body);
|
||||
@@ -406,7 +403,7 @@ namespace NzbDrone.Common.Http
|
||||
FormData.Add(new HttpFormData
|
||||
{
|
||||
Name = key,
|
||||
ContentData = Encoding.UTF8.GetBytes(Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture))
|
||||
ContentData = Encoding.GetBytes(Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture))
|
||||
});
|
||||
|
||||
return this;
|
||||
|
||||
@@ -64,10 +64,11 @@ namespace NzbDrone.Common.Http
|
||||
public bool HasHttpError => (int)StatusCode >= 400;
|
||||
|
||||
public bool HasHttpRedirect => StatusCode == HttpStatusCode.Moved ||
|
||||
StatusCode == HttpStatusCode.MovedPermanently ||
|
||||
StatusCode == HttpStatusCode.RedirectMethod ||
|
||||
StatusCode == HttpStatusCode.TemporaryRedirect ||
|
||||
StatusCode == HttpStatusCode.Found ||
|
||||
StatusCode == HttpStatusCode.SeeOther ||
|
||||
StatusCode == HttpStatusCode.TemporaryRedirect ||
|
||||
StatusCode == HttpStatusCode.MultipleChoices ||
|
||||
StatusCode == HttpStatusCode.PermanentRedirect ||
|
||||
Headers.ContainsKey("Refresh");
|
||||
|
||||
public string RedirectUrl
|
||||
@@ -117,7 +118,7 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var result = string.Format("Res: [{0}] {1}: {2}.{3}", Request.Method, Request.Url, (int)StatusCode, StatusCode);
|
||||
var result = string.Format("Res: [{0}] {1}: {2}.{3} ({4} bytes)", Request.Method, Request.Url, (int)StatusCode, StatusCode, ResponseData?.Length ?? 0);
|
||||
|
||||
if (HasHttpError && Headers.ContentType.IsNotNullOrWhiteSpace() && !Headers.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
|
||||
103
src/NzbDrone.Common/Http/XmlRpcRequestBuilder.cs
Normal file
103
src/NzbDrone.Common/Http/XmlRpcRequestBuilder.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Xml.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
public class XmlRpcRequestBuilder : HttpRequestBuilder
|
||||
{
|
||||
public static string XmlRpcContentType = "text/xml";
|
||||
|
||||
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(XmlRpcRequestBuilder));
|
||||
|
||||
public string XmlMethod { get; private set; }
|
||||
public List<object> XmlParameters { get; private set; }
|
||||
|
||||
public XmlRpcRequestBuilder(string baseUrl)
|
||||
: base(baseUrl)
|
||||
{
|
||||
Method = HttpMethod.Post;
|
||||
XmlParameters = new List<object>();
|
||||
}
|
||||
|
||||
public XmlRpcRequestBuilder(bool useHttps, string host, int port, string urlBase = null)
|
||||
: this(BuildBaseUrl(useHttps, host, port, urlBase))
|
||||
{
|
||||
}
|
||||
|
||||
public override HttpRequestBuilder Clone()
|
||||
{
|
||||
var clone = base.Clone() as XmlRpcRequestBuilder;
|
||||
clone.XmlParameters = new List<object>(XmlParameters);
|
||||
return clone;
|
||||
}
|
||||
|
||||
public XmlRpcRequestBuilder Call(string method, params object[] parameters)
|
||||
{
|
||||
var clone = Clone() as XmlRpcRequestBuilder;
|
||||
clone.XmlMethod = method;
|
||||
clone.XmlParameters = parameters.ToList();
|
||||
return clone;
|
||||
}
|
||||
|
||||
protected override void Apply(HttpRequest request)
|
||||
{
|
||||
base.Apply(request);
|
||||
|
||||
request.Headers.ContentType = XmlRpcContentType;
|
||||
|
||||
var methodCallElements = new List<XElement> { new XElement("methodName", XmlMethod) };
|
||||
|
||||
if (XmlParameters.Any())
|
||||
{
|
||||
var argElements = XmlParameters.Select(x => new XElement("param", ConvertParameter(x))).ToList();
|
||||
var paramsElement = new XElement("params", argElements);
|
||||
methodCallElements.Add(paramsElement);
|
||||
}
|
||||
|
||||
var message = new XDocument(
|
||||
new XDeclaration("1.0", "utf-8", "yes"),
|
||||
new XElement("methodCall", methodCallElements));
|
||||
|
||||
var body = message.ToString();
|
||||
|
||||
Logger.Debug($"Executing remote method: {XmlMethod}");
|
||||
|
||||
Logger.Trace($"methodCall {XmlMethod} body:\n{body}");
|
||||
|
||||
request.SetContent(body);
|
||||
}
|
||||
|
||||
private static XElement ConvertParameter(object value)
|
||||
{
|
||||
XElement data;
|
||||
|
||||
if (value is string s)
|
||||
{
|
||||
data = new XElement("string", s);
|
||||
}
|
||||
else if (value is List<string> l)
|
||||
{
|
||||
data = new XElement("array", new XElement("data", l.Select(x => new XElement("value", new XElement("string", x)))));
|
||||
}
|
||||
else if (value is int i)
|
||||
{
|
||||
data = new XElement("int", i);
|
||||
}
|
||||
else if (value is byte[] bytes)
|
||||
{
|
||||
data = new XElement("base64", Convert.ToBase64String(bytes));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Unhandled argument type {value.GetType().Name}");
|
||||
}
|
||||
|
||||
return new XElement("value", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
|
||||
<PackageReference Include="Sentry" Version="3.18.0" />
|
||||
<PackageReference Include="Sentry" Version="3.19.0" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
|
||||
51562
src/NzbDrone.Core.Test/Files/Indexers/GazelleGames/recentfeed.json
Normal file
51562
src/NzbDrone.Core.Test/Files/Indexers/GazelleGames/recentfeed.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ using NzbDrone.Common.TPL;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Http;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Security;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.Framework
|
||||
@@ -25,7 +26,8 @@ namespace NzbDrone.Core.Test.Framework
|
||||
|
||||
Mocker.SetConstant<IHttpProxySettingsProvider>(new HttpProxySettingsProvider(Mocker.Resolve<ConfigService>()));
|
||||
Mocker.SetConstant<ICreateManagedWebProxy>(new ManagedWebProxyFactory(Mocker.Resolve<CacheManager>()));
|
||||
Mocker.SetConstant<IHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>(), Mocker.Resolve<UserAgentBuilder>(), Mocker.Resolve<IPlatformInfo>(), TestLogger));
|
||||
Mocker.SetConstant<ICertificateValidationService>(new X509CertificateValidationService(Mocker.Resolve<ConfigService>(), TestLogger));
|
||||
Mocker.SetConstant<IHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>(), Mocker.Resolve<ICertificateValidationService>(), Mocker.Resolve<UserAgentBuilder>(), Mocker.Resolve<CacheManager>()));
|
||||
Mocker.SetConstant<IHttpClient>(new HttpClient(Array.Empty<IHttpRequestInterceptor>(), Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), TestLogger));
|
||||
Mocker.SetConstant<IProwlarrCloudRequestBuilder>(new ProwlarrCloudRequestBuilder());
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ using NzbDrone.Core.Test.Framework;
|
||||
namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class AvistazFixture : CoreTest<AvistaZ>
|
||||
public class AvistazFixture : CoreTest<Avistaz>
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
@@ -60,6 +60,10 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
|
||||
torrentInfo.ImdbId.Should().Be(15569106);
|
||||
torrentInfo.TmdbId.Should().Be(135144);
|
||||
torrentInfo.TvdbId.Should().Be(410548);
|
||||
torrentInfo.Languages.Should().HaveCount(1);
|
||||
torrentInfo.Languages.First().Should().Be("Japanese");
|
||||
torrentInfo.Subs.Should().HaveCount(27);
|
||||
torrentInfo.Subs.First().Should().Be("Arabic");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ using NzbDrone.Core.Test.Framework;
|
||||
namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class PrivateHDFixture : CoreTest<PrivateHD>
|
||||
public class PrivateHDFixture : CoreTest<Avistaz>
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.Definitions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class GazelleGamesFixture : CoreTest<GazelleGames>
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Subject.Definition = new IndexerDefinition()
|
||||
{
|
||||
Name = "GazelleGames",
|
||||
Settings = new GazelleGamesSettings() { Apikey = "somekey" }
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task should_parse_recent_feed_from_GazelleGames()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/GazelleGames/recentfeed.json");
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
|
||||
|
||||
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new int[] { 2000 } })).Releases;
|
||||
|
||||
releases.Should().HaveCount(1464);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
|
||||
var torrentInfo = releases.First() as TorrentInfo;
|
||||
|
||||
torrentInfo.Title.Should().Be("Microsoft_Flight_Simulator-HOODLUM");
|
||||
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
|
||||
torrentInfo.DownloadUrl.Should().Be("https://gazellegames.net/torrents.php?action=download&id=303216&authkey=prowlarr&torrent_pass=");
|
||||
torrentInfo.InfoUrl.Should().Be("https://gazellegames.net/torrents.php?id=84781&torrentid=303216");
|
||||
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
|
||||
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-07-25 6:39:11").ToUniversalTime());
|
||||
torrentInfo.Size.Should().Be(80077617780);
|
||||
torrentInfo.InfoHash.Should().Be(null);
|
||||
torrentInfo.MagnetUrl.Should().Be(null);
|
||||
torrentInfo.Peers.Should().Be(383);
|
||||
torrentInfo.Seeders.Should().Be(383);
|
||||
torrentInfo.ImdbId.Should().Be(0);
|
||||
torrentInfo.TmdbId.Should().Be(0);
|
||||
torrentInfo.TvdbId.Should().Be(0);
|
||||
torrentInfo.Languages.Should().HaveCount(0);
|
||||
torrentInfo.Subs.Should().HaveCount(0);
|
||||
torrentInfo.DownloadVolumeFactor.Should().Be(1);
|
||||
torrentInfo.UploadVolumeFactor.Should().Be(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests
|
||||
@@ -20,8 +21,8 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||
public int _supportedPageSize;
|
||||
public override int PageSize => _supportedPageSize;
|
||||
|
||||
public TestIndexer(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, IValidateNzbs nzbValidationService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, nzbValidationService, logger)
|
||||
public TestIndexer(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, IValidateNzbs nzbValidationService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, nzbValidationService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Languages
|
||||
{
|
||||
[TestFixture]
|
||||
public class LanguageFixture : CoreTest
|
||||
{
|
||||
public static object[] FromIntCases =
|
||||
{
|
||||
new object[] { 1, Language.English },
|
||||
new object[] { 2, Language.French },
|
||||
new object[] { 3, Language.Spanish },
|
||||
new object[] { 4, Language.German },
|
||||
new object[] { 5, Language.Italian },
|
||||
new object[] { 6, Language.Danish },
|
||||
new object[] { 7, Language.Dutch },
|
||||
new object[] { 8, Language.Japanese },
|
||||
new object[] { 9, Language.Icelandic },
|
||||
new object[] { 10, Language.Chinese },
|
||||
new object[] { 11, Language.Russian },
|
||||
new object[] { 12, Language.Polish },
|
||||
new object[] { 13, Language.Vietnamese },
|
||||
new object[] { 14, Language.Swedish },
|
||||
new object[] { 15, Language.Norwegian },
|
||||
new object[] { 16, Language.Finnish },
|
||||
new object[] { 17, Language.Turkish },
|
||||
new object[] { 18, Language.Portuguese },
|
||||
new object[] { 19, Language.Flemish },
|
||||
new object[] { 20, Language.Greek },
|
||||
new object[] { 21, Language.Korean },
|
||||
new object[] { 22, Language.Hungarian },
|
||||
new object[] { 23, Language.Hebrew },
|
||||
new object[] { 24, Language.Lithuanian },
|
||||
new object[] { 25, Language.Czech }
|
||||
};
|
||||
|
||||
public static object[] ToIntCases =
|
||||
{
|
||||
new object[] { Language.English, 1 },
|
||||
new object[] { Language.French, 2 },
|
||||
new object[] { Language.Spanish, 3 },
|
||||
new object[] { Language.German, 4 },
|
||||
new object[] { Language.Italian, 5 },
|
||||
new object[] { Language.Danish, 6 },
|
||||
new object[] { Language.Dutch, 7 },
|
||||
new object[] { Language.Japanese, 8 },
|
||||
new object[] { Language.Icelandic, 9 },
|
||||
new object[] { Language.Chinese, 10 },
|
||||
new object[] { Language.Russian, 11 },
|
||||
new object[] { Language.Polish, 12 },
|
||||
new object[] { Language.Vietnamese, 13 },
|
||||
new object[] { Language.Swedish, 14 },
|
||||
new object[] { Language.Norwegian, 15 },
|
||||
new object[] { Language.Finnish, 16 },
|
||||
new object[] { Language.Turkish, 17 },
|
||||
new object[] { Language.Portuguese, 18 },
|
||||
new object[] { Language.Flemish, 19 },
|
||||
new object[] { Language.Greek, 20 },
|
||||
new object[] { Language.Korean, 21 },
|
||||
new object[] { Language.Hungarian, 22 },
|
||||
new object[] { Language.Hebrew, 23 },
|
||||
new object[] { Language.Lithuanian, 24 },
|
||||
new object[] { Language.Czech, 25 }
|
||||
};
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("FromIntCases")]
|
||||
public void should_be_able_to_convert_int_to_languageTypes(int source, Language expected)
|
||||
{
|
||||
var language = (Language)source;
|
||||
language.Should().Be(expected);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("ToIntCases")]
|
||||
public void should_be_able_to_convert_languageTypes_to_int(Language source, int expected)
|
||||
{
|
||||
var i = (int)source;
|
||||
i.Should().Be(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
@@ -16,7 +15,7 @@ namespace NzbDrone.Core.Test.Localization
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Mocker.GetMock<IConfigService>().Setup(m => m.UILanguage).Returns((int)Language.English);
|
||||
Mocker.GetMock<IConfigService>().Setup(m => m.UILanguage).Returns("en");
|
||||
|
||||
Mocker.GetMock<IAppFolderInfo>().Setup(m => m.StartUpFolder).Returns(TestContext.CurrentContext.TestDirectory);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using NLog;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Configuration.Events;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Security;
|
||||
|
||||
@@ -139,9 +138,9 @@ namespace NzbDrone.Core.Configuration
|
||||
set { SetValue("EnableColorImpairedMode", value); }
|
||||
}
|
||||
|
||||
public int UILanguage
|
||||
public string UILanguage
|
||||
{
|
||||
get { return GetValueInt("UILanguage", (int)Language.English); }
|
||||
get { return GetValue("UILanguage", "en"); }
|
||||
|
||||
set { SetValue("UILanguage", value); }
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Configuration
|
||||
string TimeFormat { get; set; }
|
||||
bool ShowRelativeDates { get; set; }
|
||||
bool EnableColorImpairedMode { get; set; }
|
||||
int UILanguage { get; set; }
|
||||
string UILanguage { get; set; }
|
||||
|
||||
//Internal
|
||||
string PlexClientIdentifier { get; }
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Languages;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class DapperLanguageIntConverter : SqlMapper.TypeHandler<Language>
|
||||
{
|
||||
public override void SetValue(IDbDataParameter parameter, Language value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new InvalidOperationException("Attempted to save a language that isn't really a language");
|
||||
}
|
||||
else
|
||||
{
|
||||
parameter.Value = (int)value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Language Parse(object value)
|
||||
{
|
||||
if (value == null || value is DBNull)
|
||||
{
|
||||
return Language.Unknown;
|
||||
}
|
||||
|
||||
return (Language)Convert.ToInt32(value);
|
||||
}
|
||||
}
|
||||
|
||||
public class LanguageIntConverter : JsonConverter<Language>
|
||||
{
|
||||
public override Language Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var item = reader.GetInt32();
|
||||
return (Language)item;
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, Language value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteNumberValue((int)value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(021)]
|
||||
public class localization_setting_to_string : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Execute.WithConnection(FixLocalizationConfig);
|
||||
}
|
||||
|
||||
private void FixLocalizationConfig(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
string uiLanguage;
|
||||
string uiCulture;
|
||||
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.Transaction = tran;
|
||||
cmd.CommandText = "SELECT \"Value\" FROM \"Config\" WHERE \"Key\" = 'uilanguage'";
|
||||
|
||||
uiLanguage = (string)cmd.ExecuteScalar();
|
||||
}
|
||||
|
||||
if (uiLanguage != null && int.TryParse(uiLanguage, out var uiLanguageInt))
|
||||
{
|
||||
uiCulture = _uiMapping.GetValueOrDefault(uiLanguageInt) ?? "en";
|
||||
|
||||
using (var insertCmd = conn.CreateCommand())
|
||||
{
|
||||
insertCmd.Transaction = tran;
|
||||
insertCmd.CommandText = string.Format("UPDATE \"Config\" SET \"Value\" = '{0}' WHERE \"Key\" = 'uilanguage'", uiCulture);
|
||||
insertCmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<int, string> _uiMapping = new Dictionary<int, string>()
|
||||
{
|
||||
{ 1, "en" },
|
||||
{ 2, "fr" },
|
||||
{ 3, "es" },
|
||||
{ 4, "de" },
|
||||
{ 5, "it" },
|
||||
{ 6, "da" },
|
||||
{ 7, "nl" },
|
||||
{ 8, "ja" },
|
||||
{ 9, "is" },
|
||||
{ 10, "zh_CN" },
|
||||
{ 11, "ru" },
|
||||
{ 12, "pl" },
|
||||
{ 13, "vi" },
|
||||
{ 14, "sv" },
|
||||
{ 15, "nb_NO" },
|
||||
{ 16, "fi" },
|
||||
{ 17, "tr" },
|
||||
{ 18, "pt" },
|
||||
{ 19, "en" },
|
||||
{ 20, "el" },
|
||||
{ 21, "ko" },
|
||||
{ 22, "hu" },
|
||||
{ 23, "he" },
|
||||
{ 24, "lt" },
|
||||
{ 25, "cs" },
|
||||
{ 26, "hi" },
|
||||
{ 27, "ro" },
|
||||
{ 28, "th" },
|
||||
{ 29, "bg" },
|
||||
{ 30, "pt_BR" },
|
||||
{ 31, "ar" },
|
||||
{ 32, "uk" },
|
||||
{ 33, "fa" },
|
||||
{ 34, "be" },
|
||||
{ 35, "zh_TW" },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Text.Json;
|
||||
using Dapper;
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(22)]
|
||||
public class indexer_definition : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("Indexers")
|
||||
.AddColumn("DefinitionFile").AsString().Nullable();
|
||||
|
||||
Execute.WithConnection(MigrateCardigannDefinitions);
|
||||
}
|
||||
|
||||
private void MigrateCardigannDefinitions(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
var indexers = new List<Indexer017>();
|
||||
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.Transaction = tran;
|
||||
cmd.CommandText = "SELECT \"Id\", \"Settings\", \"Implementation\", \"ConfigContract\" FROM \"Indexers\"";
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
var id = reader.GetInt32(0);
|
||||
var settings = reader.GetString(1);
|
||||
var implementation = reader.GetString(2);
|
||||
var configContract = reader.GetString(3);
|
||||
var defFile = implementation.ToLowerInvariant();
|
||||
|
||||
if (implementation == "Cardigann")
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(settings))
|
||||
{
|
||||
var jsonObject = STJson.Deserialize<JsonElement>(settings);
|
||||
|
||||
if (jsonObject.TryGetProperty("definitionFile", out JsonElement jsonDef))
|
||||
{
|
||||
defFile = jsonDef.GetString();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (configContract == "AvistazSettings")
|
||||
{
|
||||
implementation = "Avistaz";
|
||||
}
|
||||
else if (configContract == "Unit3dSettings")
|
||||
{
|
||||
implementation = "Unit3d";
|
||||
}
|
||||
else if (configContract == "Newznab")
|
||||
{
|
||||
defFile = "";
|
||||
}
|
||||
|
||||
indexers.Add(new Indexer017
|
||||
{
|
||||
DefinitionFile = defFile,
|
||||
Implementation = implementation,
|
||||
Id = id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var updateSql = "UPDATE \"Indexers\" SET \"DefinitionFile\" = @DefinitionFile, \"Implementation\" = @Implementation WHERE \"Id\" = @Id";
|
||||
conn.Execute(updateSql, indexers, transaction: tran);
|
||||
}
|
||||
|
||||
public class Indexer017
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string DefinitionFile { get; set; }
|
||||
public string Implementation { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Instrumentation;
|
||||
using NzbDrone.Core.Jobs;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Notifications;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -113,8 +112,6 @@ namespace NzbDrone.Core.Datastore
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<int>>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<KeyValuePair<string, int>>>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<KeyValuePair<string, int>>());
|
||||
SqlMapper.AddTypeHandler(new DapperLanguageIntConverter());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<Language>>(new LanguageIntConverter()));
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<string>>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<ReleaseInfo>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<HashSet<int>>());
|
||||
|
||||
@@ -1,111 +1,161 @@
|
||||
using CookComputing.XmlRpc;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using NzbDrone.Core.Download.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Aria2
|
||||
{
|
||||
public class Aria2Version
|
||||
public class Aria2Fault
|
||||
{
|
||||
[XmlRpcMember("version")]
|
||||
public string Version;
|
||||
public Aria2Fault(XElement element)
|
||||
{
|
||||
foreach (var e in element.XPathSelectElements("./value/struct/member"))
|
||||
{
|
||||
var name = e.ElementAsString("name");
|
||||
if (name == "faultCode")
|
||||
{
|
||||
FaultCode = e.Element("value").ElementAsInt("int");
|
||||
}
|
||||
else if (name == "faultString")
|
||||
{
|
||||
FaultString = e.Element("value").GetStringValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[XmlRpcMember("enabledFeatures")]
|
||||
public string[] EnabledFeatures;
|
||||
public int FaultCode { get; set; }
|
||||
public string FaultString { get; set; }
|
||||
}
|
||||
|
||||
public class Aria2Uri
|
||||
public class Aria2Version
|
||||
{
|
||||
[XmlRpcMember("status")]
|
||||
public string Status;
|
||||
public Aria2Version(XElement element)
|
||||
{
|
||||
foreach (var e in element.XPathSelectElements("./struct/member"))
|
||||
{
|
||||
if (e.ElementAsString("name") == "version")
|
||||
{
|
||||
Version = e.Element("value").GetStringValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[XmlRpcMember("uri")]
|
||||
public string Uri;
|
||||
public string Version { get; set; }
|
||||
}
|
||||
|
||||
public class Aria2File
|
||||
{
|
||||
[XmlRpcMember("index")]
|
||||
public string Index;
|
||||
public Aria2File(XElement element)
|
||||
{
|
||||
foreach (var e in element.XPathSelectElements("./struct/member"))
|
||||
{
|
||||
var name = e.ElementAsString("name");
|
||||
|
||||
[XmlRpcMember("length")]
|
||||
public string Length;
|
||||
if (name == "path")
|
||||
{
|
||||
Path = e.Element("value").GetStringValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[XmlRpcMember("completedLength")]
|
||||
public string CompletedLength;
|
||||
public string Path { get; set; }
|
||||
}
|
||||
|
||||
[XmlRpcMember("path")]
|
||||
public string Path;
|
||||
public class Aria2Dict
|
||||
{
|
||||
public Aria2Dict(XElement element)
|
||||
{
|
||||
Dict = new Dictionary<string, string>();
|
||||
|
||||
[XmlRpcMember("selected")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public string Selected;
|
||||
foreach (var e in element.XPathSelectElements("./struct/member"))
|
||||
{
|
||||
Dict.Add(e.ElementAsString("name"), e.Element("value").GetStringValue());
|
||||
}
|
||||
}
|
||||
|
||||
[XmlRpcMember("uris")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public Aria2Uri[] Uris;
|
||||
public Dictionary<string, string> Dict { get; set; }
|
||||
}
|
||||
|
||||
public class Aria2Bittorrent
|
||||
{
|
||||
public Aria2Bittorrent(XElement element)
|
||||
{
|
||||
foreach (var e in element.Descendants("member"))
|
||||
{
|
||||
if (e.ElementAsString("name") == "name")
|
||||
{
|
||||
Name = e.Element("value").GetStringValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Name;
|
||||
}
|
||||
|
||||
public class Aria2Status
|
||||
{
|
||||
[XmlRpcMember("bittorrent")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public XmlRpcStruct Bittorrent;
|
||||
public Aria2Status(XElement element)
|
||||
{
|
||||
foreach (var e in element.XPathSelectElements("./struct/member"))
|
||||
{
|
||||
var name = e.ElementAsString("name");
|
||||
|
||||
[XmlRpcMember("bitfield")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public string Bitfield;
|
||||
if (name == "bittorrent")
|
||||
{
|
||||
Bittorrent = new Aria2Bittorrent(e.Element("value"));
|
||||
}
|
||||
else if (name == "infoHash")
|
||||
{
|
||||
InfoHash = e.Element("value").GetStringValue();
|
||||
}
|
||||
else if (name == "completedLength")
|
||||
{
|
||||
CompletedLength = e.Element("value").GetStringValue();
|
||||
}
|
||||
else if (name == "downloadSpeed")
|
||||
{
|
||||
DownloadSpeed = e.Element("value").GetStringValue();
|
||||
}
|
||||
else if (name == "files")
|
||||
{
|
||||
Files = e.XPathSelectElement("./value/array/data")
|
||||
.Elements()
|
||||
.Select(x => new Aria2File(x))
|
||||
.ToArray();
|
||||
}
|
||||
else if (name == "gid")
|
||||
{
|
||||
Gid = e.Element("value").GetStringValue();
|
||||
}
|
||||
else if (name == "status")
|
||||
{
|
||||
Status = e.Element("value").GetStringValue();
|
||||
}
|
||||
else if (name == "totalLength")
|
||||
{
|
||||
TotalLength = e.Element("value").GetStringValue();
|
||||
}
|
||||
else if (name == "uploadLength")
|
||||
{
|
||||
UploadLength = e.Element("value").GetStringValue();
|
||||
}
|
||||
else if (name == "errorMessage")
|
||||
{
|
||||
ErrorMessage = e.Element("value").GetStringValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[XmlRpcMember("infoHash")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public string InfoHash;
|
||||
|
||||
[XmlRpcMember("completedLength")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public string CompletedLength;
|
||||
|
||||
[XmlRpcMember("connections")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public string Connections;
|
||||
|
||||
[XmlRpcMember("dir")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public string Dir;
|
||||
|
||||
[XmlRpcMember("downloadSpeed")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public string DownloadSpeed;
|
||||
|
||||
[XmlRpcMember("files")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public Aria2File[] Files;
|
||||
|
||||
[XmlRpcMember("gid")]
|
||||
public string Gid;
|
||||
|
||||
[XmlRpcMember("numPieces")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public string NumPieces;
|
||||
|
||||
[XmlRpcMember("pieceLength")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public string PieceLength;
|
||||
|
||||
[XmlRpcMember("status")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public string Status;
|
||||
|
||||
[XmlRpcMember("totalLength")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public string TotalLength;
|
||||
|
||||
[XmlRpcMember("uploadLength")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public string UploadLength;
|
||||
|
||||
[XmlRpcMember("uploadSpeed")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public string UploadSpeed;
|
||||
|
||||
[XmlRpcMember("errorMessage")]
|
||||
[XmlRpcMissingMapping(MappingAction.Ignore)]
|
||||
public string ErrorMessage;
|
||||
public Aria2Bittorrent Bittorrent { get; set; }
|
||||
public string InfoHash { get; set; }
|
||||
public string CompletedLength { get; set; }
|
||||
public string DownloadSpeed { get; set; }
|
||||
public Aria2File[] Files { get; set; }
|
||||
public string Gid { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string TotalLength { get; set; }
|
||||
public string UploadLength { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using CookComputing.XmlRpc;
|
||||
using NLog;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Download.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Aria2
|
||||
{
|
||||
@@ -12,46 +12,103 @@ namespace NzbDrone.Core.Download.Clients.Aria2
|
||||
string GetVersion(Aria2Settings settings);
|
||||
string AddUri(Aria2Settings settings, string magnet);
|
||||
string AddTorrent(Aria2Settings settings, byte[] torrent);
|
||||
Dictionary<string, string> GetGlobals(Aria2Settings settings);
|
||||
List<Aria2Status> GetTorrents(Aria2Settings settings);
|
||||
Aria2Status GetFromGID(Aria2Settings settings, string gid);
|
||||
}
|
||||
|
||||
public interface IAria2 : IXmlRpcProxy
|
||||
{
|
||||
[XmlRpcMethod("aria2.getVersion")]
|
||||
Aria2Version GetVersion(string token);
|
||||
|
||||
[XmlRpcMethod("aria2.addUri")]
|
||||
string AddUri(string token, string[] uri);
|
||||
|
||||
[XmlRpcMethod("aria2.addTorrent")]
|
||||
string AddTorrent(string token, byte[] torrent);
|
||||
|
||||
[XmlRpcMethod("aria2.forceRemove")]
|
||||
string Remove(string token, string gid);
|
||||
|
||||
[XmlRpcMethod("aria2.tellStatus")]
|
||||
Aria2Status GetFromGid(string token, string gid);
|
||||
|
||||
[XmlRpcMethod("aria2.getGlobalOption")]
|
||||
XmlRpcStruct GetGlobalOption(string token);
|
||||
|
||||
[XmlRpcMethod("aria2.tellActive")]
|
||||
Aria2Status[] GetActives(string token);
|
||||
|
||||
[XmlRpcMethod("aria2.tellWaiting")]
|
||||
Aria2Status[] GetWaitings(string token, int offset, int num);
|
||||
|
||||
[XmlRpcMethod("aria2.tellStopped")]
|
||||
Aria2Status[] GetStoppeds(string token, int offset, int num);
|
||||
}
|
||||
|
||||
public class Aria2Proxy : IAria2Proxy
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly IHttpClient _httpClient;
|
||||
|
||||
public Aria2Proxy(Logger logger)
|
||||
public Aria2Proxy(IHttpClient httpClient)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public string GetVersion(Aria2Settings settings)
|
||||
{
|
||||
var response = ExecuteRequest(settings, "aria2.getVersion", GetToken(settings));
|
||||
|
||||
var element = response.XPathSelectElement("./methodResponse/params/param/value");
|
||||
|
||||
var version = new Aria2Version(element);
|
||||
|
||||
return version.Version;
|
||||
}
|
||||
|
||||
public Aria2Status GetFromGID(Aria2Settings settings, string gid)
|
||||
{
|
||||
var response = ExecuteRequest(settings, "aria2.tellStatus", GetToken(settings), gid);
|
||||
|
||||
var element = response.XPathSelectElement("./methodResponse/params/param/value");
|
||||
|
||||
return new Aria2Status(element);
|
||||
}
|
||||
|
||||
private List<Aria2Status> GetTorrentsMethod(Aria2Settings settings, string method, params object[] args)
|
||||
{
|
||||
var allArgs = new List<object> { GetToken(settings) };
|
||||
if (args.Any())
|
||||
{
|
||||
allArgs.AddRange(args);
|
||||
}
|
||||
|
||||
var response = ExecuteRequest(settings, method, allArgs.ToArray());
|
||||
|
||||
var element = response.XPathSelectElement("./methodResponse/params/param/value/array/data");
|
||||
|
||||
var torrents = element?.Elements()
|
||||
.Select(x => new Aria2Status(x))
|
||||
.ToList()
|
||||
?? new List<Aria2Status>();
|
||||
return torrents;
|
||||
}
|
||||
|
||||
public List<Aria2Status> GetTorrents(Aria2Settings settings)
|
||||
{
|
||||
var active = GetTorrentsMethod(settings, "aria2.tellActive");
|
||||
|
||||
var waiting = GetTorrentsMethod(settings, "aria2.tellWaiting", 0, 10 * 1024);
|
||||
|
||||
var stopped = GetTorrentsMethod(settings, "aria2.tellStopped", 0, 10 * 1024);
|
||||
|
||||
var items = new List<Aria2Status>();
|
||||
|
||||
items.AddRange(active);
|
||||
items.AddRange(waiting);
|
||||
items.AddRange(stopped);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public Dictionary<string, string> GetGlobals(Aria2Settings settings)
|
||||
{
|
||||
var response = ExecuteRequest(settings, "aria2.getGlobalOption", GetToken(settings));
|
||||
|
||||
var element = response.XPathSelectElement("./methodResponse/params/param/value");
|
||||
|
||||
var result = new Aria2Dict(element);
|
||||
|
||||
return result.Dict;
|
||||
}
|
||||
|
||||
public string AddUri(Aria2Settings settings, string magnet)
|
||||
{
|
||||
var response = ExecuteRequest(settings, "aria2.addUri", GetToken(settings), new List<string> { magnet });
|
||||
|
||||
var gid = response.GetStringResponse();
|
||||
|
||||
return gid;
|
||||
}
|
||||
|
||||
public string AddTorrent(Aria2Settings settings, byte[] torrent)
|
||||
{
|
||||
var response = ExecuteRequest(settings, "aria2.addTorrent", GetToken(settings), torrent);
|
||||
|
||||
var gid = response.GetStringResponse();
|
||||
|
||||
return gid;
|
||||
}
|
||||
|
||||
private string GetToken(Aria2Settings settings)
|
||||
@@ -59,86 +116,29 @@ namespace NzbDrone.Core.Download.Clients.Aria2
|
||||
return $"token:{settings?.SecretToken}";
|
||||
}
|
||||
|
||||
private string GetURL(Aria2Settings settings)
|
||||
private XDocument ExecuteRequest(Aria2Settings settings, string methodName, params object[] args)
|
||||
{
|
||||
return $"http{(settings.UseSsl ? "s" : "")}://{settings.Host}:{settings.Port}{settings.RpcPath}";
|
||||
}
|
||||
|
||||
public string GetVersion(Aria2Settings settings)
|
||||
{
|
||||
_logger.Debug("> aria2.getVersion");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
var version = ExecuteRequest(() => client.GetVersion(GetToken(settings)));
|
||||
|
||||
_logger.Debug("< aria2.getVersion");
|
||||
|
||||
return version.Version;
|
||||
}
|
||||
|
||||
public Aria2Status GetFromGID(Aria2Settings settings, string gid)
|
||||
{
|
||||
_logger.Debug("> aria2.tellStatus");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
var found = ExecuteRequest(() => client.GetFromGid(GetToken(settings), gid));
|
||||
|
||||
_logger.Debug("< aria2.tellStatus");
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
public string AddUri(Aria2Settings settings, string magnet)
|
||||
{
|
||||
_logger.Debug("> aria2.addUri");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
var gid = ExecuteRequest(() => client.AddUri(GetToken(settings), new[] { magnet }));
|
||||
|
||||
_logger.Debug("< aria2.addUri");
|
||||
|
||||
return gid;
|
||||
}
|
||||
|
||||
public string AddTorrent(Aria2Settings settings, byte[] torrent)
|
||||
{
|
||||
_logger.Debug("> aria2.addTorrent");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
var gid = ExecuteRequest(() => client.AddTorrent(GetToken(settings), torrent));
|
||||
|
||||
_logger.Debug("< aria2.addTorrent");
|
||||
|
||||
return gid;
|
||||
}
|
||||
|
||||
private IAria2 BuildClient(Aria2Settings settings)
|
||||
{
|
||||
var client = XmlRpcProxyGen.Create<IAria2>();
|
||||
client.Url = GetURL(settings);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private T ExecuteRequest<T>(Func<T> task)
|
||||
{
|
||||
try
|
||||
var requestBuilder = new XmlRpcRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.RpcPath)
|
||||
{
|
||||
return task();
|
||||
}
|
||||
catch (XmlRpcServerException ex)
|
||||
{
|
||||
throw new DownloadClientException("Unable to connect to aria2, please check your settings", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
if (ex.Status == WebExceptionStatus.TrustFailure)
|
||||
{
|
||||
throw new DownloadClientUnavailableException("Unable to connect to aria2, certificate validation failed.", ex);
|
||||
}
|
||||
LogResponseContent = true,
|
||||
};
|
||||
|
||||
throw new DownloadClientUnavailableException("Unable to connect to aria2, please check your settings", ex);
|
||||
var request = requestBuilder.Call(methodName, args).Build();
|
||||
|
||||
var response = _httpClient.Execute(request);
|
||||
|
||||
var doc = XDocument.Parse(response.Content);
|
||||
|
||||
var faultElement = doc.XPathSelectElement("./methodResponse/fault");
|
||||
|
||||
if (faultElement != null)
|
||||
{
|
||||
var fault = new Aria2Fault(faultElement);
|
||||
|
||||
throw new DownloadClientException($"Aria2 returned error code {fault.FaultCode}: {fault.FaultString}");
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentFault.cs
Normal file
28
src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentFault.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using NzbDrone.Core.Download.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
{
|
||||
public class RTorrentFault
|
||||
{
|
||||
public RTorrentFault(XElement element)
|
||||
{
|
||||
foreach (var e in element.XPathSelectElements("./value/struct/member"))
|
||||
{
|
||||
var name = e.ElementAsString("name");
|
||||
if (name == "faultCode")
|
||||
{
|
||||
FaultCode = e.Element("value").GetIntValue();
|
||||
}
|
||||
else if (name == "faultString")
|
||||
{
|
||||
FaultString = e.Element("value").GetStringValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int FaultCode { get; set; }
|
||||
public string FaultString { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using CookComputing.XmlRpc;
|
||||
using NLog;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Download.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
{
|
||||
@@ -18,122 +20,70 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
void RemoveTorrent(string hash, RTorrentSettings settings);
|
||||
void SetTorrentLabel(string hash, string label, RTorrentSettings settings);
|
||||
bool HasHashTorrent(string hash, RTorrentSettings settings);
|
||||
}
|
||||
|
||||
public interface IRTorrent : IXmlRpcProxy
|
||||
{
|
||||
[XmlRpcMethod("d.multicall2")]
|
||||
object[] TorrentMulticall(params string[] parameters);
|
||||
|
||||
[XmlRpcMethod("load.normal")]
|
||||
int LoadNormal(string target, string data, params string[] commands);
|
||||
|
||||
[XmlRpcMethod("load.start")]
|
||||
int LoadStart(string target, string data, params string[] commands);
|
||||
|
||||
[XmlRpcMethod("load.raw")]
|
||||
int LoadRaw(string target, byte[] data, params string[] commands);
|
||||
|
||||
[XmlRpcMethod("load.raw_start")]
|
||||
int LoadRawStart(string target, byte[] data, params string[] commands);
|
||||
|
||||
[XmlRpcMethod("d.erase")]
|
||||
int Remove(string hash);
|
||||
|
||||
[XmlRpcMethod("d.name")]
|
||||
string GetName(string hash);
|
||||
|
||||
[XmlRpcMethod("d.custom1.set")]
|
||||
string SetLabel(string hash, string label);
|
||||
|
||||
[XmlRpcMethod("system.client_version")]
|
||||
string GetVersion();
|
||||
void PushTorrentUniqueView(string hash, string view, RTorrentSettings settings);
|
||||
}
|
||||
|
||||
public class RTorrentProxy : IRTorrentProxy
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly IHttpClient _httpClient;
|
||||
|
||||
public RTorrentProxy(Logger logger)
|
||||
public RTorrentProxy(IHttpClient httpClient)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public string GetVersion(RTorrentSettings settings)
|
||||
{
|
||||
_logger.Debug("Executing remote method: system.client_version");
|
||||
var document = ExecuteRequest(settings, "system.client_version");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
var version = ExecuteRequest(() => client.GetVersion());
|
||||
|
||||
return version;
|
||||
return document.Descendants("string").FirstOrDefault()?.Value ?? "0.0.0";
|
||||
}
|
||||
|
||||
public List<RTorrentTorrent> GetTorrents(RTorrentSettings settings)
|
||||
{
|
||||
_logger.Debug("Executing remote method: d.multicall2");
|
||||
var document = ExecuteRequest(settings,
|
||||
"d.multicall2",
|
||||
"",
|
||||
"",
|
||||
"d.name=", // string
|
||||
"d.hash=", // string
|
||||
"d.base_path=", // string
|
||||
"d.custom1=", // string (label)
|
||||
"d.size_bytes=", // long
|
||||
"d.left_bytes=", // long
|
||||
"d.down.rate=", // long (in bytes / s)
|
||||
"d.ratio=", // long
|
||||
"d.is_open=", // long
|
||||
"d.is_active=", // long
|
||||
"d.complete=", //long
|
||||
"d.timestamp.finished="); // long (unix timestamp)
|
||||
|
||||
var client = BuildClient(settings);
|
||||
var ret = ExecuteRequest(() => client.TorrentMulticall(
|
||||
"",
|
||||
"",
|
||||
"d.name=", // string
|
||||
"d.hash=", // string
|
||||
"d.base_path=", // string
|
||||
"d.custom1=", // string (label)
|
||||
"d.size_bytes=", // long
|
||||
"d.left_bytes=", // long
|
||||
"d.down.rate=", // long (in bytes / s)
|
||||
"d.ratio=", // long
|
||||
"d.is_open=", // long
|
||||
"d.is_active=", // long
|
||||
"d.complete=")); //long
|
||||
var torrents = document.XPathSelectElement("./methodResponse/params/param/value/array/data")
|
||||
?.Elements()
|
||||
.Select(x => new RTorrentTorrent(x))
|
||||
.ToList()
|
||||
?? new List<RTorrentTorrent>();
|
||||
|
||||
_logger.Trace(ret.ToJson());
|
||||
|
||||
var items = new List<RTorrentTorrent>();
|
||||
|
||||
foreach (object[] torrent in ret)
|
||||
{
|
||||
var labelDecoded = System.Web.HttpUtility.UrlDecode((string)torrent[3]);
|
||||
|
||||
var item = new RTorrentTorrent();
|
||||
item.Name = (string)torrent[0];
|
||||
item.Hash = (string)torrent[1];
|
||||
item.Path = (string)torrent[2];
|
||||
item.Category = labelDecoded;
|
||||
item.TotalSize = (long)torrent[4];
|
||||
item.RemainingSize = (long)torrent[5];
|
||||
item.DownRate = (long)torrent[6];
|
||||
item.Ratio = (long)torrent[7];
|
||||
item.IsOpen = Convert.ToBoolean((long)torrent[8]);
|
||||
item.IsActive = Convert.ToBoolean((long)torrent[9]);
|
||||
item.IsFinished = Convert.ToBoolean((long)torrent[10]);
|
||||
|
||||
items.Add(item);
|
||||
}
|
||||
|
||||
return items;
|
||||
return torrents;
|
||||
}
|
||||
|
||||
public void AddTorrentFromUrl(string torrentUrl, string label, RTorrentPriority priority, string directory, RTorrentSettings settings)
|
||||
{
|
||||
var client = BuildClient(settings);
|
||||
var response = ExecuteRequest(() =>
|
||||
{
|
||||
if (settings.AddStopped)
|
||||
{
|
||||
_logger.Debug("Executing remote method: load.normal");
|
||||
return client.LoadNormal("", torrentUrl, GetCommands(label, priority, directory));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("Executing remote method: load.start");
|
||||
return client.LoadStart("", torrentUrl, GetCommands(label, priority, directory));
|
||||
}
|
||||
});
|
||||
var args = new List<object> { "", torrentUrl };
|
||||
args.AddRange(GetCommands(label, priority, directory));
|
||||
|
||||
if (response != 0)
|
||||
XDocument response;
|
||||
|
||||
if (settings.AddStopped)
|
||||
{
|
||||
response = ExecuteRequest(settings, "load.normal", args.ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
response = ExecuteRequest(settings, "load.start", args.ToArray());
|
||||
}
|
||||
|
||||
if (response.GetIntResponse() != 0)
|
||||
{
|
||||
throw new DownloadClientException("Could not add torrent: {0}.", torrentUrl);
|
||||
}
|
||||
@@ -141,22 +91,21 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
|
||||
public void AddTorrentFromFile(string fileName, byte[] fileContent, string label, RTorrentPriority priority, string directory, RTorrentSettings settings)
|
||||
{
|
||||
var client = BuildClient(settings);
|
||||
var response = ExecuteRequest(() =>
|
||||
{
|
||||
if (settings.AddStopped)
|
||||
{
|
||||
_logger.Debug("Executing remote method: load.raw");
|
||||
return client.LoadRaw("", fileContent, GetCommands(label, priority, directory));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("Executing remote method: load.raw_start");
|
||||
return client.LoadRawStart("", fileContent, GetCommands(label, priority, directory));
|
||||
}
|
||||
});
|
||||
var args = new List<object> { "", fileContent };
|
||||
args.AddRange(GetCommands(label, priority, directory));
|
||||
|
||||
if (response != 0)
|
||||
XDocument response;
|
||||
|
||||
if (settings.AddStopped)
|
||||
{
|
||||
response = ExecuteRequest(settings, "load.raw", args.ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
response = ExecuteRequest(settings, "load.raw_start", args.ToArray());
|
||||
}
|
||||
|
||||
if (response.GetIntResponse() != 0)
|
||||
{
|
||||
throw new DownloadClientException("Could not add torrent: {0}.", fileName);
|
||||
}
|
||||
@@ -164,25 +113,29 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
|
||||
public void SetTorrentLabel(string hash, string label, RTorrentSettings settings)
|
||||
{
|
||||
_logger.Debug("Executing remote method: d.custom1.set");
|
||||
var response = ExecuteRequest(settings, "d.custom1.set", hash, label);
|
||||
|
||||
var client = BuildClient(settings);
|
||||
var response = ExecuteRequest(() => client.SetLabel(hash, label));
|
||||
|
||||
if (response != label)
|
||||
if (response.GetStringResponse() != label)
|
||||
{
|
||||
throw new DownloadClientException("Could not set label to {1} for torrent: {0}.", hash, label);
|
||||
}
|
||||
}
|
||||
|
||||
public void PushTorrentUniqueView(string hash, string view, RTorrentSettings settings)
|
||||
{
|
||||
var response = ExecuteRequest(settings, "d.views.push_back_unique", hash, view);
|
||||
|
||||
if (response.GetIntResponse() != 0)
|
||||
{
|
||||
throw new DownloadClientException("Could not push unique view {0} for torrent: {1}.", view, hash);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveTorrent(string hash, RTorrentSettings settings)
|
||||
{
|
||||
_logger.Debug("Executing remote method: d.erase");
|
||||
var response = ExecuteRequest(settings, "d.erase", hash);
|
||||
|
||||
var client = BuildClient(settings);
|
||||
var response = ExecuteRequest(() => client.Remove(hash));
|
||||
|
||||
if (response != 0)
|
||||
if (response.GetIntResponse() != 0)
|
||||
{
|
||||
throw new DownloadClientException("Could not remove torrent: {0}.", hash);
|
||||
}
|
||||
@@ -190,13 +143,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
|
||||
public bool HasHashTorrent(string hash, RTorrentSettings settings)
|
||||
{
|
||||
_logger.Debug("Executing remote method: d.name");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
|
||||
try
|
||||
{
|
||||
var name = ExecuteRequest(() => client.GetName(hash));
|
||||
var response = ExecuteRequest(settings, "d.name", hash);
|
||||
var name = response.GetStringResponse();
|
||||
|
||||
if (name.IsNullOrWhiteSpace())
|
||||
{
|
||||
@@ -235,45 +185,34 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private IRTorrent BuildClient(RTorrentSettings settings)
|
||||
private XDocument ExecuteRequest(RTorrentSettings settings, string methodName, params object[] args)
|
||||
{
|
||||
var client = XmlRpcProxyGen.Create<IRTorrent>();
|
||||
|
||||
client.Url = string.Format(@"{0}://{1}:{2}/{3}",
|
||||
settings.UseSsl ? "https" : "http",
|
||||
settings.Host,
|
||||
settings.Port,
|
||||
settings.UrlBase);
|
||||
|
||||
client.EnableCompression = true;
|
||||
var requestBuilder = new XmlRpcRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase)
|
||||
{
|
||||
LogResponseContent = true,
|
||||
};
|
||||
|
||||
if (!settings.Username.IsNullOrWhiteSpace())
|
||||
{
|
||||
client.Credentials = new NetworkCredential(settings.Username, settings.Password);
|
||||
requestBuilder.NetworkCredential = new NetworkCredential(settings.Username, settings.Password);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
var request = requestBuilder.Call(methodName, args).Build();
|
||||
|
||||
private T ExecuteRequest<T>(Func<T> task)
|
||||
{
|
||||
try
|
||||
{
|
||||
return task();
|
||||
}
|
||||
catch (XmlRpcServerException ex)
|
||||
{
|
||||
throw new DownloadClientException("Unable to connect to rTorrent, please check your settings", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
if (ex.Status == WebExceptionStatus.TrustFailure)
|
||||
{
|
||||
throw new DownloadClientUnavailableException("Unable to connect to rTorrent, certificate validation failed.", ex);
|
||||
}
|
||||
var response = _httpClient.Execute(request);
|
||||
|
||||
throw new DownloadClientUnavailableException("Unable to connect to rTorrent, please check your settings", ex);
|
||||
var doc = XDocument.Parse(response.Content);
|
||||
|
||||
var faultElement = doc.XPathSelectElement("./methodResponse/fault");
|
||||
|
||||
if (faultElement != null)
|
||||
{
|
||||
var fault = new RTorrentFault(faultElement);
|
||||
|
||||
throw new DownloadClientException($"rTorrent returned error code {fault.FaultCode}: {fault.FaultString}");
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,35 @@
|
||||
namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Xml.Linq;
|
||||
using NzbDrone.Core.Download.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
{
|
||||
public class RTorrentTorrent
|
||||
{
|
||||
public RTorrentTorrent()
|
||||
{
|
||||
}
|
||||
|
||||
public RTorrentTorrent(XElement element)
|
||||
{
|
||||
var data = element.Descendants("value").ToList();
|
||||
|
||||
Name = data[0].GetStringValue();
|
||||
Hash = data[1].GetStringValue();
|
||||
Path = data[2].GetStringValue();
|
||||
Category = HttpUtility.UrlDecode(data[3].GetStringValue());
|
||||
TotalSize = data[4].GetLongValue();
|
||||
RemainingSize = data[5].GetLongValue();
|
||||
DownRate = data[6].GetLongValue();
|
||||
Ratio = data[7].GetLongValue();
|
||||
IsOpen = Convert.ToBoolean(data[8].GetLongValue());
|
||||
IsActive = Convert.ToBoolean(data[9].GetLongValue());
|
||||
IsFinished = Convert.ToBoolean(data[10].GetLongValue());
|
||||
FinishedTime = data[11].GetLongValue();
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Hash { get; set; }
|
||||
public string Path { get; set; }
|
||||
@@ -10,6 +38,7 @@
|
||||
public long RemainingSize { get; set; }
|
||||
public long DownRate { get; set; }
|
||||
public long Ratio { get; set; }
|
||||
public long FinishedTime { get; set; }
|
||||
public bool IsFinished { get; set; }
|
||||
public bool IsOpen { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
54
src/NzbDrone.Core/Download/Extensions/XmlExtensions.cs
Normal file
54
src/NzbDrone.Core/Download/Extensions/XmlExtensions.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
|
||||
namespace NzbDrone.Core.Download.Extensions
|
||||
{
|
||||
internal static class XmlExtensions
|
||||
{
|
||||
public static string GetStringValue(this XElement element)
|
||||
{
|
||||
return element.ElementAsString("string");
|
||||
}
|
||||
|
||||
public static long GetLongValue(this XElement element)
|
||||
{
|
||||
return element.ElementAsLong("i8");
|
||||
}
|
||||
|
||||
public static int GetIntValue(this XElement element)
|
||||
{
|
||||
return element.ElementAsInt("i4");
|
||||
}
|
||||
|
||||
public static string ElementAsString(this XElement element, XName name, bool trim = false)
|
||||
{
|
||||
var el = element.Element(name);
|
||||
|
||||
return string.IsNullOrWhiteSpace(el?.Value)
|
||||
? null
|
||||
: (trim ? el.Value.Trim() : el.Value);
|
||||
}
|
||||
|
||||
public static long ElementAsLong(this XElement element, XName name)
|
||||
{
|
||||
var el = element.Element(name);
|
||||
return long.TryParse(el?.Value, out long value) ? value : default;
|
||||
}
|
||||
|
||||
public static int ElementAsInt(this XElement element, XName name)
|
||||
{
|
||||
var el = element.Element(name);
|
||||
return int.TryParse(el?.Value, out int value) ? value : default(int);
|
||||
}
|
||||
|
||||
public static int GetIntResponse(this XDocument document)
|
||||
{
|
||||
return document.XPathSelectElement("./methodResponse/params/param/value").GetIntValue();
|
||||
}
|
||||
|
||||
public static string GetStringResponse(this XDocument document)
|
||||
{
|
||||
return document.XPathSelectElement("./methodResponse/params/param/value").GetStringValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
var currentDefs = _indexerDefinitionUpdateService.All();
|
||||
|
||||
var noDefIndexers = _indexerFactory.AllProviders(false)
|
||||
.Where(i => i.Definition.Implementation == "Cardigann" && !currentDefs.Any(d => d.File == ((CardigannSettings)i.Definition.Settings).DefinitionFile)).ToList();
|
||||
.Where(i => i.Definition.Implementation == "Cardigann" && !currentDefs.Any(d => d.File == ((IndexerDefinition)i.Definition).DefinitionFile)).ToList();
|
||||
|
||||
if (noDefIndexers.Count == 0)
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
var blocklist = _indexerDefinitionUpdateService.GetBlocklist();
|
||||
|
||||
var oldIndexers = _indexerFactory.AllProviders(false)
|
||||
.Where(i => i.IsObsolete() || (i.Definition.Implementation == "Cardigann" && blocklist.Contains(((CardigannSettings)i.Definition.Settings).DefinitionFile))).ToList();
|
||||
.Where(i => i.IsObsolete() || (i.Definition.Implementation == "Cardigann" && blocklist.Contains(((IndexerDefinition)i.Definition).DefinitionFile))).ToList();
|
||||
|
||||
if (oldIndexers.Count == 0)
|
||||
{
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
using NzbDrone.Common.Exceptions;
|
||||
using NzbDrone.Common.Http;
|
||||
|
||||
namespace NzbDrone.Core.Http.CloudFlare
|
||||
{
|
||||
public class CloudFlareCaptchaException : NzbDroneException
|
||||
{
|
||||
public HttpResponse Response { get; set; }
|
||||
|
||||
public CloudFlareCaptchaRequest CaptchaRequest { get; set; }
|
||||
|
||||
public CloudFlareCaptchaException(HttpResponse response, CloudFlareCaptchaRequest captchaRequest)
|
||||
: base("Unable to access {0}, blocked by CloudFlare CAPTCHA. Likely due to shared-IP VPN.", response.Request.Url.Host)
|
||||
{
|
||||
Response = response;
|
||||
CaptchaRequest = captchaRequest;
|
||||
}
|
||||
|
||||
public bool IsExpired => Response.Request.Cookies.ContainsKey("cf_clearance");
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using NzbDrone.Common.Http;
|
||||
|
||||
namespace NzbDrone.Core.Http.CloudFlare
|
||||
{
|
||||
public class CloudFlareCaptchaRequest
|
||||
{
|
||||
public string Host { get; set; }
|
||||
public string SiteKey { get; set; }
|
||||
|
||||
public string Ray { get; set; }
|
||||
public string SecretToken { get; set; }
|
||||
|
||||
public HttpUri ResponseUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
|
||||
namespace NzbDrone.Core.Http.CloudFlare
|
||||
{
|
||||
public class CloudFlareDetectionService
|
||||
{
|
||||
private static readonly HashSet<string> CloudflareServerNames = new HashSet<string> { "cloudflare", "cloudflare-nginx", "ddos-guard" };
|
||||
private readonly Logger _logger;
|
||||
|
||||
public CloudFlareDetectionService(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public static bool IsCloudflareProtected(HttpResponse response)
|
||||
{
|
||||
if (!response.Headers.Any(i => i.Key != null && i.Key.ToLower() == "server" && CloudflareServerNames.Contains(i.Value.ToLower())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// detect CloudFlare and DDoS-GUARD
|
||||
if (response.StatusCode.Equals(HttpStatusCode.ServiceUnavailable) ||
|
||||
response.StatusCode.Equals(HttpStatusCode.Forbidden))
|
||||
{
|
||||
return true; // Defected CloudFlare and DDoS-GUARD
|
||||
}
|
||||
|
||||
// detect Custom CloudFlare for EbookParadijs, Film-Paleis, MuziekFabriek and Puur-Hollands
|
||||
if (response.Headers.Vary == "Accept-Encoding,User-Agent" &&
|
||||
response.Headers.ContentEncoding == "" &&
|
||||
response.Content.ToLower().Contains("ddos"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
|
||||
namespace NzbDrone.Core.Http.CloudFlare
|
||||
{
|
||||
public class CloudFlareHttpInterceptor : IHttpRequestInterceptor
|
||||
{
|
||||
private const string _cloudFlareChallengeScript = "cdn-cgi/scripts/cf.challenge.js";
|
||||
private readonly Logger _logger;
|
||||
private static readonly Regex _cloudFlareRegex = new Regex(@"data-ray=""(?<Ray>[\w-_]+)"".*?data-sitekey=""(?<SiteKey>[\w-_]+)"".*?data-stoken=""(?<SecretToken>[\w-_]+)""", RegexOptions.Compiled);
|
||||
|
||||
public CloudFlareHttpInterceptor(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public HttpRequest PreRequest(HttpRequest request)
|
||||
{
|
||||
return request;
|
||||
}
|
||||
|
||||
public HttpResponse PostResponse(HttpResponse response)
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.Forbidden && response.Content.Contains(_cloudFlareChallengeScript))
|
||||
{
|
||||
_logger.Debug("CloudFlare CAPTCHA block on {0}", response.Request.Url);
|
||||
throw new CloudFlareCaptchaException(response, CreateCaptchaRequest(response));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private CloudFlareCaptchaRequest CreateCaptchaRequest(HttpResponse response)
|
||||
{
|
||||
var match = _cloudFlareRegex.Match(response.Content);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CloudFlareCaptchaRequest
|
||||
{
|
||||
Host = response.Request.Url.Host,
|
||||
SiteKey = match.Groups["SiteKey"].Value,
|
||||
Ray = match.Groups["Ray"].Value,
|
||||
SecretToken = match.Groups["SecretToken"].Value,
|
||||
ResponseUrl = response.Request.Url + new HttpUri("/cdn-cgi/l/chk_captcha")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using NzbDrone.Common.Exceptions;
|
||||
using NzbDrone.Common.Http;
|
||||
|
||||
namespace NzbDrone.Core.Http.CloudFlare
|
||||
{
|
||||
public class CloudFlareProtectionException : NzbDroneException
|
||||
{
|
||||
public HttpResponse Response { get; set; }
|
||||
|
||||
public CloudFlareProtectionException(HttpResponse response)
|
||||
: base("Unable to access {0}, blocked by CloudFlare Protection.", response.Request.Url.Host)
|
||||
{
|
||||
Response = response;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Http.CloudFlare;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
@@ -18,7 +19,6 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
{
|
||||
public class FlareSolverr : HttpIndexerProxyBase<FlareSolverrSettings>
|
||||
{
|
||||
private static readonly HashSet<string> CloudflareServerNames = new HashSet<string> { "cloudflare", "cloudflare-nginx" };
|
||||
private readonly ICached<string> _cache;
|
||||
|
||||
public FlareSolverr(IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger, ILocalizationService localizationService, ICacheManager cacheManager)
|
||||
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
|
||||
public override HttpResponse PostResponse(HttpResponse response)
|
||||
{
|
||||
if (!IsCloudflareProtected(response))
|
||||
if (!CloudFlareDetectionService.IsCloudflareProtected(response))
|
||||
{
|
||||
_logger.Debug("CF Protection not detected, returning original response");
|
||||
return response;
|
||||
@@ -53,14 +53,12 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
|
||||
var flaresolverrResponse = _httpClient.Execute(GenerateFlareSolverrRequest(response.Request));
|
||||
|
||||
FlareSolverrResponse result = null;
|
||||
|
||||
if (flaresolverrResponse.StatusCode != HttpStatusCode.OK && flaresolverrResponse.StatusCode != HttpStatusCode.InternalServerError)
|
||||
{
|
||||
throw new FlareSolverrException("HTTP StatusCode not 200 or 500. Status is :" + response.StatusCode);
|
||||
}
|
||||
|
||||
result = JsonConvert.DeserializeObject<FlareSolverrResponse>(flaresolverrResponse.Content);
|
||||
var result = JsonConvert.DeserializeObject<FlareSolverrResponse>(flaresolverrResponse.Content);
|
||||
|
||||
var newRequest = response.Request;
|
||||
|
||||
@@ -70,26 +68,12 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
|
||||
InjectCookies(newRequest, result);
|
||||
|
||||
//Request again with User-Agent and Cookies from Flaresolvrr
|
||||
//Request again with User-Agent and Cookies from Flaresolverr
|
||||
var finalResponse = _httpClient.Execute(newRequest);
|
||||
|
||||
return finalResponse;
|
||||
}
|
||||
|
||||
private static bool IsCloudflareProtected(HttpResponse response)
|
||||
{
|
||||
// check status code
|
||||
if (response.StatusCode.Equals(HttpStatusCode.ServiceUnavailable) ||
|
||||
response.StatusCode.Equals(HttpStatusCode.Forbidden))
|
||||
{
|
||||
// check response headers
|
||||
return response.Headers.Any(i =>
|
||||
i.Key != null && i.Key.ToLower() == "server" && CloudflareServerNames.Contains(i.Value.ToLower()));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void InjectCookies(HttpRequest request, FlareSolverrResponse flareSolverrResponse)
|
||||
{
|
||||
var rCookies = flareSolverrResponse.Solution.Cookies;
|
||||
|
||||
@@ -6,6 +6,9 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
{
|
||||
public string Author { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Publisher { get; set; }
|
||||
public int? Year { get; set; }
|
||||
public string Genre { get; set; }
|
||||
|
||||
public override bool RssSearch
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
public string ImdbId { get; set; }
|
||||
public int? TmdbId { get; set; }
|
||||
public int? TraktId { get; set; }
|
||||
public int? DoubanId { get; set; }
|
||||
public int? Year { get; set; }
|
||||
public string Genre { get; set; }
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
public string Artist { get; set; }
|
||||
public string Label { get; set; }
|
||||
public string Genre { get; set; }
|
||||
public string Track { get; set; }
|
||||
public int? Year { get; set; }
|
||||
|
||||
public override bool RssSearch
|
||||
|
||||
@@ -17,6 +17,9 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
public int? TvMazeId { get; set; }
|
||||
public int? TraktId { get; set; }
|
||||
public int? TmdbId { get; set; }
|
||||
public int? DoubanId { get; set; }
|
||||
public int? Year { get; set; }
|
||||
public string Genre { get; set; }
|
||||
|
||||
public string SanitizedTvSearchString => (SanitizedSearchTerm + " " + EpisodeSearchString).Trim();
|
||||
public string EpisodeSearchString => GetEpisodeSearchString();
|
||||
|
||||
@@ -4,10 +4,10 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
public class NewznabRequest
|
||||
{
|
||||
private static readonly Regex TvRegex = new Regex(@"\{((?:imdbid\:)(?<imdbid>[^{]+)|(?:tvdbid\:)(?<tvdbid>[^{]+)|(?:season\:)(?<season>[^{]+)|(?:episode\:)(?<episode>[^{]+))\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex MovieRegex = new Regex(@"\{((?:imdbid\:)(?<imdbid>[^{]+)|(?:tmdbid\:)(?<tmdbid>[^{]+))\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex MusicRegex = new Regex(@"\{((?:artist\:)(?<artist>[^{]+)|(?:album\:)(?<album>[^{]+)|(?:label\:)(?<label>[^{]+))\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex BookRegex = new Regex(@"\{((?:author\:)(?<author>[^{]+)|(?:title\:)(?<title>[^{]+))\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex TvRegex = new Regex(@"\{((?:imdbid\:)(?<imdbid>[^{]+)|(?:tvdbid\:)(?<tvdbid>[^{]+)|(?:tmdbid\:)(?<tmdbid>[^{]+)|(?:doubanid\:)(?<doubanid>[^{]+)|(?:season\:)(?<season>[^{]+)|(?:episode\:)(?<episode>[^{]+))\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex MovieRegex = new Regex(@"\{((?:imdbid\:)(?<imdbid>[^{]+)|(?:doubanid\:)(?<doubanid>[^{]+)|(?:tmdbid\:)(?<tmdbid>[^{]+))\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex MusicRegex = new Regex(@"\{((?:artist\:)(?<artist>[^{]+)|(?:album\:)(?<album>[^{]+)|(?:track\:)(?<track>[^{]+)|(?:label\:)(?<label>[^{]+))\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex BookRegex = new Regex(@"\{((?:author\:)(?<author>[^{]+)|(?:publisher\:)(?<publisher>[^{]+)|(?:title\:)(?<title>[^{]+))\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public string t { get; set; }
|
||||
public string q { get; set; }
|
||||
@@ -21,6 +21,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
public int? tvmazeid { get; set; }
|
||||
public int? traktid { get; set; }
|
||||
public int? tvdbid { get; set; }
|
||||
public int? doubanid { get; set; }
|
||||
public int? season { get; set; }
|
||||
public string ep { get; set; }
|
||||
public string album { get; set; }
|
||||
@@ -31,6 +32,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
public string genre { get; set; }
|
||||
public string author { get; set; }
|
||||
public string title { get; set; }
|
||||
public string publisher { get; set; }
|
||||
public string configured { get; set; }
|
||||
public string source { get; set; }
|
||||
public string host { get; set; }
|
||||
@@ -49,6 +51,16 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
tvdbid = int.TryParse(match.Groups["tvdbid"].Value, out var tvdb) ? tvdb : null;
|
||||
}
|
||||
|
||||
if (match.Groups["tmdbid"].Success)
|
||||
{
|
||||
tmdbid = int.TryParse(match.Groups["tmdbid"].Value, out var tmdb) ? tmdb : null;
|
||||
}
|
||||
|
||||
if (match.Groups["doubanid"].Success)
|
||||
{
|
||||
doubanid = int.TryParse(match.Groups["doubanid"].Value, out var tmdb) ? tmdb : null;
|
||||
}
|
||||
|
||||
if (match.Groups["season"].Success)
|
||||
{
|
||||
season = int.TryParse(match.Groups["season"].Value, out var seasonParsed) ? seasonParsed : null;
|
||||
@@ -79,6 +91,11 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
tmdbid = int.TryParse(match.Groups["tmdbid"].Value, out var tmdb) ? tmdb : null;
|
||||
}
|
||||
|
||||
if (match.Groups["doubanid"].Success)
|
||||
{
|
||||
doubanid = int.TryParse(match.Groups["doubanid"].Value, out var tmdb) ? tmdb : null;
|
||||
}
|
||||
|
||||
if (match.Groups["imdbid"].Success)
|
||||
{
|
||||
imdbid = match.Groups["imdbid"].Value;
|
||||
@@ -104,6 +121,11 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
album = match.Groups["album"].Value;
|
||||
}
|
||||
|
||||
if (match.Groups["track"].Success)
|
||||
{
|
||||
track = match.Groups["track"].Value;
|
||||
}
|
||||
|
||||
if (match.Groups["label"].Success)
|
||||
{
|
||||
label = match.Groups["label"].Value;
|
||||
@@ -129,6 +151,11 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
title = match.Groups["title"].Value;
|
||||
}
|
||||
|
||||
if (match.Groups["publisher"].Success)
|
||||
{
|
||||
publisher = match.Groups["publisher"].Value;
|
||||
}
|
||||
|
||||
q = q.Replace(match.Value, "").Trim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,8 +90,8 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
new XAttribute("type", protocol == DownloadProtocol.Torrent ? "application/x-bittorrent" : "application/x-nzb")),
|
||||
r.Categories == null ? null : from c in r.Categories select GetNabElement("category", c.Id, protocol),
|
||||
r.IndexerFlags == null ? null : from f in r.IndexerFlags select GetNabElement("tag", f.Name, protocol),
|
||||
r.Languages == null ? null : from c in r.Languages select GetNabElement("language", c.Id, protocol),
|
||||
r.Subs == null ? null : from c in r.Subs select GetNabElement("subs", c.Id, 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.Genres == null ? null : GetNabElement("genre", string.Join(", ", r.Genres), protocol),
|
||||
GetNabElement("rageid", r.TvRageId, protocol),
|
||||
GetNabElement("tvdbid", r.TvdbId, protocol),
|
||||
@@ -107,6 +107,8 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
GetNabElement("booktitle", RemoveInvalidXMLChars(r.BookTitle), protocol),
|
||||
GetNabElement("artist", RemoveInvalidXMLChars(r.Artist), protocol),
|
||||
GetNabElement("album", RemoveInvalidXMLChars(r.Album), protocol),
|
||||
GetNabElement("label", RemoveInvalidXMLChars(r.Label), protocol),
|
||||
GetNabElement("track", RemoveInvalidXMLChars(r.Track), protocol),
|
||||
GetNabElement("infohash", RemoveInvalidXMLChars(t.InfoHash), protocol),
|
||||
GetNabElement("minimumratio", t.MinimumRatio, protocol),
|
||||
GetNabElement("minimumseedtime", t.MinimumSeedTime, protocol),
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Common.TPL;
|
||||
using NzbDrone.Core.Analytics;
|
||||
using NzbDrone.Core.Indexers.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
@@ -16,26 +20,48 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IHttpRequestBuilderFactory _requestBuilder;
|
||||
private readonly IAnalyticsService _analyticsService;
|
||||
private readonly Debouncer _debouncer;
|
||||
private readonly Logger _logger;
|
||||
private readonly List<ReleaseInfo> _pendingUpdates;
|
||||
|
||||
public ReleaseAnalyticsService(IHttpClient httpClient, IProwlarrCloudRequestBuilder requestBuilder, IAnalyticsService analyticsService, Logger logger)
|
||||
{
|
||||
_debouncer = new Debouncer(SendReleases, TimeSpan.FromMinutes(10));
|
||||
_analyticsService = analyticsService;
|
||||
_requestBuilder = requestBuilder.Releases;
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
|
||||
_pendingUpdates = new List<ReleaseInfo>();
|
||||
}
|
||||
|
||||
public void HandleAsync(IndexerQueryEvent message)
|
||||
{
|
||||
if (_analyticsService.IsEnabled && message.QueryResult?.Releases != null)
|
||||
if (message.QueryResult?.Releases != null)
|
||||
{
|
||||
lock (_pendingUpdates)
|
||||
{
|
||||
_pendingUpdates.AddRange(message.QueryResult.Releases.Where(r => r.Title.IsNotNullOrWhiteSpace()));
|
||||
}
|
||||
|
||||
_debouncer.Execute();
|
||||
}
|
||||
}
|
||||
|
||||
public void SendReleases()
|
||||
{
|
||||
lock (_pendingUpdates)
|
||||
{
|
||||
var pendingUpdates = _pendingUpdates.ToArray();
|
||||
_pendingUpdates.Clear();
|
||||
|
||||
var request = _requestBuilder.Create().Resource("release/push").Build();
|
||||
request.Method = HttpMethod.Post;
|
||||
request.Headers.ContentType = "application/json";
|
||||
request.SuppressHttpError = true;
|
||||
request.LogHttpError = false;
|
||||
|
||||
var body = message.QueryResult.Releases.Select(x => new
|
||||
var body = pendingUpdates.DistinctBy(r => r.Title).Select(x => new
|
||||
{
|
||||
Title = x.Title,
|
||||
Categories = x.Categories?.Where(c => c.Id < 10000).Select(c => c.Id) ?? new List<int>(),
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
searchSpec.ImdbId = request.imdbid;
|
||||
searchSpec.TmdbId = request.tmdbid;
|
||||
searchSpec.TraktId = request.traktid;
|
||||
searchSpec.DoubanId = request.doubanid;
|
||||
searchSpec.Year = request.year;
|
||||
searchSpec.Genre = request.genre;
|
||||
|
||||
@@ -73,6 +74,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
searchSpec.Album = request.album;
|
||||
searchSpec.Label = request.label;
|
||||
searchSpec.Genre = request.genre;
|
||||
searchSpec.Track = request.track;
|
||||
searchSpec.Year = request.year;
|
||||
|
||||
return new NewznabResults { Releases = await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec) };
|
||||
@@ -88,8 +90,11 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
searchSpec.ImdbId = request.imdbid;
|
||||
searchSpec.TraktId = request.traktid;
|
||||
searchSpec.TmdbId = request.tmdbid;
|
||||
searchSpec.DoubanId = request.doubanid;
|
||||
searchSpec.RId = request.rid;
|
||||
searchSpec.TvMazeId = request.tvmazeid;
|
||||
searchSpec.Year = request.year;
|
||||
searchSpec.Genre = request.genre;
|
||||
|
||||
return new NewznabResults { Releases = await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec) };
|
||||
}
|
||||
@@ -100,6 +105,9 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
|
||||
searchSpec.Author = request.author;
|
||||
searchSpec.Title = request.title;
|
||||
searchSpec.Publisher = request.publisher;
|
||||
searchSpec.Year = request.year;
|
||||
searchSpec.Genre = request.genre;
|
||||
|
||||
return new NewznabResults { Releases = await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec) };
|
||||
}
|
||||
|
||||
@@ -18,17 +18,18 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
{
|
||||
public interface IIndexerDefinitionUpdateService
|
||||
{
|
||||
List<CardigannMetaDefinition> All();
|
||||
List<IndexerMetaDefinition> All();
|
||||
List<IndexerMetaDefinition> AllForImplementation(string implementation);
|
||||
CardigannDefinition GetCachedDefinition(string fileKey);
|
||||
List<string> GetBlocklist();
|
||||
}
|
||||
|
||||
public class IndexerDefinitionUpdateService : IIndexerDefinitionUpdateService, IExecute<IndexerDefinitionUpdateCommand>, IHandle<ApplicationStartedEvent>
|
||||
{
|
||||
/* 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 int DEFINITION_VERSION = 6;
|
||||
private const string DEFINITION_BRANCH = "newznab-xml-to-yml-scriptupdates";
|
||||
private const int DEFINITION_VERSION = 8;
|
||||
|
||||
//Used when moving yml to C#
|
||||
private readonly List<string> _defintionBlocklist = new List<string>()
|
||||
@@ -77,9 +78,9 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<CardigannMetaDefinition> All()
|
||||
public List<IndexerMetaDefinition> All()
|
||||
{
|
||||
var indexerList = new List<CardigannMetaDefinition>();
|
||||
var indexerList = new List<IndexerMetaDefinition>();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -87,7 +88,7 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
try
|
||||
{
|
||||
var request = new HttpRequest($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}");
|
||||
var response = _httpClient.Get<List<CardigannMetaDefinition>>(request);
|
||||
var response = _httpClient.Get<List<IndexerMetaDefinition>>(request);
|
||||
indexerList = response.Resource.Where(i => !_defintionBlocklist.Contains(i.File)).ToList();
|
||||
}
|
||||
catch
|
||||
@@ -110,6 +111,11 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
return indexerList;
|
||||
}
|
||||
|
||||
public List<IndexerMetaDefinition> AllForImplementation(string implementation)
|
||||
{
|
||||
return All().Where(d => d.Implementation == implementation).ToList();
|
||||
}
|
||||
|
||||
public CardigannDefinition GetCachedDefinition(string fileKey)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fileKey))
|
||||
@@ -127,7 +133,7 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
return _defintionBlocklist;
|
||||
}
|
||||
|
||||
private List<CardigannMetaDefinition> ReadDefinitionsFromDisk(List<CardigannMetaDefinition> defs, string path, SearchOption options = SearchOption.TopDirectoryOnly)
|
||||
private List<IndexerMetaDefinition> ReadDefinitionsFromDisk(List<IndexerMetaDefinition> defs, string path, SearchOption options = SearchOption.TopDirectoryOnly)
|
||||
{
|
||||
var indexerList = defs;
|
||||
|
||||
@@ -144,7 +150,7 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
try
|
||||
{
|
||||
var definitionString = File.ReadAllText(file.FullName);
|
||||
var definition = _deserializer.Deserialize<CardigannMetaDefinition>(definitionString);
|
||||
var definition = _deserializer.Deserialize<IndexerMetaDefinition>(definitionString);
|
||||
|
||||
definition.File = Path.GetFileNameWithoutExtension(file.Name);
|
||||
|
||||
@@ -242,6 +248,11 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
definition.Login.Method = "form";
|
||||
}
|
||||
|
||||
if (definition.Search == null)
|
||||
{
|
||||
definition.Search = new SearchBlock();
|
||||
}
|
||||
|
||||
if (definition.Search.Paths == null)
|
||||
{
|
||||
definition.Search.Paths = new List<SearchPathBlock>();
|
||||
@@ -283,7 +294,7 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
var startupFolder = _appFolderInfo.AppDataFolder;
|
||||
|
||||
var request = new HttpRequest($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}");
|
||||
var response = _httpClient.Get<List<CardigannMetaDefinition>>(request);
|
||||
var response = _httpClient.Get<List<IndexerMetaDefinition>>(request);
|
||||
|
||||
var currentDefs = _versionService.All().ToDictionary(x => x.DefinitionId, x => x.Sha);
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Indexers.Cardigann;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Cardigann
|
||||
namespace NzbDrone.Core.IndexerVersions
|
||||
{
|
||||
public class CardigannMetaDefinition
|
||||
public class IndexerMetaDefinition
|
||||
{
|
||||
public CardigannMetaDefinition()
|
||||
public IndexerMetaDefinition()
|
||||
{
|
||||
Legacylinks = new List<string>();
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
public string File { get; set; }
|
||||
public string Implementation { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Type { get; set; }
|
||||
@@ -18,8 +20,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
public string Encoding { get; set; }
|
||||
public List<string> Links { get; set; }
|
||||
public List<string> Legacylinks { get; set; }
|
||||
public List<SettingsField> Settings { get; set; }
|
||||
public string Sha { get; set; }
|
||||
public List<SettingsField> Settings { get; set; }
|
||||
public LoginBlock Login { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
@@ -9,12 +10,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class AlphaRatio : Gazelle.Gazelle
|
||||
{
|
||||
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 AlphaRatio(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -28,54 +26,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
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
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -16,6 +17,7 @@ using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -26,16 +28,15 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class Anidub : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
||||
{
|
||||
public override string Name => "Anidub";
|
||||
public override string[] IndexerUrls => new string[] { "https://tr.anidub.com/" };
|
||||
public override string Description => "Anidub is russian anime voiceover group and eponymous anime tracker.";
|
||||
public override string Language => "ru-RU";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.SemiPrivate;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public Anidub(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public Anidub(IIndexerHttpClient httpClient,
|
||||
IEventAggregator eventAggregator,
|
||||
IIndexerStatusService indexerStatusService,
|
||||
IIndexerDefinitionUpdateService definitionService,
|
||||
IConfigService configService,
|
||||
Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -98,47 +99,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "Аниме TV");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Movies, "Аниме Фильмы");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVAnime, "Аниме OVA");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVAnime, "Аниме OVA |- Аниме ONA");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TV, "Дорамы / Японские Сериалы и Фильмы");
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.TV, "Дорамы / Корейские Сериалы и Фильмы");
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.TV, "Дорамы / Китайские Сериалы и Фильмы");
|
||||
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.TV, "Дорамы");
|
||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.TVAnime, "Аниме TV / Аниме Ongoing");
|
||||
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.TVAnime, "Аниме TV / Многосерийный сёнэн");
|
||||
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.Other, "Аниме Ongoing Анонсы");
|
||||
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.XXX, "18+");
|
||||
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.TVAnime, "Аниме TV / Законченные сериалы");
|
||||
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.BooksComics, "Манга");
|
||||
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.Audio, "OST");
|
||||
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.Audio, "Подкасты");
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class AnidubRequestGenerator : IIndexerRequestGenerator
|
||||
@@ -493,7 +453,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
// Throw common http errors here before we try to parse
|
||||
if (releaseResponse.HttpResponse.HasHttpError)
|
||||
{
|
||||
if ((int)releaseResponse.HttpResponse.StatusCode == 429)
|
||||
if (releaseResponse.HttpResponse.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
throw new TooManyRequestsException(releaseRequest.HttpRequest, releaseResponse.HttpResponse);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -27,16 +28,10 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class AnimeBytes : TorrentIndexerBase<AnimeBytesSettings>
|
||||
{
|
||||
public override string Name => "AnimeBytes";
|
||||
public override string[] IndexerUrls => new string[] { "https://animebytes.tv/" };
|
||||
public override string Description => "AnimeBytes (AB) is the largest private torrent tracker that specialises in anime and anime-related content.";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public AnimeBytes(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public AnimeBytes(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -54,48 +49,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping("anime[tv_series]", NewznabStandardCategory.TVAnime, "TV Series");
|
||||
caps.Categories.AddCategoryMapping("anime[tv_special]", NewznabStandardCategory.TVAnime, "TV Special");
|
||||
caps.Categories.AddCategoryMapping("anime[ova]", NewznabStandardCategory.TVAnime, "OVA");
|
||||
caps.Categories.AddCategoryMapping("anime[ona]", NewznabStandardCategory.TVAnime, "ONA");
|
||||
caps.Categories.AddCategoryMapping("anime[dvd_special]", NewznabStandardCategory.TVAnime, "DVD Special");
|
||||
caps.Categories.AddCategoryMapping("anime[bd_special]", NewznabStandardCategory.TVAnime, "BD Special");
|
||||
caps.Categories.AddCategoryMapping("anime[movie]", NewznabStandardCategory.Movies, "Movie");
|
||||
caps.Categories.AddCategoryMapping("audio", NewznabStandardCategory.Audio, "Music");
|
||||
caps.Categories.AddCategoryMapping("gamec[game]", NewznabStandardCategory.PCGames, "Game");
|
||||
caps.Categories.AddCategoryMapping("gamec[visual_novel]", NewznabStandardCategory.PCGames, "Game Visual Novel");
|
||||
caps.Categories.AddCategoryMapping("printedtype[manga]", NewznabStandardCategory.BooksComics, "Manga");
|
||||
caps.Categories.AddCategoryMapping("printedtype[oneshot]", NewznabStandardCategory.BooksComics, "Oneshot");
|
||||
caps.Categories.AddCategoryMapping("printedtype[anthology]", NewznabStandardCategory.BooksComics, "Anthology");
|
||||
caps.Categories.AddCategoryMapping("printedtype[manhwa]", NewznabStandardCategory.BooksComics, "Manhwa");
|
||||
caps.Categories.AddCategoryMapping("printedtype[light_novel]", NewznabStandardCategory.BooksComics, "Light Novel");
|
||||
caps.Categories.AddCategoryMapping("printedtype[artbook]", NewznabStandardCategory.BooksComics, "Artbook");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class AnimeBytesRequestGenerator : IIndexerRequestGenerator
|
||||
|
||||
@@ -15,6 +15,7 @@ using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -25,16 +26,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class AnimeTorrents : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
||||
{
|
||||
public override string Name => "AnimeTorrents";
|
||||
|
||||
public override string[] IndexerUrls => new string[] { "https://animetorrents.me/" };
|
||||
public override string Description => "Definitive source for anime and manga";
|
||||
private string LoginUrl => Settings.BaseUrl + "login.php";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public AnimeTorrents(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public AnimeTorrents(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -94,43 +90,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesSD, "Anime Movie");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.MoviesHD, "Anime Movie HD");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "Anime Series");
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.TVAnime, "Anime Series HD");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.XXXDVD, "Hentai (censored)");
|
||||
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.XXXDVD, "Hentai (censored) HD");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.XXXDVD, "Hentai (un-censored)");
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.XXXDVD, "Hentai (un-censored) HD");
|
||||
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.BooksForeign, "Light Novel");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.BooksComics, "Manga");
|
||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.BooksComics, "Manga 18+");
|
||||
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.TVAnime, "OVA");
|
||||
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.TVAnime, "OVA HD");
|
||||
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.BooksComics, "Doujin Anime");
|
||||
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.XXXDVD, "Doujin Anime 18+");
|
||||
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.AudioForeign, "Doujin Music");
|
||||
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.BooksComics, "Doujinshi");
|
||||
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.BooksComics, "Doujinshi 18+");
|
||||
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.Audio, "OST");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class AnimeTorrentsRequestGenerator : IIndexerRequestGenerator
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using AngleSharp.Html.Parser;
|
||||
@@ -12,6 +13,7 @@ using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -22,16 +24,10 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class Animedia : TorrentIndexerBase<NoAuthTorrentBaseSettings>
|
||||
{
|
||||
public override string Name => "Animedia";
|
||||
public override string[] IndexerUrls => new string[] { "https://tt.animedia.tv/" };
|
||||
public override string Description => "Animedia is russian anime voiceover group and eponymous anime tracker.";
|
||||
public override string Language => "ru-RU";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public Animedia(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public Animedia(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -44,26 +40,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
return new AnimediaParser(Settings, Capabilities.Categories) { HttpClient = _httpClient, Logger = _logger };
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
}
|
||||
};
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "TV Anime");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "OVA/ONA/Special");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TV, "Dorama");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.Movies, "Movies");
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class AnimediaRequestGenerator : IIndexerRequestGenerator
|
||||
@@ -307,7 +283,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
// Throw common http errors here before we try to parse
|
||||
if (releaseResponse.HttpResponse.HasHttpError)
|
||||
{
|
||||
if ((int)releaseResponse.HttpResponse.StatusCode == 429)
|
||||
if (releaseResponse.HttpResponse.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
throw new TooManyRequestsException(releaseRequest.HttpRequest, releaseResponse.HttpResponse);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -22,17 +23,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class Anthelion : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
||||
{
|
||||
public override string Name => "Anthelion";
|
||||
public override string[] IndexerUrls => new string[] { "https://anthelion.me/" };
|
||||
private string LoginUrl => Settings.BaseUrl + "login.php";
|
||||
public override string Description => "A movies tracker";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public Anthelion(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public Anthelion(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -101,28 +96,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping("1", NewznabStandardCategory.Movies, "Film/Feature");
|
||||
caps.Categories.AddCategoryMapping("2", NewznabStandardCategory.Movies, "Film/Short");
|
||||
caps.Categories.AddCategoryMapping("3", NewznabStandardCategory.TV, "TV/Miniseries");
|
||||
caps.Categories.AddCategoryMapping("4", NewznabStandardCategory.Other, "Other");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class AnthelionRequestGenerator : IIndexerRequestGenerator
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Definitions.Avistaz;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class AvistaZ : AvistazBase
|
||||
{
|
||||
public override string Name => "AvistaZ";
|
||||
public override string[] IndexerUrls => new string[] { "https://avistaz.to/" };
|
||||
public override string Description => "Aka AsiaTorrents";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public AvistaZ(IIndexerRepository indexerRepository, IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(indexerRepository, httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new AvistazRequestGenerator()
|
||||
{
|
||||
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, TvSearchParam.ImdbId, TvSearchParam.TvdbId
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies);
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesUHD);
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesHD);
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesSD);
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TV);
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVUHD);
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVHD);
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVSD);
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio);
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,28 +6,31 @@ using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
{
|
||||
public abstract class AvistazBase : TorrentIndexerBase<AvistazSettings>
|
||||
public class Avistaz : TorrentIndexerBase<AvistazSettings>
|
||||
{
|
||||
public override string Name => "Avistaz";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override string[] IndexerUrls => new string[] { "" };
|
||||
protected virtual string LoginUrl => Settings.BaseUrl + "api/v1/jackett/auth";
|
||||
public override string Description => "";
|
||||
public override bool SupportsRss => true;
|
||||
public override bool SupportsSearch => true;
|
||||
public override int PageSize => 50;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
private IIndexerRepository _indexerRepository;
|
||||
|
||||
public AvistazBase(IIndexerRepository indexerRepository,
|
||||
public Avistaz(IIndexerRepository indexerRepository,
|
||||
IIndexerHttpClient httpClient,
|
||||
IEventAggregator eventAggregator,
|
||||
IIndexerStatusService indexerStatusService,
|
||||
IIndexerDefinitionUpdateService definitionService,
|
||||
IConfigService configService,
|
||||
Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
_indexerRepository = indexerRepository;
|
||||
}
|
||||
@@ -40,6 +40,14 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
[JsonProperty(PropertyName = "video_quality")]
|
||||
public string VideoQuality { get; set; }
|
||||
public string Type { get; set; }
|
||||
public List<AvistazLanguage> Audio { get; set; }
|
||||
public List<AvistazLanguage> Subtitle { get; set; }
|
||||
}
|
||||
|
||||
public class AvistazLanguage
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Language { get; set; }
|
||||
}
|
||||
|
||||
public class AvistazResponse
|
||||
|
||||
@@ -66,6 +66,8 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
UploadVolumeFactor = row.UploadMultiply,
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 172800, // 48 hours
|
||||
Languages = row.Audio?.Select(x => x.Language).ToList() ?? new List<string>(),
|
||||
Subs = row.Subtitle?.Select(x => x.Language).ToList() ?? new List<string>()
|
||||
};
|
||||
|
||||
if (row.MovieTvinfo != null)
|
||||
|
||||
@@ -15,6 +15,7 @@ using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -27,15 +28,10 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Name => "BB";
|
||||
public override string[] IndexerUrls => new string[] { StringUtil.FromBase64("aHR0cHM6Ly9iYWNvbmJpdHMub3JnLw==") };
|
||||
private string LoginUrl => Settings.BaseUrl + "login.php";
|
||||
public override string Description => "BB is a Private Torrent Tracker for 0DAY / GENERAL";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public BB(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public BB(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -112,50 +108,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Audio);
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.AudioMP3);
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.AudioLossless);
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.PC);
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.BooksEBook);
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.AudioAudiobook);
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Other);
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.BooksMags);
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.BooksComics);
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.TVAnime);
|
||||
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.Movies);
|
||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.TVHD);
|
||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.TVSD);
|
||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.TV);
|
||||
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.PCGames);
|
||||
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.Console);
|
||||
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.Other);
|
||||
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.Other);
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class BBRequestGenerator : IIndexerRequestGenerator
|
||||
|
||||
@@ -16,6 +16,7 @@ using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -27,15 +28,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public override string Name => "BakaBT";
|
||||
|
||||
public override string[] IndexerUrls => new string[] { "https://bakabt.me/" };
|
||||
public override string Description => "Anime Community";
|
||||
private string LoginUrl => Settings.BaseUrl + "login.php";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public BakaBT(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public BakaBT(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -124,41 +121,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "Anime Series");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "OVA");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.AudioOther, "Soundtrack");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.BooksComics, "Manga");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Movies, "Anime Movie");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TVOther, "Live Action");
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.BooksOther, "Artbook");
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.AudioVideo, "Music Video");
|
||||
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.BooksEBook, "Light Novel");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class BakaBTRequestGenerator : IIndexerRequestGenerator
|
||||
|
||||
@@ -15,6 +15,7 @@ using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -25,15 +26,10 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class BeyondHD : TorrentIndexerBase<BeyondHDSettings>
|
||||
{
|
||||
public override string Name => "BeyondHD";
|
||||
|
||||
public override string[] IndexerUrls => new string[] { "https://beyond-hd.me/" };
|
||||
public override string Description => "BeyondHD (BHD) is a Private Torrent Tracker for HD MOVIES / TV";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public BeyondHD(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public BeyondHD(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -46,26 +42,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
return new BeyondHDParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies, "Movies");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TV, "TV");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class BeyondHDRequestGenerator : IIndexerRequestGenerator
|
||||
@@ -111,7 +87,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var request = new HttpRequest(searchUrl, HttpAccept.Json);
|
||||
|
||||
request.Headers.Add("Content-type", "application/json");
|
||||
request.Headers.ContentType = "application/json";
|
||||
request.Method = HttpMethod.Post;
|
||||
request.SetContent(body.ToJson());
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -22,17 +23,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class BinSearch : UsenetIndexerBase<BinSearchSettings>
|
||||
{
|
||||
public override string Name => "BinSearch";
|
||||
public override string[] IndexerUrls => new string[] { "https://binsearch.info/" };
|
||||
public override string Description => "The binary Usenet search engine";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
public override bool SupportsRss => false;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public BinSearch(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, IValidateNzbs nzbValidationService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, nzbValidationService, logger)
|
||||
public BinSearch(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, IValidateNzbs nzbValidationService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, nzbValidationService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -45,31 +40,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
return new BinSearchParser(Capabilities.Categories, Settings);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class BinSearchRequestGenerator : IIndexerRequestGenerator
|
||||
|
||||
@@ -14,6 +14,7 @@ using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -25,16 +26,10 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class BitHDTV : TorrentIndexerBase<CookieTorrentBaseSettings>
|
||||
{
|
||||
public override string Name => "BitHDTV";
|
||||
public override string[] IndexerUrls => new string[] { "https://www.bit-hdtv.com/" };
|
||||
public override string Description => "BIT-HDTV - Home of High Definition";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.GetEncoding("iso-8859-1");
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public BitHDTV(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public BitHDTV(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -57,35 +52,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "Anime");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.MoviesBluRay, "Movies/Blu-ray");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVDocumentary, "Documentaries");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.AudioLossless, "HQ Audio");
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.Movies, "Movies");
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.AudioVideo, "Music Videos");
|
||||
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.Other, "Other");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVSport, "Sports");
|
||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.TV, "TV");
|
||||
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.TV, "TV/Seasonpack");
|
||||
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.XXX, "XXX");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class BitHDTVRequestGenerator : IIndexerRequestGenerator
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.BroadcastheNet
|
||||
@@ -10,22 +11,15 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
|
||||
public class BroadcastheNet : TorrentIndexerBase<BroadcastheNetSettings>
|
||||
{
|
||||
public override string Name => "BroadcasTheNet";
|
||||
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override bool SupportsRss => true;
|
||||
public override bool SupportsSearch => true;
|
||||
public override int PageSize => 100;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public override TimeSpan RateLimit => TimeSpan.FromSeconds(5);
|
||||
|
||||
public override string[] IndexerUrls => new string[] { "https://api.broadcasthe.net/" };
|
||||
public override string[] LegacyUrls => new string[] { "http://api.broadcasthe.net/" };
|
||||
|
||||
public override string Description => "BroadcasTheNet (BTN) is an invite-only torrent tracker focused on TV shows";
|
||||
|
||||
public BroadcastheNet(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public BroadcastheNet(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -50,27 +44,5 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
|
||||
{
|
||||
return new BroadcastheNetParser(Capabilities.Categories);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
LimitsDefault = 100,
|
||||
LimitsMax = 1000,
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.TvdbId, TvSearchParam.RId
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping("SD", NewznabStandardCategory.TVSD, "SD");
|
||||
caps.Categories.AddCategoryMapping("720p", NewznabStandardCategory.TVHD, "720p");
|
||||
caps.Categories.AddCategoryMapping("1080p", NewznabStandardCategory.TVHD, "1080p");
|
||||
caps.Categories.AddCategoryMapping("1080i", NewznabStandardCategory.TVHD, "1080i");
|
||||
caps.Categories.AddCategoryMapping("2160p", NewznabStandardCategory.TVHD, "2160p");
|
||||
caps.Categories.AddCategoryMapping("Portable Device", NewznabStandardCategory.TVSD, "Portable Device");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
@@ -8,31 +9,10 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class BrokenStones : Gazelle.Gazelle
|
||||
{
|
||||
public override string Name => "BrokenStones";
|
||||
public override string[] IndexerUrls => new string[] { "https://brokenstones.club/" };
|
||||
public override string Description => "Broken Stones is a Private site for MacOS and iOS APPS / GAMES";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public BrokenStones(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public BrokenStones(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.PCMac, "MacOS Apps");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.PCMac, "MacOS Games");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.PCMobileiOS, "iOS Apps");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.PCMobileiOS, "iOS Games");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Other, "Graphics");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.Audio, "Audio");
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.Other, "Tutorials");
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.Other, "Other");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
@@ -8,30 +9,10 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class CGPeers : Gazelle.Gazelle
|
||||
{
|
||||
public override string Name => "CGPeers";
|
||||
public override string[] IndexerUrls => new string[] { "https://cgpeers.to/" };
|
||||
public override string Description => "CGPeers is a Private Torrent Tracker for GRAPHICS SOFTWARE / TUTORIALS / ETC";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public CGPeers(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public CGPeers(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.PCISO, "Full Applications");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.PC0day, "Plugins");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Other, "Tutorials");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.Other, "Models");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Other, "Materials");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.OtherMisc, "Misc");
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.Other, "GameDev");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,15 +19,10 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
{
|
||||
public class Cardigann : TorrentIndexerBase<CardigannSettings>
|
||||
{
|
||||
private readonly IIndexerDefinitionUpdateService _definitionService;
|
||||
private readonly ICached<CardigannRequestGenerator> _generatorCache;
|
||||
|
||||
public override string Name => "Cardigann";
|
||||
public override string[] IndexerUrls => new string[] { "" };
|
||||
public override string Description => "";
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
// Page size is different per indexer, setting to 1 ensures we don't break out of paging logic
|
||||
// thinking its a partial page and insteaad all search_path requests are run for each indexer
|
||||
@@ -35,9 +30,9 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
var generator = _generatorCache.Get(Settings.DefinitionFile, () =>
|
||||
var generator = _generatorCache.Get(((IndexerDefinition)Definition).DefinitionFile, () =>
|
||||
new CardigannRequestGenerator(_configService,
|
||||
_definitionService.GetCachedDefinition(Settings.DefinitionFile),
|
||||
_definitionService.GetCachedDefinition(((IndexerDefinition)Definition).DefinitionFile),
|
||||
_logger)
|
||||
{
|
||||
HttpClient = _httpClient,
|
||||
@@ -57,7 +52,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new CardigannParser(_configService,
|
||||
_definitionService.GetCachedDefinition(Settings.DefinitionFile),
|
||||
_definitionService.GetCachedDefinition(((IndexerDefinition)Definition).DefinitionFile),
|
||||
_logger)
|
||||
{
|
||||
Settings = Settings
|
||||
@@ -74,17 +69,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
return base.GetCookies();
|
||||
}
|
||||
|
||||
public override IEnumerable<ProviderDefinition> DefaultDefinitions
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var def in _definitionService.All())
|
||||
{
|
||||
yield return GetDefinition(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Cardigann(IIndexerDefinitionUpdateService definitionService,
|
||||
IIndexerHttpClient httpClient,
|
||||
IEventAggregator eventAggregator,
|
||||
@@ -92,13 +76,12 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
IConfigService configService,
|
||||
ICacheManager cacheManager,
|
||||
Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
_definitionService = definitionService;
|
||||
_generatorCache = cacheManager.GetRollingCache<CardigannRequestGenerator>(GetType(), "CardigannGeneratorCache", TimeSpan.FromMinutes(5));
|
||||
}
|
||||
|
||||
private IndexerDefinition GetDefinition(CardigannMetaDefinition definition)
|
||||
private IndexerDefinition GetDefinition(IndexerMetaDefinition definition)
|
||||
{
|
||||
var defaultSettings = new List<SettingsField>
|
||||
{
|
||||
@@ -127,7 +110,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
Implementation = GetType().Name,
|
||||
IndexerUrls = definition.Links.ToArray(),
|
||||
LegacyUrls = definition.Legacylinks.ToArray(),
|
||||
Settings = new CardigannSettings { DefinitionFile = definition.File },
|
||||
DefinitionFile = definition.File,
|
||||
Settings = new CardigannSettings(),
|
||||
Protocol = DownloadProtocol.Torrent,
|
||||
Privacy = definition.Type switch
|
||||
{
|
||||
@@ -194,7 +178,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
throw new ReleaseUnavailableException("Downloading torrent failed", ex);
|
||||
}
|
||||
|
||||
if ((int)ex.Response.StatusCode == 429)
|
||||
if (ex.Response.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
_logger.Error("API Grab Limit reached for {0}", request.Url.FullUri);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
protected readonly IndexerCapabilitiesCategories _categories = new IndexerCapabilitiesCategories();
|
||||
protected readonly List<string> _defaultCategories = new List<string>();
|
||||
|
||||
protected readonly string[] OptionalFields = new string[] { "imdb", "imdbid", "rageid", "tmdbid", "tvdbid", "poster", "banner", "description", "doubanid" };
|
||||
protected readonly string[] OptionalFields = new string[] { "imdb", "imdbid", "tmdbid", "rageid", "tvdbid", "tvmazeid", "traktid", "doubanid", "poster", "banner", "description" };
|
||||
|
||||
protected static readonly string[] _SupportedLogicFunctions =
|
||||
{
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
public string Id { get; set; }
|
||||
public List<SettingsField> Settings { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Implementation { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Language { get; set; }
|
||||
|
||||
@@ -279,7 +279,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
}
|
||||
|
||||
var filters = search.Rows.Filters;
|
||||
var skipRelease = ParseRowFilters(filters, release, variables, row);
|
||||
var skipRelease = ParseRowFilters(filters, release, variables, row.ToHtmlPretty());
|
||||
|
||||
if (skipRelease)
|
||||
{
|
||||
@@ -595,7 +595,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
break;
|
||||
case "genre":
|
||||
release.Genres = release.Genres.Union(value.Split(',')).ToList();
|
||||
value = release.Genres.ToString();
|
||||
value = string.Join(",", release.Genres);
|
||||
break;
|
||||
case "year":
|
||||
release.Year = ParseUtil.CoerceInt(value);
|
||||
@@ -607,12 +607,21 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
case "booktitle":
|
||||
release.BookTitle = value;
|
||||
break;
|
||||
case "publisher":
|
||||
release.Publisher = value;
|
||||
break;
|
||||
case "artist":
|
||||
release.Artist = value;
|
||||
break;
|
||||
case "album":
|
||||
release.Album = value;
|
||||
break;
|
||||
case "label":
|
||||
release.Label = value;
|
||||
break;
|
||||
case "track":
|
||||
release.Track = value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -49,10 +49,12 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
variables[".Query.Movie"] = null;
|
||||
variables[".Query.Year"] = searchCriteria.Year?.ToString() ?? null;
|
||||
variables[".Query.Genre"] = searchCriteria.Genre;
|
||||
variables[".Query.IMDBID"] = searchCriteria.FullImdbId;
|
||||
variables[".Query.IMDBIDShort"] = searchCriteria.ImdbId;
|
||||
variables[".Query.TMDBID"] = searchCriteria.TmdbId?.ToString() ?? null;
|
||||
variables[".Query.TraktID"] = searchCriteria.TraktId?.ToString() ?? null;
|
||||
variables[".Query.DoubanID"] = searchCriteria.DoubanId?.ToString() ?? null;
|
||||
|
||||
pageableRequests.Add(GetRequest(variables));
|
||||
|
||||
@@ -70,7 +72,9 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
variables[".Query.Album"] = searchCriteria.Album;
|
||||
variables[".Query.Artist"] = searchCriteria.Artist;
|
||||
variables[".Query.Label"] = searchCriteria.Label;
|
||||
variables[".Query.Track"] = null;
|
||||
variables[".Query.Genre"] = searchCriteria.Genre;
|
||||
variables[".Query.Year"] = searchCriteria.Year?.ToString() ?? null;
|
||||
variables[".Query.Track"] = searchCriteria.Track;
|
||||
|
||||
pageableRequests.Add(GetRequest(variables));
|
||||
|
||||
@@ -88,6 +92,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
variables[".Query.Series"] = null;
|
||||
variables[".Query.Ep"] = searchCriteria.Episode;
|
||||
variables[".Query.Season"] = searchCriteria.Season?.ToString() ?? null;
|
||||
variables[".Query.Genre"] = searchCriteria.Genre;
|
||||
variables[".Query.Year"] = searchCriteria.Year?.ToString() ?? null;
|
||||
variables[".Query.IMDBID"] = searchCriteria.FullImdbId;
|
||||
variables[".Query.IMDBIDShort"] = searchCriteria.ImdbId;
|
||||
variables[".Query.TVDBID"] = searchCriteria.TvdbId?.ToString() ?? null;
|
||||
@@ -95,6 +101,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
variables[".Query.TVRageID"] = searchCriteria.RId?.ToString() ?? null;
|
||||
variables[".Query.TVMazeID"] = searchCriteria.TvMazeId?.ToString() ?? null;
|
||||
variables[".Query.TraktID"] = searchCriteria.TraktId?.ToString() ?? null;
|
||||
variables[".Query.DoubanID"] = searchCriteria.DoubanId?.ToString() ?? null;
|
||||
variables[".Query.Episode"] = searchCriteria.EpisodeSearchString;
|
||||
|
||||
pageableRequests.Add(GetRequest(variables));
|
||||
@@ -112,6 +119,9 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
variables[".Query.Author"] = searchCriteria.Author;
|
||||
variables[".Query.Title"] = searchCriteria.Title;
|
||||
variables[".Query.Genre"] = searchCriteria.Genre;
|
||||
variables[".Query.Publisher"] = searchCriteria.Publisher;
|
||||
variables[".Query.Year"] = searchCriteria.Year?.ToString() ?? null;
|
||||
|
||||
pageableRequests.Add(GetRequest(variables));
|
||||
|
||||
@@ -343,6 +353,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
Encoding = _encoding
|
||||
};
|
||||
|
||||
requestBuilder.SetCookies(Cookies);
|
||||
|
||||
requestBuilder.Headers.Add("Referer", loginUrl);
|
||||
|
||||
var simpleCaptchaResult = await HttpClient.ExecuteProxiedAsync(requestBuilder.Build(), Definition);
|
||||
|
||||
@@ -22,9 +22,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
ExtraFieldData = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
[FieldDefinition(0, Hidden = HiddenType.Hidden)]
|
||||
public string DefinitionFile { get; set; }
|
||||
|
||||
public Dictionary<string, object> ExtraFieldData { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Definitions.Avistaz;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class CinemaZ : AvistazBase
|
||||
{
|
||||
public override string Name => "CinemaZ";
|
||||
public override string[] IndexerUrls => new string[] { "https://cinemaz.to/" };
|
||||
public override string Description => "CinemaZ (EuTorrents) is a Private Torrent Tracker for FOREIGN NON-ASIAN MOVIES.";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public CinemaZ(IIndexerRepository indexerRepository, IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(indexerRepository, httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new AvistazRequestGenerator()
|
||||
{
|
||||
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, TvSearchParam.ImdbId
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies);
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesUHD);
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesHD);
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesSD);
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TV);
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVUHD);
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVHD);
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVSD);
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio);
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Definitions.Avistaz;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class ExoticaZ : AvistazBase
|
||||
{
|
||||
public override string Name => "ExoticaZ";
|
||||
public override string[] IndexerUrls => new string[] { "https://exoticaz.to/" };
|
||||
public override string Description => "ExoticaZ (YourExotic) is a Private Torrent Tracker for 3X";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public ExoticaZ(IIndexerRepository indexerRepository, IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(indexerRepository, httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new AvistazRequestGenerator()
|
||||
{
|
||||
Settings = Settings,
|
||||
HttpClient = _httpClient,
|
||||
Logger = _logger,
|
||||
Capabilities = Capabilities,
|
||||
};
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new ExoticaZParser(Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities();
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.XXXx264, "Video Clip");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.XXXPack, "Video Pack");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.XXXPack, "Siterip Pack");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.XXXPack, "Pornstar Pack");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.XXXDVD, "DVD");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.XXXx264, "BluRay");
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.XXXImageSet, "Photo Pack");
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.XXXImageSet, "Books & Magazines");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class ExoticaZParser : AvistazParser
|
||||
{
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
public ExoticaZParser(IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
protected override List<IndexerCategory> ParseCategories(AvistazRelease row)
|
||||
{
|
||||
var cat = row.Category;
|
||||
|
||||
return cat.SelectMany(c => _categories.MapTrackerCatToNewznab(c.Key)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.FileList
|
||||
@@ -9,17 +10,13 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
public class FileList : TorrentIndexerBase<FileListSettings>
|
||||
{
|
||||
public override string Name => "FileList.io";
|
||||
public override string[] IndexerUrls => new string[] { "https://filelist.io" };
|
||||
public override string Description => "FileList (FL) is a ROMANIAN Private Torrent Tracker for 0DAY / GENERAL";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override bool SupportsRss => true;
|
||||
public override bool SupportsSearch => true;
|
||||
public override bool SupportsRedirect => true;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public FileList(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public FileList(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -32,61 +29,5 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
{
|
||||
return new FileListParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
},
|
||||
Flags = new List<IndexerFlag>
|
||||
{
|
||||
IndexerFlag.FreeLeech
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesSD, "Filme SD");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.MoviesDVD, "Filme DVD");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.MoviesForeign, "Filme DVD-RO");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.MoviesHD, "Filme HD");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.AudioLossless, "FLAC");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.MoviesUHD, "Filme 4K");
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.XXX, "XXX");
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.PC, "Programe");
|
||||
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.PCGames, "Jocuri PC");
|
||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.Console, "Jocuri Console");
|
||||
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.Audio, "Audio");
|
||||
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.AudioVideo, "Videoclip");
|
||||
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.TVSport, "Sport");
|
||||
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.TV, "Desene");
|
||||
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.Books, "Docs");
|
||||
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.PC, "Linux");
|
||||
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.Other, "Diverse");
|
||||
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.MoviesForeign, "Filme HD-RO");
|
||||
caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.MoviesBluRay, "Filme Blu-Ray");
|
||||
caps.Categories.AddCategoryMapping(21, NewznabStandardCategory.TVHD, "Seriale HD");
|
||||
caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.PCMobileOther, "Mobile");
|
||||
caps.Categories.AddCategoryMapping(23, NewznabStandardCategory.TVSD, "Seriale SD");
|
||||
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.TVAnime, "Anime");
|
||||
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.Movies3D, "Filme 3D");
|
||||
caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.MoviesBluRay, "Filme 4K Blu-Ray");
|
||||
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.TVUHD, "Seriale 4K");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Gazelle
|
||||
@@ -16,14 +17,14 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
public override bool SupportsRss => true;
|
||||
public override bool SupportsSearch => true;
|
||||
public override int PageSize => 50;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public Gazelle(IIndexerHttpClient httpClient,
|
||||
IEventAggregator eventAggregator,
|
||||
IIndexerStatusService indexerStatusService,
|
||||
IIndexerDefinitionUpdateService definitionService,
|
||||
IConfigService configService,
|
||||
Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -43,13 +44,6 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
return new GazelleParser(Settings, Capabilities);
|
||||
}
|
||||
|
||||
protected virtual IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities();
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
protected override async Task DoLogin()
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(LoginUrl)
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
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 System.Threading.Tasks;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
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.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
@@ -23,22 +25,16 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class GazelleGames : TorrentIndexerBase<GazelleGamesSettings>
|
||||
{
|
||||
public override string Name => "GazelleGames";
|
||||
public override string[] IndexerUrls => new string[] { "https://gazellegames.net/" };
|
||||
public override string Description => "GazelleGames (GGn) is a Private Torrent Tracker for GAMES";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public GazelleGames(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public GazelleGames(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new GazelleGamesRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
||||
return new GazelleGamesRequestGenerator() { Settings = Settings, Capabilities = Capabilities, HttpClient = _httpClient };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
@@ -46,143 +42,10 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
return new GazelleGamesParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override IDictionary<string, string> GetCookies()
|
||||
protected override async Task Test(List<ValidationFailure> failures)
|
||||
{
|
||||
return CookieUtil.CookieHeaderToDictionary(Settings.Cookie);
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
{
|
||||
if (httpResponse.HasHttpRedirect && httpResponse.RedirectUrl.EndsWith("login.php"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping("Mac", NewznabStandardCategory.ConsoleOther, "Mac");
|
||||
caps.Categories.AddCategoryMapping("iOS", NewznabStandardCategory.PCMobileiOS, "iOS");
|
||||
caps.Categories.AddCategoryMapping("Apple Bandai Pippin", NewznabStandardCategory.ConsoleOther, "Apple Bandai Pippin");
|
||||
|
||||
caps.Categories.AddCategoryMapping("Android", NewznabStandardCategory.PCMobileAndroid, "Android");
|
||||
|
||||
caps.Categories.AddCategoryMapping("DOS", NewznabStandardCategory.PCGames, "DOS");
|
||||
caps.Categories.AddCategoryMapping("Windows", NewznabStandardCategory.PCGames, "Windows");
|
||||
caps.Categories.AddCategoryMapping("Xbox", NewznabStandardCategory.ConsoleXBox, "Xbox");
|
||||
caps.Categories.AddCategoryMapping("Xbox 360", NewznabStandardCategory.ConsoleXBox360, "Xbox 360");
|
||||
|
||||
caps.Categories.AddCategoryMapping("Game Boy", NewznabStandardCategory.ConsoleOther, "Game Boy");
|
||||
caps.Categories.AddCategoryMapping("Game Boy Advance", NewznabStandardCategory.ConsoleOther, "Game Boy Advance");
|
||||
caps.Categories.AddCategoryMapping("Game Boy Color", NewznabStandardCategory.ConsoleOther, "Game Boy Color");
|
||||
caps.Categories.AddCategoryMapping("NES", NewznabStandardCategory.ConsoleOther, "NES");
|
||||
caps.Categories.AddCategoryMapping("Nintendo 64", NewznabStandardCategory.ConsoleOther, "Nintendo 64");
|
||||
caps.Categories.AddCategoryMapping("Nintendo 3DS", NewznabStandardCategory.ConsoleOther, "Nintendo 3DS");
|
||||
caps.Categories.AddCategoryMapping("New Nintendo 3DS", NewznabStandardCategory.ConsoleOther, "New Nintendo 3DS");
|
||||
caps.Categories.AddCategoryMapping("Nintendo DS", NewznabStandardCategory.ConsoleNDS, "Nintendo DS");
|
||||
caps.Categories.AddCategoryMapping("Nintendo GameCube", NewznabStandardCategory.ConsoleOther, "Nintendo GameCube");
|
||||
caps.Categories.AddCategoryMapping("Pokemon Mini", NewznabStandardCategory.ConsoleOther, "Pokemon Mini");
|
||||
caps.Categories.AddCategoryMapping("SNES", NewznabStandardCategory.ConsoleOther, "SNES");
|
||||
caps.Categories.AddCategoryMapping("Virtual Boy", NewznabStandardCategory.ConsoleOther, "Virtual Boy");
|
||||
caps.Categories.AddCategoryMapping("Wii", NewznabStandardCategory.ConsoleWii, "Wii");
|
||||
caps.Categories.AddCategoryMapping("Wii U", NewznabStandardCategory.ConsoleWiiU, "Wii U");
|
||||
|
||||
caps.Categories.AddCategoryMapping("PlayStation 1", NewznabStandardCategory.ConsoleOther, "PlayStation 1");
|
||||
caps.Categories.AddCategoryMapping("PlayStation 2", NewznabStandardCategory.ConsoleOther, "PlayStation 2");
|
||||
caps.Categories.AddCategoryMapping("PlayStation 3", NewznabStandardCategory.ConsolePS3, "PlayStation 3");
|
||||
caps.Categories.AddCategoryMapping("PlayStation 4", NewznabStandardCategory.ConsolePS4, "PlayStation 4");
|
||||
caps.Categories.AddCategoryMapping("PlayStation Portable", NewznabStandardCategory.ConsolePSP, "PlayStation Portable");
|
||||
caps.Categories.AddCategoryMapping("PlayStation Vita", NewznabStandardCategory.ConsolePSVita, "PlayStation Vita");
|
||||
|
||||
caps.Categories.AddCategoryMapping("Dreamcast", NewznabStandardCategory.ConsoleOther, "Dreamcast");
|
||||
caps.Categories.AddCategoryMapping("Game Gear", NewznabStandardCategory.ConsoleOther, "Game Gear");
|
||||
caps.Categories.AddCategoryMapping("Master System", NewznabStandardCategory.ConsoleOther, "Master System");
|
||||
caps.Categories.AddCategoryMapping("Mega Drive", NewznabStandardCategory.ConsoleOther, "Mega Drive");
|
||||
caps.Categories.AddCategoryMapping("Pico", NewznabStandardCategory.ConsoleOther, "Pico");
|
||||
caps.Categories.AddCategoryMapping("Saturn", NewznabStandardCategory.ConsoleOther, "Saturn");
|
||||
caps.Categories.AddCategoryMapping("SG-1000", NewznabStandardCategory.ConsoleOther, "SG-1000");
|
||||
|
||||
caps.Categories.AddCategoryMapping("Atari 2600", NewznabStandardCategory.ConsoleOther, "Atari 2600");
|
||||
caps.Categories.AddCategoryMapping("Atari 5200", NewznabStandardCategory.ConsoleOther, "Atari 5200");
|
||||
caps.Categories.AddCategoryMapping("Atari 7800", NewznabStandardCategory.ConsoleOther, "Atari 7800");
|
||||
caps.Categories.AddCategoryMapping("Atari Jaguar", NewznabStandardCategory.ConsoleOther, "Atari Jaguar");
|
||||
caps.Categories.AddCategoryMapping("Atari Lynx", NewznabStandardCategory.ConsoleOther, "Atari Lynx");
|
||||
caps.Categories.AddCategoryMapping("Atari ST", NewznabStandardCategory.ConsoleOther, "Atari ST");
|
||||
|
||||
caps.Categories.AddCategoryMapping("Amstrad CPC", NewznabStandardCategory.ConsoleOther, "Amstrad CPC");
|
||||
|
||||
caps.Categories.AddCategoryMapping("ZX Spectrum", NewznabStandardCategory.ConsoleOther, "ZX Spectrum");
|
||||
|
||||
caps.Categories.AddCategoryMapping("MSX", NewznabStandardCategory.ConsoleOther, "MSX");
|
||||
caps.Categories.AddCategoryMapping("MSX 2", NewznabStandardCategory.ConsoleOther, "MSX 2");
|
||||
|
||||
caps.Categories.AddCategoryMapping("Game.com", NewznabStandardCategory.ConsoleOther, "Game.com");
|
||||
caps.Categories.AddCategoryMapping("Gizmondo", NewznabStandardCategory.ConsoleOther, "Gizmondo");
|
||||
|
||||
caps.Categories.AddCategoryMapping("V.Smile", NewznabStandardCategory.ConsoleOther, "V.Smile");
|
||||
caps.Categories.AddCategoryMapping("CreatiVision", NewznabStandardCategory.ConsoleOther, "CreatiVision");
|
||||
|
||||
caps.Categories.AddCategoryMapping("Board Game", NewznabStandardCategory.ConsoleOther, "Board Game");
|
||||
caps.Categories.AddCategoryMapping("Card Game", NewznabStandardCategory.ConsoleOther, "Card Game");
|
||||
caps.Categories.AddCategoryMapping("Miniature Wargames", NewznabStandardCategory.ConsoleOther, "Miniature Wargames");
|
||||
caps.Categories.AddCategoryMapping("Pen and Paper RPG", NewznabStandardCategory.ConsoleOther, "Pen and Paper RPG");
|
||||
|
||||
caps.Categories.AddCategoryMapping("3DO", NewznabStandardCategory.ConsoleOther, "3DO");
|
||||
caps.Categories.AddCategoryMapping("Bandai WonderSwan", NewznabStandardCategory.ConsoleOther, "Bandai WonderSwan");
|
||||
caps.Categories.AddCategoryMapping("Bandai WonderSwan Color", NewznabStandardCategory.ConsoleOther, "Bandai WonderSwan Color");
|
||||
caps.Categories.AddCategoryMapping("Casio Loopy", NewznabStandardCategory.ConsoleOther, "Casio Loopy");
|
||||
caps.Categories.AddCategoryMapping("Casio PV-1000", NewznabStandardCategory.ConsoleOther, "Casio PV-1000");
|
||||
caps.Categories.AddCategoryMapping("Colecovision", NewznabStandardCategory.ConsoleOther, "Colecovision");
|
||||
caps.Categories.AddCategoryMapping("Commodore 64", NewznabStandardCategory.ConsoleOther, "Commodore 64");
|
||||
caps.Categories.AddCategoryMapping("Commodore 128", NewznabStandardCategory.ConsoleOther, "Commodore 128");
|
||||
caps.Categories.AddCategoryMapping("Commodore Amiga", NewznabStandardCategory.ConsoleOther, "Commodore Amiga");
|
||||
caps.Categories.AddCategoryMapping("Commodore Plus-4", NewznabStandardCategory.ConsoleOther, "Commodore Plus-4");
|
||||
caps.Categories.AddCategoryMapping("Commodore VIC-20", NewznabStandardCategory.ConsoleOther, "Commodore VIC-20");
|
||||
caps.Categories.AddCategoryMapping("Emerson Arcadia 2001", NewznabStandardCategory.ConsoleOther, "Emerson Arcadia 2001");
|
||||
caps.Categories.AddCategoryMapping("Entex Adventure Vision", NewznabStandardCategory.ConsoleOther, "Entex Adventure Vision");
|
||||
caps.Categories.AddCategoryMapping("Epoch Super Casette Vision", NewznabStandardCategory.ConsoleOther, "Epoch Super Casette Vision");
|
||||
caps.Categories.AddCategoryMapping("Fairchild Channel F", NewznabStandardCategory.ConsoleOther, "Fairchild Channel F");
|
||||
caps.Categories.AddCategoryMapping("Funtech Super Acan", NewznabStandardCategory.ConsoleOther, "Funtech Super Acan");
|
||||
caps.Categories.AddCategoryMapping("GamePark GP32", NewznabStandardCategory.ConsoleOther, "GamePark GP32");
|
||||
caps.Categories.AddCategoryMapping("General Computer Vectrex", NewznabStandardCategory.ConsoleOther, "General Computer Vectrex");
|
||||
caps.Categories.AddCategoryMapping("Interactive DVD", NewznabStandardCategory.ConsoleOther, "Interactive DVD");
|
||||
caps.Categories.AddCategoryMapping("Linux", NewznabStandardCategory.ConsoleOther, "Linux");
|
||||
caps.Categories.AddCategoryMapping("Hartung Game Master", NewznabStandardCategory.ConsoleOther, "Hartung Game Master");
|
||||
caps.Categories.AddCategoryMapping("Magnavox-Phillips Odyssey", NewznabStandardCategory.ConsoleOther, "Magnavox-Phillips Odyssey");
|
||||
caps.Categories.AddCategoryMapping("Mattel Intellivision", NewznabStandardCategory.ConsoleOther, "Mattel Intellivision");
|
||||
caps.Categories.AddCategoryMapping("Memotech MTX", NewznabStandardCategory.ConsoleOther, "Memotech MTX");
|
||||
caps.Categories.AddCategoryMapping("Miles Gordon Sam Coupe", NewznabStandardCategory.ConsoleOther, "Miles Gordon Sam Coupe");
|
||||
caps.Categories.AddCategoryMapping("NEC PC-98", NewznabStandardCategory.ConsoleOther, "NEC PC-98");
|
||||
caps.Categories.AddCategoryMapping("NEC PC-FX", NewznabStandardCategory.ConsoleOther, "NEC PC-FX");
|
||||
caps.Categories.AddCategoryMapping("NEC SuperGrafx", NewznabStandardCategory.ConsoleOther, "NEC SuperGrafx");
|
||||
caps.Categories.AddCategoryMapping("NEC TurboGrafx-16", NewznabStandardCategory.ConsoleOther, "NEC TurboGrafx-16");
|
||||
caps.Categories.AddCategoryMapping("Nokia N-Gage", NewznabStandardCategory.ConsoleOther, "Nokia N-Gage");
|
||||
caps.Categories.AddCategoryMapping("Ouya", NewznabStandardCategory.ConsoleOther, "Ouya");
|
||||
caps.Categories.AddCategoryMapping("Philips Videopac+", NewznabStandardCategory.ConsoleOther, "Philips Videopac+");
|
||||
caps.Categories.AddCategoryMapping("Phone/PDA", NewznabStandardCategory.ConsoleOther, "Phone/PDA");
|
||||
caps.Categories.AddCategoryMapping("RCA Studio II", NewznabStandardCategory.ConsoleOther, "RCA Studio II");
|
||||
caps.Categories.AddCategoryMapping("Sharp X1", NewznabStandardCategory.ConsoleOther, "Sharp X1");
|
||||
caps.Categories.AddCategoryMapping("Sharp X68000", NewznabStandardCategory.ConsoleOther, "Sharp X68000");
|
||||
caps.Categories.AddCategoryMapping("SNK Neo Geo", NewznabStandardCategory.ConsoleOther, "SNK Neo Geo");
|
||||
caps.Categories.AddCategoryMapping("SNK Neo Geo Pocket", NewznabStandardCategory.ConsoleOther, "SNK Neo Geo Pocket");
|
||||
caps.Categories.AddCategoryMapping("Taito Type X", NewznabStandardCategory.ConsoleOther, "Taito Type X");
|
||||
caps.Categories.AddCategoryMapping("Tandy Color Computer", NewznabStandardCategory.ConsoleOther, "Tandy Color Computer");
|
||||
caps.Categories.AddCategoryMapping("Tangerine Oric", NewznabStandardCategory.ConsoleOther, "Tangerine Oric");
|
||||
caps.Categories.AddCategoryMapping("Thomson MO5", NewznabStandardCategory.ConsoleOther, "Thomson MO5");
|
||||
caps.Categories.AddCategoryMapping("Watara Supervision", NewznabStandardCategory.ConsoleOther, "Watara Supervision");
|
||||
caps.Categories.AddCategoryMapping("Retro - Other", NewznabStandardCategory.ConsoleOther, "Retro - Other");
|
||||
|
||||
caps.Categories.AddCategoryMapping("OST", NewznabStandardCategory.AudioOther, "OST");
|
||||
caps.Categories.AddCategoryMapping("Applications", NewznabStandardCategory.PC0day, "Applications");
|
||||
caps.Categories.AddCategoryMapping("E-Books", NewznabStandardCategory.BooksEBook, "E-Books");
|
||||
|
||||
return caps;
|
||||
((GazelleGamesRequestGenerator)GetRequestGenerator()).FetchPasskey();
|
||||
await base.Test(failures);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,47 +53,17 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public GazelleGamesSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
public IIndexerHttpClient HttpClient { get; set; }
|
||||
|
||||
public GazelleGamesRequestGenerator()
|
||||
{
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
|
||||
{
|
||||
var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/'));
|
||||
|
||||
var searchString = term;
|
||||
|
||||
var searchType = Settings.SearchGroupNames ? "groupname" : "searchstr";
|
||||
|
||||
var queryCollection = new NameValueCollection
|
||||
{
|
||||
{ searchType, searchString },
|
||||
{ "order_by", "time" },
|
||||
{ "order_way", "desc" },
|
||||
{ "action", "basic" },
|
||||
{ "searchsubmit", "1" }
|
||||
};
|
||||
|
||||
var i = 0;
|
||||
|
||||
foreach (var cat in Capabilities.Categories.MapTorznabCapsToTrackers(categories))
|
||||
{
|
||||
queryCollection.Add($"artistcheck[{i++}]", cat);
|
||||
}
|
||||
|
||||
searchUrl += "?" + queryCollection.GetQueryString();
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetRequest(GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories)));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -239,7 +72,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetRequest(GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories)));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -248,7 +81,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetRequest(GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories)));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -257,7 +90,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetRequest(GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories)));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -266,11 +99,67 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
pageableRequests.Add(GetRequest(GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories)));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public void FetchPasskey()
|
||||
{
|
||||
// GET on index for the passkey
|
||||
var request = RequestBuilder().Resource("api.php?request=quick_user").Build();
|
||||
var indexResponse = HttpClient.Execute(request);
|
||||
var index = Json.Deserialize<GazelleGamesUserResponse>(indexResponse.Content);
|
||||
if (index == null ||
|
||||
string.IsNullOrWhiteSpace(index.Status) ||
|
||||
index.Status != "success" ||
|
||||
string.IsNullOrWhiteSpace(index.Response.PassKey))
|
||||
{
|
||||
throw new Exception("Failed to authenticate with GazelleGames.");
|
||||
}
|
||||
|
||||
// Set passkey on settings so it can be used to generate the download URL
|
||||
Settings.Passkey = index.Response.PassKey;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetRequest(string parameters)
|
||||
{
|
||||
var req = RequestBuilder()
|
||||
.Resource($"api.php?{parameters}")
|
||||
.Build();
|
||||
|
||||
yield return new IndexerRequest(req);
|
||||
}
|
||||
|
||||
private HttpRequestBuilder RequestBuilder()
|
||||
{
|
||||
return new HttpRequestBuilder($"{Settings.BaseUrl.Trim().TrimEnd('/')}")
|
||||
.Accept(HttpAccept.Json)
|
||||
.SetHeader("X-API-Key", Settings.Apikey);
|
||||
}
|
||||
|
||||
private string GetBasicSearchParameters(string searchTerm, int[] categories)
|
||||
{
|
||||
var parameters = "request=search&search_type=torrents&empty_groups=filled&order_by=time&order_way=desc";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchTerm))
|
||||
{
|
||||
var searchType = Settings.SearchGroupNames ? "groupname" : "searchstr";
|
||||
|
||||
parameters += string.Format("&{1}={0}", searchTerm.Replace(".", " "), searchType);
|
||||
}
|
||||
|
||||
if (categories != null)
|
||||
{
|
||||
foreach (var cat in Capabilities.Categories.MapTorznabCapsToTrackers(categories))
|
||||
{
|
||||
parameters += string.Format("&artistcheck[]={0}", cat);
|
||||
}
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
@@ -290,117 +179,183 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
var rowsSelector = ".torrent_table > tbody > tr";
|
||||
|
||||
var searchResultParser = new HtmlParser();
|
||||
var searchResultDocument = searchResultParser.ParseDocument(indexerResponse.Content);
|
||||
var rows = searchResultDocument.QuerySelectorAll(rowsSelector);
|
||||
|
||||
var stickyGroup = false;
|
||||
string categoryStr;
|
||||
ICollection<IndexerCategory> groupCategory = null;
|
||||
string groupTitle = null;
|
||||
|
||||
foreach (var row in rows)
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
if (row.ClassList.Contains("torrent"))
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
||||
}
|
||||
|
||||
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
|
||||
}
|
||||
|
||||
var jsonResponse = new HttpResponse<GazelleGamesResponse>(indexerResponse.HttpResponse);
|
||||
if (jsonResponse.Resource.Status != "success" ||
|
||||
string.IsNullOrWhiteSpace(jsonResponse.Resource.Status) ||
|
||||
jsonResponse.Resource.Response == null)
|
||||
{
|
||||
return torrentInfos;
|
||||
}
|
||||
|
||||
foreach (var result in jsonResponse.Resource.Response)
|
||||
{
|
||||
Dictionary<string, GazelleGamesTorrent> torrents;
|
||||
|
||||
try
|
||||
{
|
||||
torrents = ((JObject)result.Value.Torrents).ToObject<Dictionary<string, GazelleGamesTorrent>>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// garbage rows
|
||||
continue;
|
||||
}
|
||||
else if (row.ClassList.Contains("group"))
|
||||
|
||||
if (result.Value.Torrents != null)
|
||||
{
|
||||
stickyGroup = row.ClassList.Contains("sticky");
|
||||
var dispalyname = row.QuerySelector("#displayname");
|
||||
var qCat = row.QuerySelector("td.cats_col > div");
|
||||
categoryStr = qCat.GetAttribute("title");
|
||||
var qArtistLink = dispalyname.QuerySelector("#groupplatform > a");
|
||||
if (qArtistLink != null)
|
||||
var categories = result.Value.Artists.Select(a => a.Name);
|
||||
|
||||
foreach (var torrent in torrents)
|
||||
{
|
||||
categoryStr = ParseUtil.GetArgumentFromQueryString(qArtistLink.GetAttribute("href"), "artistname");
|
||||
var id = int.Parse(torrent.Key);
|
||||
|
||||
var infoUrl = GetInfoUrl(result.Key, id);
|
||||
|
||||
var release = new TorrentInfo()
|
||||
{
|
||||
Guid = infoUrl,
|
||||
Title = torrent.Value.ReleaseTitle,
|
||||
Files = torrent.Value.FileCount,
|
||||
Grabs = torrent.Value.Snatched,
|
||||
Size = long.Parse(torrent.Value.Size),
|
||||
DownloadUrl = GetDownloadUrl(id),
|
||||
InfoUrl = infoUrl,
|
||||
Seeders = torrent.Value.Seeders,
|
||||
Categories = _categories.MapTrackerCatDescToNewznab(categories.FirstOrDefault()),
|
||||
Peers = torrent.Value.Leechers + torrent.Value.Seeders,
|
||||
PublishDate = torrent.Value.Time.ToUniversalTime(),
|
||||
DownloadVolumeFactor = torrent.Value.FreeTorrent == GazelleGamesFreeTorrent.FreeLeech || torrent.Value.FreeTorrent == GazelleGamesFreeTorrent.Neutral || torrent.Value.LowSeedFL ? 0 : 1,
|
||||
UploadVolumeFactor = torrent.Value.FreeTorrent == GazelleGamesFreeTorrent.Neutral ? 0 : 1
|
||||
};
|
||||
|
||||
torrentInfos.Add(release);
|
||||
}
|
||||
|
||||
groupCategory = _categories.MapTrackerCatToNewznab(categoryStr);
|
||||
|
||||
var qDetailsLink = dispalyname.QuerySelector("#groupname > a");
|
||||
groupTitle = qDetailsLink.TextContent;
|
||||
}
|
||||
else if (row.ClassList.Contains("group_torrent"))
|
||||
{
|
||||
if (row.QuerySelector("td.edition_info") != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var sizeString = row.QuerySelector("td:nth-child(4)").TextContent;
|
||||
if (string.IsNullOrEmpty(sizeString))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var qDetailsLink = row.QuerySelector("a[href^=\"torrents.php?id=\"]");
|
||||
var title = qDetailsLink.TextContent.Replace(", Freeleech!", "").Replace(", Neutral Leech!", "");
|
||||
|
||||
//if (stickyGroup && (query.ImdbID == null || !NewznabStandardCategory.MovieSearchImdbAvailable) && !query.MatchQueryStringAND(title)) // AND match for sticky releases
|
||||
//{
|
||||
// continue;
|
||||
//}
|
||||
var qDescription = qDetailsLink.QuerySelector("span.torrent_info_tags");
|
||||
var qDLLink = row.QuerySelector("a[href^=\"torrents.php?action=download\"]");
|
||||
var qTime = row.QuerySelector("span.time");
|
||||
var qGrabs = row.QuerySelector("td:nth-child(5)");
|
||||
var qSeeders = row.QuerySelector("td:nth-child(6)");
|
||||
var qLeechers = row.QuerySelector("td:nth-child(7)");
|
||||
var qFreeLeech = row.QuerySelector("strong.freeleech_label");
|
||||
var qNeutralLeech = row.QuerySelector("strong.neutralleech_label");
|
||||
var time = qTime.GetAttribute("title");
|
||||
var link = _settings.BaseUrl + qDLLink.GetAttribute("href");
|
||||
var seeders = ParseUtil.CoerceInt(qSeeders.TextContent);
|
||||
var publishDate = DateTime.SpecifyKind(
|
||||
DateTime.ParseExact(time, "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture),
|
||||
DateTimeKind.Unspecified).ToLocalTime();
|
||||
var details = _settings.BaseUrl + qDetailsLink.GetAttribute("href");
|
||||
var grabs = ParseUtil.CoerceInt(qGrabs.TextContent);
|
||||
var leechers = ParseUtil.CoerceInt(qLeechers.TextContent);
|
||||
var size = ParseUtil.GetBytes(sizeString);
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 288000, //80 hours
|
||||
Categories = groupCategory,
|
||||
PublishDate = publishDate,
|
||||
Size = size,
|
||||
InfoUrl = details,
|
||||
DownloadUrl = link,
|
||||
Guid = link,
|
||||
Grabs = grabs,
|
||||
Seeders = seeders,
|
||||
Peers = leechers + seeders,
|
||||
Title = title,
|
||||
Description = qDescription?.TextContent,
|
||||
UploadVolumeFactor = qNeutralLeech is null ? 1 : 0,
|
||||
DownloadVolumeFactor = qFreeLeech != null || qNeutralLeech != null ? 0 : 1
|
||||
};
|
||||
|
||||
torrentInfos.Add(release);
|
||||
}
|
||||
}
|
||||
|
||||
return torrentInfos.ToArray();
|
||||
// order by date
|
||||
return
|
||||
torrentInfos
|
||||
.OrderByDescending(o => o.PublishDate)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private string GetDownloadUrl(int torrentId)
|
||||
{
|
||||
// AuthKey is required but not checked, just pass in a dummy variable
|
||||
// to avoid having to track authkey, which is randomly cycled
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/torrents.php")
|
||||
.AddQueryParam("action", "download")
|
||||
.AddQueryParam("id", torrentId)
|
||||
.AddQueryParam("authkey", "prowlarr")
|
||||
.AddQueryParam("torrent_pass", _settings.Passkey);
|
||||
|
||||
return url.FullUri;
|
||||
}
|
||||
|
||||
private string GetInfoUrl(string groupId, int torrentId)
|
||||
{
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/torrents.php")
|
||||
.AddQueryParam("id", groupId)
|
||||
.AddQueryParam("torrentid", torrentId);
|
||||
|
||||
return url.FullUri;
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class GazelleGamesSettings : CookieTorrentBaseSettings
|
||||
public class GazelleGamesSettingsValidator : AbstractValidator<GazelleGamesSettings>
|
||||
{
|
||||
public GazelleGamesSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.Apikey).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class GazelleGamesSettings : NoAuthTorrentBaseSettings
|
||||
{
|
||||
private static readonly GazelleGamesSettingsValidator Validator = new GazelleGamesSettingsValidator();
|
||||
|
||||
public GazelleGamesSettings()
|
||||
{
|
||||
SearchGroupNames = false;
|
||||
Apikey = "";
|
||||
Passkey = "";
|
||||
}
|
||||
|
||||
[FieldDefinition(2, Label = "API Key", HelpText = "API Key from the Site (Found in Settings => Access Settings), Must have User Permissions", Privacy = PrivacyLevel.ApiKey)]
|
||||
public string Apikey { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Search Group Names", Type = FieldType.Checkbox, HelpText = "Search Group Names Only")]
|
||||
public bool SearchGroupNames { get; set; }
|
||||
|
||||
public string Passkey { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
|
||||
public class GazelleGamesResponse
|
||||
{
|
||||
public string Status { get; set; }
|
||||
public Dictionary<string, GazelleGamesGroup> Response { get; set; }
|
||||
}
|
||||
|
||||
public class GazelleGamesGroup
|
||||
{
|
||||
public List<GazelleGamesArtist> Artists { get; set; }
|
||||
public object Torrents { get; set; }
|
||||
}
|
||||
|
||||
public class GazelleGamesArtist
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class GazelleGamesTorrent
|
||||
{
|
||||
public string Size { get; set; }
|
||||
public int? Snatched { get; set; }
|
||||
public int Seeders { get; set; }
|
||||
public int Leechers { get; set; }
|
||||
public string ReleaseTitle { get; set; }
|
||||
public DateTime Time { get; set; }
|
||||
public int FileCount { get; set; }
|
||||
public GazelleGamesFreeTorrent FreeTorrent { get; set; }
|
||||
public bool PersonalFL { get; set; }
|
||||
public bool LowSeedFL { get; set; }
|
||||
}
|
||||
|
||||
public class GazelleGamesUserResponse
|
||||
{
|
||||
public string Status { get; set; }
|
||||
public GazelleGamesUser Response { get; set; }
|
||||
}
|
||||
|
||||
public class GazelleGamesUser
|
||||
{
|
||||
public string PassKey { get; set; }
|
||||
}
|
||||
|
||||
public enum GazelleGamesFreeTorrent
|
||||
{
|
||||
Normal,
|
||||
FreeLeech,
|
||||
Neutral,
|
||||
Either
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.HDBits
|
||||
@@ -9,17 +10,13 @@ namespace NzbDrone.Core.Indexers.HDBits
|
||||
public class HDBits : TorrentIndexerBase<HDBitsSettings>
|
||||
{
|
||||
public override string Name => "HDBits";
|
||||
public override string[] IndexerUrls => new string[] { "https://hdbits.org" };
|
||||
public override string Description => "Best HD Tracker";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
public override bool SupportsRedirect => true;
|
||||
|
||||
public override int PageSize => 30;
|
||||
|
||||
public HDBits(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public HDBits(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -32,31 +29,5 @@ namespace NzbDrone.Core.Indexers.HDBits
|
||||
{
|
||||
return new HDBitsParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.TvdbId
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.Audio, "Audio Track");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVDocumentary, "Documentary");
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.Other, "Misc/Demo");
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies, "Movie");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.Audio, "Music");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVSport, "Sport");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TV, "TV");
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.XXX, "XXX");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -25,17 +26,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class HDSpace : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
||||
{
|
||||
public override string Name => "HD-Space";
|
||||
public override string[] IndexerUrls => new string[] { "https://hd-space.org/" };
|
||||
private string LoginUrl => Settings.BaseUrl + "index.php?page=login";
|
||||
public override string Description => "HD-Space (HDS) is a Private Torrent Tracker for HD MOVIES / TV";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public HDSpace(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public HDSpace(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IIndexerDefinitionUpdateService definitionService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, definitionService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -102,47 +97,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.MoviesBluRay, "Movie / Blu-ray");
|
||||
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.MoviesHD, "Movie / 1080p");
|
||||
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.MoviesHD, "Movie / 720p");
|
||||
caps.Categories.AddCategoryMapping(40, NewznabStandardCategory.MoviesHD, "Movie / Remux");
|
||||
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.MoviesHD, "Movie / HD-DVD");
|
||||
caps.Categories.AddCategoryMapping(41, NewznabStandardCategory.MoviesUHD, "Movie / 4K UHD");
|
||||
caps.Categories.AddCategoryMapping(21, NewznabStandardCategory.TVHD, "TV Show / 720p HDTV");
|
||||
caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.TVHD, "TV Show / 1080p HDTV");
|
||||
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.TVDocumentary, "Documentary / 720p");
|
||||
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.TVDocumentary, "Documentary / 1080p");
|
||||
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.TVAnime, "Animation / 720p");
|
||||
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.TVAnime, "Animation / 1080p");
|
||||
caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.AudioLossless, "Music / HQ Audio");
|
||||
caps.Categories.AddCategoryMapping(31, NewznabStandardCategory.AudioVideo, "Music / Videos");
|
||||
caps.Categories.AddCategoryMapping(33, NewznabStandardCategory.XXX, "XXX / 720p");
|
||||
caps.Categories.AddCategoryMapping(34, NewznabStandardCategory.XXX, "XXX / 1080p");
|
||||
caps.Categories.AddCategoryMapping(36, NewznabStandardCategory.MoviesOther, "Trailers");
|
||||
caps.Categories.AddCategoryMapping(37, NewznabStandardCategory.PC, "Software");
|
||||
caps.Categories.AddCategoryMapping(38, NewznabStandardCategory.Other, "Others");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class HDSpaceRequestGenerator : IIndexerRequestGenerator
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user