diff --git a/.gitignore b/.gitignore index 57a1574c4f5ccee529831e15bdf01c373ea5a845..eb5a9e709eafab6f77d6104d3d957674ec578918 100644 --- a/.gitignore +++ b/.gitignore @@ -194,3 +194,5 @@ FakesAssemblies/ # Visual Studio 6 workspace options file *.opt +/Build.mono +/Build.windows diff --git a/Build.bat b/Build.bat index 320e860a81477df3b4694a8302337e6c99cf1a5f..7f9b4c60c443f5e9922556a5b6beae9a071e64a8 100644 --- a/Build.bat +++ b/Build.bat @@ -1,21 +1,32 @@ -rmdir /s /q build +rmdir /s /q build.windows +rmdir /s /q build.mono rmdir /s /q Output cd src -Msbuild Jackett.sln /t:Clean,Build /p:Configuration=Release +Msbuild Jackett.sln /t:Clean,Build /p:Configuration=Release /verbosity:minimal cd .. -xcopy src\Jackett.Console\bin\Release Build\ /e /y -copy /Y src\Jackett.Service\bin\Release\JackettService.exe build\JackettService.exe -copy /Y src\Jackett.Service\bin\Release\JackettService.exe.config build\JackettService.exe.config -copy /Y src\Jackett.Tray\bin\Release\JackettTray.exe build\JackettTray.exe -copy /Y src\Jackett.Tray\bin\Release\JackettTray.exe.config build\JackettTray.exe.config -copy /Y LICENSE build\LICENSE -copy /Y README.md build\README.md -cd build -del *.pdb -del *.xml +xcopy src\Jackett.Console\bin\Release Build.windows\ /e /y +copy /Y src\Jackett.Service\bin\Release\JackettService.exe build.windows\JackettService.exe +copy /Y src\Jackett.Service\bin\Release\JackettService.exe.config build.windows\JackettService.exe.config +copy /Y src\Jackett.Tray\bin\Release\JackettTray.exe build.windows\JackettTray.exe +copy /Y src\Jackett.Tray\bin\Release\JackettTray.exe.config build.windows\JackettTray.exe.config +copy /Y LICENSE build.windows\LICENSE +copy /Y README.md build.windows\README.md + + +cd src +Msbuild Jackett.sln /t:Clean +call "C:\Program Files (x86)\Mono\bin\xbuild.bat" Jackett.sln /t:Build /p:Configuration=Release /verbosity:minimal cd .. +xcopy src\Jackett.Console\bin\Release Build.mono\ /e /y +copy /Y src\Jackett.Service\bin\Release\JackettService.exe build.mono\JackettService.exe +copy /Y src\Jackett.Service\bin\Release\JackettService.exe.config build.mono\JackettService.exe.config +copy /Y src\Jackett.Tray\bin\Release\JackettTray.exe build.mono\JackettTray.exe +copy /Y src\Jackett.Tray\bin\Release\JackettTray.exe.config build.mono\JackettTray.exe.config +copy /Y LICENSE build.mono\LICENSE +copy /Y README.md build.mono\README.md + iscc Installer.iss diff --git a/Installer.iss b/Installer.iss index 6733eda5211220c436af5392be25fc2e08b179e2..9a3dc63868f8ea067d7b4f8f1a35922957e82dd3 100644 --- a/Installer.iss +++ b/Installer.iss @@ -36,8 +36,8 @@ Name: "windowsService"; Description: "Install as a Windows Service" Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked [Files] -Source: "Build\JackettTray.exe"; DestDir: "{app}"; Flags: ignoreversion -Source: "Build\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "Build.windows\JackettTray.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "Build.windows\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Icons] diff --git a/README.md b/README.md index cf4921a49edd4af8dcda63e6b175430c5f59fb60..7470729cabee1798e6484638630e6612fba362ec 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,21 @@ -# Jackett +## Jackett -Use just about any tracker with Sonarr +This software creates a [Torznab](https://github.com/Sonarr/Sonarr/wiki/Implementing-a-Torznab-indexer) (with [nZEDb](https://github.com/nZEDb/nZEDb/blob/master/docs/newznab_api_specification.txt) category numbering) and [TorrentPotato](https://github.com/RuudBurger/CouchPotatoServer/wiki/Couchpotato-torrent-provider) API server on your machine. Torznab enables software such as [Sonarr](https://sonarr.tv) to access data from your favorite indexers in a similar fashion to rss but with added features such as searching. TorrentPotato is an interface accessible to [CouchPotato](https://couchpota.to/). -### API Access to your favorite trackers -This software creates a [Torznab](https://github.com/Sonarr/Sonarr/wiki/Implementing-a-Torznab-indexer) API server on your machine that any Torznab enabled software can consume. Jackett works as a proxy server: it translates Torznab queries into tracker-site-specific http queries, parses the html response into Torznab results, then sends results back to the requesting software. +Jackett works as a proxy server: it translates Torznab queries into tracker-site-specific http queries, parses the html response into Torznab results, then sends results back to the requesting software which allows for getting recent uploads and performing searches. -Currently [Sonarr](https://sonarr.tv/) is the only software that uses Torznab. [Couchpotato](https://couchpota.to/) will hopefully get Torznab support in the future. +We were previously focused on TV but are working on extending searches to allow for searching other items such as movies and comics. -### Download +#### Download Download in the [Releases page](https://github.com/zone117x/Jackett/releases) -### Supported Systems -* Works on Windows by default -* Works on Linux and OS X using Mono. See instructions below... +#### Supported Systems +* Windows using .NET 4.5 +* Linux and OSX using Mono 4 -### Instructions for Mono - * Install Mono: http://www.mono-project.com/download/ - * *For MoreThanTV & ThePirateBay* install libcurl-dev for your system, [tutorial](http://curl.haxx.se/dlwiz/?type=devel) - * For apt-get systems its simply: `apt-get install libcurl4-openssl-dev` -### Running Jackett - -On Windows the recommended way of running Jackett is to install it as a windows service. When installed as a service the tray icon acts as a way to open/start/stop Jackett. If you opted to not install it as a service then Jackett will run its web server from the tray tool. - -Jackett can also be run from the command line (See --help for switches) using JackettConsole.exe if you would like to see log messages where the service and tray isn't running. On Linux / OSX you would need to run the console using "mono JackettConsole.exe". - - -### Supported Trackers +#### Supported Trackers * [AlphaRatio](https://alpharatio.cc/) * [AnimeBytes](https://animebytes.tv/) * [BakaBT](http://bakabt.me/) @@ -39,29 +27,66 @@ Jackett can also be run from the command line (See --help for switches) using Ja * [Freshon](https://freshon.tv/) * [HD-Space](https://hd-space.org/) * [HD-Torrents.org](https://hd-torrents.org/) + * [Immortalseed.me](http://immortalseed.me) * [IPTorrents](https://iptorrents.com/) * [MoreThan.tv](https://morethan.tv/) * [pretome](https://pretome.info) * [PrivateHD](https://privatehd.to/) - * [RARBG](https://rarbg.com) * [SceneAccess](https://sceneaccess.eu/login) * [SceneTime](https://www.scenetime.com/) * [ShowRSS](https://showrss.info/) * [Strike](https://getstrike.net/) * [T411](http://www.t411.io/) * [The Pirate Bay](https://thepiratebay.se/) + * [TorrentBytes](https://www.torrentbytes.net/) * [TorrentDay](https://torrentday.eu/) * [TorrentLeech](http://www.torrentleech.org/) * [TorrentShack](http://torrentshack.me/) * [Torrentz](https://torrentz.eu/) +#### Installation on Linux/OSX + 1. Install [Mono 4](http://www.mono-project.com/download/) or better + 2. Install libcurl: + * Debian/Ubunutu: apt-get install libcurl-dev + * Redhat/Fedora: yum install libcurl-devel + * Or see the [Curl docs](http://curl.haxx.se/dlwiz/?type=devel). + + + +#### Installation on Windows + +We recommend you install Jackett as a Windows service using the supplied installer. When installed as a service the tray icon acts as a way to open/start/stop Jackett. If you opted to not install it as a service then Jackett will run its web server from the tray tool. + +Jackett can also be run from the command line using JackettConsole.exe if you would like to see log messages (Ensure the server isn't already running from the tray/service). + +#### Installation on Linux/OSX + +Run Jackett using mono with the command "mono JackettConsole.exe". + + + +#### Troubleshooting + +* Command line switches + +You can pass various options when running via the command line, see --help for details. + +* Unable to connect to certain trackers on Linux + +Try running with the "--SSLFix true" if you are on Redhat/Fedora/NNS based libcurl. If the tracker is currently configured try removing it and adding it again. Alternatively try running with a different client via --UseClient (Warning: safecurl just executes curl and your details may be seen from the process list). + +* Enable logging + +You can get additional logging with the switches "-t -l". Please post logs if you are unable to resolve your issue with these switches ensuring to remove your username/password/cookies. + + ### Additional Trackers -Jackett's framework allows me (and any other volunteering dev) to implement just about any new tracker in 15-60 minutes. If you'd like support for a new tracker then feel free to leave a request on the [issues page](https://github.com/zone117x/Jackett/issues) or contact me on IRC (see below). +Jackett's framework allows our team (and any other volunteering dev) to implement new trackers in an hour or two. If you'd like support for a new tracker then feel free to leave a request on the [issues page](https://github.com/zone117x/Jackett/issues) or contact us on IRC (see below). ### Contact & Support -I can be contact on IRC at [irc.freenode.net#jackett](http://webchat.freenode.net/?channels=#jackett) & [irc.freenode.net#sonarr](http://webchat.freenode.net/?channels=#sonarr) +Use the github issues pages or talk to us directly at: [irc.freenode.net#jackett](http://webchat.freenode.net/?channels=#jackett). ### Screenshots - + \ No newline at end of file diff --git a/src/CurlSharp/CurlEasy.cs b/src/CurlSharp/CurlEasy.cs index 6c462834c6c9febee48ff8339228568d339e2449..5519965949de7159b190e266ce307189daf4e686 100644 --- a/src/CurlSharp/CurlEasy.cs +++ b/src/CurlSharp/CurlEasy.cs @@ -118,7 +118,6 @@ namespace CurlSharp private NativeMethods._CurlDebugCallback _pcbDebug; private NativeMethods._CurlIoctlCallback _pcbIoctl; private NativeMethods._CurlProgressCallback _pcbProgress; - private NativeMethods._CurlSslCtxCallback _pcbSslCtx; #endif private CurlDebugCallback _pfCurlDebug; private CurlHeaderCallback _pfCurlHeader; @@ -293,18 +292,6 @@ namespace CurlSharp return setCurlOpt(_curlDebugData, CurlOption.DebugData); } - private IntPtr _curlSslCtxData = IntPtr.Zero; - - /// <summary> - /// Object to pass to OnSslCtxCallback. - /// </summary> - /// <param name="data"></param> - /// <returns></returns> - private CurlCode setSslCtxData(object data) - { - _curlSslCtxData = getHandle(data); - return setCurlOpt(_curlSslCtxData, CurlOption.SslCtxData); - } private IntPtr _curlIoctlData = IntPtr.Zero; @@ -368,17 +355,6 @@ namespace CurlSharp } } - public object SslCtxData - { - get { return _sslContextData; } - set - { - _sslContextData = value; -#if !USE_LIBCURLSHIM - setSslCtxData(value); -#endif - } - } public object IoctlData { @@ -538,11 +514,6 @@ namespace CurlSharp set { setFunctionOptions(CurlOption.IoctlFunction, value); } } - public CurlSslContextCallback SslContextFunction - { - get { return _pfCurlSslContext; } - set { setFunctionOptions(CurlOption.SslCtxFunction, value); } - } public string LastErrorDescription { @@ -1262,7 +1233,6 @@ namespace CurlSharp freeHandle(ref _curlProgressData); freeHandle(ref _curlHeaderData); freeHandle(ref _curlIoctlData); - freeHandle(ref _curlSslCtxData); #endif NativeMethods.curl_easy_cleanup(_pCurl); @@ -1439,9 +1409,6 @@ namespace CurlSharp case CurlOption.HeaderData: _headerData = parameter; break; - case CurlOption.SslCtxData: - _sslContextData = parameter; - break; case CurlOption.IoctlData: _ioctlData = parameter; break; @@ -1593,14 +1560,6 @@ namespace CurlSharp break; } - case CurlOption.SslCtxFunction: - { - var sf = pfn as CurlSslContextCallback; - if (sf == null) - return CurlCode.BadFunctionArgument; - _pfCurlSslContext = sf; - break; - } case CurlOption.IoctlFunction: { @@ -1949,7 +1908,6 @@ namespace CurlSharp _pcbProgress = _curlProgressCallback; _pcbDebug = _curlDebugCallback; _pcbHeader = _curlHeaderCallback; - _pcbSslCtx = _curlSslCtxCallback; _pcbIoctl = _curlIoctlCallback; setLastError(NativeMethods.curl_easy_setopt_cb(_pCurl, CurlOption.WriteFunction, _pcbWrite), @@ -1962,8 +1920,6 @@ namespace CurlSharp CurlOption.HeaderFunction); setLastError(NativeMethods.curl_easy_setopt_cb(_pCurl, CurlOption.DebugFunction, _pcbDebug), CurlOption.DebugFunction); - setLastError(NativeMethods.curl_easy_setopt_cb(_pCurl, CurlOption.SslCtxFunction, _pcbSslCtx), - CurlOption.SslCtxFunction); setLastError(NativeMethods.curl_easy_setopt_cb(_pCurl, CurlOption.IoctlFunction, _pcbIoctl), CurlOption.IoctlFunction); setLastError(NativeMethods.curl_easy_setopt(_pCurl, CurlOption.NoProgress, (IntPtr) 0), @@ -1974,7 +1930,6 @@ namespace CurlSharp setHeaderData(null); setProgressData(null); setDebugData(null); - setSslCtxData(null); setIoctlData(null); #endif } diff --git a/src/CurlSharp/CurlSharp.csproj b/src/CurlSharp/CurlSharp.csproj index 849dc60ab34cb8f614fefe9408e7ea30b359ad2f..de7a139ac4d131c3e8dc09602baf044d1ab94d99 100644 --- a/src/CurlSharp/CurlSharp.csproj +++ b/src/CurlSharp/CurlSharp.csproj @@ -10,6 +10,8 @@ <RootNamespace>CurlSharp</RootNamespace> <AssemblyName>CurlSharp</AssemblyName> <FileAlignment>512</FileAlignment> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <TargetFrameworkProfile /> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -20,6 +22,7 @@ <WarningLevel>4</WarningLevel> <ConsolePause>false</ConsolePause> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <Prefer32Bit>false</Prefer32Bit> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>full</DebugType> @@ -29,6 +32,7 @@ <ConsolePause>false</ConsolePause> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <DefineConstants>LINUX</DefineConstants> + <Prefer32Bit>false</Prefer32Bit> </PropertyGroup> <ItemGroup> <Reference Include="System" /> @@ -74,6 +78,7 @@ <Compile Include="Enums\CurlVersionFeatureBitmask.cs" /> <Compile Include="Callbacks\CurlEasyCallbacks.cs" /> <Compile Include="Callbacks\CurlShareCallbacks.cs" /> + <Compile Include="SSLFix.cs" /> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. diff --git a/src/CurlSharp/Enums/CurlOption.cs b/src/CurlSharp/Enums/CurlOption.cs index 78a7fe3e321442ba18ed99be4b4048da9ae32cb2..2eecac8f6b3cce040edaae50455cd3a200ec52fb 100644 --- a/src/CurlSharp/Enums/CurlOption.cs +++ b/src/CurlSharp/Enums/CurlOption.cs @@ -1075,32 +1075,6 @@ namespace CurlSharp /// </summary> SslCipherList = 10083, - /// <summary> - /// Object reference to pass to the ssl context delegate set by the option - /// <c>SslCtxFunction</c>, this is the pointer you'll get as the - /// second parameter, otherwise <c>null</c>. (Added in 7.11.0) - /// </summary> - SslCtxData = 10109, - - /// <summary> - /// Reference to an <see cref="CurlEasy.CurlSslContextCallback" /> delegate. - /// This delegate gets called by libcurl just before the initialization of - /// an Ssl connection after having processed all other Ssl related options - /// to give a last chance to an application to modify the behaviour of - /// openssl's ssl initialization. The <see cref="CurlSslContext" /> parameter - /// wraps a pointer to an openssl SSL_CTX. If an error is returned no attempt - /// to establish a connection is made and the perform operation will return - /// the error code from this callback function. Set the parm argument with - /// the <c>SslCtxData</c> option. This option was introduced - /// in 7.11.0. - /// <note> - /// To use this properly, a non-trivial amount of knowledge of the openssl - /// libraries is necessary. Using this function allows for example to use - /// openssl callbacks to add additional validation code for certificates, - /// and even to change the actual URI of an HTTPS request. - /// </note> - /// </summary> - SslCtxFunction = 20108, /// <summary> /// Pass an <c>int</c>. Set if we should verify the common name from the diff --git a/src/CurlSharp/SSLFix.cs b/src/CurlSharp/SSLFix.cs new file mode 100644 index 0000000000000000000000000000000000000000..940767f936bf2c54e1ee2aa7a526ea4deb92ccef --- /dev/null +++ b/src/CurlSharp/SSLFix.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CurlSharp +{ + public class SSLFix + { + public const string CipherList = "rsa_aes_128_sha,ecdhe_rsa_aes_256_sha,ecdhe_ecdsa_aes_128_sha"; + } +} diff --git a/src/Jackett.Console/App.config b/src/Jackett.Console/App.config index da396d09005aed9b2a2466bd7d9bf0f17280c0a3..519060c99788e923d52c3703662eda2790a429dc 100644 --- a/src/Jackett.Console/App.config +++ b/src/Jackett.Console/App.config @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <configuration> <startup> - <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> @@ -31,4 +31,4 @@ </dependentAssembly> </assemblyBinding> </runtime> -</configuration> \ No newline at end of file +</configuration> diff --git a/src/Jackett.Console/ConsoleOptions.cs b/src/Jackett.Console/ConsoleOptions.cs index cf05c933c97bc134e63ccc8d91e5099543eb5eaa..cefa8d94fedd177a804bedd7474534ce4b3070da 100644 --- a/src/Jackett.Console/ConsoleOptions.cs +++ b/src/Jackett.Console/ConsoleOptions.cs @@ -24,8 +24,8 @@ namespace Jackett.Console [Option('t', "Tracing", HelpText = "Enable tracing")] public bool Tracing { get; set; } - [Option('c', "UseCurlExec", HelpText = "Execute curl rather than libcurl for all outgoing requests.")] - public bool UseCurlExec { get; set; } + [Option('c', "UseClient", HelpText = "Override web client selection. [automatic(Default)/libcurl/safecurl/httpclient]")] + public string Client { get; set; } [Option('s', "Start", HelpText = "Start the Jacket Windows service (Must be admin)")] public bool StartService { get; set; } @@ -33,7 +33,7 @@ namespace Jackett.Console [Option('k', "Stop", HelpText = "Stop the Jacket Windows service (Must be admin)")] public bool StopService { get; set; } - [Option('x', "ListenPublic", HelpText = "Listen publicly")] + [Option('x', "ListenPublic", HelpText = "Listen publicly [true/false]")] public bool? ListenPublic { get; set; } [Option('h', "Help", HelpText = "Show Help")] @@ -47,5 +47,8 @@ namespace Jackett.Console [Option('m', "MigrateSettings", HelpText = "Migrate settings manually (Must be admin on Windows)")] public bool MigrateSettings { get; set; } + + [Option('f', "SSLFix", HelpText = "Linux Libcurl NSS Missing ECC Ciphers workaround (Use if you can't access some trackers) [true/false].")] + public bool? SSLFix { get; set; } } } diff --git a/src/Jackett.Console/Jackett.Console.csproj b/src/Jackett.Console/Jackett.Console.csproj index 022bbaee6b39423547346cdbd5637d0824cc07c3..11b25b22394dbc96175859f0e7e15f58e89f0c5c 100644 --- a/src/Jackett.Console/Jackett.Console.csproj +++ b/src/Jackett.Console/Jackett.Console.csproj @@ -9,11 +9,12 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>Jackett.Console</RootNamespace> <AssemblyName>JackettConsole</AssemblyName> - <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <NuGetPackageImportStamp> </NuGetPackageImportStamp> + <TargetFrameworkProfile /> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PlatformTarget>AnyCPU</PlatformTarget> @@ -45,22 +46,33 @@ <HintPath>..\packages\Autofac.3.5.2\lib\net40\Autofac.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="Autofac.Integration.Owin"> + <Reference Include="Autofac.Integration.Owin, Version=3.1.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL"> <HintPath>..\packages\Autofac.Owin.3.1.0\lib\net45\Autofac.Integration.Owin.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="Autofac.Integration.WebApi, Version=3.4.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Autofac.WebApi2.3.4.0\lib\net45\Autofac.Integration.WebApi.dll</HintPath> + <Private>True</Private> </Reference> - <Reference Include="Autofac.Integration.WebApi.Owin"> + <Reference Include="Autofac.Integration.WebApi.Owin, Version=3.2.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL"> <HintPath>..\packages\Autofac.WebApi2.Owin.3.2.0\lib\net45\Autofac.Integration.WebApi.Owin.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="AutoMapper, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL"> + <HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="AutoMapper.Net4, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL"> + <HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="CommandLine, Version=1.9.71.2, Culture=neutral, PublicKeyToken=de6f01bd326f8c32, processorArchitecture=MSIL"> <HintPath>..\packages\CommandLineParser.1.9.71\lib\net45\CommandLine.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="Microsoft.AspNet.Identity.Core"> + <Reference Include="Microsoft.AspNet.Identity.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath> @@ -74,8 +86,9 @@ <HintPath>..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="Microsoft.Owin.Host.SystemWeb"> + <Reference Include="Microsoft.Owin.Host.SystemWeb, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="Microsoft.Owin.Hosting, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll</HintPath> @@ -90,8 +103,8 @@ <Private>True</Private> </Reference> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\NLog.4.0.1\lib\net45\NLog.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL"> <HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath> @@ -116,12 +129,13 @@ <HintPath>..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="System.Web.Http.Owin"> + <Reference Include="System.Web.Http.Owin, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="System.Web.Http.Tracing"> + <Reference Include="System.Web.Http.Tracing, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.AspNet.WebApi.Tracing.5.2.3\lib\net45\System.Web.Http.Tracing.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" /> @@ -137,9 +151,7 @@ </ItemGroup> <ItemGroup> <None Include="App.config" /> - <None Include="packages.config"> - <SubType>Designer</SubType> - </None> + <None Include="packages.config" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\CurlSharp\CurlSharp.csproj"> @@ -155,6 +167,7 @@ <Content Include="jackett.ico" /> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Import Project="..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets" Condition="Exists('..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets')" /> <Import Project="..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" /> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <PropertyGroup> diff --git a/src/Jackett.Console/Program.cs b/src/Jackett.Console/Program.cs index afbbd5ce1066483f895d8d367569138342facd9f..90c49c6b584415e9f5b3bfabd4758b1749538d51 100644 --- a/src/Jackett.Console/Program.cs +++ b/src/Jackett.Console/Program.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading; @@ -36,9 +37,12 @@ namespace JackettConsole { /* ====== Options ===== */ + // SSL Fix + Startup.DoSSLFix = options.SSLFix; + // Use curl - if (options.UseCurlExec) - Startup.CurlSafe = true; + if (options.Client!=null) + Startup.ClientOverride = options.Client.ToLowerInvariant(); // Logging if (options.Logging) @@ -50,15 +54,16 @@ namespace JackettConsole // Log after the fact as using the logger will cause the options above to be used - if (options.UseCurlExec) - Engine.Logger.Info("Safe curl enabled."); - if (options.Logging) Engine.Logger.Info("Logging enabled."); if (options.Tracing) Engine.Logger.Info("Tracing enabled."); + if (options.SSLFix == true) + Engine.Logger.Info("SSL ECC workaround enabled."); + else if (options.SSLFix == false) + Engine.Logger.Info("SSL ECC workaround has been disabled."); /* ====== Actions ===== */ // Install service @@ -173,7 +178,6 @@ namespace JackettConsole Engine.Server.Initalize(); Engine.Server.Start(); - Engine.Logger.Info("Running in console mode!"); Engine.RunTime.Spin(); Engine.Logger.Info("Server thread exit"); } diff --git a/src/Jackett.Console/packages.config b/src/Jackett.Console/packages.config index b883a0e513c5642dbe56cda04a24eec6b0a5a469..baad01c07506632361077465f2ca6dea80f9cca1 100644 --- a/src/Jackett.Console/packages.config +++ b/src/Jackett.Console/packages.config @@ -1,26 +1,27 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Autofac" version="3.5.2" targetFramework="net452" /> - <package id="Autofac.Owin" version="3.1.0" targetFramework="net452" /> - <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net452" /> - <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net452" /> - <package id="CommandLineParser" version="1.9.71" targetFramework="net452" /> - <package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net452" /> - <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net452" /> - <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net452" /> - <package id="Microsoft.Owin" version="3.0.1" targetFramework="net452" /> - <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net452" /> - <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net452" /> - <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net452" /> - <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net452" /> - <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net452" /> - <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net452" /> - <package id="NLog" version="4.0.1" targetFramework="net452" /> - <package id="Owin" version="1.0" targetFramework="net452" /> + <package id="Autofac" version="3.5.2" targetFramework="net45" /> + <package id="Autofac.Owin" version="3.1.0" targetFramework="net45" /> + <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net45" /> + <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net45" /> + <package id="AutoMapper" version="3.3.1" targetFramework="net45" /> + <package id="CommandLineParser" version="1.9.71" targetFramework="net45" /> + <package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" /> + <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" /> + <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" /> + <package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net45" /> + <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" /> + <package id="NLog" version="4.0.1" targetFramework="net45" /> + <package id="Owin" version="1.0" targetFramework="net45" /> </packages> \ No newline at end of file diff --git a/src/Jackett.Distribution/App.config b/src/Jackett.Distribution/App.config index 88fa4027bda397de6bf19f0940e5dd6026c877f9..d1428ad712d72a50520dab5091ddcb5ea2b5f3e6 100644 --- a/src/Jackett.Distribution/App.config +++ b/src/Jackett.Distribution/App.config @@ -1,6 +1,6 @@ -<?xml version="1.0" encoding="utf-8" ?> +<?xml version="1.0" encoding="utf-8"?> <configuration> <startup> - <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> </startup> -</configuration> \ No newline at end of file +</configuration> diff --git a/src/Jackett.Distribution/Jackett.Distribution.csproj b/src/Jackett.Distribution/Jackett.Distribution.csproj index 6cf40976a4a2910121d9e89ed656e9f5249cb379..da0fa2e12d6c099cdda67261762c6e4600859e8d 100644 --- a/src/Jackett.Distribution/Jackett.Distribution.csproj +++ b/src/Jackett.Distribution/Jackett.Distribution.csproj @@ -9,9 +9,10 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>Jackett.Distribution</RootNamespace> <AssemblyName>JackettDistribution</AssemblyName> - <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> + <TargetFrameworkProfile /> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PlatformTarget>AnyCPU</PlatformTarget> diff --git a/src/Jackett.Distribution/packages.config b/src/Jackett.Distribution/packages.config index 869635dc0165071ae92ba9eef454e71755e170f6..afa3e1ac90ff8fae543a16b74efa2a48879b13a6 100644 --- a/src/Jackett.Distribution/packages.config +++ b/src/Jackett.Distribution/packages.config @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Octokit" version="0.14.0" targetFramework="net452" /> + <package id="Octokit" version="0.14.0" targetFramework="net45" /> </packages> \ No newline at end of file diff --git a/src/Jackett.Service/App.config b/src/Jackett.Service/App.config index e74e545f8aa971185e0a54404e426885bc5abe74..ac8dbeb66b3fd7fe26fd03e8b3f320aedbeea163 100644 --- a/src/Jackett.Service/App.config +++ b/src/Jackett.Service/App.config @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <configuration> <startup> - <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> @@ -27,4 +27,4 @@ </dependentAssembly> </assemblyBinding> </runtime> -</configuration> \ No newline at end of file +</configuration> diff --git a/src/Jackett.Service/Jackett.Service.csproj b/src/Jackett.Service/Jackett.Service.csproj index ff213d5cd14d3c8b6c7a4566fd3abb29ce0d11bc..214be78777353dae79ef63efec087125ed30da9d 100644 --- a/src/Jackett.Service/Jackett.Service.csproj +++ b/src/Jackett.Service/Jackett.Service.csproj @@ -9,11 +9,12 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>Jackett.Service</RootNamespace> <AssemblyName>JackettService</AssemblyName> - <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <NuGetPackageImportStamp> </NuGetPackageImportStamp> + <TargetFrameworkProfile /> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PlatformTarget>AnyCPU</PlatformTarget> @@ -42,46 +43,49 @@ <HintPath>..\packages\Autofac.3.5.2\lib\net40\Autofac.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="Autofac.Integration.Owin"> + <Reference Include="Autofac.Integration.Owin, Version=3.1.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL"> <HintPath>..\packages\Autofac.Owin.3.1.0\lib\net45\Autofac.Integration.Owin.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="Autofac.Integration.WebApi, Version=3.4.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Autofac.WebApi2.3.4.0\lib\net45\Autofac.Integration.WebApi.dll</HintPath> + <Private>True</Private> </Reference> - <Reference Include="Autofac.Integration.WebApi.Owin"> + <Reference Include="Autofac.Integration.WebApi.Owin, Version=3.2.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL"> <HintPath>..\packages\Autofac.WebApi2.Owin.3.2.0\lib\net45\Autofac.Integration.WebApi.Owin.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="Microsoft.Owin.FileSystems, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Microsoft.Owin.FileSystems.3.0.1\lib\net45\Microsoft.Owin.FileSystems.dll</HintPath> + <Private>True</Private> </Reference> - <Reference Include="Microsoft.Owin.Host.HttpListener"> + <Reference Include="Microsoft.Owin.Host.HttpListener, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="Microsoft.Owin.Hosting, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll</HintPath> <Private>True</Private> </Reference> <Reference Include="Microsoft.Owin.StaticFiles, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Microsoft.Owin.StaticFiles.3.0.1\lib\net45\Microsoft.Owin.StaticFiles.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\NLog.4.0.1\lib\net45\NLog.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="System" /> <Reference Include="System.Core" /> @@ -90,8 +94,8 @@ <Private>True</Private> </Reference> <Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="System.Net.Http.Primitives, Version=4.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath> @@ -99,15 +103,16 @@ </Reference> <Reference Include="System.Net.Http.WebRequest" /> <Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="System.Web.Http.Owin, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll</HintPath> + <Private>True</Private> </Reference> - <Reference Include="System.Web.Http.Tracing"> + <Reference Include="System.Web.Http.Tracing, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.AspNet.WebApi.Tracing.5.2.3\lib\net45\System.Web.Http.Tracing.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" /> diff --git a/src/Jackett.Service/packages.config b/src/Jackett.Service/packages.config index c3b90ef0907c7394c97ec4050b140dcc6ed56a82..eb26439ff27bc14fa5904ed765b79c92f510d0c1 100644 --- a/src/Jackett.Service/packages.config +++ b/src/Jackett.Service/packages.config @@ -1,23 +1,23 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Autofac" version="3.5.2" targetFramework="net452" /> - <package id="Autofac.Owin" version="3.1.0" targetFramework="net452" /> - <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net452" /> - <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net452" /> - <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net452" /> - <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net452" /> - <package id="Microsoft.Owin" version="3.0.1" targetFramework="net452" /> - <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net452" /> - <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net452" /> - <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net452" /> - <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net452" /> - <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net452" /> - <package id="NLog" version="4.0.1" targetFramework="net452" /> - <package id="Owin" version="1.0" targetFramework="net452" /> + <package id="Autofac" version="3.5.2" targetFramework="net45" /> + <package id="Autofac.Owin" version="3.1.0" targetFramework="net45" /> + <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net45" /> + <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" /> + <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" /> + <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" /> + <package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net45" /> + <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" /> + <package id="NLog" version="4.0.1" targetFramework="net45" /> + <package id="Owin" version="1.0" targetFramework="net45" /> </packages> \ No newline at end of file diff --git a/src/Jackett.Test/Indexers/BakaBTTests.cs b/src/Jackett.Test/Indexers/BakaBTTests.cs index 0b9b471eb73a403d3ac361d73187f90b8649083e..660ae9328b9c5bbdb701895b04ee3d51f987f9c6 100644 --- a/src/Jackett.Test/Indexers/BakaBTTests.cs +++ b/src/Jackett.Test/Indexers/BakaBTTests.cs @@ -156,22 +156,22 @@ namespace JackettTest.Indexers var indexer = TestUtil.Container.ResolveNamed<IIndexer>(BakaBT.GetIndexerID(typeof(BakaBT))) as BakaBT; indexer.LoadFromSavedConfiguration(JObject.Parse("{\"cookies\":\"bbtid=c\"}")); - var results = await indexer.PerformQuery(new Jackett.Models.TorznabQuery() { SanitizedSearchTerm = "Series S1", Season = 1 }); - - results.Length.Should().Be(44); - results[0].Title.Should().Be("Golden Time Season 1 (BD 720p) [FFF]"); - results[0].Guid.Should().Be("http://bakabt.me/torrent/180302/golden-time-bd-720p-fff"); - results[0].Comments.Should().Be("http://bakabt.me/torrent/180302/golden-time-bd-720p-fff"); - results[0].Size.Should().Be(10307921920); - results[0].Description.Should().Be("Golden Time Season 1 (BD 720p) [FFF]"); - results[0].Link.Should().Be("http://bakabt.me/torrent/180302/golden-time-bd-720p-fff"); - results[0].Peers.Should().Be(161); - results[0].Seeders.Should().Be(151); - results[0].MinimumRatio.Should().Be(1); - - results[1].Title.Should().Be("Yowamushi Pedal Season 1 (BD 720p) [Commie]"); - results[4].Title.Should().Be("Dungeon ni Deai o Motomeru no wa Machigatte Iru Darouka: Familia Myth Season 1 (480p) [HorribleSubs]"); - results[5].Title.Should().Be("Is It Wrong to Try to Pick Up Girls in a Dungeon? Season 1 (480p) [HorribleSubs]"); + var results = await indexer.PerformQuery(new Jackett.Models.TorznabQuery() { SearchTerm = "Series S1", Season = 1 }); + + results.Count().Should().Be(44); + results.First().Title.Should().Be("Golden Time Season 1 (BD 720p) [FFF]"); + results.First().Guid.Should().Be("http://bakabt.me/torrent/180302/golden-time-bd-720p-fff"); + results.First().Comments.Should().Be("http://bakabt.me/torrent/180302/golden-time-bd-720p-fff"); + results.First().Size.Should().Be(10307921920); + results.First().Description.Should().Be("Golden Time Season 1 (BD 720p) [FFF]"); + results.First().Link.Should().Be("http://bakabt.me/torrent/180302/golden-time-bd-720p-fff"); + results.First().Peers.Should().Be(161); + results.First().Seeders.Should().Be(151); + results.First().MinimumRatio.Should().Be(1); + + results.ElementAt(1).Title.Should().Be("Yowamushi Pedal Season 1 (BD 720p) [Commie]"); + results.ElementAt(4).Title.Should().Be("Dungeon ni Deai o Motomeru no wa Machigatte Iru Darouka: Familia Myth Season 1 (480p) [HorribleSubs]"); + results.ElementAt(5).Title.Should().Be("Is It Wrong to Try to Pick Up Girls in a Dungeon? Season 1 (480p) [HorribleSubs]"); } } } diff --git a/src/Jackett.Test/Jackett.Test.csproj b/src/Jackett.Test/Jackett.Test.csproj index 8d0bbc459c94e3693a26dda90d1ebd105008c5d0..234f876d19d19caed14485c9d1f557f1c5b40b1c 100644 --- a/src/Jackett.Test/Jackett.Test.csproj +++ b/src/Jackett.Test/Jackett.Test.csproj @@ -8,7 +8,7 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>JackettTest</RootNamespace> <AssemblyName>JackettTest</AssemblyName> - <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> @@ -18,6 +18,7 @@ <TestProjectType>UnitTest</TestProjectType> <NuGetPackageImportStamp> </NuGetPackageImportStamp> + <TargetFrameworkProfile /> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -158,6 +159,7 @@ <Compile Include="TestIIndexerManagerServiceHelper.cs" /> <Compile Include="TestUtil.cs" /> <Compile Include="TestWebClient.cs" /> + <Compile Include="Util\ServerUtilTests.cs" /> </ItemGroup> <ItemGroup> <None Include="app.config" /> diff --git a/src/Jackett.Test/TestWebClient.cs b/src/Jackett.Test/TestWebClient.cs index dd375c38c855be0b88f5dbd094a798e934d7bded..4104972ed35d0436186c5043a9f1fffaa5fc32b1 100644 --- a/src/Jackett.Test/TestWebClient.cs +++ b/src/Jackett.Test/TestWebClient.cs @@ -31,5 +31,10 @@ namespace JackettTest { return Task.FromResult<WebClientStringResult>(stringCallbacks.Where(r => r.Key.Equals(request)).First().Value.Invoke(request)); } + + public void Init() + { + + } } } diff --git a/src/Jackett.Test/Util/ServerUtilTests.cs b/src/Jackett.Test/Util/ServerUtilTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..c59b942e59350fcef694e41e38d77339dd1dc73b --- /dev/null +++ b/src/Jackett.Test/Util/ServerUtilTests.cs @@ -0,0 +1,50 @@ +using Jackett.Utils.Clients; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Autofac; +using Jackett.Indexers; +using FluentAssertions; +using Newtonsoft.Json.Linq; +using Jackett; +using Newtonsoft.Json; +using Jackett.Utils; + +namespace JackettTest.Indexers +{ + [TestFixture] + class ServerUtilTests : TestBase + { + [Test] + public void ResureRedirectIsFullyQualified_makes_redicts_fully_qualified() + { + var res = new WebClientByteResult() + { + RedirectingTo = "list?p=1" + }; + + var req = new WebRequest() + { + Url = "http://my.domain.com/page.php" + }; + + // Not fully qualified requiring redirect + ServerUtil.ResureRedirectIsFullyQualified(req, res); + Assert.AreEqual(res.RedirectingTo, "http://my.domain.com/list?p=1"); + + // Fully qualified not needing modified + res.RedirectingTo = "http://a.domain/page.htm"; + ServerUtil.ResureRedirectIsFullyQualified(req, res); + Assert.AreEqual(res.RedirectingTo, "http://a.domain/page.htm"); + + // Relative requiring redirect + req.Url = "http://my.domain.com/dir/page.php"; + res.RedirectingTo = "a/dir/page.html"; + ServerUtil.ResureRedirectIsFullyQualified(req, res); + Assert.AreEqual(res.RedirectingTo, "http://my.domain.com/dir/a/dir/page.html"); + } + } +} diff --git a/src/Jackett.Test/app.config b/src/Jackett.Test/app.config index c0ff2bc0ee0c2305995993b67249bed92da43f25..eda8c8545ee4aead9709d3c7ea5285a5762afd7d 100644 --- a/src/Jackett.Test/app.config +++ b/src/Jackett.Test/app.config @@ -24,4 +24,4 @@ </dependentAssembly> </assemblyBinding> </runtime> -</configuration> \ No newline at end of file +<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /></startup></configuration> diff --git a/src/Jackett.Test/packages.config b/src/Jackett.Test/packages.config index 8ac788eefbe16855f82b422b56e8da78c593a3f5..3813a8d524c979069f10fb74c974c2e9c21f16d4 100644 --- a/src/Jackett.Test/packages.config +++ b/src/Jackett.Test/packages.config @@ -1,25 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Autofac" version="3.5.2" targetFramework="net452" /> - <package id="Autofac.Owin" version="3.1.0" targetFramework="net452" /> - <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net452" /> - <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net452" /> - <package id="CsQuery" version="1.3.4" targetFramework="net452" /> - <package id="FluentAssertions" version="3.4.1" targetFramework="net452" /> - <package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net452" /> - <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net452" /> - <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net452" /> - <package id="Microsoft.Owin" version="3.0.1" targetFramework="net452" /> - <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net452" /> - <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net452" /> - <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net452" /> - <package id="NLog" version="4.0.1" targetFramework="net452" /> - <package id="NUnit" version="2.6.4" targetFramework="net452" /> - <package id="NUnitTestAdapter" version="2.0.0" targetFramework="net452" /> - <package id="Owin" version="1.0" targetFramework="net452" /> + <package id="Autofac" version="3.5.2" targetFramework="net45" /> + <package id="Autofac.Owin" version="3.1.0" targetFramework="net45" /> + <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net45" /> + <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net45" /> + <package id="CsQuery" version="1.3.4" targetFramework="net45" /> + <package id="FluentAssertions" version="3.4.1" targetFramework="net45" /> + <package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" /> + <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" /> + <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" /> + <package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net45" /> + <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" /> + <package id="NLog" version="4.0.1" targetFramework="net45" /> + <package id="NUnit" version="2.6.4" targetFramework="net45" /> + <package id="NUnitTestAdapter" version="2.0.0" targetFramework="net45" /> + <package id="Owin" version="1.0" targetFramework="net45" /> </packages> \ No newline at end of file diff --git a/src/Jackett.Tray/App.config b/src/Jackett.Tray/App.config index e74e545f8aa971185e0a54404e426885bc5abe74..ac8dbeb66b3fd7fe26fd03e8b3f320aedbeea163 100644 --- a/src/Jackett.Tray/App.config +++ b/src/Jackett.Tray/App.config @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <configuration> <startup> - <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> @@ -27,4 +27,4 @@ </dependentAssembly> </assemblyBinding> </runtime> -</configuration> \ No newline at end of file +</configuration> diff --git a/src/Jackett.Tray/Jackett.Tray.csproj b/src/Jackett.Tray/Jackett.Tray.csproj index a7801f0dcff5dbdb8579b3a8ebcfd9419eac6189..ce52770116bddd32ad71b6fd9c73b120763e73e9 100644 --- a/src/Jackett.Tray/Jackett.Tray.csproj +++ b/src/Jackett.Tray/Jackett.Tray.csproj @@ -9,11 +9,12 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>Jackett.Tray</RootNamespace> <AssemblyName>JackettTray</AssemblyName> - <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <NuGetPackageImportStamp> </NuGetPackageImportStamp> + <TargetFrameworkProfile /> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PlatformTarget>AnyCPU</PlatformTarget> @@ -54,6 +55,14 @@ <HintPath>..\packages\Autofac.WebApi2.Owin.3.2.0\lib\net45\Autofac.Integration.WebApi.Owin.dll</HintPath> <Private>True</Private> </Reference> + <Reference Include="AutoMapper, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL"> + <HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="AutoMapper.Net4, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL"> + <HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll</HintPath> + <Private>True</Private> + </Reference> <Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath> <Private>True</Private> @@ -143,8 +152,11 @@ <Compile Include="Properties\Resources.Designer.cs"> <AutoGen>True</AutoGen> <DependentUpon>Resources.resx</DependentUpon> + <DesignTime>True</DesignTime> </Compile> - <None Include="packages.config" /> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> <None Include="Properties\Settings.settings"> <Generator>SettingsSingleFileGenerator</Generator> <LastGenOutput>Settings.Designer.cs</LastGenOutput> @@ -156,7 +168,9 @@ </Compile> </ItemGroup> <ItemGroup> - <None Include="App.config" /> + <None Include="App.config"> + <SubType>Designer</SubType> + </None> </ItemGroup> <ItemGroup> <Content Include="jackett.ico" /> @@ -186,6 +200,7 @@ </PropertyGroup> <Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" /> </Target> + <Import Project="..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets" Condition="Exists('..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets')" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> diff --git a/src/Jackett.Tray/Main.Designer.cs b/src/Jackett.Tray/Main.Designer.cs index ae47ee2bdc71ee024b90076ce28a6b7569ca083a..1c3a770d1c22bd846e894bdfeb421c9d06e1c117 100644 --- a/src/Jackett.Tray/Main.Designer.cs +++ b/src/Jackett.Tray/Main.Designer.cs @@ -36,9 +36,9 @@ this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); this.backgroundMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.serviceControlMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); this.toolStripMenuItemAutoStart = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItemShutdown = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); this.contextMenuStrip1.SuspendLayout(); this.SuspendLayout(); // @@ -88,12 +88,18 @@ this.serviceControlMenuItem.Text = "Start Service"; this.serviceControlMenuItem.Click += new System.EventHandler(this.serviceControlMenuItem_Click); // + // toolStripSeparator2 + // + this.toolStripSeparator2.Name = "toolStripSeparator2"; + this.toolStripSeparator2.Size = new System.Drawing.Size(288, 6); + // // toolStripMenuItemAutoStart // this.toolStripMenuItemAutoStart.CheckOnClick = true; this.toolStripMenuItemAutoStart.Name = "toolStripMenuItemAutoStart"; this.toolStripMenuItemAutoStart.Size = new System.Drawing.Size(291, 22); this.toolStripMenuItemAutoStart.Text = "Auto-start on boot"; + this.toolStripMenuItemAutoStart.Visible = false; // // toolStripMenuItemShutdown // @@ -101,11 +107,6 @@ this.toolStripMenuItemShutdown.Size = new System.Drawing.Size(291, 22); this.toolStripMenuItemShutdown.Text = "Shutdown"; // - // toolStripSeparator2 - // - this.toolStripSeparator2.Name = "toolStripSeparator2"; - this.toolStripSeparator2.Size = new System.Drawing.Size(288, 6); - // // Main // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); diff --git a/src/Jackett.Tray/Main.cs b/src/Jackett.Tray/Main.cs index fee4ca124f8480a054cccb117da06ae35e46a00f..7a02445e8f4b246a76cf0ed0e009324c3b0fab00 100644 --- a/src/Jackett.Tray/Main.cs +++ b/src/Jackett.Tray/Main.cs @@ -28,6 +28,11 @@ namespace JackettTray toolStripMenuItemWebUI.Click += toolStripMenuItemWebUI_Click; toolStripMenuItemShutdown.Click += toolStripMenuItemShutdown_Click; +#if __MonoCS__ + // No shortcuts on linux +#else + toolStripMenuItemAutoStart.Visible = true; +#endif Engine.Server.Initalize(); @@ -91,13 +96,16 @@ namespace JackettTray private void CreateShortcut() { - +#if __MonoCS__ + // No shortcuts on linux +#else var appPath = Assembly.GetExecutingAssembly().Location; var shell = new IWshRuntimeLibrary.WshShell(); var shortcut = (IWshRuntimeLibrary.IWshShortcut)shell.CreateShortcut(ShortcutPath); shortcut.Description = Assembly.GetExecutingAssembly().GetName().Name; shortcut.TargetPath = appPath; shortcut.Save(); +#endif } private void contextMenuStrip1_Opening(object sender, CancelEventArgs e) diff --git a/src/Jackett.Tray/Properties/Resources.Designer.cs b/src/Jackett.Tray/Properties/Resources.Designer.cs index b6ce33f0b003cff2f785569faf90dbcea67554ce..a4cb133198c5f85e4bee3f835fc14d58c5c7894f 100644 --- a/src/Jackett.Tray/Properties/Resources.Designer.cs +++ b/src/Jackett.Tray/Properties/Resources.Designer.cs @@ -8,10 +8,10 @@ // </auto-generated> //------------------------------------------------------------------------------ -namespace Jackett.Tray.Properties -{ - - +namespace Jackett.Tray.Properties { + using System; + + /// <summary> /// A strongly-typed resource class, for looking up localized strings, etc. /// </summary> @@ -22,48 +22,40 @@ namespace Jackett.Tray.Properties [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - + internal class Resources { + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { + internal Resources() { } - + /// <summary> /// Returns the cached ResourceManager instance used by this class. /// </summary> [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Jackett.Tray.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } - + /// <summary> /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// </summary> [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { + internal static global::System.Globalization.CultureInfo Culture { + get { return resourceCulture; } - set - { + set { resourceCulture = value; } } diff --git a/src/Jackett.Tray/Properties/Settings.Designer.cs b/src/Jackett.Tray/Properties/Settings.Designer.cs index c110e9e5ed4c5c6f19ed36a66a568d76c3e86e64..f4fbc6273e68fce880112ff542fdf9d6b60e1f30 100644 --- a/src/Jackett.Tray/Properties/Settings.Designer.cs +++ b/src/Jackett.Tray/Properties/Settings.Designer.cs @@ -8,21 +8,17 @@ // </auto-generated> //------------------------------------------------------------------------------ -namespace Jackett.Tray.Properties -{ - - +namespace Jackett.Tray.Properties { + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { + + public static Settings Default { + get { return defaultInstance; } } diff --git a/src/Jackett.Tray/packages.config b/src/Jackett.Tray/packages.config index c3b90ef0907c7394c97ec4050b140dcc6ed56a82..13667dfd31b06c6063af94b9b275fd6653966bea 100644 --- a/src/Jackett.Tray/packages.config +++ b/src/Jackett.Tray/packages.config @@ -1,23 +1,24 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Autofac" version="3.5.2" targetFramework="net452" /> - <package id="Autofac.Owin" version="3.1.0" targetFramework="net452" /> - <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net452" /> - <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net452" /> - <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net452" /> - <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net452" /> - <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net452" /> - <package id="Microsoft.Owin" version="3.0.1" targetFramework="net452" /> - <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net452" /> - <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net452" /> - <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net452" /> - <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net452" /> - <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net452" /> - <package id="NLog" version="4.0.1" targetFramework="net452" /> - <package id="Owin" version="1.0" targetFramework="net452" /> + <package id="Autofac" version="3.5.2" targetFramework="net45" /> + <package id="Autofac.Owin" version="3.1.0" targetFramework="net45" /> + <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net45" /> + <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net45" /> + <package id="AutoMapper" version="3.3.1" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" /> + <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" /> + <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" /> + <package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net45" /> + <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" /> + <package id="NLog" version="4.0.1" targetFramework="net45" /> + <package id="Owin" version="1.0" targetFramework="net45" /> </packages> \ No newline at end of file diff --git a/src/Jackett/App.config b/src/Jackett/App.config index 547fa70d5a3e2401a33267f1952c9285486bb6ea..5415cbdd9a3baaae6df6fc38ff493426fd16703c 100644 --- a/src/Jackett/App.config +++ b/src/Jackett/App.config @@ -2,7 +2,7 @@ <configuration> <startup> - <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" /> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <runtime> diff --git a/src/Jackett/Content/custom.css b/src/Jackett/Content/custom.css index 1ac9fb8f7456bea9188d1be6a59d349d1ede1bec..e4b2279a9d9bece81b688c34de9d9b741e996000 100644 --- a/src/Jackett/Content/custom.css +++ b/src/Jackett/Content/custom.css @@ -49,7 +49,7 @@ } .indexer { - height: 180px; + height: 235px; } .add-indexer { @@ -114,6 +114,10 @@ height: 20px; } +[data-type=hiddendata]{ + display: none; +} + .spinner { -webkit-animation: spin 2s infinite linear; -moz-animation: spin 2s infinite linear; @@ -210,4 +214,13 @@ hr { .modal-fillwidth { width: 1200px; +} + +.indexer-caps { + padding: 0px 15px 15px 15px; + border-top: 1px solid #e5e5e5; +} + +.indexer-caps table { + border-bottom: 1px solid #ddd; } \ No newline at end of file diff --git a/src/Jackett/Content/custom.js b/src/Jackett/Content/custom.js index 9cb3c434993d4893dce0745fdf4f6c7ddacc35fd..5aca89c2bf15d79adfb5dca1a9b53272c11d2eb3 100644 --- a/src/Jackett/Content/custom.js +++ b/src/Jackett/Content/custom.js @@ -136,7 +136,8 @@ function displayIndexers(items) { var unconfiguredIndexerTemplate = Handlebars.compile($("#templates > .unconfigured-indexer")[0].outerHTML); for (var i = 0; i < items.length; i++) { var item = items[i]; - item.torznab_host = resolveUrl("/api/" + item.id); + item.torznab_host = resolveUrl("/torznab/" + item.id); + item.potato_host = resolveUrl("/potato/" + item.id); if (item.configured) $('#indexers').append(indexerTemplate(item)); else @@ -211,7 +212,7 @@ function displayIndexerSetup(id) { return; } - populateSetupForm(id, data.name, data.config); + populateSetupForm(id, data.name, data.config, data.caps); }).fail(function () { doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); @@ -236,17 +237,12 @@ function populateConfigItems(configForm, config) { } } -function newConfigModal(title, config) { - //config-setup-modal - var configTemplate = Handlebars.compile($("#templates > .config-setup-modal")[0].outerHTML); - var configForm = $(configTemplate({ title: title })); - +function newConfigModal(title, config, caps) { + var configTemplate = Handlebars.compile($("#jackett-config-setup-modal").html()); + var configForm = $(configTemplate({ title: title, caps: caps })); $("#modals").append(configForm); - populateConfigItems(configForm, config); - return configForm; - //modal.remove(); } function getConfigModalJson(configForm) { @@ -256,6 +252,9 @@ function getConfigModalJson(configForm) { var type = $el.data("type"); var id = $el.data("id"); switch (type) { + case "hiddendata": + configJson[id] = $el.find(".setup-item-hiddendata input").val(); + break; case "inputstring": configJson[id] = $el.find(".setup-item-inputstring input").val(); break; @@ -267,8 +266,8 @@ function getConfigModalJson(configForm) { return configJson; } -function populateSetupForm(indexerId, name, config) { - var configForm = newConfigModal(name, config); +function populateSetupForm(indexerId, name, config, caps) { + var configForm = newConfigModal(name, config, caps); var $goButton = configForm.find(".setup-indexer-go"); $goButton.click(function () { var data = { indexer: indexerId, name: name }; diff --git a/src/Jackett/Content/index.html b/src/Jackett/Content/index.html index a94688fd71e721abb8207ca3577555b6e91399d2..22157c4ffb595b1c4b64c29fa7cc3e6d1e06b8b0 100644 --- a/src/Jackett/Content/index.html +++ b/src/Jackett/Content/index.html @@ -38,6 +38,7 @@ <th>First Seen</th> <th>Tracker</th> <th>Name</th> + <th>Category</th> <th>Seeds</th> <th>Leechers</th> <th>Download</th> @@ -52,6 +53,7 @@ <td>{{formatRelative FirstSeen}}</td> <td>{{Tracker}}</td> <td><a href="{{Comments}}">{{Title}}</a></td> + <td>{{CategoryDesc}}</td> <td>{{Seeders}}</td> <td>{{Peers}}</td> <td><a href="{{Link}}"><i class="fa fa-download"></i></a></td> @@ -68,6 +70,45 @@ </div> </script> + <script id="jackett-config-setup-modal" type="text/x-handlebars-template"> + <div class="config-setup-modal modal fade" tabindex="-1" role="dialog" aria-hidden="true"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> + <h4 class="modal-title">{{title}}</h4> + </div> + <div class="modal-body"> + <form class="config-setup-form"></form> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> + <button type="button" class="btn btn-primary setup-indexer-go">Okay</button> + </div> + <div class="indexer-caps"> + <h4>Capabilities</h4> + <table class="dataTable compact cell-border hover stripe"> + <thead> + <tr> + <th>Category</th> + <th>Description</th> + </tr> + </thead> + <tbody> + {{#each caps}} + <tr> + <td>{{ID}}</td> + <td>{{Name}}</td> + </tr> + {{/each}} + </tbody> + </table> + </div> + </div> + </div> + </div> + </script> + <title>Jackett</title> </head> <body> @@ -147,24 +188,6 @@ <div id="modals"></div> <div id="templates"> - <div class="config-setup-modal modal fade" tabindex="-1" role="dialog" aria-hidden="true"> - <div class="modal-dialog"> - <div class="modal-content"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> - <h4 class="modal-title">{{title}}</h4> - </div> - <div class="modal-body"> - <form class="config-setup-form"></form> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> - <button type="button" class="btn btn-primary setup-indexer-go">Okay</button> - </div> - </div> - </div> - </div> - <button class="indexer card add-indexer" data-toggle="modal" data-target="#select-indexer-modal"> <div class="indexer-add-content"> <span class="glyphicon glyphicon glyphicon-plus" aria-hidden="true"></span> @@ -191,6 +214,13 @@ <div class="indexer-host"> <b>Torznab Host:</b> <input class="form-control" type="text" value="{{torznab_host}}" placeholder="Torznab Host" readonly=""> + <b>CouchPotato Host:</b> + {{#if potatoenabled}} + + <input class="form-control" type="text" value="{{potato_host}}" placeholder="Torznab Host" readonly=""> + {{else}} + <input class="form-control" type="text" value="Not availible" placeholder="Torznab Host" readonly=""> + {{/if}} </div> </div> @@ -213,7 +243,6 @@ {{else}} <input class="form-control" type="text" value="{{{value}}}" /> {{/if}} - </div> <div class="setup-item-inputbool"> @@ -225,6 +254,9 @@ </div> <img class="setup-item-displayimage" src="{{{value}}}" /> <div class="setup-item-displayinfo alert alert-info" role="alert">{{{value}}}</div> + <div class="setup-item-hiddendata"> + <input class="form-control" type="text" value="{{{value}}}" /> + </div> <span class="spinner glyphicon glyphicon-refresh"></span> diff --git a/src/Jackett/Content/logos/BakaBT.png b/src/Jackett/Content/logos/bakabt.png similarity index 100% rename from src/Jackett/Content/logos/BakaBT.png rename to src/Jackett/Content/logos/bakabt.png diff --git a/src/Jackett/Content/logos/immortalseed.png b/src/Jackett/Content/logos/immortalseed.png new file mode 100644 index 0000000000000000000000000000000000000000..7e8503e115d02cd22d7eebf24994e341075a2011 Binary files /dev/null and b/src/Jackett/Content/logos/immortalseed.png differ diff --git a/src/Jackett/Content/logos/rarbg.png b/src/Jackett/Content/logos/rarbg.png deleted file mode 100644 index da8bebbbc75833e41f4835d2262de87c6d7b7f3e..0000000000000000000000000000000000000000 Binary files a/src/Jackett/Content/logos/rarbg.png and /dev/null differ diff --git a/src/Jackett/Content/logos/torrentbytes.png b/src/Jackett/Content/logos/torrentbytes.png new file mode 100644 index 0000000000000000000000000000000000000000..245c86aeac2555bdecb545223b5af6c9ce441b1b Binary files /dev/null and b/src/Jackett/Content/logos/torrentbytes.png differ diff --git a/src/Jackett/Controllers/AdminController.cs b/src/Jackett/Controllers/AdminController.cs index 4900f951a692311c0a2c578140231f9f839e95f8..651a92aacc1d49ee5ac1d64b3c469e9b02e9914f 100644 --- a/src/Jackett/Controllers/AdminController.cs +++ b/src/Jackett/Controllers/AdminController.cs @@ -1,8 +1,11 @@ using Autofac; +using Jackett.Indexers; using Jackett.Models; using Jackett.Services; using Jackett.Utils; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using NLog; using System; using System.Collections.Generic; using System.IO; @@ -32,8 +35,9 @@ namespace Jackett.Controllers private ISecuityService securityService; private IProcessService processService; private ICacheService cacheService; + private Logger logger; - public AdminController(IConfigurationService config, IIndexerManagerService i, IServerService ss, ISecuityService s, IProcessService p, ICacheService c) + public AdminController(IConfigurationService config, IIndexerManagerService i, IServerService ss, ISecuityService s, IProcessService p, ICacheService c, Logger l) { this.config = config; indexerService = i; @@ -41,6 +45,7 @@ namespace Jackett.Controllers securityService = s; processService = p; cacheService = c; + logger = l; } private async Task<JToken> ReadPostDataJson() @@ -128,6 +133,7 @@ namespace Jackett.Controllers } catch (Exception ex) { + logger.Error(ex, "Exception in SetAdminPassword"); jsonReply["result"] = "error"; jsonReply["error"] = ex.Message; } @@ -145,11 +151,13 @@ namespace Jackett.Controllers var indexer = indexerService.GetIndexer((string)postData["indexer"]); var config = await indexer.GetConfigurationForSetup(); jsonReply["config"] = config.ToJson(); + jsonReply["caps"] = indexer.TorznabCaps.CapsToJson(); jsonReply["name"] = indexer.DisplayName; jsonReply["result"] = "success"; } catch (Exception ex) { + logger.Error(ex, "Exception in GetConfigForm"); jsonReply["result"] = "error"; jsonReply["error"] = ex.Message; } @@ -160,12 +168,13 @@ namespace Jackett.Controllers [HttpPost] public async Task<IHttpActionResult> Configure() { - JToken jsonReply = new JObject(); + var jsonReply = new JObject(); + IIndexer indexer = null; try { var postData = await ReadPostDataJson(); string indexerString = (string)postData["indexer"]; - var indexer = indexerService.GetIndexer((string)postData["indexer"]); + indexer = indexerService.GetIndexer((string)postData["indexer"]); jsonReply["name"] = indexer.DisplayName; await indexer.ApplyConfiguration(postData["config"]); await indexerService.TestIndexer((string)postData["indexer"]); @@ -175,9 +184,15 @@ namespace Jackett.Controllers { jsonReply["result"] = "error"; jsonReply["error"] = ex.Message; + var baseIndexer = indexer as BaseIndexer; + if (null != baseIndexer) + baseIndexer.ResetBaseConfig(); if (ex is ExceptionWithConfigData) { jsonReply["config"] = ((ExceptionWithConfigData)ex).ConfigData.ToJson(); + } else + { + logger.Error(ex, "Exception in Configure"); } } return Json(jsonReply); @@ -201,12 +216,14 @@ namespace Jackett.Controllers item["description"] = indexer.DisplayDescription; item["configured"] = indexer.IsConfigured; item["site_link"] = indexer.SiteLink; + item["potatoenabled"] = indexer.TorznabCaps.Categories.Select(c => c.ID).Any(i => PotatoController.MOVIE_CATS.Contains(i)); items.Add(item); } jsonReply["items"] = items; } catch (Exception ex) { + logger.Error(ex, "Exception in get_indexers"); jsonReply["result"] = "error"; jsonReply["error"] = ex.Message; } @@ -228,6 +245,7 @@ namespace Jackett.Controllers } catch (Exception ex) { + logger.Error(ex, "Exception in test_indexer"); jsonReply["result"] = "error"; jsonReply["error"] = ex.Message; } @@ -247,6 +265,7 @@ namespace Jackett.Controllers } catch (Exception ex) { + logger.Error(ex, "Exception in delete_indexer"); jsonReply["result"] = "error"; jsonReply["error"] = ex.Message; } @@ -269,14 +288,9 @@ namespace Jackett.Controllers jsonReply["config"] = cfg; jsonReply["app_version"] = config.GetVersion(); jsonReply["result"] = "success"; - } - catch (CustomException ex) - { - jsonReply["result"] = "error"; - jsonReply["error"] = ex.Message; - } - catch (Exception ex) + }catch (Exception ex) { + logger.Error(ex, "Exception in get_jackett_config"); jsonReply["result"] = "error"; jsonReply["error"] = ex.Message; } @@ -350,6 +364,7 @@ namespace Jackett.Controllers } catch (Exception ex) { + logger.Error(ex, "Exception in set_port"); jsonReply["result"] = "error"; jsonReply["error"] = ex.Message; } diff --git a/src/Jackett/Controllers/DownloadController.cs b/src/Jackett/Controllers/DownloadController.cs index e40fa1121bce45bbd79d86917ddc9ba38470af40..f2266b60ff56ea28305e0b42827c6a1c75ea659b 100644 --- a/src/Jackett/Controllers/DownloadController.cs +++ b/src/Jackett/Controllers/DownloadController.cs @@ -39,7 +39,7 @@ namespace Jackett.Controllers } var remoteFile = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(path)); - var downloadBytes = await indexer.Download(new Uri(remoteFile)); + var downloadBytes = await indexer.Download(new Uri(remoteFile, UriKind.RelativeOrAbsolute)); var result = new HttpResponseMessage(HttpStatusCode.OK); result.Content = new ByteArrayContent(downloadBytes); diff --git a/src/Jackett/Controllers/PotatoController.cs b/src/Jackett/Controllers/PotatoController.cs new file mode 100644 index 0000000000000000000000000000000000000000..b3245f5349ef234f1ebfb25f4c4d700aa7bbb55a --- /dev/null +++ b/src/Jackett/Controllers/PotatoController.cs @@ -0,0 +1,151 @@ +using Jackett.Models; +using Jackett.Services; +using Jackett.Utils; +using Jackett.Utils.Clients; +using Newtonsoft.Json.Linq; +using NLog; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using System.Web.Http; + +namespace Jackett.Controllers +{ + [AllowAnonymous] + public class PotatoController : ApiController + { + private IIndexerManagerService indexerService; + private Logger logger; + private IServerService serverService; + private ICacheService cacheService; + private IWebClient webClient; + + public static readonly int[] MOVIE_CATS = new int[] {2000, 2040, 2030, 2010}; + + public PotatoController(IIndexerManagerService i, Logger l, IServerService s, ICacheService c, IWebClient w) + { + indexerService = i; + logger = l; + serverService = s; + cacheService = c; + webClient = w; + } + + [HttpGet] + public async Task<HttpResponseMessage> Call(string indexerID, [FromUri]TorrentPotatoRequest request) + { + var indexer = indexerService.GetIndexer(indexerID); + + var allowBadApiDueToDebug = false; +#if DEBUG + allowBadApiDueToDebug = Debugger.IsAttached; +#endif + + if (!allowBadApiDueToDebug && !string.Equals(request.passkey, serverService.Config.APIKey, StringComparison.InvariantCultureIgnoreCase)) + { + logger.Warn(string.Format("A request from {0} was made with an incorrect API key.", Request.GetOwinContext().Request.RemoteIpAddress)); + return Request.CreateResponse(HttpStatusCode.Forbidden, "Incorrect API key"); + } + + if (!indexer.IsConfigured) + { + logger.Warn(string.Format("Rejected a request to {0} which is unconfigured.", indexer.DisplayName)); + return Request.CreateResponse(HttpStatusCode.Forbidden, "This indexer is not configured."); + } + + if (!indexer.TorznabCaps.Categories.Select(c => c.ID).Any(i => MOVIE_CATS.Contains(i))){ + logger.Warn(string.Format("Rejected a request to {0} which does not support searching for movies.", indexer.DisplayName)); + return Request.CreateResponse(HttpStatusCode.Forbidden, "This indexer does not support movies."); + } + + if (string.IsNullOrWhiteSpace(request.search)) + { + // We are searching by IMDB id so look up the name + var response = await webClient.GetString(new Utils.Clients.WebRequest("http://www.omdbapi.com/?type=movie&i=" + request.imdbid)); + if (response.Status == HttpStatusCode.OK) + { + JObject result = JObject.Parse(response.Content); + if (result["Title"] != null) + { + request.search = result["Title"].ToString(); + } + } + } + + var torznabQuery = new TorznabQuery() + { + ApiKey = request.passkey, + Categories = MOVIE_CATS, + SearchTerm = request.search + }; + + IEnumerable<ReleaseInfo> releases = new List<ReleaseInfo>(); + + if (!string.IsNullOrWhiteSpace(torznabQuery.SanitizedSearchTerm)) + { + releases = await indexer.PerformQuery(torznabQuery); + } + + // Cache non query results + if (string.IsNullOrEmpty(torznabQuery.SanitizedSearchTerm)) + { + cacheService.CacheRssResults(indexer.DisplayName, releases); + } + + releases = indexer.FilterResults(torznabQuery, releases); + + var severUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port); + // add Jackett proxy to download links... + foreach (var release in releases) + { + if (release.Link == null || (release.Link.IsAbsoluteUri && release.Link.Scheme == "magnet")) + continue; + var originalLink = release.Link; + var encodedLink = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(originalLink.ToString())) + "/t.torrent"; + var proxyLink = string.Format("{0}api/{1}/download/{2}", severUrl, indexer.ID, encodedLink); + release.Link = new Uri(proxyLink); + } + + var potatoResponse = new TorrentPotatoResponse(); + + foreach(var release in releases) + { + potatoResponse.results.Add(new TorrentPotatoResponseItem() + { + release_name = release.Title + "[" + indexer.DisplayName + "]", // Suffix the indexer so we can see which tracker we are using in CPS as it just says torrentpotato >.> + torrent_id = release.Guid.ToString(), + details_url = release.Comments.ToString(), + download_url = release.Link.ToString(), + // imdb_id = request.imdbid, + freeleech = false, + type = "movie", + size = (long)release.Size/ (1024 * 1024), // This is in MB + leechers = (int)release.Peers - (int)release.Seeders, + seeders = (int)release.Seeders + }); + } + + // Log info + if (string.IsNullOrWhiteSpace(torznabQuery.SanitizedSearchTerm)) + { + logger.Info(string.Format("Found {0} torrentpotato releases from {1}", releases.Count(), indexer.DisplayName)); + } + else + { + logger.Info(string.Format("Found {0} torrentpotato releases from {1} for: {2} {3}", releases.Count(), indexer.DisplayName, torznabQuery.SanitizedSearchTerm, torznabQuery.GetEpisodeSearchString())); + } + + // Force the return as Json + return new HttpResponseMessage() + { + Content = new JsonContent(potatoResponse) + }; + } + } +} diff --git a/src/Jackett/Controllers/APIController.cs b/src/Jackett/Controllers/TorznabController.cs similarity index 85% rename from src/Jackett/Controllers/APIController.cs rename to src/Jackett/Controllers/TorznabController.cs index 1ba8fac6db070efc0ff534165d0d13d0dea93779..304921ba27896ec2f01d1f55b8ff9ac800af2fec 100644 --- a/src/Jackett/Controllers/APIController.cs +++ b/src/Jackett/Controllers/TorznabController.cs @@ -15,14 +15,14 @@ using System.Web.Http; namespace Jackett.Controllers { [AllowAnonymous] - public class APIController : ApiController + public class TorznabController : ApiController { private IIndexerManagerService indexerService; private Logger logger; private IServerService serverService; private ICacheService cacheService; - public APIController(IIndexerManagerService i, Logger l, IServerService s, ICacheService c) + public TorznabController(IIndexerManagerService i, Logger l, IServerService s, ICacheService c) { indexerService = i; logger = l; @@ -69,14 +69,16 @@ namespace Jackett.Controllers cacheService.CacheRssResults(indexer.DisplayName, releases); } + releases = indexer.FilterResults(torznabQuery, releases); + // Log info if (string.IsNullOrWhiteSpace(torznabQuery.SanitizedSearchTerm)) { - logger.Info(string.Format("Found {0} releases from {1}", releases.Length, indexer.DisplayName)); + logger.Info(string.Format("Found {0} releases from {1}", releases.Count(), indexer.DisplayName)); } else { - logger.Info(string.Format("Found {0} releases from {1} for: {2}", releases.Length, indexer.DisplayName, torznabQuery.SanitizedSearchTerm)); + logger.Info(string.Format("Found {0} releases from {1} for: {2} {3}", releases.Count(), indexer.DisplayName, torznabQuery.SanitizedSearchTerm, torznabQuery.GetEpisodeSearchString())); } var severUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port); @@ -85,20 +87,20 @@ namespace Jackett.Controllers { Title = indexer.DisplayName, Description = indexer.DisplayDescription, - Link = indexer.SiteLink, + Link = new Uri(indexer.SiteLink), ImageUrl = new Uri(severUrl + "logos/" + indexer.ID + ".png"), ImageTitle = indexer.DisplayName, - ImageLink = indexer.SiteLink, + ImageLink = new Uri(indexer.SiteLink), ImageDescription = indexer.DisplayName }); // add Jackett proxy to download links... foreach (var release in releases) { - if (release.Link == null || release.Link.Scheme == "magnet") + if (release.Link == null || (release.Link.IsAbsoluteUri && release.Link.Scheme == "magnet")) continue; var originalLink = release.Link; - var encodedLink = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(originalLink.ToString())) + "/download.torrent"; + var encodedLink = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(originalLink.ToString())) + "/t.torrent"; var proxyLink = string.Format("{0}api/{1}/download/{2}", severUrl, indexer.ID, encodedLink); release.Link = new Uri(proxyLink); } diff --git a/src/Jackett/CurlHelper.cs b/src/Jackett/CurlHelper.cs index 22df91bddcb3a247f01b008a06a2c3461fc0a5d3..99d066fa35374f033e1b6136d7a6a7dee3ea8b9d 100644 --- a/src/Jackett/CurlHelper.cs +++ b/src/Jackett/CurlHelper.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using System.Net.Http.Headers; using Jackett.Utils; using System.Net; +using System.Threading; namespace Jackett { @@ -16,23 +17,12 @@ namespace Jackett { private static readonly object instance = new object(); - static CurlHelper() - { - Engine.Logger.Debug("LibCurl init" + Curl.GlobalInit(CurlInitFlag.All).ToString()); - Engine.Logger.Debug("LibCurl version " + Curl.Version); - } - public class CurlRequest { - public string Url { get; private set; } - public string Cookies { get; private set; } - public string Referer { get; private set; } - public HttpMethod Method { get; private set; } - public Dictionary<string, string> PostData { get; set; } public CurlRequest(HttpMethod method, string url, string cookies = null, string referer = null) @@ -46,49 +36,17 @@ namespace Jackett public class CurlResponse { - public Dictionary<string, string> Headers { get; private set; } public List<string[]> HeaderList { get; private set; } public byte[] Content { get; private set; } public HttpStatusCode Status { get; private set;} - public Dictionary<string, string> Cookies { get; private set; } - public List<string> CookiesFlat { get { return Cookies.Select(c => c.Key + "=" + c.Value).ToList(); } } - public string CookieHeader { get { return string.Join("; ", CookiesFlat); } } + public string Cookies { set; get; } - public CurlResponse(List<string[]> headers, byte[] content, HttpStatusCode s) + public CurlResponse(List<string[]> headers, byte[] content, HttpStatusCode s, string cookies) { - Headers = new Dictionary<string, string>(); - Cookies = new Dictionary<string, string>(); HeaderList = headers; Content = content; Status = s; - foreach (var h in headers) - { - Headers[h[0]] = h[1]; - } - } - - public void AddCookiesFromHeaderValue(string cookieHeaderValue) - { - var rawCookies = cookieHeaderValue.Split(';'); - foreach (var rawCookie in rawCookies) - { - var parts = rawCookie.Split(new char[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries); - if (parts.Length == 1) - Cookies[rawCookie.Trim()] = string.Empty; - else - Cookies[parts[0].Trim()] = parts[1].Trim(); - } - } - - public void AddCookiesFromHeaders(List<string[]> headers) - { - foreach (var h in headers) - { - if (h[0] == "set-cookie") - { - AddCookiesFromHeaderValue(h[1]); - } - } + Cookies = cookies; } } @@ -107,36 +65,14 @@ namespace Jackett return result; } - private static async Task<CurlResponse> FollowRedirect(string url, CurlResponse response) - { - var uri = new Uri(url); - string redirect; - if (response.Headers.TryGetValue("location", out redirect)) - { - string cookie = response.CookieHeader; - if (!redirect.StartsWith("http://") && !redirect.StartsWith("https://")) - { - if (redirect.StartsWith("/")) - redirect = string.Format("{0}://{1}{2}", uri.Scheme, uri.Host, redirect); - else - redirect = string.Format("{0}://{1}/{2}", uri.Scheme, uri.Host, redirect); - } - var newRedirect = await GetAsync(redirect, cookie); - foreach (var c in response.Cookies) - newRedirect.Cookies[c.Key] = c.Value; - newRedirect.AddCookiesFromHeaders(response.HeaderList); - return newRedirect; - } - else - return response; - } - - public static async Task<CurlResponse> PerformCurlAsync(CurlRequest curlRequest) { return await Task.Run(() => PerformCurl(curlRequest)); } + public delegate void ErrorMessage(string s); + public static ErrorMessage OnErrorMessage; + public static CurlResponse PerformCurl(CurlRequest curlRequest) { lock (instance) @@ -151,11 +87,13 @@ namespace Jackett easy.UserAgent = BrowserUtil.ChromeUserAgent; easy.FollowLocation = false; easy.ConnectTimeout = 20; + easy.WriteFunction = (byte[] buf, int size, int nmemb, object data) => { contentBuffers.Add(buf); return size * nmemb; }; + easy.HeaderFunction = (byte[] buf, int size, int nmemb, object extraData) => { headerBuffers.Add(buf); @@ -176,7 +114,25 @@ namespace Jackett easy.PostFieldSize = Encoding.UTF8.GetByteCount(postString); } + if (Startup.DoSSLFix == true) + { + // http://stackoverflow.com/questions/31107851/how-to-fix-curl-35-cannot-communicate-securely-with-peer-no-common-encryptio + // https://git.fedorahosted.org/cgit/mod_nss.git/plain/docs/mod_nss.html + easy.SslCipherList = SSLFix.CipherList; + easy.FreshConnect = true; + easy.ForbidReuse = true; + } + easy.Perform(); + + if(easy.LastErrorCode != CurlCode.Ok) + { + var message = "Error " + easy.LastErrorCode.ToString() + " " + easy.LastErrorDescription; + if (null != OnErrorMessage) + OnErrorMessage(message); + else + Console.WriteLine(message); + } } var headerBytes = Combine(headerBuffers.ToArray()); @@ -185,10 +141,14 @@ namespace Jackett var headers = new List<string[]>(); var headerCount = 0; HttpStatusCode status = HttpStatusCode.InternalServerError; + var cookieBuilder = new StringBuilder(); foreach (var headerPart in headerParts) { if (headerCount == 0) { + var split = headerPart.Split(' '); + if (split.Length < 2) + throw new Exception("HTTP Header missing"); var responseCode = int.Parse(headerPart.Split(' ')[1]); status = (HttpStatusCode)responseCode; } @@ -197,7 +157,17 @@ namespace Jackett var keyVal = headerPart.Split(new char[] { ':' }, 2); if (keyVal.Length > 1) { - headers.Add(new[] { keyVal[0].ToLower().Trim(), keyVal[1].Trim() }); + var key = keyVal[0].ToLower().Trim(); + var value = keyVal[1].Trim(); + + if (key == "set-cookie") + { + cookieBuilder.AppendFormat("{0} ", value.Substring(0, value.IndexOf(';') + 1)); + } + else + { + headers.Add(new[] { key, value }); + } } } @@ -205,12 +175,7 @@ namespace Jackett } var contentBytes = Combine(contentBuffers.ToArray()); - - var curlResponse = new CurlResponse(headers, contentBytes, status); - if (!string.IsNullOrEmpty(curlRequest.Cookies)) - curlResponse.AddCookiesFromHeaderValue(curlRequest.Cookies); - curlResponse.AddCookiesFromHeaders(headers); - + var curlResponse = new CurlResponse(headers, contentBytes, status, cookieBuilder.ToString().TrimEnd()); return curlResponse; } } diff --git a/src/Jackett/Engine.cs b/src/Jackett/Engine.cs index 389f9f5543ce09614aba8be96f9e20d03f71077c..19a2bc8748b1be62c4ad0fc78a0d1c41154460c6 100644 --- a/src/Jackett/Engine.cs +++ b/src/Jackett/Engine.cs @@ -119,7 +119,7 @@ namespace Jackett logFile.FileName = Path.Combine(ConfigurationService.GetAppDataFolderStatic(), "log.txt"); logFile.ArchiveFileName = "log.{#####}.txt"; logFile.ArchiveAboveSize = 500000; - logFile.MaxArchiveFiles = 1; + logFile.MaxArchiveFiles = 5; logFile.KeepFileOpen = false; logFile.ArchiveNumbering = ArchiveNumberingMode.DateAndSequence; var logFileRule = new LoggingRule("*", logLevel, logFile); diff --git a/src/Jackett/ExceptionWithConfigData.cs b/src/Jackett/ExceptionWithConfigData.cs index 9280d5f14e8226ceb8632c7672681e5d036ca3db..9c4c7125e6ef6ea7bcc8e88ad54529d8fbed3019 100644 --- a/src/Jackett/ExceptionWithConfigData.cs +++ b/src/Jackett/ExceptionWithConfigData.cs @@ -18,11 +18,4 @@ namespace Jackett } } - - public class CustomException : Exception - { - public CustomException(string message) - : base(message) - { } - } } diff --git a/src/Jackett/Indexers/AlphaRatio.cs b/src/Jackett/Indexers/AlphaRatio.cs index 5a39584c5695686ad14d784359c5dee147990850..dd4e7f16a4c66604da590126d5eedd6ad48154d7 100644 --- a/src/Jackett/Indexers/AlphaRatio.cs +++ b/src/Jackett/Indexers/AlphaRatio.cs @@ -19,100 +19,48 @@ namespace Jackett.Indexers { public class AlphaRatio : BaseIndexer, IIndexer { - private readonly string LoginUrl = ""; - private readonly string SearchUrl = ""; - private readonly string DownloadUrl = ""; - private readonly string GuidUrl = ""; - - private IWebClient webclient; - private string cookieHeader = ""; + private string LoginUrl { get { return SiteLink + "login.php"; } } + private string SearchUrl { get { return SiteLink + "ajax.php?action=browse&searchstr="; } } + private string DownloadUrl { get { return SiteLink + "torrents.php?action=download&id="; } } + private string GuidUrl { get { return SiteLink + "torrents.php?torrentid="; } } public AlphaRatio(IIndexerManagerService i, IWebClient w, Logger l) : base(name: "AlphaRatio", description: "Legendary", - link: new Uri("https://alpharatio.cc"), + link: "https://alpharatio.cc/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: w, logger: l) { - LoginUrl = SiteLink + "login.php"; - SearchUrl = SiteLink + "ajax.php?action=browse&searchstr="; - DownloadUrl = SiteLink + "torrents.php?action=download&id="; - GuidUrl = SiteLink + "torrents.php?torrentid="; webclient = w; } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataBasicLogin(); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); } public async Task ApplyConfiguration(JToken configJson) { var incomingConfig = new ConfigurationDataBasicLogin(); incomingConfig.LoadValuesFromJson(configJson); - var pairs = new Dictionary<string, string> { - { "username", incomingConfig.Username.Value }, - { "password", incomingConfig.Password.Value }, - { "login", "Login" }, - { "keeplogged", "1" } - }; + { "username", incomingConfig.Username.Value }, + { "password", incomingConfig.Password.Value }, + { "login", "Login" }, + { "keeplogged", "1" } + }; // Do the login - var response = await webclient.GetString(new Utils.Clients.WebRequest() - { - PostData = pairs, - Referer = LoginUrl, - Type = RequestType.POST, - Url = LoginUrl - }); - - cookieHeader = response.Cookies; - - if (response.Status == HttpStatusCode.Found || response.Status == HttpStatusCode.Redirect || response.Status == HttpStatusCode.RedirectMethod) - { - response = await webclient.GetString(new Utils.Clients.WebRequest() - { - Url = SiteLink.ToString(), - Referer = LoginUrl.ToString(), - Cookies = cookieHeader - }); - } - - if (!response.Content.Contains("logout.php?")) - { - CQ dom = response.Content; - dom["#loginform > table"].Remove(); - var errorMessage = dom["#loginform"].Text().Trim().Replace("\n\t", " "); - throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig); - } - else - { - var configSaveData = new JObject(); - configSaveData["cookie_header"] = cookieHeader; - SaveConfig(configSaveData); - IsConfigured = true; - } - } - - HttpRequestMessage CreateHttpRequest(Uri uri) - { - var message = new HttpRequestMessage(); - message.Method = HttpMethod.Post; - message.RequestUri = uri; - message.Headers.UserAgent.ParseAdd(BrowserUtil.ChromeUserAgent); - return message; - } - - public void LoadFromSavedConfiguration(JToken jsonConfig) - { - cookieHeader = (string)jsonConfig["cookie_header"]; - if (!string.IsNullOrEmpty(cookieHeader)) - { - IsConfigured = true; - } + var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, string.Empty, true, SiteLink); + await ConfigureIfOK(response.Cookies, response.Content!=null && response.Content.Contains("logout.php?"), () => + { + CQ dom = response.Content; + dom["#loginform > table"].Remove(); + var errorMessage = dom["#loginform"].Text().Trim().Replace("\n\t", " "); + throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig); + }); } void FillReleaseInfoFromJson(ReleaseInfo release, JObject r) @@ -126,24 +74,16 @@ namespace Jackett.Indexers release.Link = new Uri(DownloadUrl + id); } - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); + var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString); - - var results = await webclient.GetString(new Utils.Clients.WebRequest() - { - Cookies = cookieHeader, - Type = RequestType.GET, - Url = episodeSearchUrl - }); - + var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl); try { - - var json = JObject.Parse(results.Content); + var json = JObject.Parse(response.Content); foreach (JObject r in json["response"]["results"]) { DateTime pubDate = DateTime.MinValue; @@ -179,10 +119,10 @@ namespace Jackett.Indexers } catch (Exception ex) { - OnParseError(results.Content, ex); + OnParseError(response.Content, ex); } - return releases.ToArray(); + return releases; } static DateTime UnixTimestampToDateTime(double unixTime) @@ -191,17 +131,5 @@ namespace Jackett.Indexers long unixTimeStampInTicks = (long)(unixTime * TimeSpan.TicksPerSecond); return new DateTime(unixStart.Ticks + unixTimeStampInTicks); } - - public async Task<byte[]> Download(Uri link) - { - var response = await webclient.GetBytes(new Utils.Clients.WebRequest() - { - Url = link.ToString(), - Cookies = cookieHeader - }); - - return response.Content; - } - } } diff --git a/src/Jackett/Indexers/AnimeBytes.cs b/src/Jackett/Indexers/AnimeBytes.cs index 613888f987ad304b171032eaca7e8fcc4477644b..b8ae9758b6b58d1f85ea8a53b751c9ec089a950c 100644 --- a/src/Jackett/Indexers/AnimeBytes.cs +++ b/src/Jackett/Indexers/AnimeBytes.cs @@ -1,5 +1,6 @@ using CsQuery; using Jackett.Models; +using Jackett.Models.IndexerConfig; using Jackett.Services; using Jackett.Utils; using Jackett.Utils.Clients; @@ -22,50 +23,24 @@ namespace Jackett.Indexers { public class AnimeBytes : BaseIndexer, IIndexer { - class ConfigurationDataBasicLoginAnimeBytes : ConfigurationDataBasicLogin - { - public BoolItem IncludeRaw { get; private set; } - public DisplayItem DateWarning { get; private set; } - - public ConfigurationDataBasicLoginAnimeBytes() - : base() - { - IncludeRaw = new BoolItem() { Name = "IncludeRaw", Value = false }; - DateWarning = new DisplayItem("This tracker does not supply upload dates so they are based off year of release.") { Name = "DateWarning" }; - } - - public override Item[] GetItems() - { - return new Item[] { Username, Password, IncludeRaw, DateWarning }; - } - } - - private readonly string LoginUrl = ""; - private readonly string SearchUrl = ""; + private string LoginUrl { get { return SiteLink + "user/login"; } } + private string SearchUrl { get { return SiteLink + "torrents.php?filter_cat[1]=1"; } } public bool AllowRaws { get; private set; } - private IWebClient webclient; - private string cookieHeader = ""; - public AnimeBytes(IIndexerManagerService i, IWebClient client, Logger l) : base(name: "AnimeBytes", - description: "The web's best Chinese cartoons", - link: new Uri("https://animebytes.tv"), - caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), + link: "https://animebytes.tv/", + description: "Powered by Tentacles", manager: i, + client: client, + caps: new TorznabCapabilities(TorznabCatType.Anime), logger: l) { - TorznabCaps.Categories.Clear(); - TorznabCaps.Categories.Add(new TorznabCategory { ID = "5070", Name = "TV/Anime" }); - LoginUrl = SiteLink + "user/login"; - SearchUrl = SiteLink + "torrents.php?filter_cat[1]=1"; - webclient = client; } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataBasicLoginAnimeBytes(); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLoginAnimeBytes()); } public async Task ApplyConfiguration(JToken configJson) @@ -73,6 +48,10 @@ namespace Jackett.Indexers var config = new ConfigurationDataBasicLoginAnimeBytes(); config.LoadValuesFromJson(configJson); + lock (cache) + { + cache.Clear(); + } // Get the login form as we need the CSRF Token var loginPage = await webclient.GetString(new Utils.Clients.WebRequest() @@ -85,46 +64,36 @@ namespace Jackett.Indexers // Build login form var pairs = new Dictionary<string, string> { - { "csrf_token", csrfToken.Attr("value") }, - { "username", config.Username.Value }, - { "password", config.Password.Value }, - { "keeplogged_sent", "true" }, - { "keeplogged", "on" }, - { "login", "Log In!" } + { "csrf_token", csrfToken.Attr("value") }, + { "username", config.Username.Value }, + { "password", config.Password.Value }, + { "keeplogged_sent", "true" }, + { "keeplogged", "on" }, + { "login", "Log In!" } }; - var content = new FormUrlEncodedContent(pairs); - // Do the login - var response = await webclient.GetString(new Utils.Clients.WebRequest() + var request = new Utils.Clients.WebRequest() { Cookies = loginPage.Cookies, - PostData = pairs, - Referer = LoginUrl, - Type = RequestType.POST, - Url = LoginUrl - }); + PostData = pairs, + Referer = LoginUrl, + Type = RequestType.POST, + Url = LoginUrl + }; + var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, null); // Follow the redirect - if (response.Status == HttpStatusCode.RedirectMethod) - { - cookieHeader = response.Cookies; - response = await webclient.GetString(new Utils.Clients.WebRequest() - { - Url = SearchUrl, - PostData = pairs, - Referer = SiteLink.ToString(), - Cookies = cookieHeader - }); - } + await FollowIfRedirect(response, request.Url, SearchUrl); - if (!response.Content.Contains("/user/logout")) + if (!(response.Content != null && response.Content.Contains("/user/logout"))) { // Their login page appears to be broken and just gives a 500 error. throw new ExceptionWithConfigData("Failed to login, 6 failed attempts will get you banned for 6 hours.", (ConfigurationData)config); } else { + cookieHeader = response.Cookies; AllowRaws = config.IncludeRaw.Value; var configSaveData = new JObject(); configSaveData["cookies"] = cookieHeader; @@ -134,7 +103,7 @@ namespace Jackett.Indexers } } - public void LoadFromSavedConfiguration(JToken jsonConfig) + public override void LoadFromSavedConfiguration(JToken jsonConfig) { // The old config used an array - just fail to load it if (!(jsonConfig["cookies"] is JArray)) @@ -145,24 +114,7 @@ namespace Jackett.Indexers } } - - private string Hash(string input) - { - // Use input string to calculate MD5 hash - MD5 md5 = System.Security.Cryptography.MD5.Create(); - byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input); - byte[] hashBytes = md5.ComputeHash(inputBytes); - - // Convert the byte array to hexadecimal string - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < hashBytes.Length; i++) - { - sb.Append(hashBytes[i].ToString("X2")); - } - return sb.ToString(); - } - - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { // The result list var releases = new List<ReleaseInfo>(); @@ -175,7 +127,7 @@ namespace Jackett.Indexers return releases.ToArray(); } - public async Task<ReleaseInfo[]> GetResults(string searchTerm) + public async Task<IEnumerable<ReleaseInfo>> GetResults(string searchTerm) { // This tracker only deals with full seasons so chop off the episode/season number if we have it D: if (!string.IsNullOrWhiteSpace(searchTerm)) @@ -207,12 +159,7 @@ namespace Jackett.Indexers } // Get the content from the tracker - var response = await webclient.GetString(new Utils.Clients.WebRequest() - { - Cookies = cookieHeader, - Url = queryUrl, - Type = RequestType.GET - }); + var response = await RequestStringWithCookiesAndRetry(queryUrl); CQ dom = response.Content; // Parse @@ -310,9 +257,9 @@ namespace Jackett.Indexers release.PublishDate = release.PublishDate.AddDays(Math.Min(DateTime.Now.DayOfYear, 365) - 1); var infoLink = links.Get(1); - release.Comments = new Uri(SiteLink + "/" + infoLink.Attributes.GetAttribute("href")); - release.Guid = new Uri(SiteLink + "/" + infoLink.Attributes.GetAttribute("href") + "&nh=" + Hash(title)); // Sonarr should dedupe on this url - allow a url per name. - release.Link = new Uri(SiteLink + "/" + downloadLink.Attributes.GetAttribute("href")); + release.Comments = new Uri(SiteLink + infoLink.Attributes.GetAttribute("href")); + release.Guid = new Uri(SiteLink + infoLink.Attributes.GetAttribute("href") + "&nh=" + StringUtil.Hash(title)); // Sonarr should dedupe on this url - allow a url per name. + release.Link = new Uri(downloadLink.Attributes.GetAttribute("href"), UriKind.Relative); // We dont actually have a release name >.> so try to create one var releaseTags = infoLink.InnerText.Split("|".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList(); @@ -380,14 +327,16 @@ namespace Jackett.Indexers cache.Add(new CachedQueryResult(searchTerm, releases)); } - return releases.Select(s => (ReleaseInfo)s.Clone()).ToArray(); + return releases.Select(s => (ReleaseInfo)s.Clone()); } - public async Task<byte[]> Download(Uri link) + + public async override Task<byte[]> Download(Uri link) { + // The urls for this tracker are quite long so append the domain after the incoming request. var response = await webclient.GetBytes(new Utils.Clients.WebRequest() { - Url = link.ToString(), + Url = SiteLink + link.ToString(), Cookies = cookieHeader }); diff --git a/src/Jackett/Indexers/BB.cs b/src/Jackett/Indexers/BB.cs index 9e7c39251a89fdd54edadf6c66f251d75c5fad6d..7ba01fcf2de1980c9637196e5ecd70cddc8d47ea 100644 --- a/src/Jackett/Indexers/BB.cs +++ b/src/Jackett/Indexers/BB.cs @@ -2,6 +2,7 @@ using Jackett.Models; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -19,61 +20,42 @@ namespace Jackett.Indexers public class BB : BaseIndexer, IIndexer { - private readonly string BaseUrl = ""; - private readonly string LoginUrl = ""; - private readonly string SearchUrl = ""; + private string BaseUrl { get { return StringUtil.FromBase64("aHR0cHM6Ly9iYWNvbmJpdHMub3JnLw=="); } } + private Uri BaseUri { get { return new Uri(BaseUrl); } } + private string LoginUrl { get { return BaseUri + "login.php"; } } + private string SearchUrl { get { return BaseUri + "torrents.php?searchstr={0}&searchtags=&tags_type=0&order_by=s3&order_way=desc&disablegrouping=1&filter_cat%5B10%5D=1"; } } - CookieContainer cookies; - HttpClientHandler handler; - HttpClient client; - - public BB(IIndexerManagerService i, Logger l) + public BB(IIndexerManagerService i, Logger l, IWebClient w) : base(name: "bB", description: "bB", - link: new Uri("http://www.reddit.com/r/baconbits"), + link: "http://www.reddit.com/r/baconbits/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: w, logger: l) { - - BaseUrl = StringUtil.FromBase64("aHR0cHM6Ly9iYWNvbmJpdHMub3Jn"); - LoginUrl = BaseUrl + "/login.php"; - SearchUrl = BaseUrl + "/torrents.php?searchstr={0}&searchtags=&tags_type=0&order_by=s3&order_way=desc&disablegrouping=1&filter_cat%5B10%5D=1"; - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataBasicLogin(); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); } public async Task ApplyConfiguration(JToken configJson) { - var config = new ConfigurationDataBasicLogin(); - config.LoadValuesFromJson(configJson); - + var incomingConfig = new ConfigurationDataBasicLogin(); + incomingConfig.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { - { "username", config.Username.Value }, - { "password", config.Password.Value }, + { "username", incomingConfig.Username.Value }, + { "password", incomingConfig.Password.Value }, { "keeplogged", "1" }, { "login", "Log In!" } - }; - - var content = new FormUrlEncodedContent(pairs); - var response = await client.PostAsync(LoginUrl, content); - var responseContent = await response.Content.ReadAsStringAsync(); + }; - if (!responseContent.Contains("logout.php")) + var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, SiteLink); + await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("logout.php"), () => { - CQ dom = responseContent; + CQ dom = response.Content; var messageEl = dom["#loginform"]; var messages = new List<string>(); for (var i = 0; i < 13; i++) @@ -82,35 +64,22 @@ namespace Jackett.Indexers messages.Add(child.Cq().Text().Trim()); } var message = string.Join(" ", messages); - throw new ExceptionWithConfigData(message, (ConfigurationData)config); - } - else - { - - var configSaveData = new JObject(); - cookies.DumpToJson(BaseUrl, configSaveData); - SaveConfig(configSaveData); - IsConfigured = true; - } + throw new ExceptionWithConfigData(message, (ConfigurationData)incomingConfig); + }); } - public void LoadFromSavedConfiguration(JToken jsonConfig) - { - cookies.FillFromJson(new Uri(BaseUrl), jsonConfig, logger); - IsConfigured = true; - } - - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { List<ReleaseInfo> releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString)); - var results = await client.GetStringAsync(episodeSearchUrl); + var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl); + try { - CQ dom = results; + CQ dom = results.Content; var rows = dom["#torrent_table > tbody > tr.torrent"]; foreach (var row in rows) { @@ -143,15 +112,9 @@ namespace Jackett.Indexers } catch (Exception ex) { - OnParseError(results, ex); + OnParseError(results.Content, ex); } - return releases.ToArray(); + return releases; } - - public Task<byte[]> Download(Uri link) - { - return client.GetByteArrayAsync(link); - } - } } diff --git a/src/Jackett/Indexers/BakaBT.cs b/src/Jackett/Indexers/BakaBT.cs index 647c6f878b3e265d76c746ecc53a8fc6bf86867b..5680e1748c4899366aaeb40b5db1f44ceaef4e97 100644 --- a/src/Jackett/Indexers/BakaBT.cs +++ b/src/Jackett/Indexers/BakaBT.cs @@ -18,31 +18,23 @@ namespace Jackett.Indexers { public class BakaBT : BaseIndexer, IIndexer { - public readonly string SearchUrl = ""; - public readonly string LoginUrl = ""; - private string cookieHeader = ""; - private IWebClient webclient; + public string SearchUrl { get { return SiteLink + "browse.php?only=0&hentai=1&incomplete=1&lossless=1&hd=1&multiaudio=1&bonus=1&c1=1&reorder=1&q="; } } + public string LoginUrl { get { return SiteLink + "login.php"; } } public BakaBT(IIndexerManagerService i, IWebClient wc, Logger l) : base(name: "BakaBT", description: "Anime Community", - link: new Uri("http://bakabt.me/"), - caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), + link: "http://bakabt.me/", + caps: new TorznabCapabilities(TorznabCatType.Anime), manager: i, + client: wc, logger: l) { - TorznabCaps.Categories.Clear(); - TorznabCaps.Categories.Add(new TorznabCategory { ID = "5070", Name = "TV/Anime" }); - - SearchUrl = SiteLink + "browse.php?only=0&hentai=1&incomplete=1&lossless=1&hd=1&multiaudio=1&bonus=1&c1=1&reorder=1&q="; - LoginUrl = SiteLink + "login.php"; - webclient = wc; } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataBasicLogin(); - return Task.FromResult<ConfigurationData>((ConfigurationData)config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); } public async Task ApplyConfiguration(JToken configJson) @@ -62,69 +54,32 @@ namespace Jackett.Indexers { "returnto", "/index.php" } }; - var response = await webclient.GetString(new Utils.Clients.WebRequest() - { - Url = LoginUrl, - PostData = pairs, - Referer = SiteLink.ToString(), - Type = RequestType.POST, - Cookies = loginForm.Cookies - }); - - cookieHeader = response.Cookies; - if (response.Status == HttpStatusCode.Found) - { - response = await webclient.GetString(new Utils.Clients.WebRequest() - { - Url = SearchUrl, - Cookies = cookieHeader - }); - } - + var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginForm.Cookies, true, null, SiteLink); var responseContent = response.Content; - - if (!responseContent.Contains("<a href=\"logout.php\">Logout</a>")) - { - CQ dom = responseContent; - var messageEl = dom[".error"].First(); - var errorMessage = messageEl.Text().Trim(); - throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config); - } - else - { - var configSaveData = new JObject(); - configSaveData["cookies"] = cookieHeader; - SaveConfig(configSaveData); - IsConfigured = true; - } - } - - public void LoadFromSavedConfiguration(JToken jsonConfig) - { - cookieHeader = (string)jsonConfig["cookies"]; - IsConfigured = true; + await ConfigureIfOK(response.Cookies, responseContent.Contains("<a href=\"logout.php\">Logout</a>"), () => + { + CQ dom = responseContent; + var messageEl = dom[".error"].First(); + var errorMessage = messageEl.Text().Trim(); + throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config); + }); } - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { // This tracker only deals with full seasons so chop off the episode/season number if we have it D: - if (!string.IsNullOrWhiteSpace(query.SanitizedSearchTerm)) + if (!string.IsNullOrWhiteSpace(query.SearchTerm)) { - var splitindex = query.SanitizedSearchTerm.LastIndexOf(' '); + var splitindex = query.SearchTerm.LastIndexOf(' '); if (splitindex > -1) - query.SanitizedSearchTerm = query.SanitizedSearchTerm.Substring(0, splitindex); + query.SearchTerm = query.SearchTerm.Substring(0, splitindex); } var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm; var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString); - - var response = await webclient.GetString(new Utils.Clients.WebRequest() - { - Url = episodeSearchUrl, - Cookies = cookieHeader - }); + var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl); try { @@ -215,17 +170,12 @@ namespace Jackett.Indexers OnParseError(response.Content, ex); } - return releases.ToArray(); + return releases; } - public async Task<byte[]> Download(Uri link) + public override async Task<byte[]> Download(Uri link) { - var downloadPage = await webclient.GetString(new Utils.Clients.WebRequest() - { - Url = link.ToString(), - Cookies = cookieHeader - }); - + var downloadPage = await RequestStringWithCookies(link.ToString()); CQ dom = downloadPage.Content; var downloadLink = dom.Find(".download_link").First().Attr("href"); @@ -234,14 +184,7 @@ namespace Jackett.Indexers throw new Exception("Unable to find download link."); } - downloadLink = SiteLink + downloadLink; - - var response = await webclient.GetBytes(new Utils.Clients.WebRequest() - { - Url = downloadLink, - Cookies = cookieHeader - }); - + var response = await RequestBytesWithCookies(SiteLink + downloadLink); return response.Content; } } diff --git a/src/Jackett/Indexers/BaseIndexer.cs b/src/Jackett/Indexers/BaseIndexer.cs index 9151e36dde5f2235b880266a13888f4d0cedc75c..50999b6a6d67ca96eadfbe26463f308049c31433 100644 --- a/src/Jackett/Indexers/BaseIndexer.cs +++ b/src/Jackett/Indexers/BaseIndexer.cs @@ -8,39 +8,69 @@ using Newtonsoft.Json.Linq; using NLog; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; +using AutoMapper; +using System.Threading; namespace Jackett.Indexers { public abstract class BaseIndexer { + public string SiteLink { get; private set; } public string DisplayDescription { get; private set; } public string DisplayName { get; private set; } public string ID { get { return GetIndexerID(GetType()); } } public bool IsConfigured { get; protected set; } - public Uri SiteLink { get; private set; } - public TorznabCapabilities TorznabCaps { get; private set; } - protected Logger logger; protected IIndexerManagerService indexerService; - protected static List<CachedQueryResult> cache = new List<CachedQueryResult>(); protected static readonly TimeSpan cacheTime = new TimeSpan(0, 9, 0); + protected IWebClient webclient; + protected string cookieHeader = ""; - public static string GetIndexerID(Type type) - { - return StringUtil.StripNonAlphaNumeric(type.Name.ToLowerInvariant()); - } + private List<CategoryMapping> categoryMapping = new List<CategoryMapping>(); - public BaseIndexer(string name, string description, Uri link, TorznabCapabilities caps, IIndexerManagerService manager,Logger logger) + public BaseIndexer(string name, string link, string description, IIndexerManagerService manager, IWebClient client, Logger logger, TorznabCapabilities caps = null) { + if (!link.EndsWith("/")) + throw new Exception("Site link must end with a slash."); + DisplayName = name; DisplayDescription = description; SiteLink = link; - TorznabCaps = caps; this.logger = logger; indexerService = manager; + webclient = client; + + if (caps == null) + caps = TorznabCapsUtil.CreateDefaultTorznabTVCaps(); + TorznabCaps = caps; + } + + protected int MapTrackerCatToNewznab(string input) + { + if (null != input) { + input = input.ToLowerInvariant(); + var mapping = categoryMapping.Where(m => m.TrackerCategory == input).FirstOrDefault(); + if(mapping!= null) + { + return mapping.NewzNabCategory; + } + } + return 0; + } + + public static string GetIndexerID(Type type) + { + return StringUtil.StripNonAlphaNumeric(type.Name.ToLowerInvariant()); + } + + public void ResetBaseConfig() + { + cookieHeader = string.Empty; + IsConfigured = false; } protected void SaveConfig(JToken config) @@ -64,5 +94,281 @@ namespace Jackett.Indexers cache.Remove(expired); } } + + protected async Task FollowIfRedirect(WebClientStringResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null) + { + var byteResult = new WebClientByteResult(); + // Map to byte + Mapper.Map(response, byteResult); + await FollowIfRedirect(byteResult, referrer, overrideRedirectUrl, overrideCookies); + // Map to string + Mapper.Map(byteResult, response); + } + + protected async Task FollowIfRedirect(WebClientByteResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null) + { + // Follow up to 5 redirects + for (int i = 0; i < 5; i++) + { + if (!response.IsRedirect) + break; + await DoFollowIfRedirect(response, referrer, overrideRedirectUrl, overrideCookies); + } + } + + private async Task DoFollowIfRedirect(WebClientByteResult incomingResponse, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null) + { + if (incomingResponse.IsRedirect) + { + // Do redirect + var redirectedResponse = await webclient.GetBytes(new WebRequest() + { + Url = overrideRedirectUrl ?? incomingResponse.RedirectingTo, + Referer = referrer, + Cookies = overrideCookies ?? cookieHeader + }); + Mapper.Map(redirectedResponse, incomingResponse); + } + } + + + protected void LoadCookieHeaderAndConfigure(JToken jsonConfig) + { + cookieHeader = (string)jsonConfig["cookie_header"]; + if (!string.IsNullOrEmpty(cookieHeader)) + { + IsConfigured = true; + } + else + { + // Legacy cookie key + var jcookes = jsonConfig["cookies"]; + if (jcookes is JArray) { + var array = (JArray)jsonConfig["cookies"]; + cookieHeader = string.Empty; + for (int i = 0; i < array.Count; i++) + { + if (i != 0) + cookieHeader += "; "; + cookieHeader += array[i]; + } + } + else + cookieHeader = (string)jsonConfig["cookies"]; + + if (!string.IsNullOrEmpty(cookieHeader)) + { + IsConfigured = true; + } + } + } + + protected void SaveCookieHeaderAndConfigure() + { + var configSaveData = new JObject(); + configSaveData["cookie_header"] = cookieHeader; + SaveConfig(configSaveData); + IsConfigured = !string.IsNullOrEmpty(cookieHeader); + } + + public virtual void LoadFromSavedConfiguration(JToken jsonConfig) + { + LoadCookieHeaderAndConfigure(jsonConfig); + } + + public async virtual Task<byte[]> Download(Uri link) + { + var response = await RequestBytesWithCookiesAndRetry(link.ToString()); + return response.Content; + } + + protected async Task<WebClientByteResult> RequestBytesWithCookiesAndRetry(string url, string cookieOverride = null) + { + Exception lastException = null; + for (int i = 0; i < 3; i++) + { + try + { + return await RequestBytesWithCookies(url, cookieOverride); + } + catch (Exception e) + { + logger.Error(string.Format("On attempt {0} downloading from {1}: {2}", (i + 1), DisplayName, e.Message)); + lastException = e; + await Task.Delay(500); + } + } + + throw lastException; + } + + protected async Task<WebClientStringResult> RequestStringWithCookies(string url, string cookieOverride = null, string referer = null) + { + var request = new Utils.Clients.WebRequest() + { + Url = url, + Type = RequestType.GET, + Cookies = cookieHeader, + Referer = referer + }; + + if (cookieOverride != null) + request.Cookies = cookieOverride; + return await webclient.GetString(request); + } + + protected async Task<WebClientStringResult> RequestStringWithCookiesAndRetry(string url, string cookieOverride = null, string referer = null) + { + Exception lastException = null; + for (int i = 0; i < 3; i++) + { + try + { + return await RequestStringWithCookies(url, cookieOverride, referer); + } + catch (Exception e) + { + logger.Error(string.Format("On attempt {0} checking for results from {1}: {2}", (i + 1), DisplayName, e.Message)); + lastException= e; + await Task.Delay(500); + } + } + + throw lastException; + } + + protected async Task<WebClientByteResult> RequestBytesWithCookies(string url, string cookieOverride = null) + { + var request = new Utils.Clients.WebRequest() + { + Url = url, + Type = RequestType.GET, + Cookies = cookieOverride ?? cookieHeader + }; + + if (cookieOverride != null) + request.Cookies = cookieOverride; + return await webclient.GetBytes(request); + } + + protected async Task<WebClientStringResult> PostDataWithCookies(string url, Dictionary<string, string> data, string cookieOverride = null) + { + var request = new Utils.Clients.WebRequest() + { + Url = url, + Type = RequestType.POST, + Cookies = cookieOverride ?? cookieHeader, + PostData = data + }; + return await webclient.GetString(request); + } + + protected async Task<WebClientStringResult> PostDataWithCookiesAndRetry(string url, Dictionary<string, string> data, string cookieOverride = null) + { + Exception lastException = null; + for (int i = 0; i < 3; i++) + { + try + { + return await PostDataWithCookies(url,data,cookieOverride); + } + catch (Exception e) + { + logger.Error(string.Format("On attempt {0} checking for results from {1}: {2}", (i + 1), DisplayName, e.Message)); + lastException = e; + await Task.Delay(500); + } + } + + throw lastException; + } + + protected async Task<WebClientStringResult> RequestLoginAndFollowRedirect(string url, Dictionary<string, string> data, string cookies, bool returnCookiesFromFirstCall, string redirectUrlOverride = null, string referer =null) + { + var request = new Utils.Clients.WebRequest() + { + Url = url, + Type = RequestType.POST, + Cookies = cookies, + Referer = referer, + PostData = data + }; + var response = await webclient.GetString(request); + var firstCallCookies = response.Cookies; + + if (response.IsRedirect) + { + await FollowIfRedirect(response, request.Url, null, response.Cookies); + } + + if (returnCookiesFromFirstCall) + { + response.Cookies = firstCallCookies; + } + + return response; + } + + protected async Task ConfigureIfOK(string cookies, bool isLoggedin, Func<Task> onError) + { + if (isLoggedin) + { + cookieHeader = cookies; + SaveCookieHeaderAndConfigure(); + } + else + { + await onError(); + } + } + + public virtual IEnumerable<ReleaseInfo> FilterResults(TorznabQuery query, IEnumerable<ReleaseInfo> results) + { + foreach(var result in results) + { + if(query.Categories.Length == 0 || query.Categories.Contains(result.Category) || result.Category == 0 || TorznabCatType.QueryContainsParentCategory(query.Categories, result.Category)) + { + yield return result; + } + } + } + + protected void AddCategoryMapping(string trackerCategory, int newznabCategory) + { + categoryMapping.Add(new CategoryMapping(trackerCategory, newznabCategory)); + } + + protected void AddCategoryMapping(int trackerCategory, TorznabCategory newznabCategory) + { + categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory.ID)); + if (!TorznabCaps.Categories.Contains(newznabCategory)) + TorznabCaps.Categories.Add(newznabCategory); + } + + protected void AddCategoryMapping(string trackerCategory, TorznabCategory newznabCategory) + { + categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory.ID)); + if (!TorznabCaps.Categories.Contains(newznabCategory)) + TorznabCaps.Categories.Add(newznabCategory); + } + + protected void AddCategoryMapping(int trackerCategory, int newznabCategory) + { + categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory)); + } + + protected List<string> MapTorznabCapsToTrackers(TorznabQuery query) + { + var result = new List<string>(); + foreach (var cat in query.Categories) + { + foreach (var mapping in categoryMapping.Where(c => c.NewzNabCategory == cat)) + { + result.Add(mapping.TrackerCategory); + } + } + + return result; + } } } diff --git a/src/Jackett/Indexers/BeyondHD.cs b/src/Jackett/Indexers/BeyondHD.cs index 152a9b9f950863884662ae4c96c08d0b1756b80f..cab8600b939d6fe9c5fe4f6710342c506fb38bcf 100644 --- a/src/Jackett/Indexers/BeyondHD.cs +++ b/src/Jackett/Indexers/BeyondHD.cs @@ -2,6 +2,7 @@ using Jackett.Models; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -17,82 +18,55 @@ namespace Jackett.Indexers { public class BeyondHD : BaseIndexer, IIndexer { - private readonly string SearchUrl = ""; - private readonly string DownloadUrl = ""; + private string SearchUrl { get { return SiteLink + "browse.php?c40=1&c44=1&c48=1&c89=1&c46=1&c45=1&searchin=title&incldead=0&search={0}"; } } + private string DownloadUrl { get { return SiteLink + "download.php?torrent={0}"; } } - CookieContainer cookies; - HttpClientHandler handler; - HttpClient client; - - public BeyondHD(IIndexerManagerService i, Logger l) + public BeyondHD(IIndexerManagerService i, Logger l, IWebClient w) : base(name: "BeyondHD", description: "Without BeyondHD, your HDTV is just a TV", - link: new Uri("https://beyondhd.me"), + link: "https://beyondhd.me/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: w, logger: l) { - SearchUrl = SiteLink + "browse.php?c40=1&c44=1&c48=1&c89=1&c46=1&c45=1&searchin=title&incldead=0&search={0}"; - DownloadUrl = SiteLink + "download.php?torrent={0}"; - - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataCookie(); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataCookie()); } public async Task ApplyConfiguration(JToken configJson) { var config = new ConfigurationDataCookie(); config.LoadValuesFromJson(configJson); + cookieHeader = config.CookieHeader; - var jsonCookie = new JObject(); - jsonCookie["cookie_header"] = config.CookieHeader; - cookies.FillFromJson(SiteLink, jsonCookie, logger); - - var responseContent = await client.GetStringAsync(SiteLink); + var response = await webclient.GetString(new Utils.Clients.WebRequest() + { + Url = SiteLink, + Cookies = cookieHeader + }); - if (!responseContent.Contains("logout.php")) + await ConfigureIfOK(cookieHeader, response.Content.Contains("logout.php"), () => { - CQ dom = responseContent; + CQ dom = response.Content; throw new ExceptionWithConfigData("Invalid cookie header", (ConfigurationData)config); - } - else - { - var configSaveData = new JObject(); - cookies.DumpToJson(SiteLink, configSaveData); - SaveConfig(configSaveData); - IsConfigured = true; - } - } - - public void LoadFromSavedConfiguration(JToken jsonConfig) - { - cookies.FillFromJson(SiteLink, jsonConfig, logger); - IsConfigured = true; + }); } - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { List<ReleaseInfo> releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString)); - var results = await client.GetStringAsync(episodeSearchUrl); - + var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl); + await FollowIfRedirect(results); try { - CQ dom = results; + CQ dom = results.Content; var rows = dom["table.torrenttable > tbody > tr.browse_color"]; foreach (var row in rows) { @@ -128,15 +102,10 @@ namespace Jackett.Indexers } catch (Exception ex) { - OnParseError(results, ex); + OnParseError(results.Content, ex); } - return releases.ToArray(); - } - - public Task<byte[]> Download(Uri link) - { - return client.GetByteArrayAsync(link); + return releases; } } } diff --git a/src/Jackett/Indexers/BitHdtv.cs b/src/Jackett/Indexers/BitHdtv.cs index 66ea5b035f2ed3a2a644d64b153ff6e376d59b35..1dd1e165fc0597cbe37c678011edd032e9237993 100644 --- a/src/Jackett/Indexers/BitHdtv.cs +++ b/src/Jackett/Indexers/BitHdtv.cs @@ -2,6 +2,7 @@ using Jackett.Models; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -18,91 +19,57 @@ namespace Jackett.Indexers { public class BitHdtv : BaseIndexer, IIndexer { - private readonly string LoginUrl = ""; - private readonly string SearchUrl = ""; - private readonly string DownloadUrl = ""; + private string LoginUrl { get { return SiteLink + "takelogin.php"; } } + private string SearchUrl { get { return SiteLink + "torrents.php?cat=0&search="; } } + private string DownloadUrl { get { return SiteLink + "download.php?/{0}/dl.torrent"; } } - CookieContainer cookies; - HttpClientHandler handler; - HttpClient client; - - public BitHdtv(IIndexerManagerService i, Logger l) + public BitHdtv(IIndexerManagerService i, Logger l, IWebClient w) : base(name: "BIT-HDTV", description: "Home of high definition invites", - link: new Uri("https://www.bit-hdtv.com"), + link: "https://www.bit-hdtv.com/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: w, logger: l) { - LoginUrl = SiteLink + "takelogin.php"; - SearchUrl = SiteLink + "torrents.php?cat=0&search="; - DownloadUrl = SiteLink + "download.php?/{0}/dl.torrent"; - - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataBasicLogin(); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); } public async Task ApplyConfiguration(JToken configJson) { - var config = new ConfigurationDataBasicLogin(); - config.LoadValuesFromJson(configJson); + var incomingConfig = new ConfigurationDataBasicLogin(); + incomingConfig.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { - { "username", config.Username.Value }, - { "password", config.Password.Value } + { "username", incomingConfig.Username.Value }, + { "password", incomingConfig.Password.Value } }; - var content = new FormUrlEncodedContent(pairs); - - var response = await client.PostAsync(LoginUrl, content); - var responseContent = await response.Content.ReadAsStringAsync(); - - if (!responseContent.Contains("logout.php")) + var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, SiteLink); + await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("logout.php"), () => { - CQ dom = responseContent; + CQ dom = response.Content; var messageEl = dom["table.detail td.text"].Last(); messageEl.Children("a").Remove(); messageEl.Children("style").Remove(); var errorMessage = messageEl.Text().Trim(); - throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config); - } - else - { - var configSaveData = new JObject(); - cookies.DumpToJson(SiteLink, configSaveData); - SaveConfig(configSaveData); - IsConfigured = true; - } + throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig); + }); } - public void LoadFromSavedConfiguration(JToken jsonConfig) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { - cookies.FillFromJson(SiteLink, jsonConfig, logger); - IsConfigured = true; - } - - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) - { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - + var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString); - var results = await client.GetStringAsync(episodeSearchUrl); + var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl); try { - CQ dom = results; + CQ dom = results.Content; dom["#needseed"].Remove(); var rows = dom["table[width='750'] > tbody"].Children(); foreach (var row in rows.Skip(1)) @@ -136,15 +103,10 @@ namespace Jackett.Indexers } catch (Exception ex) { - OnParseError(results, ex); + OnParseError(results.Content, ex); } - return releases.ToArray(); - } - - public Task<byte[]> Download(Uri link) - { - return client.GetByteArrayAsync(link); + return releases; } } } diff --git a/src/Jackett/Indexers/BitMeTV.cs b/src/Jackett/Indexers/BitMeTV.cs index 4ee4e8aad91e306e003386351d8669348de72bf7..e8b7cc123d02e3e3777089c5311c3f58ee717f63 100644 --- a/src/Jackett/Indexers/BitMeTV.cs +++ b/src/Jackett/Indexers/BitMeTV.cs @@ -1,7 +1,9 @@ using CsQuery; using Jackett.Models; +using Jackett.Models.IndexerConfig; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -19,68 +21,33 @@ namespace Jackett.Indexers { public class BitMeTV : BaseIndexer, IIndexer { - class BmtvConfig : ConfigurationData - { - public StringItem Username { get; private set; } - - public StringItem Password { get; private set; } - - public ImageItem CaptchaImage { get; private set; } - - public StringItem CaptchaText { get; private set; } + private string LoginUrl { get { return SiteLink + "login.php"; } } + private string LoginPost { get { return SiteLink + "takelogin.php"; } } + private string CaptchaUrl { get { return SiteLink + "visual.php"; } } + private string SearchUrl { get { return SiteLink + "browse.php"; } } - public BmtvConfig() - { - Username = new StringItem { Name = "Username" }; - Password = new StringItem { Name = "Password" }; - CaptchaImage = new ImageItem { Name = "Captcha Image" }; - CaptchaText = new StringItem { Name = "Captcha Text" }; - } - - public override Item[] GetItems() - { - return new Item[] { Username, Password, CaptchaImage, CaptchaText }; - } - } - - private readonly string LoginUrl = ""; - private readonly string LoginPost = ""; - private readonly string CaptchaUrl = ""; - private readonly string SearchUrl = ""; - - CookieContainer cookies; - HttpClientHandler handler; - HttpClient client; - - public BitMeTV(IIndexerManagerService i, Logger l) + public BitMeTV(IIndexerManagerService i, Logger l, IWebClient c) : base(name: "BitMeTV", description: "TV Episode specialty tracker", - link: new Uri("http://www.bitmetv.org"), + link: "http://www.bitmetv.org/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: c, logger: l) { - LoginUrl = SiteLink + "login.php"; - LoginPost = SiteLink + "takelogin.php"; - CaptchaUrl = SiteLink + "visual.php"; - SearchUrl = SiteLink + "browse.php"; - - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); } public async Task<ConfigurationData> GetConfigurationForSetup() { - await client.GetAsync(LoginUrl); - var captchaImage = await client.GetByteArrayAsync(CaptchaUrl); + var response = await webclient.GetString(new Utils.Clients.WebRequest() + { + Url = LoginUrl + }); + cookieHeader = response.Cookies; + var captchaImage = await RequestBytesWithCookies(CaptchaUrl); var config = new BmtvConfig(); - config.CaptchaImage.Value = captchaImage; + config.CaptchaImage.Value = captchaImage.Content; + config.CaptchaCookie.Value = captchaImage.Cookies; return (ConfigurationData)config; } @@ -95,46 +62,29 @@ namespace Jackett.Indexers { "secimage", config.CaptchaText.Value } }; - var content = new FormUrlEncodedContent(pairs); - - var response = await client.PostAsync(LoginPost, content); - var responseContent = await response.Content.ReadAsStringAsync(); - - if (!responseContent.Contains("/logout.php")) + var response = await RequestLoginAndFollowRedirect(LoginPost, pairs, config.CaptchaCookie.Value, true); + await ConfigureIfOK(response.Cookies, response.Content.Contains("/logout.php"), async () => { - CQ dom = responseContent; + CQ dom = response.Content; var messageEl = dom["table tr > td.embedded > h2"].Last(); var errorMessage = messageEl.Text(); - var captchaImage = await client.GetByteArrayAsync(CaptchaUrl); - config.CaptchaImage.Value = captchaImage; + var captchaImage = await RequestBytesWithCookies(CaptchaUrl); + config.CaptchaImage.Value = captchaImage.Content; config.CaptchaText.Value = ""; + config.CaptchaCookie.Value = captchaImage.Cookies; throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config); - } - else - { - var configSaveData = new JObject(); - cookies.DumpToJson(SiteLink, configSaveData); - SaveConfig(configSaveData); - IsConfigured = true; - } + }); } - public void LoadFromSavedConfiguration(JToken jsonConfig) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { - cookies.FillFromJson(SiteLink, jsonConfig, logger); - IsConfigured = true; - } - - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) - { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - + var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var episodeSearchUrl = string.Format("{0}?search={1}&cat=0", SearchUrl, HttpUtility.UrlEncode(searchString)); - var results = await client.GetStringAsync(episodeSearchUrl); + var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl); try { - CQ dom = results; + CQ dom = results.Content; var table = dom["tbody > tr > .latest"].Parent().Parent(); @@ -177,17 +127,10 @@ namespace Jackett.Indexers } catch (Exception ex) { - OnParseError(results, ex); + OnParseError(results.Content, ex); } - return releases.ToArray(); - + return releases; } - - public Task<byte[]> Download(Uri link) - { - return client.GetByteArrayAsync(link); - } - } } diff --git a/src/Jackett/Indexers/FrenchTorrentDb.cs b/src/Jackett/Indexers/FrenchTorrentDb.cs index 982fabd30de444a4a0e7b2216791f4a0e145f0d4..bfd7854d29ac31ca20de773dbf06f2ed818e8bf7 100644 --- a/src/Jackett/Indexers/FrenchTorrentDb.cs +++ b/src/Jackett/Indexers/FrenchTorrentDb.cs @@ -1,7 +1,9 @@ using CsQuery; using Jackett.Models; +using Jackett.Models.IndexerConfig; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -16,111 +18,53 @@ namespace Jackett.Indexers { class FrenchTorrentDb : BaseIndexer, IIndexer { - public event Action<IIndexer, Newtonsoft.Json.Linq.JToken> OnSaveConfigurationRequested; + private string MainUrl { get { return SiteLink + "?section=INDEX"; } } + private string SearchUrl { get { return SiteLink + "?section=TORRENTS&exact=1&name={0}&submit=GO"; } } - public event Action<IIndexer, string, Exception> OnResultParsingError; - - class ConfigurationDataBasicLoginFrenchTorrentDb : ConfigurationData - { - public StringItem Cookie { get; private set; } - - public ConfigurationDataBasicLoginFrenchTorrentDb() - { - Cookie = new StringItem { Name = "Cookie" }; - } - - public override Item[] GetItems() - { - return new Item[] { Cookie }; - } - } - - - private readonly string MainUrl = ""; - private readonly string SearchUrl = ""; - - string cookie = string.Empty; - - CookieContainer cookies; - HttpClientHandler handler; - HttpClient client; - - public FrenchTorrentDb(IIndexerManagerService i, Logger l) + public FrenchTorrentDb(IIndexerManagerService i, Logger l, IWebClient c) : base(name: "FrenchTorrentDb", description: "One the biggest French Torrent Tracker", - link: new Uri("http://www.frenchtorrentdb.com/"), + link: "http://www.frenchtorrentdb.com/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: c, logger: l) { - MainUrl = SiteLink + "?section=INDEX"; - SearchUrl = SiteLink + "?section=TORRENTS&exact=1&name={0}&submit=GO"; - - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); - client.DefaultRequestHeaders.UserAgent.ParseAdd(BrowserUtil.ChromeUserAgent); } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataUrl(SiteLink); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataUrl(SiteLink)); } public async Task ApplyConfiguration(Newtonsoft.Json.Linq.JToken configJson) { var config = new ConfigurationDataBasicLoginFrenchTorrentDb(); config.LoadValuesFromJson(configJson); - cookies.SetCookies(SiteLink, "WebsiteID=" + config.Cookie.Value); - var mainPage = await client.GetAsync(MainUrl); - string responseContent = await mainPage.Content.ReadAsStringAsync(); + var cookies = "WebsiteID=" + config.Cookie.Value; + var response = await webclient.GetString(new Utils.Clients.WebRequest() + { + Url = MainUrl, + Type = RequestType.GET, + Cookies = cookies + }); - if (!responseContent.Contains("/?section=LOGOUT")) + await ConfigureIfOK(cookies, response.Content.Contains("/?section=LOGOUT"), () => { throw new ExceptionWithConfigData("Failed to login", (ConfigurationData)config); - } - else - { - var configSaveData = new JObject(); - configSaveData["cookie"] = config.Cookie.Value; - - if (OnSaveConfigurationRequested != null) - OnSaveConfigurationRequested(this, configSaveData); - - IsConfigured = true; - } + }); } - public void LoadFromSavedConfiguration(Newtonsoft.Json.Linq.JToken jsonConfig) - { - cookie = (string)jsonConfig["cookie"]; - cookies.SetCookies(SiteLink, "WebsiteID=" + cookie); - IsConfigured = true; - } - - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { List<ReleaseInfo> releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString)); - - var message = new HttpRequestMessage(); - message.Method = HttpMethod.Get; - message.RequestUri = new Uri(episodeSearchUrl); - - var response = await client.SendAsync(message); - var results = await response.Content.ReadAsStringAsync(); + var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl); try { - - CQ dom = results; + CQ dom = response.Cookies; var rows = dom[".results_index ul"]; foreach (var row in rows) { @@ -147,16 +91,10 @@ namespace Jackett.Indexers } catch (Exception ex) { - OnResultParsingError(this, results, ex); - throw ex; + OnParseError(response.Content, ex); } - return releases.ToArray(); - } - - public Task<byte[]> Download(Uri link) - { - return client.GetByteArrayAsync(link); + return releases; } } } diff --git a/src/Jackett/Indexers/Freshon.cs b/src/Jackett/Indexers/Freshon.cs index cc59ab6a4d8b7699677af9e393ad1b137146b1c3..169a7d7e3bae6f9a281443a1e701f0e76b6642c2 100644 --- a/src/Jackett/Indexers/Freshon.cs +++ b/src/Jackett/Indexers/Freshon.cs @@ -3,6 +3,7 @@ using Jackett.Indexers; using Jackett.Models; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -21,100 +22,51 @@ namespace Jackett.Indexers { public class Freshon : BaseIndexer, IIndexer { - private readonly string LoginUrl = ""; - private readonly string LoginPostUrl = ""; - private readonly string SearchUrl = ""; + private string LoginUrl { get { return SiteLink + "login.php"; } } + private string LoginPostUrl { get { return SiteLink + "login.php?action=makelogin"; } } + private string SearchUrl { get { return SiteLink + "browse.php"; } } - CookieContainer cookies; - HttpClientHandler handler; - HttpClient client; - - public Freshon(IIndexerManagerService i, Logger l) + public Freshon(IIndexerManagerService i, Logger l, IWebClient c) : base(name: "FreshOnTV", description: "Our goal is to provide the latest stuff in the TV show domain", - link: new Uri("https://freshon.tv"), + link: "https://freshon.tv/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: c, logger: l) { - - LoginUrl = SiteLink + "login.php"; - LoginPostUrl = SiteLink + "login.php?action=makelogin"; - SearchUrl = SiteLink + "browse.php"; - - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); } public async Task<ConfigurationData> GetConfigurationForSetup() { - var request = CreateHttpRequest(new Uri(LoginUrl)); - var response = await client.SendAsync(request); - await response.Content.ReadAsStreamAsync(); - var config = new ConfigurationDataBasicLogin(); - return config; + return await Task.FromResult< ConfigurationData>(new ConfigurationDataBasicLogin()); } public async Task ApplyConfiguration(JToken configJson) { - var config = new ConfigurationDataBasicLogin(); - config.LoadValuesFromJson(configJson); - + var incomingConfig = new ConfigurationDataBasicLogin(); + incomingConfig.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { - { "username", config.Username.Value }, - { "password", config.Password.Value } + { "username", incomingConfig.Username.Value }, + { "password", incomingConfig.Password.Value } }; - var content = new FormUrlEncodedContent(pairs); - var message = CreateHttpRequest(new Uri(LoginPostUrl)); - message.Method = HttpMethod.Post; - message.Content = content; - message.Headers.Referrer = new Uri(LoginUrl); + // Get inital cookies + cookieHeader = string.Empty; + var response = await RequestLoginAndFollowRedirect(LoginPostUrl, pairs, cookieHeader, true, null, LoginUrl); - var response = await client.SendAsync(message); - var responseContent = await response.Content.ReadAsStringAsync(); - - if (!responseContent.Contains("/logout.php")) + await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("/logout.php"), () => { - CQ dom = responseContent; + CQ dom = response.Content; var messageEl = dom[".error_text"]; var errorMessage = messageEl.Text().Trim(); - throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config); - } - else - { - var configSaveData = new JObject(); - cookies.DumpToJson(SiteLink, configSaveData); - SaveConfig(configSaveData); - IsConfigured = true; - } + throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig); + }); } - public void LoadFromSavedConfiguration(JToken jsonConfig) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { - cookies.FillFromJson(SiteLink, jsonConfig, logger); - IsConfigured = true; - } - - HttpRequestMessage CreateHttpRequest(Uri uri) - { - var message = new HttpRequestMessage(); - message.Method = HttpMethod.Get; - message.RequestUri = uri; - message.Headers.UserAgent.ParseAdd(BrowserUtil.ChromeUserAgent); - return message; - } - - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) - { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - + var releases = new List<ReleaseInfo>(); string episodeSearchUrl; if (string.IsNullOrEmpty(query.SanitizedSearchTerm)) @@ -125,12 +77,10 @@ namespace Jackett.Indexers episodeSearchUrl = string.Format("{0}?search={1}&cat=0", SearchUrl, HttpUtility.UrlEncode(searchString)); } - var request = CreateHttpRequest(new Uri(episodeSearchUrl)); - var response = await client.SendAsync(request); - var results = await response.Content.ReadAsStringAsync(); + var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl); try { - CQ dom = results; + CQ dom = results.Content; var rows = dom["#highlight > tbody > tr"]; @@ -170,18 +120,10 @@ namespace Jackett.Indexers } catch (Exception ex) { - OnParseError(results, ex); + OnParseError(results.Content, ex); } - return releases.ToArray(); - } - - public async Task<byte[]> Download(Uri link) - { - var request = CreateHttpRequest(link); - var response = await client.SendAsync(request); - var bytes = await response.Content.ReadAsByteArrayAsync(); - return bytes; + return releases; } } } diff --git a/src/Jackett/Indexers/HDSpace.cs b/src/Jackett/Indexers/HDSpace.cs index 53d9ef57e7a406e5ead7b4445da2653b2760a7bf..d266a079b0c22dbfda9731067e3459aad62116a1 100644 --- a/src/Jackett/Indexers/HDSpace.cs +++ b/src/Jackett/Indexers/HDSpace.cs @@ -18,30 +18,23 @@ namespace Jackett.Indexers { public class HDSpace : BaseIndexer, IIndexer { - - private readonly string LoginUrl = ""; - private readonly string SearchUrl = ""; - private string cookieHeader = ""; - - private IWebClient webclient; + private string LoginUrl { get { return SiteLink + "index.php?page=login"; } } + private string SearchUrl { get { return SiteLink + "index.php?page=torrents&active=0&options=0&category=21%3B22&search={0}"; } } public HDSpace(IIndexerManagerService i, IWebClient wc, Logger l) : base(name: "HD-Space", description: "Sharing The Universe", - link: new Uri("https://hd-space.org"), + link: "https://hd-space.org/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: wc, logger: l) { - LoginUrl = SiteLink + "index.php?page=login"; - SearchUrl = SiteLink + "index.php?page=torrents&active=0&options=0&category=21%3B22&search={0}"; - webclient = wc; } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataBasicLogin(); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); } public async Task ApplyConfiguration(JToken configJson) @@ -49,11 +42,7 @@ namespace Jackett.Indexers var config = new ConfigurationDataBasicLogin(); config.LoadValuesFromJson(configJson); - var loginPage = await webclient.GetString(new WebRequest() - { - Url = LoginUrl, - Type = RequestType.GET - }); + var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty); var pairs = new Dictionary<string, string> { { "uid", config.Username.Value }, @@ -61,76 +50,24 @@ namespace Jackett.Indexers }; // Send Post - var loginPost = await webclient.GetString(new WebRequest() - { - Url = LoginUrl, - PostData = pairs, - Referer = LoginUrl, - Type = RequestType.POST, - Cookies = loginPage.Cookies - }); + var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, null, LoginUrl); - if (loginPost.Status == System.Net.HttpStatusCode.OK) + await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("logout.php"), () => { var errorStr = "You have {0} remaining login attempts"; var remainingAttemptSpan = new Regex(string.Format(errorStr, "(.*?)")).Match(loginPage.Content).Groups[1].ToString(); var attempts = Regex.Replace(remainingAttemptSpan, "<.*?>", String.Empty); var errorMessage = string.Format(errorStr, attempts); throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config); - } - - // Get result from redirect - var loginResult = await webclient.GetString(new WebRequest() - { - Url = SiteLink + loginPost.RedirectingTo, - Type = RequestType.GET, - Cookies = loginPost.Cookies - }); - - if (!loginResult.Content.Contains("logout.php")) - { - throw new ExceptionWithConfigData("Login failed", (ConfigurationData)config); - } - else - { - cookieHeader = loginPost.Cookies; - var configSaveData = new JObject(); - configSaveData["cookies"] = cookieHeader; - SaveConfig(configSaveData); - IsConfigured = true; - } - - } - - public void LoadFromSavedConfiguration(JToken jsonConfig) - { - cookieHeader = (string)jsonConfig["cookies"]; - IsConfigured = true; - } - - public async Task<byte[]> Download(Uri link) - { - var response = await webclient.GetBytes(new WebRequest() - { - Url = link.ToString(), - Cookies = cookieHeader }); - return response.Content; } - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - + var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString)); - - var response = await webclient.GetString(new WebRequest() - { - Url = episodeSearchUrl, - Referer = SiteLink.ToString(), - Cookies = cookieHeader - }); + var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl); var results = response.Content; try @@ -179,7 +116,7 @@ namespace Jackett.Indexers { OnParseError(results, ex); } - return releases.ToArray(); + return releases; } } } diff --git a/src/Jackett/Indexers/HDTorrents.cs b/src/Jackett/Indexers/HDTorrents.cs index 9d8edc90dd5f9581ee2c070b080a5ff188c6446d..d8e3d5158ca854e7fd81e2394f68e1f99c232798 100644 --- a/src/Jackett/Indexers/HDTorrents.cs +++ b/src/Jackett/Indexers/HDTorrents.cs @@ -2,6 +2,7 @@ using Jackett.Models; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -18,185 +19,119 @@ namespace Jackett.Indexers { public class HDTorrents : BaseIndexer, IIndexer { - private readonly string SearchUrl = ""; - private static string LoginUrl = ""; + private string SearchUrl { get { return SiteLink + "torrents.php?search={0}&active=1&options=0&category%5B%5D=59&category%5B%5D=60&category%5B%5D=30&category%5B%5D=38&page=0"; } } + private string LoginUrl { get { return SiteLink + "login.php"; } } private const int MAXPAGES = 3; - CookieContainer cookies; - HttpClientHandler handler; - HttpClient client; - - public HDTorrents(IIndexerManagerService i, Logger l) + public HDTorrents(IIndexerManagerService i, Logger l, IWebClient w) : base(name: "HD-Torrents", description: "HD-Torrents is a private torrent website with HD torrents and strict rules on their content.", - link: new Uri("http://hdts.ru"),// Of the accessible domains the .ru seems the most reliable. https://hdts.ru | https://hd-torrents.org | https://hd-torrents.net | https://hd-torrents.me + link: "http://hdts.ru/",// Of the accessible domains the .ru seems the most reliable. https://hdts.ru | https://hd-torrents.org | https://hd-torrents.net | https://hd-torrents.me caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: w, logger: l) { - SearchUrl = SiteLink + "torrents.php?search={0}&active=1&options=0&category%5B%5D=59&category%5B%5D=60&category%5B%5D=30&category%5B%5D=38&page={1}"; - LoginUrl = SiteLink + "login.php"; - - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataBasicLogin(); - return Task.FromResult<ConfigurationData>(config); - } - - HttpRequestMessage CreateHttpRequest(string url) - { - var message = new HttpRequestMessage(); - message.Method = HttpMethod.Get; - message.RequestUri = new Uri(url); - message.Headers.UserAgent.ParseAdd(BrowserUtil.ChromeUserAgent); - return message; + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); } public async Task ApplyConfiguration(JToken configJson) { - var config = new ConfigurationDataBasicLogin(); - config.LoadValuesFromJson(configJson); - - var startMessage = CreateHttpRequest(LoginUrl); - var results = await (await client.SendAsync(startMessage)).Content.ReadAsStringAsync(); - + var incomingConfig = new ConfigurationDataBasicLogin(); + incomingConfig.LoadValuesFromJson(configJson); + var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty); var pairs = new Dictionary<string, string> { - { "uid", config.Username.Value }, - { "pwd", config.Password.Value } - }; - - var content = new FormUrlEncodedContent(pairs); - - var loginRequest = CreateHttpRequest(LoginUrl); - loginRequest.Method = HttpMethod.Post; - loginRequest.Content = content; - loginRequest.Headers.Referrer = new Uri("https://hd-torrents.org/torrents.php"); + { "uid", incomingConfig.Username.Value }, + { "pwd", incomingConfig.Password.Value } + }; - var response = await client.SendAsync(loginRequest); - var responseContent = await response.Content.ReadAsStringAsync(); + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, null, LoginUrl); - if (!responseContent.Contains("If your browser doesn't have javascript enabled")) + await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("If your browser doesn't have javascript enabled"), () => { var errorMessage = "Couldn't login"; - throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config); - } - else - { - var configSaveData = new JObject(); - cookies.DumpToJson(SiteLink, configSaveData); - SaveConfig(configSaveData); - IsConfigured = true; - } + throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig); + }); } - public void LoadFromSavedConfiguration(JToken jsonConfig) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { - cookies.FillFromJson(SiteLink, jsonConfig, logger); - IsConfigured = true; - } - - async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query, Uri baseUrl) - { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - List<string> searchurls = new List<string>(); - + var releases = new List<ReleaseInfo>(); + var searchurls = new List<string>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); - for (int page = 0; page < MAXPAGES; page++) + var searchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString.Trim())); + var results = await RequestStringWithCookiesAndRetry(searchUrl); + try { - searchurls.Add(string.Format(SearchUrl, HttpUtility.UrlEncode(searchString.Trim()), page)); - } + CQ dom = results.Content; + ReleaseInfo release; - foreach (string SearchUrl in searchurls) - { - var results = await client.GetStringAsync(SearchUrl); - try + int rowCount = 0; + var rows = dom[".mainblockcontenttt > tbody > tr"]; + foreach (var row in rows) { - CQ dom = results; - ReleaseInfo release; - - int rowCount = 0; - var rows = dom[".mainblockcontenttt > tbody > tr"]; - foreach (var row in rows) + CQ qRow = row.Cq(); + if (rowCount < 2 || qRow.Children().Count() != 12) //skip 2 rows because there's an empty row & a title/sort row { - CQ qRow = row.Cq(); - if (rowCount < 2 || qRow.Children().Count() != 12) //skip 2 rows because there's an empty row & a title/sort row - { - rowCount++; - continue; - } + rowCount++; + continue; + } - release = new ReleaseInfo(); + release = new ReleaseInfo(); - release.Title = qRow.Find("td.mainblockcontent b a").Text(); - release.Description = release.Title; + release.Title = qRow.Find("td.mainblockcontent b a").Text(); + release.Description = release.Title; - if (0 != qRow.Find("td.mainblockcontent u").Length) + if (0 != qRow.Find("td.mainblockcontent u").Length) + { + var imdbStr = qRow.Find("td.mainblockcontent u").Parent().First().Attr("href").Replace("http://www.imdb.com/title/tt", "").Replace("/", ""); + long imdb; + if (ParseUtil.TryCoerceLong(imdbStr, out imdb)) { - var imdbStr = qRow.Find("td.mainblockcontent u").Parent().First().Attr("href").Replace("http://www.imdb.com/title/tt", "").Replace("/", ""); - long imdb; - if (ParseUtil.TryCoerceLong(imdbStr, out imdb)) - { - release.Imdb = imdb; - } + release.Imdb = imdb; } + } - release.MinimumRatio = 1; - release.MinimumSeedTime = 172800; + release.MinimumRatio = 1; + release.MinimumSeedTime = 172800; - int seeders, peers; - if (ParseUtil.TryCoerceInt(qRow.Find("td").Get(9).FirstChild.FirstChild.InnerText, out seeders)) + int seeders, peers; + if (ParseUtil.TryCoerceInt(qRow.Find("td").Get(9).FirstChild.FirstChild.InnerText, out seeders)) + { + release.Seeders = seeders; + if (ParseUtil.TryCoerceInt(qRow.Find("td").Get(10).FirstChild.FirstChild.InnerText, out peers)) { - release.Seeders = seeders; - if (ParseUtil.TryCoerceInt(qRow.Find("td").Get(10).FirstChild.FirstChild.InnerText, out peers)) - { - release.Peers = peers + release.Seeders; - } + release.Peers = peers + release.Seeders; } + } - string fullSize = qRow.Find("td.mainblockcontent").Get(6).InnerText; - release.Size = ReleaseInfo.GetBytes(fullSize); + string fullSize = qRow.Find("td.mainblockcontent").Get(6).InnerText; + release.Size = ReleaseInfo.GetBytes(fullSize); - release.Guid = new Uri(SiteLink + "/" + qRow.Find("td.mainblockcontent b a").Attr("href")); - release.Link = new Uri(SiteLink + "/" + qRow.Find("td.mainblockcontent").Get(3).FirstChild.GetAttribute("href")); - release.Comments = new Uri(SiteLink + "/" + qRow.Find("td.mainblockcontent b a").Attr("href") + "#comments"); + release.Guid = new Uri(SiteLink + "/" + qRow.Find("td.mainblockcontent b a").Attr("href")); + release.Link = new Uri(SiteLink + "/" + qRow.Find("td.mainblockcontent").Get(3).FirstChild.GetAttribute("href")); + release.Comments = new Uri(SiteLink + "/" + qRow.Find("td.mainblockcontent b a").Attr("href") + "#comments"); - string[] dateSplit = qRow.Find("td.mainblockcontent").Get(5).InnerHTML.Split(','); - string dateString = dateSplit[1].Substring(0, dateSplit[1].IndexOf('>')); - release.PublishDate = DateTime.Parse(dateString, CultureInfo.InvariantCulture); + string[] dateSplit = qRow.Find("td.mainblockcontent").Get(5).InnerHTML.Split(','); + string dateString = dateSplit[1].Substring(0, dateSplit[1].IndexOf('>')); + release.PublishDate = DateTime.Parse(dateString, CultureInfo.InvariantCulture); - releases.Add(release); - } - } - catch (Exception ex) - { - OnParseError(results, ex); + releases.Add(release); } } + catch (Exception ex) + { + OnParseError(results.Content, ex); + } - return releases.ToArray(); - } - - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) - { - return await PerformQuery(query, SiteLink); - } - - public Task<byte[]> Download(Uri link) - { - return client.GetByteArrayAsync(link); + return releases; } } } diff --git a/src/Jackett/Indexers/IIndexer.cs b/src/Jackett/Indexers/IIndexer.cs index cf6ba4c749e3d16ab2eeff55e56ea85c09314c20..c768591eb9bfbdfddff7d1660de23d88734d55f8 100644 --- a/src/Jackett/Indexers/IIndexer.cs +++ b/src/Jackett/Indexers/IIndexer.cs @@ -11,12 +11,12 @@ namespace Jackett.Indexers { public interface IIndexer { + string SiteLink { get; } + string DisplayName { get; } string DisplayDescription { get; } string ID { get; } - Uri SiteLink { get; } - TorznabCapabilities TorznabCaps { get; } // Whether this indexer has been configured, verified and saved in the past and has the settings required for functioning @@ -31,7 +31,9 @@ namespace Jackett.Indexers // Called on startup when initializing indexers from saved configuration void LoadFromSavedConfiguration(JToken jsonConfig); - Task<ReleaseInfo[]> PerformQuery(TorznabQuery query); + Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query); + + IEnumerable<ReleaseInfo> FilterResults(TorznabQuery query, IEnumerable<ReleaseInfo> input); Task<byte[]> Download(Uri link); } diff --git a/src/Jackett/Indexers/IPTorrents.cs b/src/Jackett/Indexers/IPTorrents.cs index f9b7a2de20e26d18bb11491c4a71e24338a07e01..975c7a74e53d49bc1f65a06c21a4386ef979930f 100644 --- a/src/Jackett/Indexers/IPTorrents.cs +++ b/src/Jackett/Indexers/IPTorrents.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json.Linq; using NLog; using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Globalization; using System.Linq; using System.Net; @@ -19,111 +20,118 @@ namespace Jackett.Indexers { public class IPTorrents : BaseIndexer, IIndexer { - private readonly string SearchUrl = ""; - private string cookieHeader = ""; - - private IWebClient webclient; + private string BrowseUrl { get { return SiteLink + "t"; } } public IPTorrents(IIndexerManagerService i, IWebClient wc, Logger l) : base(name: "IPTorrents", description: "Always a step ahead.", - link: new Uri("https://iptorrents.com/"), + link: "https://iptorrents.com/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: wc, logger: l) { - TorznabCaps.Categories.Add(new TorznabCategory { ID = "5070", Name = "TV/Anime" }); - SearchUrl = SiteLink + "t?26=&55=&78=&23=&24=&25=&66=&82=&65=&83=&79=&22=&5=&4=&60=&q="; - webclient = wc; + AddCategoryMapping(72, TorznabCatType.Movies); + AddCategoryMapping(77, TorznabCatType.MoviesSD); + AddCategoryMapping(89, TorznabCatType.MoviesSD); + AddCategoryMapping(90, TorznabCatType.MoviesSD); + AddCategoryMapping(96, TorznabCatType.MoviesSD); + AddCategoryMapping(6, TorznabCatType.MoviesSD); + AddCategoryMapping(48, TorznabCatType.MoviesHD); + AddCategoryMapping(54, TorznabCatType.Movies); + AddCategoryMapping(62, TorznabCatType.MoviesSD); + AddCategoryMapping(38, TorznabCatType.MoviesForeign); + AddCategoryMapping(68, TorznabCatType.Movies); + AddCategoryMapping(20, TorznabCatType.MoviesHD); + AddCategoryMapping(7, TorznabCatType.MoviesSD); + + AddCategoryMapping(73, TorznabCatType.TV); + AddCategoryMapping(26, TorznabCatType.TVSD); + AddCategoryMapping(55, TorznabCatType.TVSD); + AddCategoryMapping(78, TorznabCatType.TVSD); + AddCategoryMapping(23, TorznabCatType.TVHD); + AddCategoryMapping(24, TorznabCatType.TVSD); + AddCategoryMapping(25, TorznabCatType.TVSD); + AddCategoryMapping(66, TorznabCatType.TVSD); + AddCategoryMapping(82, TorznabCatType.TVSD); + AddCategoryMapping(65, TorznabCatType.TV); + AddCategoryMapping(83, TorznabCatType.TV); + AddCategoryMapping(79, TorznabCatType.TVSD); + AddCategoryMapping(22, TorznabCatType.TVHD); + AddCategoryMapping(79, TorznabCatType.TVSD); + AddCategoryMapping(4, TorznabCatType.TVSD); + AddCategoryMapping(5, TorznabCatType.TVHD); + + AddCategoryMapping(75, TorznabCatType.Audio); + AddCategoryMapping(73, TorznabCatType.Audio); + AddCategoryMapping(80, TorznabCatType.AudioLossless); + AddCategoryMapping(93, TorznabCatType.Audio); + + AddCategoryMapping(60, TorznabCatType.Anime); + AddCategoryMapping(1, TorznabCatType.Apps); + AddCategoryMapping(64, TorznabCatType.AudioBooks); + AddCategoryMapping(35, TorznabCatType.Books); + AddCategoryMapping(94, TorznabCatType.Comic); } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataBasicLogin(); - return Task.FromResult<ConfigurationData>((ConfigurationData)config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); } public async Task ApplyConfiguration(JToken configJson) { - - var config = new ConfigurationDataBasicLogin(); - config.LoadValuesFromJson(configJson); - + var incomingConfig = new ConfigurationDataBasicLogin(); + incomingConfig.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { - { "username", config.Username.Value }, - { "password", config.Password.Value } - }; - - var response = await webclient.GetString(new Utils.Clients.WebRequest() + { "username", incomingConfig.Username.Value }, + { "password", incomingConfig.Password.Value } + }; + var request = new Utils.Clients.WebRequest() { - Url = SiteLink.ToString(), - PostData = pairs, - Referer = SiteLink.ToString(), - Type = RequestType.POST - }); - - cookieHeader = response.Cookies; - if (response.Status == HttpStatusCode.Found) + Url = SiteLink, + Type = RequestType.POST, + Referer = SiteLink, + PostData = pairs + }; + var response = await webclient.GetString(request); + var firstCallCookies = response.Cookies; + // Redirect to ? then to /t + await FollowIfRedirect(response, request.Url, null, firstCallCookies); + + await ConfigureIfOK(firstCallCookies, response.Content.Contains("/my.php"), () => { - response = await webclient.GetString(new Utils.Clients.WebRequest() - { - Url = SearchUrl, - Referer = SiteLink.ToString(), - Cookies = response.Cookies - }); - } - - var responseContent = response.Content; - - if (!responseContent.Contains("/my.php")) - { - CQ dom = responseContent; + CQ dom = response.Content; var messageEl = dom["body > div"].First(); var errorMessage = messageEl.Text().Trim(); - throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config); - } - else - { - var configSaveData = new JObject(); - configSaveData["cookie_header"] = cookieHeader; - SaveConfig(configSaveData); - IsConfigured = true; - } - } - - public void LoadFromSavedConfiguration(JToken jsonConfig) - { - cookieHeader = (string)jsonConfig["cookie_header"]; - IsConfigured = true; + throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig); + }); } - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); - var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString); + var searchUrl = BrowseUrl; + var trackerCats = MapTorznabCapsToTrackers(query); + var queryCollection = new NameValueCollection(); - WebClientStringResult response = null; + if (!string.IsNullOrWhiteSpace(searchString)) + { + queryCollection.Add("q", searchString); + } - // Their web server is fairly flakey - try up to three times. - for (int i = 0; i < 3; i++) + foreach (var cat in MapTorznabCapsToTrackers(query)) { - try - { - response = await webclient.GetString(new Utils.Clients.WebRequest() - { - Url = episodeSearchUrl, - Referer = SiteLink.ToString(), - Cookies = cookieHeader - }); + queryCollection.Add(cat, string.Empty); + } - break; - } - catch (Exception e) - { - logger.Error("On attempt " + (i + 1) + " checking for results from IPTorrents: " + e.Message); - } + if (queryCollection.Count > 0) + { + searchUrl += "?" + queryCollection.GetQueryString(); } + + var response = await RequestStringWithCookiesAndRetry(searchUrl, null, BrowseUrl); var results = response.Content; try @@ -162,6 +170,9 @@ namespace Jackett.Indexers release.Seeders = ParseUtil.CoerceInt(qRow.Find(".t_seeders").Text().Trim()); release.Peers = ParseUtil.CoerceInt(qRow.Find(".t_leechers").Text().Trim()) + release.Seeders; + var cat = row.Cq().Find("td:eq(0) a").First().Attr("href").Substring(1); + release.Category = MapTrackerCatToNewznab(cat); + releases.Add(release); } } @@ -170,18 +181,7 @@ namespace Jackett.Indexers OnParseError(results, ex); } - return releases.ToArray(); - } - - public async Task<byte[]> Download(Uri link) - { - var response = await webclient.GetBytes(new Utils.Clients.WebRequest() - { - Url = link.ToString(), - Cookies = cookieHeader - }); - - return response.Content; + return releases; } } } diff --git a/src/Jackett/Indexers/ImmortalSeed.cs b/src/Jackett/Indexers/ImmortalSeed.cs new file mode 100644 index 0000000000000000000000000000000000000000..7bd11569ea4df46b1b9f837b467a174146fdb5dd --- /dev/null +++ b/src/Jackett/Indexers/ImmortalSeed.cs @@ -0,0 +1,158 @@ +using CsQuery; +using Jackett.Models; +using Jackett.Services; +using Jackett.Utils; +using Jackett.Utils.Clients; +using Newtonsoft.Json.Linq; +using NLog; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Web; + +namespace Jackett.Indexers +{ + public class ImmortalSeed : BaseIndexer, IIndexer + { + private string BrowsePage { get { return SiteLink + "browse.php"; } } + private string LoginUrl { get { return SiteLink + "takelogin.php"; } } + private string QueryString { get { return "?do=search&keywords={0}&search_type=t_name&category=0&include_dead_torrents=no"; } } + + public ImmortalSeed(IIndexerManagerService i, IWebClient wc, Logger l) + : base(name: "ImmortalSeed", + description: "ImmortalSeed", + link: "http://immortalseed.me/", + caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), + manager: i, + client: wc, + logger: l) + { + AddCategoryMapping(32, TorznabCatType.Anime); + AddCategoryMapping(47, TorznabCatType.TVSD); + AddCategoryMapping(8, TorznabCatType.TVHD); + AddCategoryMapping(48, TorznabCatType.TVHD); + AddCategoryMapping(9, TorznabCatType.TVSD); + AddCategoryMapping(4, TorznabCatType.TVHD); + AddCategoryMapping(6, TorznabCatType.TVSD); + + AddCategoryMapping(22, TorznabCatType.Books); + AddCategoryMapping(41, TorznabCatType.Comic); + AddCategoryMapping(23, TorznabCatType.Apps); + + AddCategoryMapping(16, TorznabCatType.MoviesHD); + AddCategoryMapping(17, TorznabCatType.MoviesSD); + AddCategoryMapping(14, TorznabCatType.MoviesSD); + AddCategoryMapping(34, TorznabCatType.MoviesForeign); + AddCategoryMapping(18, TorznabCatType.MoviesForeign); + AddCategoryMapping(33, TorznabCatType.MoviesForeign); + + AddCategoryMapping(34, TorznabCatType.Audio); + AddCategoryMapping(37, TorznabCatType.AudioLossless); + AddCategoryMapping(35, TorznabCatType.AudioBooks); + AddCategoryMapping(36, TorznabCatType.AudioLossy); + + } + + public Task<ConfigurationData> GetConfigurationForSetup() + { + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); + } + + public async Task ApplyConfiguration(JToken configJson) + { + var incomingConfig = new ConfigurationDataBasicLogin(); + incomingConfig.LoadValuesFromJson(configJson); + var pairs = new Dictionary<string, string> { + { "username", incomingConfig.Username.Value }, + { "password", incomingConfig.Password.Value } + }; + var request = new Utils.Clients.WebRequest() + { + Url = LoginUrl, + Type = RequestType.POST, + Referer = SiteLink, + PostData = pairs + }; + var response = await webclient.GetString(request); + CQ splashDom = response.Content; + var link = splashDom[".trow2 a"].First(); + var resultPage = await RequestStringWithCookies(link.Attr("href"), response.Cookies); + CQ resultDom = resultPage.Content; + + await ConfigureIfOK(response.Cookies, resultPage.Content.Contains("/logout.php"), () => + { + var tries = resultDom["#main tr:eq(1) td font"].First().Text(); + var errorMessage = "Incorrect username or password! " + tries + " tries remaining."; + throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig); + }); + } + + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) + { + var releases = new List<ReleaseInfo>(); + var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); + var searchUrl = BrowsePage; + + if (!string.IsNullOrWhiteSpace(searchString)) + { + searchUrl += string.Format(QueryString, HttpUtility.UrlEncode(searchString)); + } + + var results = await RequestStringWithCookiesAndRetry(searchUrl); + + try + { + CQ dom = results.Content; + + var rows = dom["#sortabletable tr"]; + foreach (var row in rows.Skip(1)) + { + var release = new ReleaseInfo(); + var qRow = row.Cq(); + release.Title = qRow.Find(".tooltip-content div").First().Text(); + if (string.IsNullOrWhiteSpace(release.Title)) + continue; + release.Description = qRow.Find(".tooltip-content div").Get(1).InnerText.Trim(); + + var qLink = row.Cq().Find("td:eq(2) a:eq(1)"); + release.Link = new Uri(qLink.Attr("href")); + release.Guid = release.Link; + release.Comments = new Uri(qRow.Find(".tooltip-target a").First().Attr("href")); + + // 07-22-2015 11:08 AM + var dateString = qRow.Find("td:eq(1) div").Last().Children().Remove().End().Text().Trim(); + release.PublishDate = DateTime.ParseExact(dateString, "MM-dd-yyyy hh:mm tt", CultureInfo.InvariantCulture); + + var sizeStr = qRow.Find("td:eq(4)").Text().Trim(); + release.Size = ReleaseInfo.GetBytes(sizeStr); + + release.Seeders = ParseUtil.CoerceInt(qRow.Find("td:eq(6)").Text().Trim()); + release.Peers = ParseUtil.CoerceInt(qRow.Find("td:eq(7)").Text().Trim()) + release.Seeders; + + var catLink = row.Cq().Find("td:eq(0) a").First().Attr("href"); + var catSplit = catLink.IndexOf("category="); + if (catSplit > -1) + { + catLink = catLink.Substring(catSplit + 9); + } + + release.Category = MapTrackerCatToNewznab(catLink); + releases.Add(release); + } + } + catch (Exception ex) + { + OnParseError(results.Content, ex); + } + + return releases; + } + + + } +} diff --git a/src/Jackett/Indexers/MoreThanTV.cs b/src/Jackett/Indexers/MoreThanTV.cs index 1d8c3199f9da8fc35cc2762199b7c66974644ff1..8d4d74a8cbb6b009c12f92348dc54ca38b0ad881 100644 --- a/src/Jackett/Indexers/MoreThanTV.cs +++ b/src/Jackett/Indexers/MoreThanTV.cs @@ -19,33 +19,25 @@ namespace Jackett.Indexers { public class MoreThanTV : BaseIndexer, IIndexer { - private readonly string LoginUrl = ""; - private readonly string SearchUrl = ""; - private readonly string DownloadUrl = ""; - private readonly string GuidUrl = ""; - - private IWebClient client; - private string cookieHeader = ""; + private string LoginUrl { get { return SiteLink + "login.php"; } } + private string SearchUrl { get { return SiteLink + "ajax.php?action=browse&searchstr="; } } + private string DownloadUrl { get { return SiteLink + "torrents.php?action=download&id="; } } + private string GuidUrl { get { return SiteLink + "torrents.php?torrentid="; } } public MoreThanTV(IIndexerManagerService i, IWebClient c, Logger l) : base(name: "MoreThanTV", description: "ROMANIAN Private Torrent Tracker for TV / MOVIES, and the internal tracker for the release group DRACULA.", - link: new Uri("https://www.morethan.tv"), + link: "https://www.morethan.tv/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: c, logger: l) { - LoginUrl = SiteLink + "login.php"; - SearchUrl = SiteLink + "ajax.php?action=browse&searchstr="; - DownloadUrl = SiteLink + "torrents.php?action=download&id="; - GuidUrl = SiteLink + "torrents.php?torrentid="; - client = c; } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataBasicLogin(); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); } public async Task ApplyConfiguration(JToken configJson) @@ -58,45 +50,15 @@ namespace Jackett.Indexers { "login", "Log in" }, { "keeplogged", "1" } }; - - var loginResponse = await client.GetString(new Utils.Clients.WebRequest() - { - PostData = pairs, - Url = LoginUrl, - Type = RequestType.POST - }); - - if (loginResponse.Status == HttpStatusCode.Found) - { - cookieHeader = loginResponse.Cookies; - loginResponse = await client.GetString(new Utils.Clients.WebRequest() - { - Url = SiteLink.ToString(), - Cookies = cookieHeader - }); - } - if (!loginResponse.Content.Contains("logout.php?")) + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, SearchUrl, SiteLink); + await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php?"), () => { - CQ dom = loginResponse.Content; + CQ dom = result.Content; dom["#loginform > table"].Remove(); var errorMessage = dom["#loginform"].Text().Trim().Replace("\n\t", " "); throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config); - - } - else - { - var configSaveData = new JObject(); - configSaveData["cookie_header"] = cookieHeader; - SaveConfig(configSaveData); - IsConfigured = true; - } - } - - public void LoadFromSavedConfiguration(JToken jsonConfig) - { - cookieHeader = (string)jsonConfig["cookie_header"]; - IsConfigured = true; + }); } private void FillReleaseInfoFromJson(ReleaseInfo release, JObject r) @@ -110,33 +72,15 @@ namespace Jackett.Indexers release.Link = new Uri(DownloadUrl + id); } - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - + var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString); - WebClientStringResult response = null; + WebClientStringResult response = null; - // Their web server is fairly flakey - try up to three times. - for(int i = 0; i < 3; i++) - { - try - { - response = await client.GetString(new Utils.Clients.WebRequest() - { - Url = episodeSearchUrl, - Type = RequestType.GET, - Cookies = cookieHeader - }); + response = await RequestStringWithCookiesAndRetry(episodeSearchUrl); - break; - } - catch (Exception e){ - logger.Error("On attempt " + (i+1) + " checking for results from MoreThanTv: " + e.Message ); - } - } - try { var json = JObject.Parse(response.Content); @@ -173,7 +117,6 @@ namespace Jackett.Indexers FillReleaseInfoFromJson(release, r); releases.Add(release); } - } } catch (Exception ex) @@ -181,19 +124,7 @@ namespace Jackett.Indexers OnParseError(response.Content, ex); } - return releases.ToArray(); - } - - public async Task<byte[]> Download(Uri link) - { - var result = await client.GetBytes(new Utils.Clients.WebRequest() - { - Cookies = cookieHeader, - Url = link.ToString(), - Type = RequestType.GET - }); - - return result.Content; + return releases; } } } diff --git a/src/Jackett/Indexers/Pretome.cs b/src/Jackett/Indexers/Pretome.cs index 21329864c919aba06c07132fc7b46659aea5d903..2b45755612b18c0e9f9f4b3f08ce1252563dec95 100644 --- a/src/Jackett/Indexers/Pretome.cs +++ b/src/Jackett/Indexers/Pretome.cs @@ -11,52 +11,30 @@ using NLog; using Jackett.Utils; using CsQuery; using System.Web; +using Jackett.Models.IndexerConfig; namespace Jackett.Indexers { public class Pretome : BaseIndexer, IIndexer { - - class PretomeConfiguration : ConfigurationDataBasicLogin - { - public StringItem Pin { get; private set; } - - public PretomeConfiguration() : base() - { - Pin = new StringItem { Name = "Login Pin Number" }; - } - - public override Item[] GetItems() - { - return new Item[] { Pin, Username, Password }; - } - } - - private readonly string LoginUrl = ""; - private readonly string LoginReferer = ""; - private readonly string SearchUrl = ""; - private string cookieHeader = ""; - - private IWebClient webclient; + private string LoginUrl { get { return SiteLink + "takelogin.php"; } } + private string LoginReferer { get { return SiteLink + "index.php?cat=1"; } } + private string SearchUrl { get { return SiteLink + "browse.php?tags=&st=1&tf=all&cat%5B%5D=7&search={0}"; } } public Pretome(IIndexerManagerService i, IWebClient wc, Logger l) : base(name: "PreToMe", description: "BitTorrent site for High Quality, High Definition (HD) movies and TV Shows", - link: new Uri("https://pretome.info/"), + link: "https://pretome.info/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), + client: wc, manager: i, logger: l) { - LoginUrl = SiteLink + "takelogin.php"; - LoginReferer = SiteLink + "index.php?cat=1"; - SearchUrl = SiteLink + "browse.php?tags=&st=1&tf=all&cat%5B%5D=7&search={0}"; - webclient = wc; } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new PretomeConfiguration(); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new PretomeConfiguration()); } public async Task ApplyConfiguration(JToken configJson) @@ -64,11 +42,7 @@ namespace Jackett.Indexers var config = new PretomeConfiguration(); config.LoadValuesFromJson(configJson); - var loginPage = await webclient.GetString(new WebRequest() - { - Url = LoginUrl, - Type = RequestType.GET - }); + var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty); var pairs = new Dictionary<string, string> { { "returnto", "%2F" }, @@ -78,78 +52,34 @@ namespace Jackett.Indexers { "login", "Login" } }; - // Send Post - var loginPost = await webclient.GetString(new WebRequest() - { - Url = LoginUrl, - PostData = pairs, - Referer = LoginReferer, - Type = RequestType.POST, - Cookies = loginPage.Cookies - }); - - if (loginPost.RedirectingTo == null) + var result = await PostDataWithCookies(LoginUrl, pairs, loginPage.Cookies); + if (result.RedirectingTo == null) { throw new ExceptionWithConfigData("Login failed. Did you use the PIN number that pretome emailed you?", (ConfigurationData)config); } - + var loginCookies = result.Cookies; // Get result from redirect - var loginResult = await webclient.GetString(new WebRequest() - { - Url = loginPost.RedirectingTo, - Type = RequestType.GET, - Cookies = loginPost.Cookies - }); + await FollowIfRedirect(result,LoginUrl,null, loginCookies); - if (!loginResult.Content.Contains("logout.php")) + await ConfigureIfOK(loginCookies, result.Content != null && result.Content.Contains("logout.php"), () => { + cookieHeader = string.Empty; throw new ExceptionWithConfigData("Failed", (ConfigurationData)config); - } - else - { - cookieHeader = loginPost.Cookies; - var configSaveData = new JObject(); - configSaveData["cookies"] = cookieHeader; - SaveConfig(configSaveData); - IsConfigured = true; - } - } - - public void LoadFromSavedConfiguration(JToken jsonConfig) - { - cookieHeader = (string)jsonConfig["cookies"]; - IsConfigured = true; - } - - public async Task<byte[]> Download(Uri link) - { - var response = await webclient.GetBytes(new WebRequest() - { - Url = link.ToString(), - Cookies = cookieHeader }); - return response.Content; } - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - + var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString)); - var response = await webclient.GetString(new WebRequest() - { - Url = episodeSearchUrl, - Referer = SiteLink.ToString(), - Cookies = cookieHeader - }); - var results = response.Content; + var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl); try { - CQ dom = results; + CQ dom = response.Content; var rows = dom["table > tbody > tr.browse"]; foreach (var row in rows) { @@ -186,9 +116,9 @@ namespace Jackett.Indexers } catch (Exception ex) { - OnParseError(results, ex); + OnParseError(response.Content, ex); } - return releases.ToArray(); + return releases; } } } diff --git a/src/Jackett/Indexers/PrivateHD.cs b/src/Jackett/Indexers/PrivateHD.cs index 6337b8b5026dd9b5628a51475c31f20d6277e6e9..23a79a399fef63b45c924ea60de62be03d086c55 100644 --- a/src/Jackett/Indexers/PrivateHD.cs +++ b/src/Jackett/Indexers/PrivateHD.cs @@ -19,121 +19,59 @@ namespace Jackett.Indexers { public class PrivateHD : BaseIndexer, IIndexer { - private readonly string LoginUrl = ""; - private readonly string SearchUrl = ""; - private string cookieHeader = ""; - - private IWebClient webclient; + private string LoginUrl { get { return SiteLink + "auth/login"; } } + private string SearchUrl { get { return SiteLink + "torrents?in=1&type=2&search={0}"; } } public PrivateHD(IIndexerManagerService i, IWebClient wc, Logger l) : base(name: "PrivateHD", description: "BitTorrent site for High Quality, High Definition (HD) movies and TV Shows", - link: new Uri("https://privatehd.to"), + link: "https://privatehd.to/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: wc, logger: l) { - LoginUrl = SiteLink + "auth/login"; - SearchUrl = SiteLink + "torrents?in=1&type=2&search={0}"; - webclient = wc; } public async Task ApplyConfiguration(JToken configJson) { - var config = new ConfigurationDataBasicLogin(); - config.LoadValuesFromJson(configJson); - - var loginPage = await webclient.GetString(new Utils.Clients.WebRequest() - { - Url = LoginUrl, - Type = RequestType.GET - }); - + var incomingConfig = new ConfigurationDataBasicLogin(); + incomingConfig.LoadValuesFromJson(configJson); + var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty); var token = new Regex("Avz.CSRF_TOKEN = '(.*?)';").Match(loginPage.Content).Groups[1].ToString(); var pairs = new Dictionary<string, string> { { "_token", token }, - { "username_email", config.Username.Value }, - { "password", config.Password.Value }, + { "username_email", incomingConfig.Username.Value }, + { "password", incomingConfig.Password.Value }, { "remember", "on" } }; - // Send Post - var loginPost = await webclient.GetString(new Utils.Clients.WebRequest() + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, null, LoginUrl); + await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("auth/logout"), () => { - Url = LoginUrl, - PostData = pairs, - Referer = LoginUrl, - Type = RequestType.POST, - Cookies = loginPage.Cookies - }); - - // Get result from redirect - var loginResult = await webclient.GetString(new Utils.Clients.WebRequest() - { - Url = loginPost.RedirectingTo, - Type = RequestType.GET, - Cookies = loginPost.Cookies - }); - - if (!loginResult.Content.Contains("auth/logout")) - { - CQ dom = loginResult.Content; + CQ dom = result.Content; var messageEl = dom[".form-error"]; var errorMessage = messageEl.Text().Trim(); - throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config); - } - else - { - cookieHeader = loginPost.Cookies; - var configSaveData = new JObject(); - configSaveData["cookies"] = cookieHeader; - SaveConfig(configSaveData); - IsConfigured = true; - } - - } - - public async Task<byte[]> Download(Uri link) - { - var response = await webclient.GetBytes(new Utils.Clients.WebRequest() - { - Url = link.ToString(), - Cookies = cookieHeader + throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig); }); - - return response.Content; } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataBasicLogin(); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); } - public void LoadFromSavedConfiguration(JToken jsonConfig) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { - cookieHeader = (string)jsonConfig["cookies"]; - IsConfigured = true; - } - - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) - { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - + var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString)); - var response = await webclient.GetString(new Utils.Clients.WebRequest() - { - Url = episodeSearchUrl, - Referer = SiteLink.ToString(), - Cookies = cookieHeader - }); - var results = response.Content; + var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl); try { - CQ dom = results; + CQ dom = response.Content; var rows = dom["table > tbody > tr"]; foreach (var row in rows) { @@ -165,9 +103,9 @@ namespace Jackett.Indexers } catch (Exception ex) { - OnParseError(results, ex); + OnParseError(response.Content, ex); } - return releases.ToArray(); + return releases; } } } diff --git a/src/Jackett/Indexers/Rarbg.cs b/src/Jackett/Indexers/Rarbg.cs deleted file mode 100644 index 4d43927b1a02b4c63a8a6f7b7cbd927cc7ecc87b..0000000000000000000000000000000000000000 --- a/src/Jackett/Indexers/Rarbg.cs +++ /dev/null @@ -1,147 +0,0 @@ -using CsQuery; -using Jackett.Models; -using Jackett.Services; -using Jackett.Utils; -using Newtonsoft.Json.Linq; -using NLog; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; - -namespace Jackett.Indexers -{ - public class Rarbg : BaseIndexer, IIndexer - { - const string DefaultUrl = "http://torrentapi.org"; - const string TokenUrl = "/pubapi.php?get_token=get_token&format=json"; - const string SearchTVRageUrl = "/pubapi.php?mode=search&search_tvrage={0}&token={1}&format=json&min_seeders=1"; - const string SearchQueryUrl = "/pubapi.php?mode=search&search_string={0}&token={1}&format=json&min_seeders=1"; - private string BaseUrl; - - CookieContainer cookies; - HttpClientHandler handler; - HttpClient client; - - - public Rarbg(IIndexerManagerService i, Logger l) - : base(name: "RARBG", - description: "RARBG", - link: new Uri("https://rarbg.com"), - caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), - manager: i, - logger: l) - { - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); - } - - public Task<ConfigurationData> GetConfigurationForSetup() - { - var config = new ConfigurationDataUrl(DefaultUrl); - return Task.FromResult<ConfigurationData>(config); - } - - public async Task ApplyConfiguration(JToken configJson) - { - var config = new ConfigurationDataUrl(DefaultUrl); - config.LoadValuesFromJson(configJson); - - var formattedUrl = config.GetFormattedHostUrl(); - var token = await GetToken(formattedUrl); - /*var releases = await PerformQuery(new TorznabQuery(), formattedUrl); - if (releases.Length == 0) - throw new Exception("Could not find releases from this URL");*/ - - BaseUrl = formattedUrl; - - var configSaveData = new JObject(); - configSaveData["base_url"] = BaseUrl; - SaveConfig(configSaveData); - IsConfigured = true; - } - - public void LoadFromSavedConfiguration(JToken jsonConfig) - { - BaseUrl = (string)jsonConfig["base_url"]; - IsConfigured = true; - } - - HttpRequestMessage CreateHttpRequest(string uri) - { - var message = new HttpRequestMessage(); - message.Method = HttpMethod.Get; - message.RequestUri = new Uri(uri); - message.Headers.UserAgent.ParseAdd(BrowserUtil.ChromeUserAgent); - return message; - } - - async Task<string> GetToken(string url) - { - var request = CreateHttpRequest(url + TokenUrl); - var response = await client.SendAsync(request); - var result = await response.Content.ReadAsStringAsync(); - JObject obj = JObject.Parse(result); - return (string)obj["token"]; - } - - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) - { - return await PerformQuery(query, BaseUrl); - } - - async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query, string baseUrl) - { - - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - - string token = await GetToken(baseUrl); - string searchUrl; - if (query.RageID != 0) - searchUrl = string.Format(baseUrl + SearchTVRageUrl, query.RageID, token); - else - searchUrl = string.Format(baseUrl + SearchQueryUrl, query.SanitizedSearchTerm, token); - - var request = CreateHttpRequest(searchUrl); - var response = await client.SendAsync(request); - var results = await response.Content.ReadAsStringAsync(); - try - { - var jItems = JArray.Parse(results); - foreach (JObject item in jItems) - { - var release = new ReleaseInfo(); - release.Title = (string)item["f"]; - release.MagnetUri = new Uri((string)item["d"]); - release.Guid = release.MagnetUri; - release.PublishDate = new DateTime(1970, 1, 1); - release.Size = 0; - release.Seeders = 1; - release.Peers = 1; - release.MinimumRatio = 1; - release.MinimumSeedTime = 172800; - releases.Add(release); - } - } - catch (Exception ex) - { - OnParseError(results, ex); - } - return releases.ToArray(); - } - - public Task<byte[]> Download(Uri link) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Jackett/Indexers/SceneAccess.cs b/src/Jackett/Indexers/SceneAccess.cs index 176aed44e318361e1af118b3cd6e8025dc438a84..6add4bd218dd6bf9ea83c22b527c09ffa34d81df 100644 --- a/src/Jackett/Indexers/SceneAccess.cs +++ b/src/Jackett/Indexers/SceneAccess.cs @@ -17,29 +17,23 @@ namespace Jackett.Indexers { class SceneAccess : BaseIndexer, IIndexer { - private readonly string LoginUrl = ""; - private readonly string SearchUrl = ""; - - private IWebClient webclient; - private string cookieHeader = ""; + private string LoginUrl { get { return SiteLink + "login"; } } + private string SearchUrl { get { return SiteLink + "{0}?method=1&c{1}=1&search={2}"; } } public SceneAccess(IIndexerManagerService i, IWebClient c, Logger l) : base(name: "SceneAccess", description: "Your gateway to the scene", - link: new Uri("https://sceneaccess.eu/"), + link: "https://sceneaccess.eu/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: c, logger: l) { - LoginUrl = SiteLink + "login"; - SearchUrl = SiteLink + "{0}?method=1&c{1}=1&search={2}"; - webclient = c; } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataBasicLogin(); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); } public async Task ApplyConfiguration(JToken configJson) @@ -53,72 +47,26 @@ namespace Jackett.Indexers { "submit", "come on in" } }; - var content = new FormUrlEncodedContent(pairs); - - // Do the login - var response = await webclient.GetString(new Utils.Clients.WebRequest() - { - PostData = pairs, - Referer = LoginUrl, - Type = RequestType.POST, - Url = LoginUrl - }); - - cookieHeader = response.Cookies; - - if (response.Status == HttpStatusCode.Found) - { - response = await webclient.GetString(new Utils.Clients.WebRequest() - { - Url = SiteLink.ToString(), - Referer = LoginUrl.ToString(), - Cookies = cookieHeader - }); - } + var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty); - if (!response.Content.Contains("nav_profile")) + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, SiteLink, LoginUrl); + await ConfigureIfOK(result.Cookies + " " + loginPage.Cookies, result.Content != null && result.Content.Contains("nav_profile"), () => { - CQ dom = response.Content; + CQ dom = result.Content; var messageEl = dom["#login_box_desc"]; var errorMessage = messageEl.Text().Trim(); throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config); - } - else - { - var configSaveData = new JObject(); - configSaveData["cookie_header"] = cookieHeader; - SaveConfig(configSaveData); - IsConfigured = true; - } - } - - public void LoadFromSavedConfiguration(JToken jsonConfig) - { - cookieHeader = (string)jsonConfig["cookie_header"]; - if (!string.IsNullOrEmpty(cookieHeader)) - { - IsConfigured = true; - } + }); } - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - + var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var searchSection = string.IsNullOrEmpty(query.Episode) ? "archive" : "browse"; var searchCategory = string.IsNullOrEmpty(query.Episode) ? "26" : "27"; - var searchUrl = string.Format(SearchUrl, searchSection, searchCategory, searchString); - - - var results = await webclient.GetString(new Utils.Clients.WebRequest() - { - Referer = LoginUrl, - Cookies = cookieHeader, - Type = RequestType.GET, - Url = searchUrl - }); + var results = await RequestStringWithCookiesAndRetry(searchUrl); try { @@ -158,18 +106,7 @@ namespace Jackett.Indexers OnParseError(results.Content, ex); } - return releases.ToArray(); - } - - public async Task<byte[]> Download(Uri link) - { - var response = await webclient.GetBytes(new Utils.Clients.WebRequest() - { - Url = link.ToString(), - Cookies = cookieHeader - }); - - return response.Content; - } + return releases; } } +} diff --git a/src/Jackett/Indexers/SceneTime.cs b/src/Jackett/Indexers/SceneTime.cs index a7cb9aa1ae48d7836bdca48a43d0f2d943e59af7..0144dfc3a960eedf10a93ec132d2de5a4fc37d1f 100644 --- a/src/Jackett/Indexers/SceneTime.cs +++ b/src/Jackett/Indexers/SceneTime.cs @@ -2,6 +2,7 @@ using Jackett.Models; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -18,102 +19,62 @@ namespace Jackett.Indexers { public class SceneTime : BaseIndexer, IIndexer { - private readonly string LoginUrl = ""; - private readonly string SearchUrl = ""; - private readonly string DownloadUrl = ""; + private string LoginUrl { get { return SiteLink + "takelogin.php"; } } + private string SearchUrl { get { return SiteLink + "browse_API.php"; } } + private string DownloadUrl { get { return SiteLink + "download.php/{0}/download.torrent"; } } - CookieContainer cookies; - HttpClientHandler handler; - HttpClient client; - - public SceneTime(IIndexerManagerService i, Logger l) + public SceneTime(IIndexerManagerService i, Logger l, IWebClient w) : base(name: "SceneTime", description: "Always on time", - link: new Uri("https://www.scenetime.com/"), + link: "https://www.scenetime.com/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: w, logger: l) { - LoginUrl = SiteLink + "takelogin.php"; - SearchUrl = SiteLink + "browse_API.php"; - DownloadUrl = SiteLink + "download.php/{0}/download.torrent"; - - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataBasicLogin(); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); } public async Task ApplyConfiguration(JToken configJson) { - var config = new ConfigurationDataBasicLogin(); - config.LoadValuesFromJson(configJson); - + var incomingConfig = new ConfigurationDataBasicLogin(); + incomingConfig.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { - { "username", config.Username.Value }, - { "password", config.Password.Value } + { "username", incomingConfig.Username.Value }, + { "password", incomingConfig.Password.Value } }; - var content = new FormUrlEncodedContent(pairs); - - var response = await client.PostAsync(LoginUrl, content); - var responseContent = await response.Content.ReadAsStringAsync(); - - if (!responseContent.Contains("logout.php")) + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, LoginUrl); + await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () => { - CQ dom = responseContent; + CQ dom = result.Content; var errorMessage = dom["td.text"].Text().Trim(); - throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config); - } - else - { - var configSaveData = new JObject(); - cookies.DumpToJson(SiteLink, configSaveData); - SaveConfig(configSaveData); - IsConfigured = true; - } + throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig); + }); } - public void LoadFromSavedConfiguration(JToken jsonConfig) + private Dictionary<string, string> GetSearchFormData(string searchString) { - cookies.FillFromJson(SiteLink, jsonConfig, logger); - IsConfigured = true; - } - - FormUrlEncodedContent GetSearchFormData(string searchString) - { - var pairs = new Dictionary<string, string> { + return new Dictionary<string, string> { { "c2", "1" }, { "c43", "1" }, { "c9", "1" }, { "c63", "1" }, { "c77", "1" }, { "c100", "1" }, { "c101", "1" }, { "cata", "yes" }, { "sec", "jax" }, { "search", searchString} }; - var content = new FormUrlEncodedContent(pairs); - return content; } - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - + var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); - - var searchContent = GetSearchFormData(searchString); - var response = await client.PostAsync(SearchUrl, searchContent); - var results = await response.Content.ReadAsStringAsync(); + var results = await PostDataWithCookiesAndRetry(SearchUrl, GetSearchFormData(searchString)); try { - CQ dom = results; + CQ dom = results.Content; var rows = dom["tr.browse"]; foreach (var row in rows) { @@ -148,14 +109,9 @@ namespace Jackett.Indexers } catch (Exception ex) { - OnParseError(results, ex); + OnParseError(results.Content, ex); } - return releases.ToArray(); - } - - public Task<byte[]> Download(Uri link) - { - return client.GetByteArrayAsync(link); + return releases; } } } diff --git a/src/Jackett/Indexers/ShowRSS.cs b/src/Jackett/Indexers/ShowRSS.cs index 6bb4f644bdea80f5bb6f9ba9c9941228a469228a..c6fffcf7cc1ff8baec4c628820d744a386abe30c 100644 --- a/src/Jackett/Indexers/ShowRSS.cs +++ b/src/Jackett/Indexers/ShowRSS.cs @@ -1,6 +1,7 @@ using Jackett.Models; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -18,37 +19,23 @@ namespace Jackett.Indexers { public class ShowRSS : BaseIndexer, IIndexer { - private readonly string searchAllUrl = ""; - string BaseUrl; + private string searchAllUrl { get { return SiteLink + "feeds/all.rss"; } } + private string BaseUrl; - CookieContainer cookies; - HttpClientHandler handler; - HttpClient client; - - public ShowRSS(IIndexerManagerService i, Logger l) + public ShowRSS(IIndexerManagerService i, Logger l, IWebClient wc) : base(name: "ShowRSS", description: "showRSS is a service that allows you to keep track of your favorite TV shows", - link: new Uri("http://showrss.info"), + link: "http://showrss.info/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: wc, logger: l) { - searchAllUrl = SiteLink + "feeds/all.rss"; - - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataUrl(SiteLink); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataUrl(SiteLink)); } public async Task ApplyConfiguration(Newtonsoft.Json.Linq.JToken configJson) @@ -58,7 +45,7 @@ namespace Jackett.Indexers var formattedUrl = config.GetFormattedHostUrl(); var releases = await PerformQuery(new TorznabQuery(), formattedUrl); - if (releases.Length == 0) + if (releases.Count() == 0) throw new Exception("Could not find releases from this URL"); BaseUrl = formattedUrl; @@ -69,50 +56,33 @@ namespace Jackett.Indexers IsConfigured = true; } - public void LoadFromSavedConfiguration(Newtonsoft.Json.Linq.JToken jsonConfig) + public override void LoadFromSavedConfiguration(Newtonsoft.Json.Linq.JToken jsonConfig) { BaseUrl = (string)jsonConfig["base_url"]; IsConfigured = true; } - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { return await PerformQuery(query, BaseUrl); } - public Task<byte[]> Download(Uri link) + public override Task<byte[]> Download(Uri link) { throw new NotImplementedException(); } - private WebClient getWebClient() - { - WebClient wc = new WebClient(); - WebHeaderCollection headers = new WebHeaderCollection(); - headers.Add("User-Agent", BrowserUtil.ChromeUserAgent); - wc.Headers = headers; - return wc; - } - - async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query, string baseUrl) + async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query, string baseUrl) { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - + var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var episodeSearchUrl = string.Format(searchAllUrl); - - XmlDocument xmlDoc = new XmlDocument(); - string xml = string.Empty; - WebClient wc = getWebClient(); + var result = await RequestStringWithCookiesAndRetry(episodeSearchUrl, string.Empty); + var xmlDoc = new XmlDocument(); try { - using (wc) - { - xml = await wc.DownloadStringTaskAsync(new Uri(episodeSearchUrl)); - xmlDoc.LoadXml(xml); - } - + xmlDoc.LoadXml(result.Content); ReleaseInfo release; string serie_title; @@ -127,7 +97,9 @@ namespace Jackett.Indexers release.Title = serie_title; release.Comments = new Uri(node.SelectSingleNode("link").InnerText); - release.Category = node.SelectSingleNode("title").InnerText; + int category = 0; + int.TryParse(node.SelectSingleNode("title").InnerText, out category); + release.Category = category; var test = node.SelectSingleNode("enclosure"); release.Guid = new Uri(test.Attributes["url"].Value); release.PublishDate = DateTime.Parse(node.SelectSingleNode("pubDate").InnerText, CultureInfo.InvariantCulture); @@ -143,10 +115,10 @@ namespace Jackett.Indexers } catch (Exception ex) { - OnParseError(xml, ex); + OnParseError(result.Content, ex); } - return releases.ToArray(); + return releases; } } } diff --git a/src/Jackett/Indexers/SpeedCD.cs b/src/Jackett/Indexers/SpeedCD.cs index ba48e71619bc3f1936ae1a4a1da6e8878c6ec54c..bc4009333c15ecbf489af3a53ca51f44b81c66b5 100644 --- a/src/Jackett/Indexers/SpeedCD.cs +++ b/src/Jackett/Indexers/SpeedCD.cs @@ -2,6 +2,7 @@ using Jackett.Models; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -19,96 +20,56 @@ namespace Jackett.Indexers { public class SpeedCD : BaseIndexer, IIndexer { - private readonly string LoginUrl = ""; - private readonly string SearchUrl = ""; - private readonly string SearchFormData = "c53=1&c49=1&c2=1&c52=1&c41=1&c50=1&c30=1&jxt=4&jxw=b"; - private readonly string CommentsUrl = ""; - private readonly string DownloadUrl = ""; - - CookieContainer cookies; - HttpClientHandler handler; - HttpClient client; - - public SpeedCD(IIndexerManagerService i, Logger l) + private string LoginUrl { get { return SiteLink + "take_login.php"; } } + private string SearchUrl { get { return SiteLink + "V3/API/API.php"; } } + private string SearchFormData { get { return "c53=1&c49=1&c2=1&c52=1&c41=1&c50=1&c30=1&jxt=4&jxw=b"; } } + private string CommentsUrl { get { return SiteLink + "t/{0}"; } } + private string DownloadUrl { get { return SiteLink + "download.php?torrent={0}"; } } + + public SpeedCD(IIndexerManagerService i, Logger l, IWebClient wc) : base(name: "Speed.cd", description: "Your home now!", - link: new Uri("http://speed.cd"), + link: "http://speed.cd/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: wc, logger: l) { - LoginUrl = SiteLink + "take_login.php"; - SearchUrl = SiteLink + "V3/API/API.php"; - CommentsUrl = SiteLink + "t/{0}"; - DownloadUrl = SiteLink + "download.php?torrent={0}"; - - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataBasicLogin(); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); } public async Task ApplyConfiguration(JToken configJson) { - var config = new ConfigurationDataBasicLogin(); - config.LoadValuesFromJson(configJson); - + var incomingConfig = new ConfigurationDataBasicLogin(); + incomingConfig.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { - { "username", config.Username.Value }, - { "password", config.Password.Value }, + { "username", incomingConfig.Username.Value }, + { "password", incomingConfig.Password.Value }, }; - var content = new FormUrlEncodedContent(pairs); - - var response = await client.PostAsync(LoginUrl, content); - var responseContent = await response.Content.ReadAsStringAsync(); - - if (!responseContent.Contains("logout.php")) + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, SiteLink); + await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () => { - CQ dom = responseContent; + CQ dom = result.Content; var errorMessage = dom["h5"].First().Text().Trim(); - throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config); - } - else - { - var configSaveData = new JObject(); - cookies.DumpToJson(SiteLink, configSaveData); - SaveConfig(configSaveData); - IsConfigured = true; - } + throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig); + }); } - public void LoadFromSavedConfiguration(JToken jsonConfig) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { - cookies.FillFromJson(SiteLink, jsonConfig, logger); - IsConfigured = true; - } - - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) - { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - + var releases = new List<ReleaseInfo>(); var formData = HttpUtility.ParseQueryString(SearchFormData); var formDict = formData.AllKeys.ToDictionary(t => t, t => formData[t]); formDict.Add("search", query.SanitizedSearchTerm); - var content = new FormUrlEncodedContent(formDict); - - var response = await client.PostAsync(SearchUrl, content); - var results = await response.Content.ReadAsStringAsync(); - + var response = await PostDataWithCookiesAndRetry(SearchUrl, formDict); try { - var jsonResult = JObject.Parse(results); + var jsonResult = JObject.Parse(response.Content); var resultArray = ((JArray)jsonResult["Fs"])[0]["Cn"]["torrents"]; foreach (var jobj in resultArray) { @@ -138,14 +99,9 @@ namespace Jackett.Indexers } catch (Exception ex) { - OnParseError(results, ex); + OnParseError(response.Content, ex); } - return releases.ToArray(); - } - - public Task<byte[]> Download(Uri link) - { - return client.GetByteArrayAsync(link); + return releases; } } } diff --git a/src/Jackett/Indexers/Strike.cs b/src/Jackett/Indexers/Strike.cs index 5c63476f27984136fb5c5b9f0f2dd17e6e1a21c2..997b55eacfd9cef34cecdefb88c05005638aab4a 100644 --- a/src/Jackett/Indexers/Strike.cs +++ b/src/Jackett/Indexers/Strike.cs @@ -1,6 +1,7 @@ using Jackett.Models; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -17,74 +18,57 @@ namespace Jackett.Indexers { public class Strike : BaseIndexer, IIndexer { - private readonly string DownloadUrl = "/torrents/api/download/{0}.torrent"; - private readonly string SearchUrl = "/api/v2/torrents/search/?category=TV&phrase={0}"; - private string BaseUrl; + private string DownloadUrl { get { return baseUri + "torrents/api/download/{0}.torrent"; } } + private string SearchUrl { get { return baseUri + "api/v2/torrents/search/?category=TV&phrase={0}"; } } + private string baseUrl = null; + private Uri baseUri { get { return new Uri(baseUrl); } } - private CookieContainer cookies; - private HttpClientHandler handler; - private HttpClient client; - - public Strike(IIndexerManagerService i, Logger l) + public Strike(IIndexerManagerService i, Logger l, IWebClient wc) : base(name: "Strike", description: "Torrent search engine", - link: new Uri("https://getstrike.net"), + link: "https://getstrike.net/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: wc, logger: l) { - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataUrl(SiteLink); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataUrl(SiteLink)); } - public async Task ApplyConfiguration(JToken configJson) + public Task ApplyConfiguration(JToken configJson) { var config = new ConfigurationDataUrl(SiteLink); config.LoadValuesFromJson(configJson); - - var formattedUrl = config.GetFormattedHostUrl(); - var releases = await PerformQuery(new TorznabQuery(), formattedUrl); - if (releases.Length == 0) - throw new Exception("Could not find releases from this URL"); - - BaseUrl = formattedUrl; - + baseUrl = config.GetFormattedHostUrl(); var configSaveData = new JObject(); - configSaveData["base_url"] = BaseUrl; + configSaveData["base_url"] = baseUrl; SaveConfig(configSaveData); IsConfigured = true; + return Task.FromResult(0); } - public void LoadFromSavedConfiguration(JToken jsonConfig) + public override void LoadFromSavedConfiguration(JToken jsonConfig) { - BaseUrl = (string)jsonConfig["base_url"]; - IsConfigured = true; + baseUrl = (string)jsonConfig["base_url"]; + IsConfigured = !string.IsNullOrEmpty(baseUrl); } - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query, string baseUrl) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { List<ReleaseInfo> releases = new List<ReleaseInfo>(); var searchTerm = string.IsNullOrEmpty(query.SanitizedSearchTerm) ? "2015" : query.SanitizedSearchTerm; var searchString = searchTerm + " " + query.GetEpisodeSearchString(); - var episodeSearchUrl = baseUrl + string.Format(SearchUrl, HttpUtility.UrlEncode(searchString.Trim())); - var results = await client.GetStringAsync(episodeSearchUrl); + var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString.Trim())); + var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl, string.Empty); try { - var jResults = JObject.Parse(results); + var jResults = JObject.Parse(results.Content); foreach (JObject result in (JArray)jResults["torrents"]) { var release = new ReleaseInfo(); @@ -112,25 +96,20 @@ namespace Jackett.Indexers release.InfoHash = (string)result["torrent_hash"]; release.MagnetUri = new Uri((string)result["magnet_uri"]); - release.Link = new Uri(string.Format("{0}{1}", baseUrl, string.Format(DownloadUrl, release.InfoHash))); + release.Link = new Uri(string.Format(DownloadUrl, release.InfoHash)); releases.Add(release); } } catch (Exception ex) { - OnParseError(results, ex); + OnParseError(results.Content, ex); } - return releases.ToArray(); - } - - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) - { - return await PerformQuery(query, BaseUrl); + return releases; } - public Task<byte[]> Download(Uri link) + public override Task<byte[]> Download(Uri link) { throw new NotImplementedException(); } diff --git a/src/Jackett/Indexers/T411.cs b/src/Jackett/Indexers/T411.cs index 81475355897a427adcbc9b2d304f9c9a838ee38d..17b2a6d673528f3ca0c321ab611ce3d45ee5c329 100644 --- a/src/Jackett/Indexers/T411.cs +++ b/src/Jackett/Indexers/T411.cs @@ -1,6 +1,7 @@ using Jackett.Models; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -32,12 +33,13 @@ namespace Jackett.Indexers string token = string.Empty; DateTime lastTokenFetch = DateTime.MinValue; - public T411(IIndexerManagerService i, Logger l) + public T411(IIndexerManagerService i, Logger l,IWebClient wc) : base(name: "T411", description: "French Torrent Tracker", - link: new Uri("http://www.t411.io"), + link: "http://www.t411.io/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: wc, logger: l) { CommentsUrl = SiteLink + "/torrents/{0}"; @@ -107,7 +109,7 @@ namespace Jackett.Indexers IsConfigured = true; } - public void LoadFromSavedConfiguration(JToken jsonConfig) + public override void LoadFromSavedConfiguration(JToken jsonConfig) { username = (string)jsonConfig["username"]; password = (string)jsonConfig["password"]; @@ -116,10 +118,9 @@ namespace Jackett.Indexers IsConfigured = true; } - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - + var releases = new List<ReleaseInfo>(); var searchTerm = string.IsNullOrEmpty(query.SanitizedSearchTerm) ? "%20" : query.SanitizedSearchTerm; var searchString = searchTerm + " " + query.GetEpisodeSearchString(); var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString)); @@ -164,10 +165,10 @@ namespace Jackett.Indexers { OnParseError(results, ex); } - return releases.ToArray(); + return releases; } - public async Task<byte[]> Download(Uri link) + public override async Task<byte[]> Download(Uri link) { var message = new HttpRequestMessage(); message.Method = HttpMethod.Get; diff --git a/src/Jackett/Indexers/ThePirateBay.cs b/src/Jackett/Indexers/ThePirateBay.cs index e30169b19e5bef0aada4b23477a68cada0685257..f37a91f10284a4b7e27aa6b508f60d66c7328b1c 100644 --- a/src/Jackett/Indexers/ThePirateBay.cs +++ b/src/Jackett/Indexers/ThePirateBay.cs @@ -2,6 +2,7 @@ using Jackett.Models; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -19,37 +20,25 @@ namespace Jackett.Indexers { public class ThePirateBay : BaseIndexer, IIndexer { - const string SearchUrl = "/search/{0}/0/99/208,205"; - string BaseUrl; + private const string SearchUrl = "/search/{0}/0/99/208,205"; + private string BaseUrl; - CookieContainer cookies; - HttpClientHandler handler; - HttpClient client; - - public ThePirateBay(IIndexerManagerService i, Logger l) + public ThePirateBay(IIndexerManagerService i, Logger l, IWebClient wc) : base(name: "The Pirate Bay", description: "The worlds largest bittorrent indexer", - link: new Uri("https://thepiratebay.mn"), + link: "https://thepiratebay.mn/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: wc, logger: l) { BaseUrl = SiteLink.ToString(); IsConfigured = false; - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataUrl(BaseUrl); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataUrl(BaseUrl)); } public async Task ApplyConfiguration(JToken configJson) @@ -59,7 +48,7 @@ namespace Jackett.Indexers var formattedUrl = config.GetFormattedHostUrl(); var releases = await PerformQuery(new TorznabQuery(), formattedUrl); - if (releases.Length == 0) + if (releases.Count() == 0) throw new Exception("Could not find releases from this URL"); BaseUrl = formattedUrl; @@ -70,40 +59,28 @@ namespace Jackett.Indexers IsConfigured = true; } - public void LoadFromSavedConfiguration(JToken jsonConfig) + public override void LoadFromSavedConfiguration(JToken jsonConfig) { BaseUrl = (string)jsonConfig["base_url"]; IsConfigured = true; } - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { return await PerformQuery(query, BaseUrl); } - async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query, string baseUrl) + async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query, string baseUrl) { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - + var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var queryStr = HttpUtility.UrlEncode(searchString); var episodeSearchUrl = baseUrl + string.Format(SearchUrl, queryStr); - - string results; - - if (Engine.IsWindows) - { - results = await client.GetStringAsync(episodeSearchUrl); - } - else - { - var response = await CurlHelper.GetAsync(episodeSearchUrl, null, episodeSearchUrl); - results = Encoding.UTF8.GetString(response.Content); - } + var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl, string.Empty); try { - CQ dom = results; + CQ dom = response.Content; var rows = dom["#searchResult > tbody > tr"]; foreach (var row in rows) @@ -162,18 +139,14 @@ namespace Jackett.Indexers } catch (Exception ex) { - // OnResultParsingError(this, results, ex); - throw ex; + OnParseError(response.Content, ex); } return releases.ToArray(); } - - public Task<byte[]> Download(Uri link) + public override Task<byte[]> Download(Uri link) { throw new NotImplementedException(); } - - } } diff --git a/src/Jackett/Indexers/TorrentBytes.cs b/src/Jackett/Indexers/TorrentBytes.cs new file mode 100644 index 0000000000000000000000000000000000000000..03304012735f297459a62439299a8dc925019058 --- /dev/null +++ b/src/Jackett/Indexers/TorrentBytes.cs @@ -0,0 +1,176 @@ +using CsQuery; +using Jackett.Models; +using Jackett.Services; +using Jackett.Utils; +using Jackett.Utils.Clients; +using Newtonsoft.Json.Linq; +using NLog; +using System; +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.Threading.Tasks; +using System.Web; + +namespace Jackett.Indexers +{ + public class TorrentBytes : BaseIndexer, IIndexer + { + private string BrowseUrl { get { return SiteLink + "browse.php"; } } + private string LoginUrl { get { return SiteLink + "takelogin.php"; } } + + public TorrentBytes(IIndexerManagerService i, IWebClient wc, Logger l) + : base(name: "TorrentBytes", + description: "A decade of torrentbytes", + link: "https://www.torrentbytes.net/", + caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), + manager: i, + client: wc, + logger: l) + { + + AddCategoryMapping(41, TorznabCatType.TV); + AddCategoryMapping(33, TorznabCatType.TVSD); + AddCategoryMapping(38, TorznabCatType.TVHD); + AddCategoryMapping(32, TorznabCatType.TVSD); + AddCategoryMapping(37, TorznabCatType.TVSD); + AddCategoryMapping(44, TorznabCatType.TVSD); + + AddCategoryMapping(40, TorznabCatType.Movies); + AddCategoryMapping(19, TorznabCatType.MoviesSD); + AddCategoryMapping(5, TorznabCatType.MoviesHD); + AddCategoryMapping(20, TorznabCatType.MoviesSD); + AddCategoryMapping(28, TorznabCatType.MoviesForeign); + AddCategoryMapping(45, TorznabCatType.MoviesSD); + + AddCategoryMapping(43, TorznabCatType.Audio); + AddCategoryMapping(48, TorznabCatType.AudioLossless); + AddCategoryMapping(6, TorznabCatType.AudioLossy); + AddCategoryMapping(46, TorznabCatType.Movies); + + AddCategoryMapping(1, TorznabCatType.Apps); + AddCategoryMapping(2, TorznabCatType.Apps); + AddCategoryMapping(23, TorznabCatType.Anime); + AddCategoryMapping(21, TorznabCatType.XXX); + AddCategoryMapping(9, TorznabCatType.XXXSD); + AddCategoryMapping(39, TorznabCatType.XXXHD); + AddCategoryMapping(29, TorznabCatType.XXXSD); + AddCategoryMapping(24, TorznabCatType.XXXImg); + } + + public Task<ConfigurationData> GetConfigurationForSetup() + { + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); + } + + public async Task ApplyConfiguration(JToken configJson) + { + var incomingConfig = new ConfigurationDataBasicLogin(); + incomingConfig.LoadValuesFromJson(configJson); + var pairs = new Dictionary<string, string> { + { "username", incomingConfig.Username.Value }, + { "password", incomingConfig.Password.Value }, + { "returnto", "/" }, + { "login", "Log in!" } + }; + + var loginPage = await RequestStringWithCookies(SiteLink, string.Empty); + + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, SiteLink, SiteLink); + await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () => + { + CQ dom = result.Content; + var messageEl = dom["body > div"].First(); + var errorMessage = messageEl.Text().Trim(); + throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig); + }); + } + + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) + { + var releases = new List<ReleaseInfo>(); + var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); + var searchUrl = BrowseUrl; + var trackerCats = MapTorznabCapsToTrackers(query); + var queryCollection = new NameValueCollection(); + + // Tracker can only search OR return things in categories + if (!string.IsNullOrWhiteSpace(searchString)) + { + queryCollection.Add("search", searchString); + queryCollection.Add("cat", "0"); + queryCollection.Add("sc", "1"); + } + else + { + foreach (var cat in MapTorznabCapsToTrackers(query)) + { + queryCollection.Add("c" + cat, "1"); + } + + queryCollection.Add("incldead", "0"); + } + + searchUrl += "?" + queryCollection.GetQueryString(); + + // 15 results per page - really don't want to call the server twice but only 15 results per page is a bit crap! + await ProcessPage(releases, searchUrl); + await ProcessPage(releases, searchUrl + "&page=1"); + return releases; + } + + private async Task ProcessPage(List<ReleaseInfo> releases, string searchUrl) + { + var response = await RequestStringWithCookiesAndRetry(searchUrl, null, BrowseUrl); + var results = response.Content; + try + { + CQ dom = results; + + var rows = dom["#content table:eq(4) tr"]; + foreach (var row in rows.Skip(1)) + { + var release = new ReleaseInfo(); + + var link = row.Cq().Find("td:eq(1) a:eq(1)").First(); + release.Guid = new Uri(SiteLink + link.Attr("href")); + release.Comments = release.Guid; + release.Title = link.Text().Trim(); + release.Description = release.Title; + + // If we search an get no results, we still get a table just with no info. + if (string.IsNullOrWhiteSpace(release.Title)) + { + break; + } + + var cat = row.Cq().Find("td:eq(0) a").First().Attr("href").Substring(15); + release.Category = MapTrackerCatToNewznab(cat); + + + var qLink = row.Cq().Find("td:eq(1) a").First(); + release.Link = new Uri(SiteLink + qLink.Attr("href")); + + var added = row.Cq().Find("td:eq(4)").First().Text().Trim(); + release.PublishDate = DateTimeUtil.FromTimeAgo(added); + + var sizeStr = row.Cq().Find("td:eq(6)").First().Text().Trim(); + release.Size = ReleaseInfo.GetBytes(sizeStr); + + release.Seeders = ParseUtil.CoerceInt(row.Cq().Find("td:eq(8)").First().Text().Trim()); + release.Peers = ParseUtil.CoerceInt(row.Cq().Find("td:eq(9)").First().Text().Trim()) + release.Seeders; + + releases.Add(release); + } + } + catch (Exception ex) + { + OnParseError(results, ex); + } + } + } +} diff --git a/src/Jackett/Indexers/TorrentDay.cs b/src/Jackett/Indexers/TorrentDay.cs index 7399366ec615954adb7d030eb36d5ff64be4a169..cbb9daefadc23830c8b2c4618db4b4a2eb2174c5 100644 --- a/src/Jackett/Indexers/TorrentDay.cs +++ b/src/Jackett/Indexers/TorrentDay.cs @@ -2,6 +2,7 @@ using Jackett.Models; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -18,49 +19,24 @@ namespace Jackett.Indexers { public class TorrentDay : BaseIndexer, IIndexer { - private readonly string StartPageUrl = ""; - private readonly string LoginUrl = ""; - private readonly string SearchUrl = ""; + private string StartPageUrl { get { return SiteLink + "login.php"; } } + private string LoginUrl { get { return SiteLink + "tak3login.php"; } } + private string SearchUrl { get { return SiteLink + "browse.php?search={0}&cata=yes&c2=1&c7=1&c14=1&c24=1&c26=1&c31=1&c32=1&c33=1"; } } - CookieContainer cookies; - HttpClientHandler handler; - HttpClient client; - - public TorrentDay(IIndexerManagerService i, Logger l) + public TorrentDay(IIndexerManagerService i, Logger l, IWebClient wc) : base(name: "TorrentDay", description: "TorrentDay", - link: new Uri("https://torrentday.eu"), + link: "https://torrentday.eu/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: wc, logger: l) { - StartPageUrl = SiteLink + "login.php"; - LoginUrl = SiteLink + "tak3login.php"; - SearchUrl = SiteLink + "browse.php?search={0}&cata=yes&c2=1&c7=1&c14=1&c24=1&c26=1&c31=1&c32=1&c33=1"; - - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataBasicLogin(); - return Task.FromResult<ConfigurationData>(config); - } - - HttpRequestMessage CreateHttpRequest(string uri) - { - var message = new HttpRequestMessage(); - message.Method = HttpMethod.Get; - message.RequestUri = new Uri(uri); - message.Headers.UserAgent.ParseAdd(BrowserUtil.ChromeUserAgent); - return message; + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); } public async Task ApplyConfiguration(JToken configJson) @@ -68,56 +44,35 @@ namespace Jackett.Indexers var config = new ConfigurationDataBasicLogin(); config.LoadValuesFromJson(configJson); - var startMessage = CreateHttpRequest(StartPageUrl); - var results = await (await client.SendAsync(startMessage)).Content.ReadAsStringAsync(); + var startMessage = await RequestStringWithCookies(StartPageUrl, string.Empty); var pairs = new Dictionary<string, string> { { "username", config.Username.Value }, { "password", config.Password.Value } }; - var content = new FormUrlEncodedContent(pairs); - var loginRequest = CreateHttpRequest(LoginUrl); - loginRequest.Method = HttpMethod.Post; - loginRequest.Content = content; - loginRequest.Headers.Referrer = new Uri(StartPageUrl); - - var response = await client.SendAsync(loginRequest); - var responseContent = await response.Content.ReadAsStringAsync(); - if (!responseContent.Contains("logout.php")) + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, SiteLink, LoginUrl); + await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () => { - CQ dom = responseContent; + CQ dom = result.Content; var messageEl = dom["#login"]; messageEl.Children("form").Remove(); var errorMessage = messageEl.Text().Trim(); throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config); - } - else - { - var configSaveData = new JObject(); - cookies.DumpToJson(SiteLink, configSaveData); - SaveConfig(configSaveData); - IsConfigured = true; - } + }); } - public void LoadFromSavedConfiguration(JToken jsonConfig) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { - cookies.FillFromJson(SiteLink, jsonConfig, logger); - IsConfigured = true; - } - - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) - { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - + var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString)); - var results = await client.GetStringAsync(episodeSearchUrl); + var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl); + try { - CQ dom = results; + CQ dom = results.Content; var rows = dom["#torrentTable > tbody > tr.browse"]; foreach (var row in rows) { @@ -146,14 +101,9 @@ namespace Jackett.Indexers } catch (Exception ex) { - OnParseError(results, ex); + OnParseError(results.Content, ex); } - return releases.ToArray(); - } - - public Task<byte[]> Download(Uri link) - { - return client.GetByteArrayAsync(link); + return releases; } } } diff --git a/src/Jackett/Indexers/TorrentLeech.cs b/src/Jackett/Indexers/TorrentLeech.cs index 52c4bc4bc948675d6ee7382aaa9a51442a627793..3067861008777b085ac964676970c15ef24c0ce8 100644 --- a/src/Jackett/Indexers/TorrentLeech.cs +++ b/src/Jackett/Indexers/TorrentLeech.cs @@ -2,6 +2,7 @@ using Jackett.Models; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -18,45 +19,29 @@ namespace Jackett.Indexers { public class TorrentLeech : BaseIndexer, IIndexer { - private readonly string LoginUrl = ""; - private readonly string SearchUrl = ""; + private string LoginUrl { get { return SiteLink + "user/account/login/"; } } + private string SearchUrl { get { return SiteLink + "torrents/browse/index/query/{0}/categories/2%2C26%2C27%2C32/orderby/added?"; } } - CookieContainer cookies; - HttpClientHandler handler; - HttpClient client; - - public TorrentLeech(IIndexerManagerService i, Logger l) + public TorrentLeech(IIndexerManagerService i, Logger l, IWebClient wc) : base(name: "TorrentLeech", description: "This is what happens when you seed", - link: new Uri("http://www.torrentleech.org"), + link: "http://www.torrentleech.org/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: wc, logger: l) { - LoginUrl = SiteLink + "user/account/login/"; - SearchUrl = SiteLink + "torrents/browse/index/query/{0}/categories/2%2C26%2C27%2C32/orderby/added?"; - - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataBasicLogin(); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); } public async Task ApplyConfiguration(JToken configJson) { var config = new ConfigurationDataBasicLogin(); config.LoadValuesFromJson(configJson); - var pairs = new Dictionary<string, string> { { "username", config.Username.Value }, { "password", config.Password.Value }, @@ -64,43 +49,25 @@ namespace Jackett.Indexers { "login", "submit" } }; - var content = new FormUrlEncodedContent(pairs); - - var response = await client.PostAsync(LoginUrl, content); - var responseContent = await response.Content.ReadAsStringAsync(); - - if (!responseContent.Contains("/user/account/logout")) + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true,null, LoginUrl); + await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("/user/account/logout"), () => { - CQ dom = responseContent; + CQ dom = result.Content; var messageEl = dom[".ui-state-error"].Last(); var errorMessage = messageEl.Text().Trim(); throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config); - } - else - { - var configSaveData = new JObject(); - cookies.DumpToJson(SiteLink, configSaveData); - SaveConfig(configSaveData); - IsConfigured = true; - } + }); } - public void LoadFromSavedConfiguration(JToken jsonConfig) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { - cookies.FillFromJson(SiteLink, jsonConfig, logger); - IsConfigured = true; - } - - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) - { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - + var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString)); - var results = await client.GetStringAsync(episodeSearchUrl); + var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl); try { - CQ dom = results; + CQ dom = results.Content; CQ qRows = dom["#torrenttable > tbody > tr"]; @@ -140,18 +107,10 @@ namespace Jackett.Indexers catch (Exception ex) { - OnParseError(results, ex); + OnParseError(results.Content, ex); } - return releases.ToArray(); - } - - public Task<byte[]> Download(Uri link) - { - return client.GetByteArrayAsync(link); + return releases; } - - - } } diff --git a/src/Jackett/Indexers/TorrentShack.cs b/src/Jackett/Indexers/TorrentShack.cs index 97eb3f99a287969032a5604a905ddce767cb0aaf..3823ce379022e8eae1838a25f8e21fc8e9b0f5c0 100644 --- a/src/Jackett/Indexers/TorrentShack.cs +++ b/src/Jackett/Indexers/TorrentShack.cs @@ -2,6 +2,7 @@ using Jackett.Models; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -18,37 +19,23 @@ namespace Jackett.Indexers { public class TorrentShack : BaseIndexer, IIndexer { - private readonly string LoginUrl = ""; - private readonly string SearchUrl = ""; + private string LoginUrl { get { return SiteLink + "login.php"; } } + private string SearchUrl { get { return SiteLink + "torrents.php?searchstr={0}&release_type=both&searchtags=&tags_type=0&order_by=s3&order_way=desc&torrent_preset=all&filter_cat%5B600%5D=1&filter_cat%5B620%5D=1&filter_cat%5B700%5D=1&filter_cat%5B981%5D=1&filter_cat%5B980%5D=1"; } } - CookieContainer cookies; - HttpClientHandler handler; - HttpClient client; - - public TorrentShack(IIndexerManagerService i, Logger l) + public TorrentShack(IIndexerManagerService i, Logger l, IWebClient wc) : base(name: "TorrentShack", description: "TorrentShack", - link: new Uri("http://torrentshack.me"), + link: "http://torrentshack.me/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), + client: wc, manager: i, logger: l) { - LoginUrl = SiteLink + "login.php"; - SearchUrl = SiteLink + "torrents.php?searchstr={0}&release_type=both&searchtags=&tags_type=0&order_by=s3&order_way=desc&torrent_preset=all&filter_cat%5B600%5D=1&filter_cat%5B620%5D=1&filter_cat%5B700%5D=1&filter_cat%5B981%5D=1&filter_cat%5B980%5D=1"; - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataBasicLogin(); - return Task.FromResult<ConfigurationData>(config); + return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin()); } public async Task ApplyConfiguration(JToken configJson) @@ -63,46 +50,26 @@ namespace Jackett.Indexers { "login", "Login" } }; - var content = new FormUrlEncodedContent(pairs); - - var response = await client.PostAsync(LoginUrl, content); - var responseContent = await response.Content.ReadAsStringAsync(); - - if (!responseContent.Contains("logout.php")) + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, LoginUrl); + await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () => { - CQ dom = responseContent; + CQ dom = result.Content; var messageEl = dom["#loginform"]; messageEl.Children("table").Remove(); var errorMessage = messageEl.Text().Trim(); throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config); - } - else - { - var configSaveData = new JObject(); - cookies.DumpToJson(SiteLink, configSaveData); - SaveConfig(configSaveData); - IsConfigured = true; - } - + }); } - public void LoadFromSavedConfiguration(JToken jsonConfig) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { - cookies.FillFromJson(SiteLink, jsonConfig, logger); - IsConfigured = true; - } - - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) - { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - - + var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString)); - var results = await client.GetStringAsync(episodeSearchUrl); + var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl); try { - CQ dom = results; + CQ dom = results.Content; var rows = dom["#torrent_table > tbody > tr.torrent"]; foreach (var row in rows) { @@ -130,14 +97,9 @@ namespace Jackett.Indexers } catch (Exception ex) { - OnParseError(results, ex); + OnParseError(results.Content, ex); } - return releases.ToArray(); - } - - public Task<byte[]> Download(Uri link) - { - return client.GetByteArrayAsync(link); + return releases; } } } diff --git a/src/Jackett/Indexers/Torrentz.cs b/src/Jackett/Indexers/Torrentz.cs index b0246d82d1eb2563f74ec93ac4f0979134d85d85..c1d846da42547a7cb39176e9212388b347387c7d 100644 --- a/src/Jackett/Indexers/Torrentz.cs +++ b/src/Jackett/Indexers/Torrentz.cs @@ -1,6 +1,7 @@ using Jackett.Models; using Jackett.Services; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -12,43 +13,29 @@ using System.Threading.Tasks; using System.Web; using System.Windows.Forms; using System.Xml; +using System.Linq; namespace Jackett.Indexers { public class Torrentz : BaseIndexer, IIndexer { - private readonly string SearchUrl = ""; - string BaseUrl; + private string SearchUrl { get { return SiteLink + "feed_verifiedP?f={0}"; } } + private string BaseUrl; - CookieContainer cookies; - HttpClientHandler handler; - HttpClient client; - - public Torrentz(IIndexerManagerService i, Logger l) + public Torrentz(IIndexerManagerService i, Logger l, IWebClient wc) : base(name: "Torrentz", description: "Torrentz is a meta-search engine and a Multisearch. This means we just search other search engines.", - link: new Uri("https://torrentz.eu"), + link: "https://torrentz.eu/", caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), manager: i, + client: wc, logger: l) { - - SearchUrl = SiteLink + "feed_verifiedP?f={0}"; - cookies = new CookieContainer(); - handler = new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = true, - UseCookies = true, - }; - client = new HttpClient(handler); } public Task<ConfigurationData> GetConfigurationForSetup() { - var config = new ConfigurationDataUrl(SiteLink); - return Task.FromResult<ConfigurationData>(config); - + return Task.FromResult<ConfigurationData>(new ConfigurationDataUrl(SiteLink)); } public async Task ApplyConfiguration(JToken configJson) @@ -57,46 +44,29 @@ namespace Jackett.Indexers config.LoadValuesFromJson(configJson); var formattedUrl = config.GetFormattedHostUrl(); - var releases = await PerformQuery(new TorznabQuery(), formattedUrl); - if (releases.Length == 0) + IEnumerable<ReleaseInfo> releases = await PerformQuery(new TorznabQuery(), formattedUrl); + if (releases.Count() == 0) throw new Exception("Could not find releases from this URL"); BaseUrl = formattedUrl; - var configSaveData = new JObject(); configSaveData["base_url"] = BaseUrl; SaveConfig(configSaveData); IsConfigured = true; - } - private WebClient getWebClient() + async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query, string baseUrl) { - WebClient wc = new WebClient(); - WebHeaderCollection headers = new WebHeaderCollection(); - headers.Add("User-Agent", BrowserUtil.ChromeUserAgent); - wc.Headers = headers; - return wc; - } - - async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query, string baseUrl) - { - List<ReleaseInfo> releases = new List<ReleaseInfo>(); - + var releases = new List<ReleaseInfo>(); var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString.Trim())); - - XmlDocument xmlDoc = new XmlDocument(); + var xmlDoc = new XmlDocument(); string xml = string.Empty; - WebClient wc = getWebClient(); + var result = await RequestStringWithCookiesAndRetry(episodeSearchUrl); try { - using (wc) - { - xml = await wc.DownloadStringTaskAsync(new Uri(episodeSearchUrl)); - xmlDoc.LoadXml(xml); - } + xmlDoc.LoadXml(result.Content); ReleaseInfo release; TorrentzHelper td; @@ -112,7 +82,9 @@ namespace Jackett.Indexers release.Title = serie_title; release.Comments = new Uri(node.SelectSingleNode("link").InnerText); - release.Category = node.SelectSingleNode("category").InnerText; + int category = 0; + int.TryParse(node.SelectSingleNode("category").InnerText, out category); + release.Category = category; release.Guid = new Uri(node.SelectSingleNode("guid").InnerText); release.PublishDate = DateTime.Parse(node.SelectSingleNode("pubDate").InnerText, CultureInfo.InvariantCulture); @@ -131,22 +103,22 @@ namespace Jackett.Indexers OnParseError(xml, ex); } - return releases.ToArray(); + return releases; } - public void LoadFromSavedConfiguration(JToken jsonConfig) + public override void LoadFromSavedConfiguration(JToken jsonConfig) { BaseUrl = (string)jsonConfig["base_url"]; IsConfigured = true; } - public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { return await PerformQuery(query, BaseUrl); } - public Task<byte[]> Download(Uri link) + public override Task<byte[]> Download(Uri link) { throw new NotImplementedException(); } @@ -206,7 +178,7 @@ namespace Jackett.Indexers switch (counter) { case 0: - this.Size = ReleaseInfo.BytesFromMB(ParseUtil.CoerceLong(val.Substring(0, val.IndexOf(" ") - 1))); + this.Size = ReleaseInfo.GetBytes(val); break; case 1: this.Seeders = ParseUtil.CoerceInt(val.Contains(",") ? val.Remove(val.IndexOf(","), 1) : val); diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj index 9191460f1b4e3a70f130408ca2490a95b4de07d0..3762f618ba73ac8b40453c82d679a2bfcb9d24be 100644 --- a/src/Jackett/Jackett.csproj +++ b/src/Jackett/Jackett.csproj @@ -9,7 +9,7 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>Jackett</RootNamespace> <AssemblyName>Jackett</AssemblyName> - <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <TargetFrameworkProfile /> <PublishUrl>publish\</PublishUrl> @@ -62,15 +62,17 @@ <HintPath>..\packages\Autofac.3.5.2\lib\net40\Autofac.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="Autofac.Integration.Owin"> + <Reference Include="Autofac.Integration.Owin, Version=3.1.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL"> <HintPath>..\packages\Autofac.Owin.3.1.0\lib\net45\Autofac.Integration.Owin.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="Autofac.Integration.WebApi, Version=3.4.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Autofac.WebApi2.3.4.0\lib\net45\Autofac.Integration.WebApi.dll</HintPath> + <Private>True</Private> </Reference> - <Reference Include="Autofac.Integration.WebApi.Owin"> + <Reference Include="Autofac.Integration.WebApi.Owin, Version=3.2.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL"> <HintPath>..\packages\Autofac.WebApi2.Owin.3.2.0\lib\net45\Autofac.Integration.WebApi.Owin.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="AutoMapper, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL"> <HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll</HintPath> @@ -80,11 +82,13 @@ <HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="CsQuery"> + <Reference Include="CsQuery, Version=1.3.3.249, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\CsQuery.1.3.4\lib\net40\CsQuery.dll</HintPath> + <Private>True</Private> </Reference> - <Reference Include="Microsoft.AspNet.Identity.Core"> + <Reference Include="Microsoft.AspNet.Identity.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath> @@ -98,8 +102,9 @@ <HintPath>..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="Microsoft.Owin.Host.SystemWeb"> + <Reference Include="Microsoft.Owin.Host.SystemWeb, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="Microsoft.Owin.Hosting, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll</HintPath> @@ -109,6 +114,14 @@ <HintPath>..\packages\Microsoft.Owin.StaticFiles.3.0.1\lib\net45\Microsoft.Owin.StaticFiles.dll</HintPath> <Private>True</Private> </Reference> + <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> + <HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> + <HintPath>..\packages\NLog.4.0.1\lib\net45\NLog.dll</HintPath> + <Private>True</Private> + </Reference> <Reference Include="NLog.Windows.Forms, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\NLog.Windows.Forms.2.0.0.0\lib\net35\NLog.Windows.Forms.dll</HintPath> <Private>True</Private> @@ -122,15 +135,17 @@ <Reference Include="System.Core" /> <Reference Include="System.Drawing" /> <Reference Include="System.Net.Http" /> - <Reference Include="System.Net.Http.Extensions"> + <Reference Include="System.Net.Http.Extensions, Version=2.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="System.Net.Http.Primitives"> + <Reference Include="System.Net.Http.Primitives, Version=4.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="System.Net.Http.WebRequest" /> <Reference Include="System.ServiceProcess" /> @@ -143,8 +158,9 @@ <HintPath>..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="System.Web.Http.Tracing"> + <Reference Include="System.Web.Http.Tracing, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.AspNet.WebApi.Tracing.5.2.3\lib\net45\System.Web.Http.Tracing.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="System.Windows.Forms" /> <Reference Include="System.Xml.Linq" /> @@ -152,31 +168,58 @@ <Reference Include="Microsoft.CSharp" /> <Reference Include="System.Data" /> <Reference Include="System.Xml" /> - <Reference Include="Newtonsoft.Json"> - <HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> - </Reference> - <Reference Include="NLog"> - <HintPath>..\packages\NLog.4.0.1\lib\net45\NLog.dll</HintPath> - </Reference> </ItemGroup> <ItemGroup> - <Compile Include="Controllers\APIController.cs" /> + <Compile Include="Controllers\PotatoController.cs" /> + <Compile Include="Controllers\TorznabController.cs" /> <Compile Include="Controllers\DownloadController.cs" /> <Compile Include="Engine.cs" /> + <Compile Include="Indexers\AlphaRatio.cs" /> <Compile Include="Indexers\BakaBT.cs" /> <Compile Include="Indexers\BaseIndexer.cs" /> <Compile Include="Indexers\BB.cs" /> + <Compile Include="Indexers\BeyondHD.cs" /> + <Compile Include="Indexers\BitHdtv.cs" /> + <Compile Include="Indexers\BitMeTV.cs" /> + <Compile Include="Indexers\FrenchTorrentDb.cs" /> + <Compile Include="Indexers\Freshon.cs" /> <Compile Include="Indexers\HDSpace.cs" /> + <Compile Include="Indexers\HDTorrents.cs" /> + <Compile Include="Indexers\IIndexer.cs" /> + <Compile Include="Indexers\ImmortalSeed.cs" /> + <Compile Include="Indexers\TorrentBytes.cs" /> + <Compile Include="Indexers\IPTorrents.cs" /> + <Compile Include="Indexers\MoreThanTV.cs" /> <Compile Include="Indexers\Pretome.cs" /> <Compile Include="Indexers\PrivateHD.cs" /> + <Compile Include="Indexers\SceneAccess.cs" /> + <Compile Include="Indexers\SceneTime.cs" /> + <Compile Include="Indexers\ShowRSS.cs" /> <Compile Include="Indexers\SpeedCD.cs" /> + <Compile Include="Indexers\Strike.cs" /> + <Compile Include="Indexers\T411.cs" /> + <Compile Include="Indexers\ThePirateBay.cs" /> + <Compile Include="Indexers\TorrentDay.cs" /> + <Compile Include="Indexers\TorrentLeech.cs" /> + <Compile Include="Indexers\TorrentShack.cs" /> + <Compile Include="Indexers\Torrentz.cs" /> <Compile Include="Models\CachedResult.cs" /> + <Compile Include="Models\CategoryMapping.cs" /> + <Compile Include="Models\IndexerConfig\BmtvConfig.cs" /> + <Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginAnimeBytes.cs" /> + <Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginFrenchTorrentDb.cs" /> + <Compile Include="Models\IndexerConfig\PretomeConfiguration.cs" /> + <Compile Include="Models\TorrentPotatoRequest.cs" /> + <Compile Include="Models\TorrentPotatoResponse.cs" /> + <Compile Include="Models\TorrentPotatoResponseItem.cs" /> <Compile Include="Models\TorznabCapabilities.cs" /> <Compile Include="Models\Config\ServerConfig.cs" /> <Compile Include="Models\TorznabCategory.cs" /> + <Compile Include="Models\TorznabCatType.cs" /> <Compile Include="Models\TrackerCache.cs" /> <Compile Include="Models\TrackerCacheResult.cs" /> <Compile Include="Services\CacheService.cs" /> + <Compile Include="Utils\Clients\BaseWebResult.cs" /> <Compile Include="Utils\Clients\UnixLibCurlWebClient.cs" /> <Compile Include="Utils\Clients\WebByteResult.cs" /> <Compile Include="Utils\Clients\WebClientResult.cs" /> @@ -198,31 +241,12 @@ <Compile Include="Utils\DataUrl.cs" /> <Compile Include="ExceptionWithConfigData.cs" /> <Compile Include="HttpClientExtensions.cs" /> - <Compile Include="Indexers\IIndexer.cs" /> - <Compile Include="Indexers\BeyondHD.cs" /> - <Compile Include="Indexers\BitHdtv.cs" /> - <Compile Include="Indexers\BitMeTV.cs" /> - <Compile Include="Indexers\FrenchTorrentDb.cs" /> - <Compile Include="Indexers\Freshon.cs" /> - <Compile Include="Indexers\HDTorrents.cs" /> - <Compile Include="Indexers\IPTorrents.cs" /> - <Compile Include="Indexers\MoreThanTV.cs" /> - <Compile Include="Indexers\Rarbg.cs" /> - <Compile Include="Indexers\SceneAccess.cs" /> - <Compile Include="Indexers\SceneTime.cs" /> - <Compile Include="Indexers\ShowRSS.cs" /> - <Compile Include="Indexers\Strike.cs" /> - <Compile Include="Indexers\T411.cs" /> - <Compile Include="Indexers\ThePirateBay.cs" /> - <Compile Include="Indexers\TorrentDay.cs" /> <Compile Include="Indexers\AnimeBytes.cs" /> - <Compile Include="Indexers\TorrentLeech.cs" /> - <Compile Include="Indexers\TorrentShack.cs" /> - <Compile Include="Indexers\Torrentz.cs" /> <Compile Include="JackettModule.cs" /> <Compile Include="Utils\DateTimeUtil.cs" /> <Compile Include="Utils\Clients\IWebClient.cs" /> <Compile Include="Utils\JackettAuthorizedAttribute.cs" /> + <Compile Include="Utils\JsonContent.cs" /> <Compile Include="Utils\ParseUtil.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\Resources.Designer.cs"> @@ -239,17 +263,19 @@ <Compile Include="Startup.cs" /> <Compile Include="Models\TorznabQuery.cs" /> <Compile Include="CurlHelper.cs" /> - <Compile Include="Indexers\AlphaRatio.cs" /> <Compile Include="Utils\StringUtil.cs" /> <Compile Include="Utils\TorznabCapsUtil.cs" /> <Compile Include="Utils\Clients\UnixSafeCurlWebClient.cs" /> <Compile Include="Utils\WebApiRootRedirectMiddleware.cs" /> <Compile Include="Utils\WebAPIRequestLogger.cs" /> <Compile Include="Utils\WebAPIToNLogTracer.cs" /> - <Compile Include="Utils\Clients\WindowsWebClient.cs" /> + <Compile Include="Utils\Clients\HttpWebClient.cs" /> + <Compile Include="WebAPIExceptionLogger.cs" /> </ItemGroup> <ItemGroup> - <None Include="App.config" /> + <None Include="App.config"> + <SubType>Designer</SubType> + </None> <None Include="Content\fonts\fontawesome-webfont.eot"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> @@ -274,14 +300,12 @@ <None Include="Content\fonts\glyphicons-halflings-regular.woff2"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> - <None Include="packages.config"> - <SubType>Designer</SubType> - </None> <None Include="Content\fonts\glyphicons-halflings-regular.woff"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup> <ItemGroup> + <None Include="packages.config" /> <None Include="Resources\test.xml" /> </ItemGroup> <ItemGroup> @@ -363,6 +387,9 @@ <Content Include="Content\logos\hdtorrents.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> + <Content Include="Content\logos\immortalseed.png"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> <Content Include="Content\logos\pretome.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> @@ -384,6 +411,9 @@ <Content Include="Content\logos\t411.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> + <Content Include="Content\logos\torrentbytes.png"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> <Content Include="Content\logos\torrentday.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> @@ -441,9 +471,6 @@ <Content Include="Content\logos\morethantv.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> - <Content Include="Content\logos\rarbg.png"> - <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> - </Content> <Content Include="Content\logos\strike.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> @@ -505,6 +532,7 @@ </Properties> </MonoDevelop> </ProjectExtensions> + <Import Project="..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets" Condition="Exists('..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets')" /> <Import Project="..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" /> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <PropertyGroup> @@ -512,5 +540,4 @@ </PropertyGroup> <Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" /> </Target> - <Import Project="..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets" Condition="Exists('..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets')" /> </Project> \ No newline at end of file diff --git a/src/Jackett/JackettModule.cs b/src/Jackett/JackettModule.cs index 23cfae951aea0dd975f17d0c83123ba2dfe344c0..74790ad76f2cc2a29499db1bdc20cbf0dd562c2f 100644 --- a/src/Jackett/JackettModule.cs +++ b/src/Jackett/JackettModule.cs @@ -8,6 +8,8 @@ using Autofac.Integration.WebApi; using Jackett.Indexers; using Jackett.Utils; using Jackett.Utils.Clients; +using AutoMapper; +using Jackett.Models; namespace Jackett { @@ -20,18 +22,29 @@ namespace Jackett builder.RegisterAssemblyTypes(thisAssembly).Except<IIndexer>().AsImplementedInterfaces().SingleInstance(); builder.RegisterApiControllers(thisAssembly).InstancePerRequest(); - // Register the best web client for the platform or exec curl as a safe option - if (Startup.CurlSafe) + // Register the best web client for the platform or the override + switch (Startup.ClientOverride) { - builder.RegisterType<UnixSafeCurlWebClient>().As<IWebClient>(); - } - else if(System.Environment.OSVersion.Platform == PlatformID.Unix) - { - builder.RegisterType<UnixLibCurlWebClient>().As<IWebClient>(); - } - else - { - builder.RegisterType<WindowsWebClient>().As<IWebClient>(); + case "httpclient": + builder.RegisterType<HttpWebClient>().As<IWebClient>(); + break; + case "safecurl": + builder.RegisterType<UnixSafeCurlWebClient>().As<IWebClient>(); + break; + case "libcurl": + builder.RegisterType<UnixLibCurlWebClient>().As<IWebClient>(); + break; + case "automatic": + default: + if (System.Environment.OSVersion.Platform == PlatformID.Unix) + { + builder.RegisterType<UnixLibCurlWebClient>().As<IWebClient>(); + } + else + { + builder.RegisterType<HttpWebClient>().As<IWebClient>(); + } + break; } // Register indexers @@ -41,6 +54,27 @@ namespace Jackett { builder.RegisterType(indexer).Named<IIndexer>(BaseIndexer.GetIndexerID(indexer)); } + + Mapper.CreateMap<WebClientByteResult, WebClientStringResult>().ForMember(x => x.Content, opt => opt.Ignore()).AfterMap((be, str) => + { + str.Content = Encoding.UTF8.GetString(be.Content); + }); + + Mapper.CreateMap<WebClientStringResult, WebClientByteResult>().ForMember(x => x.Content, opt => opt.Ignore()).AfterMap((str, be) => + { + if (!string.IsNullOrEmpty(str.Content)) + { + be.Content = Encoding.UTF8.GetBytes(str.Content); + } + }); + + Mapper.CreateMap<WebClientStringResult, WebClientStringResult>(); + Mapper.CreateMap<WebClientByteResult, WebClientByteResult>(); + + Mapper.CreateMap<ReleaseInfo, TrackerCacheResult>().AfterMap((r, t) => + { + t.CategoryDesc = TorznabCatType.GetCatDesc(r.Category); + }); } } } diff --git a/src/Jackett/Models/CategoryMapping.cs b/src/Jackett/Models/CategoryMapping.cs new file mode 100644 index 0000000000000000000000000000000000000000..6ab314c4841e84d2dbacc4d926ad0377c8a0d76d --- /dev/null +++ b/src/Jackett/Models/CategoryMapping.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Models +{ + class CategoryMapping + { + public CategoryMapping(string trackerCat, int newzCat) + { + TrackerCategory = trackerCat.ToLowerInvariant(); + NewzNabCategory = newzCat; + } + + public string TrackerCategory { get; private set; } + public int NewzNabCategory { get; private set; } + } +} diff --git a/src/Jackett/Models/Config/ServerConfig.cs b/src/Jackett/Models/Config/ServerConfig.cs index 58a221a5f051d955d10a1898fa141fe5c562c3f7..36f8e1d39d2f2bc3527105c2c786aa3a1526bb53 100644 --- a/src/Jackett/Models/Config/ServerConfig.cs +++ b/src/Jackett/Models/Config/ServerConfig.cs @@ -12,6 +12,7 @@ namespace Jackett.Models.Config public ServerConfig() { Port = 9117; + AllowExternal = System.Environment.OSVersion.Platform == PlatformID.Unix; } public int Port { get; set; } diff --git a/src/Jackett/Models/ConfigurationData.cs b/src/Jackett/Models/ConfigurationData.cs index 21344cf9c68decad3e35fb85741d47dd0acf8833..be18414467e1daea1e03638bf042610602ffa41e 100644 --- a/src/Jackett/Models/ConfigurationData.cs +++ b/src/Jackett/Models/ConfigurationData.cs @@ -19,6 +19,16 @@ namespace Jackett.Models HiddenData } + public ConfigurationData() + { + + } + + public ConfigurationData(JToken json) + { + LoadValuesFromJson(json); + } + public void LoadValuesFromJson(JToken json) { // todo: match up ids with items and fill values @@ -33,6 +43,9 @@ namespace Jackett.Models case ItemType.InputBool: ((BoolItem)item).Value = (bool)dictionary[item.ID]; break; + case ItemType.HiddenData: + ((HiddenItem)item).Value = (string)dictionary[item.ID]; + break; } } } @@ -74,6 +87,15 @@ namespace Jackett.Models public string ID { get { return Name.Replace(" ", "").ToLower(); } } } + public class HiddenItem : StringItem + { + public HiddenItem(string value) + { + Value = value; + ItemType = ItemType.HiddenData; + } + } + public class DisplayItem : StringItem { public DisplayItem(string value) diff --git a/src/Jackett/Models/ConfigurationDataBasicLogin.cs b/src/Jackett/Models/ConfigurationDataBasicLogin.cs index 66afbffe358b5e333a2b426786c89b41bd74eff6..fc75502847b81ae7997ee5c8bd2dc3cb88c01cee 100644 --- a/src/Jackett/Models/ConfigurationDataBasicLogin.cs +++ b/src/Jackett/Models/ConfigurationDataBasicLogin.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json.Linq; +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/src/Jackett/Models/IndexerConfig/BmtvConfig.cs b/src/Jackett/Models/IndexerConfig/BmtvConfig.cs new file mode 100644 index 0000000000000000000000000000000000000000..c7f1af5587b855bd543e514d072427fcf103e52e --- /dev/null +++ b/src/Jackett/Models/IndexerConfig/BmtvConfig.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Models.IndexerConfig +{ + class BmtvConfig : ConfigurationData + { + public StringItem Username { get; private set; } + + public StringItem Password { get; private set; } + + public ImageItem CaptchaImage { get; private set; } + + public StringItem CaptchaText { get; private set; } + + public HiddenItem CaptchaCookie { get; private set; } + + public BmtvConfig() + { + Username = new StringItem { Name = "Username" }; + Password = new StringItem { Name = "Password" }; + CaptchaImage = new ImageItem { Name = "Captcha Image" }; + CaptchaText = new StringItem { Name = "Captcha Text" }; + CaptchaCookie = new HiddenItem("") { Name = "Captcha Cookie" }; + } + + public override Item[] GetItems() + { + return new Item[] { Username, Password, CaptchaImage, CaptchaText, CaptchaCookie }; + } + } +} diff --git a/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginAnimeBytes.cs b/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginAnimeBytes.cs new file mode 100644 index 0000000000000000000000000000000000000000..249bec8d76cdaa448dbcdec85513b610da57a405 --- /dev/null +++ b/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginAnimeBytes.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Models.IndexerConfig +{ + class ConfigurationDataBasicLoginAnimeBytes : ConfigurationDataBasicLogin + { + public BoolItem IncludeRaw { get; private set; } + public DisplayItem DateWarning { get; private set; } + + public ConfigurationDataBasicLoginAnimeBytes() + : base() + { + IncludeRaw = new BoolItem() { Name = "IncludeRaw", Value = false }; + DateWarning = new DisplayItem("This tracker does not supply upload dates so they are based off year of release.") { Name = "DateWarning" }; + } + + public override Item[] GetItems() + { + return new Item[] { Username, Password, IncludeRaw, DateWarning }; + } + } +} diff --git a/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginFrenchTorrentDb.cs b/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginFrenchTorrentDb.cs new file mode 100644 index 0000000000000000000000000000000000000000..9cfbb6d6dd9a41b907148b3daf444a0d8b020729 --- /dev/null +++ b/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginFrenchTorrentDb.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Models.IndexerConfig +{ + class ConfigurationDataBasicLoginFrenchTorrentDb : ConfigurationData + { + public StringItem Cookie { get; private set; } + + public ConfigurationDataBasicLoginFrenchTorrentDb() + { + Cookie = new StringItem { Name = "Cookie" }; + } + + public override Item[] GetItems() + { + return new Item[] { Cookie }; + } + } +} diff --git a/src/Jackett/Models/IndexerConfig/PretomeConfiguration.cs b/src/Jackett/Models/IndexerConfig/PretomeConfiguration.cs new file mode 100644 index 0000000000000000000000000000000000000000..27cb36688df40f1a868e670a51ef75346a22f168 --- /dev/null +++ b/src/Jackett/Models/IndexerConfig/PretomeConfiguration.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Models.IndexerConfig +{ + class PretomeConfiguration : ConfigurationDataBasicLogin + { + public StringItem Pin { get; private set; } + + public PretomeConfiguration() : base() + { + Pin = new StringItem { Name = "Login Pin Number" }; + } + + public override Item[] GetItems() + { + return new Item[] { Pin, Username, Password }; + } + } +} diff --git a/src/Jackett/Models/ReleaseInfo.cs b/src/Jackett/Models/ReleaseInfo.cs index a12dac0bfea5c656f8429e9ea99f8ff9c1375373..2e24293b391a6cc972bb328713b8d68409b0c982 100644 --- a/src/Jackett/Models/ReleaseInfo.cs +++ b/src/Jackett/Models/ReleaseInfo.cs @@ -16,7 +16,7 @@ namespace Jackett.Models public Uri Link { get; set; } public Uri Comments { get; set; } public DateTime PublishDate { get; set; } - public string Category { get; set; } + public int Category { get; set; } public long? Size { get; set; } public string Description { get; set; } public long? RageID { get; set; } diff --git a/src/Jackett/Models/ResultPage.cs b/src/Jackett/Models/ResultPage.cs index fe238e2252311bdb84d1faf241d0a949193064c5..7593ea0b66b31c76c82d313af9c1f49279bfeb2d 100644 --- a/src/Jackett/Models/ResultPage.cs +++ b/src/Jackett/Models/ResultPage.cs @@ -63,8 +63,6 @@ namespace Jackett.Models new XElement("link", ChannelInfo.ImageLink.ToString()), new XElement("description", ChannelInfo.ImageDescription) ), - - from r in Releases select new XElement("item", new XElement("title", r.Title), @@ -74,7 +72,7 @@ namespace Jackett.Models r.Size == null ? null : new XElement("size", r.Size), new XElement("description", r.Description), new XElement("link", r.Link ?? r.MagnetUri), - r.Category == null ? null : new XElement("category", r.Category), + r.Category == 0 ? null : new XElement("category", r.Category), new XElement( "enclosure", new XAttribute("url", r.Link ?? r.MagnetUri), diff --git a/src/Jackett/Models/TorrentPotatoRequest.cs b/src/Jackett/Models/TorrentPotatoRequest.cs new file mode 100644 index 0000000000000000000000000000000000000000..4581bcaa067c51e8857a2fff3efbab54fa479743 --- /dev/null +++ b/src/Jackett/Models/TorrentPotatoRequest.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Models +{ + public class TorrentPotatoRequest + { + public string username { get; set; } + public string passkey { get; set; } + public string imdbid { get; set; } + public string search { get; set; } + } +} diff --git a/src/Jackett/Models/TorrentPotatoResponse.cs b/src/Jackett/Models/TorrentPotatoResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..ea76ff6acb3eeff8f0f09973b25719d4aa0de667 --- /dev/null +++ b/src/Jackett/Models/TorrentPotatoResponse.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Models +{ + public class TorrentPotatoResponse + { + public TorrentPotatoResponse() + { + results = new List<TorrentPotatoResponseItem>(); + } + public List<TorrentPotatoResponseItem> results { get; set; } + + public int total_results + { + get { return results.Count; } + } + } +} diff --git a/src/Jackett/Models/TorrentPotatoResponseItem.cs b/src/Jackett/Models/TorrentPotatoResponseItem.cs new file mode 100644 index 0000000000000000000000000000000000000000..809ada2b68bde9cc6ce2f8d087d34c84f9ad2568 --- /dev/null +++ b/src/Jackett/Models/TorrentPotatoResponseItem.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Models +{ + public class TorrentPotatoResponseItem + { + public string release_name { get; set; } + public string torrent_id { get; set; } + public string details_url { get; set; } + public string download_url { get; set; } + // public string imdb_id { get; set; } + public bool freeleech { get; set; } + public string type { get; set; } + public long size { get; set; } + public int leechers { get; set; } + public int seeders { get; set; } + } +} diff --git a/src/Jackett/Models/TorznabCapabilities.cs b/src/Jackett/Models/TorznabCapabilities.cs index 012ad8e5702f202c5ac7b897782c4feced49f6b0..f85f2275d415add33ccec46853ea09b5dfc09522 100644 --- a/src/Jackett/Models/TorznabCapabilities.cs +++ b/src/Jackett/Models/TorznabCapabilities.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json.Linq; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -21,6 +22,18 @@ namespace Jackett.Models public TorznabCapabilities() { Categories = new List<TorznabCategory>(); + SearchAvailable = true; + TVSearchAvailable = true; + SupportsTVRageSearch = false; + } + + public TorznabCapabilities(params TorznabCategory[] cats) + { + SearchAvailable = true; + TVSearchAvailable = true; + SupportsTVRageSearch = false; + Categories = new List<TorznabCategory>(); + Categories.AddRange(cats); } string SupportedTVSearchParams @@ -34,6 +47,16 @@ namespace Jackett.Models } } + public JArray CapsToJson() + { + var jArray = new JArray(); + foreach (var cat in Categories.GroupBy(p => p.ID).Select(g => g.First()).OrderBy(c=>c.ID)) + { + jArray.Add(cat.ToJson()); + } + return jArray; + } + public string ToXml() { var xdoc = new XDocument( diff --git a/src/Jackett/Models/TorznabCatType.cs b/src/Jackett/Models/TorznabCatType.cs new file mode 100644 index 0000000000000000000000000000000000000000..f78fb39950b6948f57a2f6c01359570629aafea0 --- /dev/null +++ b/src/Jackett/Models/TorznabCatType.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Models +{ + class TorznabCatType + { + private static Dictionary<int, string> cats = new Dictionary<int, string>(); + + static TorznabCatType() + { + cats.Add(5000, "TV"); + cats.Add(5030, "TV/SD"); + cats.Add(5040, "TV/HD"); + cats.Add(5070, "TV/Anime"); + cats.Add(8000, "Books"); + cats.Add(8020, "Books/Comics"); + cats.Add(4000, "PC"); + cats.Add(3030, "Audio/Audiobook"); + cats.Add(2000, "Movies"); + cats.Add(2040, "Movies/HD"); + cats.Add(2030, "Movies/SD"); + cats.Add(2010, "Movies/Foreign"); + cats.Add(3000, "Audio"); + cats.Add(3040, "Audio/Lossless"); + cats.Add(3010, "Audio/MP3"); + cats.Add(6000, "XXX"); + cats.Add(6040, "XXX/x264"); + cats.Add(6010, "XXX/DVD"); + cats.Add(6060, "XXX/Imageset"); + } + + public static bool QueryContainsParentCategory(int[] queryCats, int releaseCat) + { + if (cats.ContainsKey(releaseCat) && queryCats!=null) + { + var ncab = cats[releaseCat]; + var split = ncab.IndexOf("/"); + if (split > -1) + { + string parentCatName = ncab.Substring(0,split); + if (cats.ContainsValue(parentCatName)) + { + var parentCat = cats.Where(c => c.Value == parentCatName).First().Key; + return queryCats.Contains(parentCat); + } + } + } + + return false; + } + + public static string GetCatDesc(int newznabcat) + { + if (cats.ContainsKey(newznabcat)) + { + return cats[newznabcat]; + } + + return string.Empty; + } + + private static TorznabCategory GetCat(int id) + { + return new TorznabCategory() + { + ID = id, + Name = cats[id] + }; + } + + public static TorznabCategory Anime + { + get { return GetCat(5070); } + } + + public static TorznabCategory TV + { + get { return GetCat(5000); } + } + + public static TorznabCategory TVSD + { + get { return GetCat(5030); } + } + + public static TorznabCategory TVHD + { + get { return GetCat(5040); } + } + + public static TorznabCategory Books + { + get { return GetCat(8000); } + } + + public static TorznabCategory Comic + { + get { return GetCat(8020); } + } + + public static TorznabCategory Apps + { + get { return GetCat(4000); } + } + + public static TorznabCategory AudioBooks + { + get { return GetCat(3030); } + } + + public static TorznabCategory Movies + { + get { return GetCat(2000); } + } + + public static TorznabCategory MoviesHD + { + get { return GetCat(2040); } + } + + public static TorznabCategory MoviesSD + { + get { return GetCat(2030); } + } + + public static TorznabCategory MoviesForeign + { + get { return GetCat(2040); } + } + + public static TorznabCategory Audio + { + get { return GetCat(3000); } + } + + public static TorznabCategory AudioLossless + { + get { return GetCat(3040); } + } + + public static TorznabCategory AudioLossy + { + get { return GetCat(3010); } + } + + public static TorznabCategory XXX + { + get { return GetCat(6000); } + } + + public static TorznabCategory XXXHD + { + get { return GetCat(6040); } + } + + public static TorznabCategory XXXSD + { + get { return GetCat(6010); } + } + + public static TorznabCategory XXXImg + { + get { return GetCat(6060); } + } + } +} diff --git a/src/Jackett/Models/TorznabCategory.cs b/src/Jackett/Models/TorznabCategory.cs index 53425c540a97e43e18b4babbae48a3545db2b33e..2e9e939f9d998c429ec70c51ba480a5d2aad1ab4 100644 --- a/src/Jackett/Models/TorznabCategory.cs +++ b/src/Jackett/Models/TorznabCategory.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json.Linq; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -8,7 +9,7 @@ namespace Jackett.Models { public class TorznabCategory { - public string ID { get; set; } + public int ID { get; set; } public string Name { get; set; } public List<TorznabCategory> SubCategories { get; private set; } @@ -17,5 +18,13 @@ namespace Jackett.Models { SubCategories = new List<TorznabCategory>(); } + + public JToken ToJson() + { + var t = new JObject(); + t["ID"] = ID; + t["Name"] = Name; + return t; + } } } diff --git a/src/Jackett/Models/TorznabQuery.cs b/src/Jackett/Models/TorznabQuery.cs index d27698517a711ab0f3dc5742a484e0c35982e844..b3bd85782b6cfc266a6031c0e216bad9aca4d9e8 100644 --- a/src/Jackett/Models/TorznabQuery.cs +++ b/src/Jackett/Models/TorznabQuery.cs @@ -12,7 +12,7 @@ namespace Jackett.Models public class TorznabQuery { public string QueryType { get; set; } - public string[] Categories { get; set; } + public int[] Categories { get; set; } public int Extended { get; set; } public string ApiKey { get; set; } public int Limit { get; set; } @@ -22,7 +22,30 @@ namespace Jackett.Models public int Season { get; set; } public string Episode { get; set; } public string SearchTerm { get; set; } - public string SanitizedSearchTerm { get; set; } + + public string SanitizedSearchTerm + { + get + { + if (SearchTerm == null) + return string.Empty; + + char[] arr = SearchTerm.ToCharArray(); + + arr = Array.FindAll<char>(arr, c => (char.IsLetterOrDigit(c) + || char.IsWhiteSpace(c) + || c == '-' + || c == '.' + )); + var safetitle = new string(arr); + return safetitle; + } + } + + public TorznabQuery() + { + Categories = new int[0]; + } public string GetEpisodeSearchString() { @@ -41,19 +64,6 @@ namespace Jackett.Models return episodeString; } - static string SanitizeSearchTerm(string title) - { - char[] arr = title.ToCharArray(); - - arr = Array.FindAll<char>(arr, c => (char.IsLetterOrDigit(c) - || char.IsWhiteSpace(c) - || c == '-' - || c == '.' - )); - title = new string(arr); - return title; - } - public static TorznabQuery FromHttpQuery(NameValueCollection query) { @@ -64,17 +74,18 @@ namespace Jackett.Models if (query["q"] == null) { q.SearchTerm = string.Empty; - q.SanitizedSearchTerm = string.Empty; } else { q.SearchTerm = query["q"]; - q.SanitizedSearchTerm = SanitizeSearchTerm(q.SearchTerm); } if (query["cat"] != null) { - q.Categories = query["cat"].Split(','); + q.Categories = query["cat"].Split(',').Select(s => int.Parse(s)).ToArray(); + }else + { + q.Categories = new int[0]; } if (query["extended"] != null) diff --git a/src/Jackett/Models/TrackerCacheResult.cs b/src/Jackett/Models/TrackerCacheResult.cs index 0ea9af6501a37351e24acaf5f2d690c6c46952e5..13b1569e8fbe2297277eada265cc529de066c748 100644 --- a/src/Jackett/Models/TrackerCacheResult.cs +++ b/src/Jackett/Models/TrackerCacheResult.cs @@ -10,5 +10,6 @@ namespace Jackett.Models { public DateTime FirstSeen { get; set; } public string Tracker { get; set; } + public string CategoryDesc { get; set; } } } diff --git a/src/Jackett/Properties/AssemblyInfo.cs b/src/Jackett/Properties/AssemblyInfo.cs index c854b4d9e8daff5e35a21cf29c2c763120599831..bd946bcd97976dab5723be2785996673b6033c1e 100644 --- a/src/Jackett/Properties/AssemblyInfo.cs +++ b/src/Jackett/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.5.1.0")] +[assembly: AssemblyVersion("0.6.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Jackett/Properties/Resources.Designer.cs b/src/Jackett/Properties/Resources.Designer.cs index b35923b6e50f0ce11bbde08b95ace025d6db827b..b9898d2b78b1b004fc3b00840c0f03641da9ffa3 100644 --- a/src/Jackett/Properties/Resources.Designer.cs +++ b/src/Jackett/Properties/Resources.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:4.0.30319.34209 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -72,7 +72,7 @@ namespace Jackett.Properties { /// <webMaster>($email) (HDA Invites)</webMaster> /// <category>search</category> /// <image> - /// <url>https://hdac [rest of string was truncated]";. + /// <url>h [rest of string was truncated]";. /// </summary> internal static string test { get { diff --git a/src/Jackett/Services/CacheService.cs b/src/Jackett/Services/CacheService.cs index 400649f389aac52091be78c4be764a9e045ac631..84548bf378d0fa69045c01f5f29621c30c97c9c2 100644 --- a/src/Jackett/Services/CacheService.cs +++ b/src/Jackett/Services/CacheService.cs @@ -10,22 +10,17 @@ namespace Jackett.Services { public interface ICacheService { - void CacheRssResults(string trackerId, ReleaseInfo[] releases); + void CacheRssResults(string trackerId, IEnumerable<ReleaseInfo> releases); List<TrackerCacheResult> GetCachedResults(); } public class CacheService : ICacheService { private readonly List<TrackerCache> cache = new List<TrackerCache>(); - private readonly int MAX_RESULTS_PER_TRACKER = 100; + private readonly int MAX_RESULTS_PER_TRACKER = 250; private readonly TimeSpan AGE_LIMIT = new TimeSpan(2, 0, 0, 0); - static CacheService() - { - Mapper.CreateMap<ReleaseInfo,TrackerCacheResult>(); - } - - public void CacheRssResults(string trackerId, ReleaseInfo[] releases) + public void CacheRssResults(string trackerId, IEnumerable<ReleaseInfo> releases) { lock (cache) { diff --git a/src/Jackett/Services/IndexerManagerService.cs b/src/Jackett/Services/IndexerManagerService.cs index 47113a7413aeb695c3bcd063732e197767ca0b53..f95c44a52e0592e6e7165c8947c14425940f6cf8 100644 --- a/src/Jackett/Services/IndexerManagerService.cs +++ b/src/Jackett/Services/IndexerManagerService.cs @@ -2,6 +2,7 @@ using Jackett.Indexers; using Jackett.Models; using Jackett.Utils; +using Jackett.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using System; @@ -29,16 +30,20 @@ namespace Jackett.Services private IConfigurationService configService; private Logger logger; private Dictionary<string, IIndexer> indexers = new Dictionary<string, IIndexer>(); + private ICacheService cacheService; - public IndexerManagerService(IContainer c, IConfigurationService config, Logger l) + public IndexerManagerService(IContainer c, IConfigurationService config, Logger l, ICacheService cache) { container = c; configService = config; logger = l; + cacheService = cache; } public void InitIndexers() { + logger.Info("Using HTTP Client: " + container.Resolve<IWebClient>().GetType().Name); + foreach (var idx in container.Resolve<IEnumerable<IIndexer>>().OrderBy(_ => _.DisplayName)) { indexers.Add(idx.ID, idx); @@ -74,9 +79,10 @@ namespace Jackett.Services var indexer = GetIndexer(name); var browseQuery = new TorznabQuery(); var results = await indexer.PerformQuery(browseQuery); - logger.Info(string.Format("Found {0} releases from {1}", results.Length, indexer.DisplayName)); - if (results.Length == 0) + logger.Info(string.Format("Found {0} releases from {1}", results.Count(), indexer.DisplayName)); + if (results.Count() == 0) throw new Exception("Found no results while trying to browse this tracker"); + cacheService.CacheRssResults(indexer.DisplayName, results); } public void DeleteIndexer(string name) diff --git a/src/Jackett/Services/ServerService.cs b/src/Jackett/Services/ServerService.cs index ba97771782ff57c856b51dbc968e718057abec3b..cdd82b782b1ee25365fb00ce1afb1255bd41c2db 100644 --- a/src/Jackett/Services/ServerService.cs +++ b/src/Jackett/Services/ServerService.cs @@ -1,6 +1,7 @@ using Autofac; using Jackett.Models.Config; using Jackett.Services; +using Jackett.Utils.Clients; using Microsoft.Owin.Hosting; using Newtonsoft.Json.Linq; using NLog; @@ -41,14 +42,16 @@ namespace Jackett.Services private ISerializeService serializeService; private IConfigurationService configService; private Logger logger; + private IWebClient client; - public ServerService(IIndexerManagerService i, IProcessService p, ISerializeService s, IConfigurationService c, Logger l) + public ServerService(IIndexerManagerService i, IProcessService p, ISerializeService s, IConfigurationService c, Logger l, IWebClient w) { indexerService = i; processService = p; serializeService = s; configService = c; logger = l; + client = w; LoadConfig(); } @@ -105,12 +108,9 @@ namespace Jackett.Services { logger.Info("Starting Jackett " + configService.GetVersion()); CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US"); - - // Allow all SSL.. sucks I know but mono on linux is having problems without it.. - ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; - // Load indexers indexerService.InitIndexers(); + client.Init(); } public void Start() diff --git a/src/Jackett/Startup.cs b/src/Jackett/Startup.cs index 779d49d51f9d9ea70121570f085b155f33de3ed5..d427afd4a2a02e46e96665bcf0fa5a58b8645f2d 100644 --- a/src/Jackett/Startup.cs +++ b/src/Jackett/Startup.cs @@ -34,7 +34,13 @@ namespace Jackett set; } - public static bool CurlSafe + public static string ClientOverride + { + get; + set; + } + + public static bool? DoSSLFix { get; set; @@ -70,18 +76,42 @@ namespace Jackett config.Routes.MapHttpRoute( name: "apiDefault", routeTemplate: "api/{indexerID}", - defaults: new { controller = "API", action = "Call" } + defaults: new { controller = "Torznab", action = "Call" } ); config.Routes.MapHttpRoute( name: "api", routeTemplate: "api/{indexerID}/api", - defaults: new { controller = "API", action = "Call" } + defaults: new { controller = "Torznab", action = "Call" } + ); + + config.Routes.MapHttpRoute( + name: "torznabDefault", + routeTemplate: "torznab/{indexerID}", + defaults: new { controller = "Torznab", action = "Call" } + ); + + config.Routes.MapHttpRoute( + name: "torznab", + routeTemplate: "torznab/{indexerID}/api", + defaults: new { controller = "Torznab", action = "Call" } + ); + + config.Routes.MapHttpRoute( + name: "potatoDefault", + routeTemplate: "potato/{indexerID}", + defaults: new { controller = "Potato", action = "Call" } + ); + + config.Routes.MapHttpRoute( + name: "potato", + routeTemplate: "potato/{indexerID}/api", + defaults: new { controller = "Potato", action = "Call" } ); config.Routes.MapHttpRoute( name: "download", - routeTemplate: "api/{indexerID}/download/{path}/download.torrent", + routeTemplate: "api/{indexerID}/download/{path}/t.torrent", defaults: new { controller = "Download", action = "Download" } ); diff --git a/src/Jackett/Utils/Clients/BaseWebResult.cs b/src/Jackett/Utils/Clients/BaseWebResult.cs new file mode 100644 index 0000000000000000000000000000000000000000..699751c22465d139ebf95ee3060482c1a54817a6 --- /dev/null +++ b/src/Jackett/Utils/Clients/BaseWebResult.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Utils.Clients +{ + public abstract class BaseWebResult + { + public HttpStatusCode Status { get; set; } + public string Cookies { get; set; } + public string RedirectingTo { get; set; } + + public bool IsRedirect + { + get + { + return Status == System.Net.HttpStatusCode.Redirect || + Status == System.Net.HttpStatusCode.RedirectKeepVerb || + Status == System.Net.HttpStatusCode.RedirectMethod || + Status == System.Net.HttpStatusCode.Found || + Status == System.Net.HttpStatusCode.MovedPermanently; + } + } + } +} diff --git a/src/Jackett/Utils/Clients/WindowsWebClient.cs b/src/Jackett/Utils/Clients/HttpWebClient.cs similarity index 54% rename from src/Jackett/Utils/Clients/WindowsWebClient.cs rename to src/Jackett/Utils/Clients/HttpWebClient.cs index 12048fe0556f11269569502f5c60f05a2a519c86..92a88b99f481eac0d0bc5649204d7714cc7c9977 100644 --- a/src/Jackett/Utils/Clients/WindowsWebClient.cs +++ b/src/Jackett/Utils/Clients/HttpWebClient.cs @@ -1,4 +1,5 @@ -using Jackett.Models; +using AutoMapper; +using Jackett.Models; using NLog; using System; using System.Collections.Generic; @@ -10,21 +11,38 @@ using System.Threading.Tasks; namespace Jackett.Utils.Clients { - class WindowsWebClient : IWebClient + class HttpWebClient : IWebClient { private Logger logger; - - public WindowsWebClient(Logger l) + public HttpWebClient(Logger l) { logger = l; - + } + + + public void Init() + { } public async Task<WebClientByteResult> GetBytes(WebRequest request) { logger.Debug(string.Format("WindowsWebClient:GetBytes(Url:{0})", request.Url)); + var result = await Run(request); + logger.Debug(string.Format("WindowsWebClient: Returning {0} => {1} bytes", result.Status, (result.Content == null ? "<NULL>" : result.Content.Length.ToString()))); + return result; + } + + public async Task<WebClientStringResult> GetString(WebRequest request) + { + logger.Debug(string.Format("WindowsWebClient:GetString(Url:{0})", request.Url)); + var result = await Run(request); + logger.Debug(string.Format("WindowsWebClient: Returning {0} => {1}", result.Status, (result.Content == null ? "<NULL>" : Encoding.UTF8.GetString(result.Content)))); + return Mapper.Map<WebClientStringResult>(result); + } + private async Task<WebClientByteResult> Run(WebRequest request) + { var cookies = new CookieContainer(); if (!string.IsNullOrEmpty(request.Cookies)) { @@ -51,7 +69,7 @@ namespace Jackett.Utils.Clients client.DefaultRequestHeaders.Add("User-Agent", BrowserUtil.ChromeUserAgent); HttpResponseMessage response = null; - + if (request.Type == RequestType.POST) { var content = new FormUrlEncodedContent(request.PostData); @@ -64,69 +82,11 @@ namespace Jackett.Utils.Clients var result = new WebClientByteResult(); result.Content = await response.Content.ReadAsByteArrayAsync(); - - result.Status = response.StatusCode; - - // Compatiblity issue between the cookie format and httpclient - // Pull it out manually ignoring the expiry date then set it manually - // http://stackoverflow.com/questions/14681144/httpclient-not-storing-cookies-in-cookiecontainer - IEnumerable<string> cookieHeaders; - if (response.Headers.TryGetValues("set-cookie", out cookieHeaders)) - { - var cookieBuilder = new StringBuilder(); - foreach (var c in cookieHeaders) - { - cookieBuilder.AppendFormat("{0} ", c.Substring(0, c.LastIndexOf(';'))); - } - - result.Cookies = cookieBuilder.ToString().TrimEnd(); - } - - return result; - } - - public async Task<WebClientStringResult> GetString(WebRequest request) - { - logger.Debug(string.Format("WindowsWebClient:GetString(Url:{0})", request.Url)); - var cookies = new CookieContainer(); - - if (!string.IsNullOrEmpty(request.Cookies)) + if (response.Headers.Location != null) { - var uri = new Uri(request.Url); - foreach (var c in request.Cookies.Split(';')) - { - try - { - cookies.SetCookies(uri, c); - } - catch (CookieException ex) - { - logger.Info("(Non-critical) Problem loading cookie {0}, {1}, {2}", uri, c, ex.Message); - } - } - } - - var client = new HttpClient(new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = false, // Do not use this - Bugs ahoy! Lost cookies and more. - UseCookies = true, - }); - - client.DefaultRequestHeaders.Add("User-Agent", BrowserUtil.ChromeUserAgent); - HttpResponseMessage response = null; - - if (request.Type == RequestType.POST) - { - var content = new FormUrlEncodedContent(request.PostData); - response = await client.PostAsync(request.Url, content); - } else - { - response = await client.GetAsync(request.Url); + result.RedirectingTo = response.Headers.Location.ToString(); } - - var result = new WebClientStringResult(); - result.Content = await response.Content.ReadAsStringAsync(); + result.Status = response.StatusCode; // Compatiblity issue between the cookie format and httpclient // Pull it out manually ignoring the expiry date then set it manually @@ -137,22 +97,13 @@ namespace Jackett.Utils.Clients var cookieBuilder = new StringBuilder(); foreach (var c in cookieHeaders) { - if (cookieBuilder.Length > 0) - { - cookieBuilder.Append("; "); - } - - cookieBuilder.Append( c.Substring(0, c.IndexOf(';'))); + cookieBuilder.AppendFormat("{0} ", c.Substring(0, c.IndexOf(';')+1)); } - result.Cookies = cookieBuilder.ToString(); + result.Cookies = cookieBuilder.ToString().TrimEnd(); } - result.Status = response.StatusCode; - if (null != response.Headers.Location) - { - result.RedirectingTo = response.Headers.Location.ToString(); - } + ServerUtil.ResureRedirectIsFullyQualified(request, result); return result; } } diff --git a/src/Jackett/Utils/Clients/IWebClient.cs b/src/Jackett/Utils/Clients/IWebClient.cs index b7d3e1947734fdd86da4a8af2154a217d98a7acc..24ad8dfcb4d5071755654ece6ef1d090edc90d70 100644 --- a/src/Jackett/Utils/Clients/IWebClient.cs +++ b/src/Jackett/Utils/Clients/IWebClient.cs @@ -11,5 +11,6 @@ namespace Jackett.Utils.Clients { Task<WebClientStringResult> GetString(WebRequest request); Task<WebClientByteResult> GetBytes(WebRequest request); + void Init(); } } diff --git a/src/Jackett/Utils/Clients/UnixLibCurlWebClient.cs b/src/Jackett/Utils/Clients/UnixLibCurlWebClient.cs index db04057881ba1f58bbe58e745a47d008b20a6c09..58f2e024f9e0b0eafbcae896918b8c96ac0f683b 100644 --- a/src/Jackett/Utils/Clients/UnixLibCurlWebClient.cs +++ b/src/Jackett/Utils/Clients/UnixLibCurlWebClient.cs @@ -1,4 +1,6 @@ -using Jackett.Models; +using AutoMapper; +using CurlSharp; +using Jackett.Models; using Jackett.Services; using NLog; using System; @@ -23,54 +25,86 @@ namespace Jackett.Utils.Clients public async Task<WebClientByteResult> GetBytes(WebRequest request) { - Jackett.CurlHelper.CurlResponse response; - logger.Debug(string.Format("UnixLibCurlWebClient:GetBytes(Url:{0})", request.Url)); + var result = await Run(request); + logger.Debug(string.Format("UnixLibCurlWebClient:GetBytes Returning {0} => {1} bytes", result.Status, (result.Content==null?"<NULL>":result.Content.Length.ToString()))); + return result; + } + + public async Task<WebClientStringResult> GetString(WebRequest request) + { + logger.Debug(string.Format("UnixLibCurlWebClient:GetString(Url:{0})", request.Url)); + var result = await Run(request); + logger.Debug(string.Format("UnixLibCurlWebClient:GetString Returning {0} => {1}", result.Status, (result.Content== null?"<NULL>": Encoding.UTF8.GetString(result.Content)))); + return Mapper.Map<WebClientStringResult>(result); + } + public void Init() + { + try { + Engine.Logger.Info("LibCurl init " + Curl.GlobalInit(CurlInitFlag.All).ToString()); + CurlHelper.OnErrorMessage += (msg) => + { + Engine.Logger.Error(msg); + }; + } + catch(Exception e) + { + Engine.Logger.Warn("Libcurl failed to initalize. Did you install it?"); + Engine.Logger.Warn("Debian: apt-get install libcurl4-openssl-dev"); + Engine.Logger.Warn("Redhat: yum install libcurl-devel"); + throw e; + } + + var version = Curl.Version; + Engine.Logger.Info("LibCurl version " + version); + + if (!Startup.DoSSLFix.HasValue && version.IndexOf("NSS")>-1) + { + Engine.Logger.Info("NSS Detected SSL ECC workaround enabled."); + Startup.DoSSLFix = true; + } + } + + private async Task<WebClientByteResult> Run(WebRequest request) + { + Jackett.CurlHelper.CurlResponse response; if (request.Type == RequestType.GET) { response = await CurlHelper.GetAsync(request.Url, request.Cookies, request.Referer); } else { + if (request.PostData != null && request.PostData.Count > 0) + { + logger.Debug("UnixLibCurlWebClient: Posting " + new FormUrlEncodedContent(request.PostData).ReadAsStringAsync().Result); + } + response = await CurlHelper.PostAsync(request.Url, request.PostData, request.Cookies, request.Referer); } var result = new WebClientByteResult() { Content = response.Content, - Cookies = response.CookieHeader, + Cookies = response.Cookies, Status = response.Status }; - if (response.Headers != null) + if (response.HeaderList != null) { - foreach(var header in response.Headers) + foreach (var header in response.HeaderList) { - if(string.Equals(header.Key, "location", StringComparison.InvariantCultureIgnoreCase) && header.Value !=null) + switch (header[0].ToLowerInvariant()) { - result.RedirectingTo = header.Value; + case "location": + result.RedirectingTo = header[1]; + break; } } } - logger.Debug(string.Format("UnixLibCurlWebClient: Returning", result.Status)); + ServerUtil.ResureRedirectIsFullyQualified(request, result); return result; } - - public async Task<WebClientStringResult> GetString(WebRequest request) - { - logger.Debug(string.Format("UnixLibCurlWebClient:GetString(Url:{0})", request.Url)); - var result = await GetBytes(request); - - var sresult = new WebClientStringResult() - { - Content = Encoding.UTF8.GetString(result.Content), - Cookies = result.Cookies, - Status = result.Status - }; - - return sresult; - } } } diff --git a/src/Jackett/Utils/Clients/UnixSafeCurlWebClient.cs b/src/Jackett/Utils/Clients/UnixSafeCurlWebClient.cs index f0822950844127a888ff08a1320d163bde864ee9..7f3d8fe474639c4d2d1284b33e564c754ece0fd0 100644 --- a/src/Jackett/Utils/Clients/UnixSafeCurlWebClient.cs +++ b/src/Jackett/Utils/Clients/UnixSafeCurlWebClient.cs @@ -1,4 +1,6 @@ -using Jackett.Models; +using AutoMapper; +using CurlSharp; +using Jackett.Models; using Jackett.Services; using NLog; using System; @@ -23,30 +25,30 @@ namespace Jackett.Utils.Clients logger = l; } - public Task<WebClientByteResult> GetBytes(WebRequest request) + public void Init() + { + } + + public async Task<WebClientByteResult> GetBytes(WebRequest request) { logger.Debug(string.Format("UnixSafeCurlWebClient:GetBytes(Url:{0})", request.Url)); - return Run(request); + var result = await Run(request); + logger.Debug(string.Format("UnixSafeCurlWebClient: Returning {0} => {1} bytes", result.Status, (result.Content == null ? "<NULL>" : result.Content.Length.ToString()))); + return result; } public async Task<WebClientStringResult> GetString(WebRequest request) { logger.Debug(string.Format("UnixSafeCurlWebClient:GetString(Url:{0})", request.Url)); - var byteResult = await Run(request); - return new WebClientStringResult() - { - Cookies = byteResult.Cookies, - Status = byteResult.Status, - Content = Encoding.UTF8.GetString(byteResult.Content), - RedirectingTo = byteResult.RedirectingTo - }; + var result = await Run(request); + logger.Debug(string.Format("UnixSafeCurlWebClient: Returning {0} => {1}", result.Status, (result.Content == null ? "<NULL>" : Encoding.UTF8.GetString(result.Content)))); + return Mapper.Map<WebClientStringResult>(result); } private async Task<WebClientByteResult> Run(WebRequest request) { var args = new StringBuilder(); args.AppendFormat("--url \"{0}\" ", request.Url); - args.AppendFormat("-i -sS --user-agent \"{0}\" ", BrowserUtil.ChromeUserAgent); if (!string.IsNullOrWhiteSpace(request.Cookies)) @@ -66,9 +68,15 @@ namespace Jackett.Utils.Clients } var tempFile = Path.GetTempFileName(); - args.AppendFormat("--output \"{0}\" ", tempFile); + if (Startup.DoSSLFix == true) + { + // http://stackoverflow.com/questions/31107851/how-to-fix-curl-35-cannot-communicate-securely-with-peer-no-common-encryptio + // https://git.fedorahosted.org/cgit/mod_nss.git/plain/docs/mod_nss.html + args.Append("--cipher " + SSLFix.CipherList); + } + string stdout = null; await Task.Run(() => { @@ -77,9 +85,7 @@ namespace Jackett.Utils.Clients var outputData = File.ReadAllBytes(tempFile); File.Delete(tempFile); - stdout = Encoding.UTF8.GetString(outputData); - var result = new WebClientByteResult(); var headSplit = stdout.IndexOf("\r\n\r\n"); if (headSplit < 0) @@ -127,6 +133,7 @@ namespace Jackett.Utils.Clients } logger.Debug("WebClientByteResult returned " + result.Status); + ServerUtil.ResureRedirectIsFullyQualified(request, result); return result; } } diff --git a/src/Jackett/Utils/Clients/WebByteResult.cs b/src/Jackett/Utils/Clients/WebByteResult.cs index b904ec0705ea542e6511ec2cbf3bf09f9d87224e..f0ae9de900f72ab1f833797074867a6c4cac01aa 100644 --- a/src/Jackett/Utils/Clients/WebByteResult.cs +++ b/src/Jackett/Utils/Clients/WebByteResult.cs @@ -7,11 +7,8 @@ using System.Threading.Tasks; namespace Jackett.Utils.Clients { - public class WebClientByteResult + public class WebClientByteResult : BaseWebResult { - public HttpStatusCode Status { get; set; } - public string Cookies { get; set; } public byte[] Content { get; set; } - public string RedirectingTo { get; set; } } } diff --git a/src/Jackett/Utils/Clients/WebClientResult.cs b/src/Jackett/Utils/Clients/WebClientResult.cs index a801e880081f019e5f251974fa7ac0809e371241..2898e05ebc6a26c1c8c2101bd5360e245f20a463 100644 --- a/src/Jackett/Utils/Clients/WebClientResult.cs +++ b/src/Jackett/Utils/Clients/WebClientResult.cs @@ -7,11 +7,8 @@ using System.Threading.Tasks; namespace Jackett.Utils.Clients { - public class WebClientStringResult + public class WebClientStringResult: BaseWebResult { - public HttpStatusCode Status { get; set; } - public string Cookies { get; set; } public string Content { get; set; } - public string RedirectingTo { get; set; } } } diff --git a/src/Jackett/Utils/Clients/WebRequest.cs b/src/Jackett/Utils/Clients/WebRequest.cs index 5dd60ea9b2fc533f26a55543d637b46b1cef0ed1..78fc2f4638b816eb99b4f52d553dcdeaac8be85f 100644 --- a/src/Jackett/Utils/Clients/WebRequest.cs +++ b/src/Jackett/Utils/Clients/WebRequest.cs @@ -14,6 +14,13 @@ namespace Jackett.Utils.Clients Type = RequestType.GET; } + public WebRequest(string url) + { + PostData = new Dictionary<string, string>(); + Type = RequestType.GET; + Url = url; + } + public string Url { get; set; } public Dictionary<string, string> PostData { get; set; } public string Cookies { get; set; } diff --git a/src/Jackett/Utils/JsonContent.cs b/src/Jackett/Utils/JsonContent.cs new file mode 100644 index 0000000000000000000000000000000000000000..412962bda23b5dfe6001ef7b7faad48f637b7a37 --- /dev/null +++ b/src/Jackett/Utils/JsonContent.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Utils +{ + public class JsonContent : HttpContent + { + private readonly object _value; + + public JsonContent(object value) + { + _value = value; + Headers.ContentType = new MediaTypeHeaderValue("application/json"); + } + + protected override async Task SerializeToStreamAsync(Stream stream, + TransportContext context) + { + var json = JsonConvert.SerializeObject(_value, Formatting.Indented); + var writer = new StreamWriter(stream); + writer.Write(json); + await writer.FlushAsync(); + } + + protected override bool TryComputeLength(out long length) + { + length = -1; + return false; + } + } +} diff --git a/src/Jackett/Utils/ServerUtil.cs b/src/Jackett/Utils/ServerUtil.cs index 6e3f16d320c82f0a7f554a8185d25a41addf256c..a176e810fd4c720e6dc381051c243b7051a0caa0 100644 --- a/src/Jackett/Utils/ServerUtil.cs +++ b/src/Jackett/Utils/ServerUtil.cs @@ -1,4 +1,5 @@ -using System; +using Jackett.Utils.Clients; +using System; using System.Collections.Generic; using System.Linq; using System.Security.Principal; @@ -98,5 +99,19 @@ namespace Jackett.Utils } return isAdmin; } + + public static void ResureRedirectIsFullyQualified(WebRequest req, BaseWebResult result) + { + if (!string.IsNullOrEmpty(result.RedirectingTo)) + { + var destLower = result.RedirectingTo.ToLowerInvariant(); + if (!destLower.StartsWith("http")) + { + var hostUri = new Uri(req.Url); + var fullUri = new Uri(hostUri, result.RedirectingTo); + result.RedirectingTo = fullUri.ToString(); + } + } + } } } diff --git a/src/Jackett/Utils/StringUtil.cs b/src/Jackett/Utils/StringUtil.cs index 6a40ca5f96858d239f0539fa4a68114ad09164eb..6fa5864e416c51400aa53eb227e2ae1fa55bb632 100644 --- a/src/Jackett/Utils/StringUtil.cs +++ b/src/Jackett/Utils/StringUtil.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Web; namespace Jackett.Utils { @@ -21,5 +24,43 @@ namespace Jackett.Utils return Encoding.UTF8.GetString(Convert.FromBase64String(str)); } + public static string Hash(string input) + { + // Use input string to calculate MD5 hash + MD5 md5 = System.Security.Cryptography.MD5.Create(); + byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input); + byte[] hashBytes = md5.ComputeHash(inputBytes); + + // Convert the byte array to hexadecimal string + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < hashBytes.Length; i++) + { + sb.Append(hashBytes[i].ToString("X2")); + } + return sb.ToString(); + } + + + public static string GetExceptionDetails(this Exception exception) + { + var properties = exception.GetType() + .GetProperties(); + var fields = properties + .Select(property => new { + Name = property.Name, + Value = property.GetValue(exception, null) + }) + .Select(x => String.Format( + "{0} = {1}", + x.Name, + x.Value != null ? x.Value.ToString() : String.Empty + )); + return String.Join("\n", fields); + } + + public static string GetQueryString(this NameValueCollection collection) + { + return string.Join("&", collection.AllKeys.Select(a => a + "=" + HttpUtility.UrlEncode(collection[a]))); + } } } diff --git a/src/Jackett/Utils/TorznabCapsUtil.cs b/src/Jackett/Utils/TorznabCapsUtil.cs index 789d38236baa1abe81ebae9470ef671dcbc44095..35c3b953fd6ec0de34a3558cc8d835f297eda944 100644 --- a/src/Jackett/Utils/TorznabCapsUtil.cs +++ b/src/Jackett/Utils/TorznabCapsUtil.cs @@ -12,16 +12,12 @@ namespace Jackett.Utils public static TorznabCapabilities CreateDefaultTorznabTVCaps() { var caps = new TorznabCapabilities(); - caps.SearchAvailable = true; - caps.TVSearchAvailable = true; - caps.SupportsTVRageSearch = false; - caps.Categories.AddRange(new[] { - new TorznabCategory { ID = "5000", Name = "TV" }, - new TorznabCategory { ID = "5030", Name = "TV/SD" }, - new TorznabCategory { ID = "5040", Name = "TV/HD" } + caps.Categories.AddRange(new[] { + TorznabCatType.TV, + TorznabCatType.TVSD, + TorznabCatType.TVHD }); return caps; } - } } diff --git a/src/Jackett/WebAPIExceptionLogger.cs b/src/Jackett/WebAPIExceptionLogger.cs new file mode 100644 index 0000000000000000000000000000000000000000..240ffda7c3512f80b0ef05a4fd0c36e6d089daa9 --- /dev/null +++ b/src/Jackett/WebAPIExceptionLogger.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http.ExceptionHandling; +using Jackett.Utils; + +namespace Jackett +{ + class WebAPIExceptionLogger : IExceptionLogger + { + public async Task LogAsync(ExceptionLoggerContext context, CancellationToken cancellationToken) + { + // OWIN seems to give lots of these exceptions but we are not interested in them. + if (context.Exception.Message != "Error while copying content to a stream.") + { + Engine.Logger.Error("Unhandled exception: " + context.Exception.GetExceptionDetails()); + var request = await context.Request.Content.ReadAsStringAsync(); + Engine.Logger.Error("Unhandled exception url: " + context.Request.RequestUri); + } + } + } +} diff --git a/src/Jackett/packages.config b/src/Jackett/packages.config index 13069d9740fa82c09c07a04f7e14a45ff111d68f..741a3aaaa78511894fd2e7b99766de8ea3ac44e7 100644 --- a/src/Jackett/packages.config +++ b/src/Jackett/packages.config @@ -1,29 +1,29 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Autofac" version="3.5.2" targetFramework="net451" /> - <package id="Autofac.Owin" version="3.1.0" targetFramework="net451" /> - <package id="Autofac.WebApi" version="3.1.0" targetFramework="net451" /> - <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net451" /> - <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net451" /> - <package id="AutoMapper" version="3.3.1" targetFramework="net451" /> - <package id="CsQuery" version="1.3.4" targetFramework="net451" /> - <package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net451" /> - <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net451" /> - <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net451" /> - <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net451" /> - <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net451" /> - <package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net451" /> - <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net451" /> - <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net451" /> - <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net451" /> - <package id="Microsoft.Owin" version="3.0.1" targetFramework="net451" /> - <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net451" /> - <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net451" /> - <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net451" /> - <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net451" /> - <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net451" /> - <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net451" /> - <package id="NLog" version="4.0.1" targetFramework="net451" /> - <package id="NLog.Windows.Forms" version="2.0.0.0" targetFramework="net451" /> - <package id="Owin" version="1.0" targetFramework="net451" /> + <package id="Autofac" version="3.5.2" targetFramework="net45" /> + <package id="Autofac.Owin" version="3.1.0" targetFramework="net45" /> + <package id="Autofac.WebApi" version="3.1.0" targetFramework="net45" /> + <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net45" /> + <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net45" /> + <package id="AutoMapper" version="3.3.1" targetFramework="net45" /> + <package id="CsQuery" version="1.3.4" targetFramework="net45" /> + <package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net45" /> + <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" /> + <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" /> + <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" /> + <package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net45" /> + <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net45" /> + <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" /> + <package id="NLog" version="4.0.1" targetFramework="net45" /> + <package id="NLog.Windows.Forms" version="2.0.0.0" targetFramework="net45" /> + <package id="Owin" version="1.0" targetFramework="net45" /> </packages> \ No newline at end of file