Compare commits
85 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3fd4b08da2 | ||
![]() |
1a7f3c2e3e | ||
![]() |
f4ad01dda7 | ||
![]() |
f2f50e21fa | ||
![]() |
5f011d3222 | ||
![]() |
757c3ee87a | ||
![]() |
c21c83a13e | ||
![]() |
9595dad645 | ||
![]() |
dd658b2e77 | ||
![]() |
389926d86b | ||
![]() |
5744e6ff08 | ||
![]() |
ba89d25563 | ||
![]() |
6e575a70e3 | ||
![]() |
bd8463387b | ||
![]() |
3b22973b6d | ||
![]() |
3a6899343a | ||
![]() |
56bbee2f1f | ||
![]() |
b58566c782 | ||
![]() |
79ee4b4da0 | ||
![]() |
dd31c249fd | ||
![]() |
c6357bf30f | ||
![]() |
e0e3c12d5e | ||
![]() |
8e0b138086 | ||
![]() |
a6213c1b9a | ||
![]() |
9ffa461ae3 | ||
![]() |
257fbc07b2 | ||
![]() |
8d08279895 | ||
![]() |
ecdd1f655b | ||
![]() |
edb629adb4 | ||
![]() |
43b0403ab4 | ||
![]() |
387f367041 | ||
![]() |
7cf55f41d6 | ||
![]() |
de38afd346 | ||
![]() |
8806995a65 | ||
![]() |
e3766edc56 | ||
![]() |
53482a6d7d | ||
![]() |
b0f4957d1d | ||
![]() |
dd98aa6592 | ||
![]() |
916df7393b | ||
![]() |
29f72a26d2 | ||
![]() |
121f3c0197 | ||
![]() |
8fe9d08f35 | ||
![]() |
1a162bc3fa | ||
![]() |
acf5d90945 | ||
![]() |
51ed4166d8 | ||
![]() |
1c980f7241 | ||
![]() |
8e475e1681 | ||
![]() |
976f42b48c | ||
![]() |
0311c3f9a9 | ||
![]() |
e2daa21914 | ||
![]() |
68c3f6ed4e | ||
![]() |
c9df7bc47a | ||
![]() |
f2749ed311 | ||
![]() |
6a3cface7a | ||
![]() |
9f54f87c62 | ||
![]() |
857a123162 | ||
![]() |
27f7ec61f0 | ||
![]() |
adc06388e2 | ||
![]() |
d898725704 | ||
![]() |
e93e95a940 | ||
![]() |
ba419a1b8e | ||
![]() |
eb4b068d54 | ||
![]() |
489c900e00 | ||
![]() |
7753ffdad4 | ||
![]() |
4aca537a92 | ||
![]() |
c0a7d00ffc | ||
![]() |
68dd53b962 | ||
![]() |
9ba12621a5 | ||
![]() |
1faf5099f5 | ||
![]() |
bc8b1d14d5 | ||
![]() |
f781deb4a9 | ||
![]() |
8d39a4b1a7 | ||
![]() |
edf97f5d05 | ||
![]() |
507ef9424b | ||
![]() |
4cd12332d2 | ||
![]() |
335e16dd3e | ||
![]() |
26f1ba7016 | ||
![]() |
59cf8c3e9f | ||
![]() |
dfd68d16aa | ||
![]() |
9a4c5ffe5c | ||
![]() |
8cf45a5e45 | ||
![]() |
7834411bbd | ||
![]() |
789f22972c | ||
![]() |
bf3759020a | ||
![]() |
0530bed226 |
26
README.md
@@ -1,5 +1,7 @@
|
||||
# Jackett
|
||||
|
||||
Use just about any tracker with Sonarr
|
||||
|
||||
### 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.
|
||||
@@ -10,7 +12,14 @@ Currently [Sonarr](https://sonarr.tv/) is the only software that uses Torznab. [
|
||||
Download in the [Releases page](https://github.com/zone117x/Jackett/releases)
|
||||
|
||||
### Supported Systems
|
||||
Windows. Tested and working on Linux using Mono. Should also work on OSX.
|
||||
* Works on Windows by default
|
||||
* Works on Linux and OS X using Mono. See instructions below...
|
||||
|
||||
### 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`
|
||||
|
||||
|
||||
### Supported Trackers
|
||||
* [BitMeTV](http://www.bitmetv.org/)
|
||||
@@ -18,7 +27,20 @@ Windows. Tested and working on Linux using Mono. Should also work on OSX.
|
||||
* [BIT-HDTV](https://www.bit-hdtv.com)
|
||||
* [Freshon](https://freshon.tv/)
|
||||
* [IPTorrents](https://iptorrents.com/)
|
||||
* [TorrentLeech](http://www.torrentleech.org/)
|
||||
* [Strike](https://getstrike.net/)
|
||||
* [The Pirate Bay](https://thepiratebay.se/)
|
||||
* [RARBG](https://rarbg.com)
|
||||
* [TorrentDay](https://torrentday.eu/)
|
||||
* [TorrentShack](http://torrentshack.me/)
|
||||
* [AlphaRatio](https://alpharatio.cc/)
|
||||
* [AnimeBytes](https://animebytes.tv/)
|
||||
* [SceneAccess](https://sceneaccess.eu/login)
|
||||
* [ShowRSS](https://showrss.info/)
|
||||
* [Torrentz](https://torrentz.eu/)
|
||||
* [HD-Torrents.org](https://hd-torrents.org/)
|
||||
* [SceneTime](https://www.scenetime.com/)
|
||||
* [BeyondHD](https://beyondhd.me/)
|
||||
|
||||
|
||||
### Additional Trackers
|
||||
@@ -29,4 +51,4 @@ I can be contact on IRC at [irc.freenode.net#sonarr](http://webchat.freenode.net
|
||||
|
||||
### Screenshots
|
||||
|
||||

|
||||

|
||||
|
239
src/CurlSharp/Callbacks/CurlEasyCallbacks.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when cURL has debug information for the client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For usage, see the sample <c>Upload.cs</c>.
|
||||
/// Arguments passed to the recipient include:
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Argument</term>
|
||||
/// <description>Description</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>infoType</term>
|
||||
/// <description>
|
||||
/// Type of debug information, see
|
||||
/// <see cref="CurlInfoType" />.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>message</term>
|
||||
/// <description>Debug information as a string.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>extraData</term>
|
||||
/// <description>Client-provided extra data.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public delegate void CurlDebugCallback(CurlInfoType infoType, String message, Object extraData);
|
||||
|
||||
/// <summary>
|
||||
/// Called when cURL has header data for the client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For usage, see the sample <c>Headers.cs</c>.
|
||||
/// Arguments passed to the recipient include:
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Argument</term>
|
||||
/// <description>Description</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>buf</term>
|
||||
/// <description>Header data from cURL to the client.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>size</term>
|
||||
/// <description>Size of a character, in bytes.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>nmemb</term>
|
||||
/// <description>Number of characters.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>extraData</term>
|
||||
/// <description>Client-provided extra data.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// Your implementation should return the number of bytes (not
|
||||
/// characters) processed. Usually this is <c>size * nmemb</c>.
|
||||
/// Return -1 to abort the transfer.
|
||||
/// </remarks>
|
||||
public delegate int CurlHeaderCallback(byte[] buf, int size, int nmemb, Object extraData);
|
||||
|
||||
/// <summary>
|
||||
/// Called when cURL needs for the client to perform an
|
||||
/// IOCTL operation. An example might be when an FTP
|
||||
/// upload requires rewinding of the input file to deal
|
||||
/// with a resend occasioned by an error.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Argument</term>
|
||||
/// <description>Description</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>cmd</term>
|
||||
/// <description>
|
||||
/// A <see cref="CurlIoCommand" />; for now, only
|
||||
/// <c>RestartRead</c> should be passed.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>extraData</term>
|
||||
/// <description>
|
||||
/// Client-provided extra data; in the
|
||||
/// case of an FTP upload, it might be a
|
||||
/// <c>FileStream</c> object.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// Your implementation should return a <see cref="CurlIoError" />,
|
||||
/// which should be <see cref="CurlIoError.Ok" /> if everything
|
||||
/// is okay.
|
||||
/// </remarks>
|
||||
public delegate CurlIoError CurlIoctlCallback(CurlIoCommand cmd, Object extraData);
|
||||
|
||||
/// <summary>
|
||||
/// Called when cURL wants to report progress.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For usage, see the sample <c>Upload.cs</c>.
|
||||
/// Arguments passed to the recipient include:
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Argument</term>
|
||||
/// <description>Description</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>extraData</term>
|
||||
/// <description>Client-provided extra data.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>dlTotal</term>
|
||||
/// <description>Number of bytes to download.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>dlNow</term>
|
||||
/// <description>Number of bytes downloaded so far.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>ulTotal</term>
|
||||
/// <description>Number of bytes to upload.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>ulNow</term>
|
||||
/// <description>Number of bytes uploaded so far.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// Your implementation should return 0 to continue, or a non-zero
|
||||
/// value to abort the transfer.
|
||||
/// </remarks>
|
||||
public delegate int CurlProgressCallback(Object extraData, double dlTotal, double dlNow,
|
||||
double ulTotal, double ulNow);
|
||||
|
||||
/// <summary>
|
||||
/// Called when cURL wants to read data from the client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For usage, see the sample <c>Upload.cs</c>.
|
||||
/// Arguments passed to the recipient include:
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Argument</term>
|
||||
/// <description>Description</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>buf</term>
|
||||
/// <description>
|
||||
/// Buffer into which your client should write data
|
||||
/// for cURL.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>size</term>
|
||||
/// <description>Size of a character, usually 1.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>nmemb</term>
|
||||
/// <description>Number of characters.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>extraData</term>
|
||||
/// <description>Client-provided extra data.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// Your implementation should return the number of bytes (not
|
||||
/// characters) written to <c>buf</c>. Return 0 to abort the transfer.
|
||||
/// </remarks>
|
||||
public delegate int CurlReadCallback([Out] byte[] buf, int size, int nmemb, Object extraData);
|
||||
|
||||
/// <summary>
|
||||
/// Called when cURL wants to report an Ssl event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For usage, see the sample <c>SSLGet.cs</c>.
|
||||
/// Arguments passed to the recipient include:
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Argument</term>
|
||||
/// <description>Description</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>ctx</term>
|
||||
/// <description>
|
||||
/// An <see cref="CurlSslContext" /> object that wraps an
|
||||
/// OpenSSL <c>SSL_CTX</c> pointer.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>extraData</term>
|
||||
/// <description>Client-provided extra data.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// Your implementation should return a <see cref="CurlCode" />,
|
||||
/// which should be <see cref="CurlCode.Ok" /> if everything
|
||||
/// is okay.
|
||||
/// </remarks>
|
||||
public delegate CurlCode CurlSslContextCallback(CurlSslContext ctx, Object extraData);
|
||||
|
||||
/// <summary>
|
||||
/// Called when cURL has data for the client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For usage, see the example <c>EasyGet.cs</c>.
|
||||
/// Arguments passed to the delegate implementation include:
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Argument</term>
|
||||
/// <description>Description</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>buf</term>
|
||||
/// <description>Data cURL is providing to the client.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>size</term>
|
||||
/// <description>Size of a character, usually 1.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>nmemb</term>
|
||||
/// <description>Number of characters.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>extraData</term>
|
||||
/// <description>Client-provided extra data.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// Your implementation should return the number of bytes (not
|
||||
/// characters) processed. Return 0 to abort the transfer.
|
||||
/// </remarks>
|
||||
public delegate int CurlWriteCallback(byte[] buf, int size, int nmemb, Object extraData);
|
||||
}
|
75
src/CurlSharp/Callbacks/CurlShareCallbacks.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when <c>cURL</c> wants to lock a shared resource.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For a usage example, refer to the <c>ShareDemo.cs</c> sample.
|
||||
/// Arguments passed to your delegate implementation include:
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Argument</term>
|
||||
/// <term>Description</term>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>data</term>
|
||||
/// <term>
|
||||
/// Type of data to lock; one of the values in the
|
||||
/// <see cref="CurlLockData" /> enumeration.
|
||||
/// </term>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>access</term>
|
||||
/// <term>
|
||||
/// Lock access requested; one of the values in the
|
||||
/// <see cref="CurlLockAccess" /> enumeration.
|
||||
/// </term>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>userData</term>
|
||||
/// <term>
|
||||
/// Client-provided data that is not touched internally by
|
||||
/// <c>cURL</c>. This is set via
|
||||
/// <see cref="CurlShareOption.UserData" /> when calling the
|
||||
/// <see cref="CurlShare.SetOpt" /> member of the <see cref="CurlShare" />
|
||||
/// class.
|
||||
/// </term>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public delegate void CurlShareLockCallback(CurlLockData data, CurlLockAccess access, Object userData);
|
||||
|
||||
/// <summary>
|
||||
/// Called when <c>cURL</c> wants to unlock a shared resource.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For a usage example, refer to the <c>ShareDemo.cs</c> sample.
|
||||
/// Arguments passed to your delegate implementation include:
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Argument</term>
|
||||
/// <term>Description</term>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>data</term>
|
||||
/// <term>
|
||||
/// Type of data to unlock; one of the values in the
|
||||
/// <see cref="CurlLockData" /> enumeration.
|
||||
/// </term>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>userData</term>
|
||||
/// <term>
|
||||
/// Client-provided data that is not touched internally by
|
||||
/// <c>cURL</c>. This is set via
|
||||
/// <see cref="CurlShareOption.UserData" /> when calling the
|
||||
/// <see cref="CurlShare.SetOpt" /> member of the <see cref="CurlShare" />
|
||||
/// class.
|
||||
/// </term>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public delegate void CurlShareUnlockCallback(CurlLockData data, Object userData);
|
||||
}
|
170
src/CurlSharp/Curl.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
/***************************************************************************
|
||||
*
|
||||
* CurlS#arp
|
||||
*
|
||||
* Copyright (c) 2013 Dr. Masroor Ehsan (masroore@gmail.com)
|
||||
* Portions copyright (c) 2004, 2005 Jeff Phillips (jeff@jeffp.net)
|
||||
*
|
||||
* This software is licensed as described in the file LICENSE, which you
|
||||
* should have received as part of this distribution.
|
||||
*
|
||||
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
* copies of this Software, and permit persons to whom the Software is
|
||||
* furnished to do so, under the terms of the LICENSE file.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
|
||||
* ANY KIND, either express or implied.
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Top-level class for initialization and cleanup.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It also implements static methods for capabilities that don't
|
||||
/// logically belong in a class.
|
||||
/// </remarks>
|
||||
public static class Curl
|
||||
{
|
||||
// for state management
|
||||
private static CurlCode _initCode;
|
||||
|
||||
/// <summary>
|
||||
/// Class constructor - initialize global status.
|
||||
/// </summary>
|
||||
static Curl()
|
||||
{
|
||||
_initCode = CurlCode.FailedInit;
|
||||
}
|
||||
|
||||
// hidden instance stuff
|
||||
|
||||
/// <summary>
|
||||
/// Get the underlying cURL version as a string, example "7.12.2".
|
||||
/// </summary>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// Thrown if cURL isn't properly initialized.
|
||||
/// </exception>
|
||||
public static string Version
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureCurl();
|
||||
return Marshal.PtrToStringAnsi(NativeMethods.curl_version());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process-wide initialization -- call only once per process.
|
||||
/// </summary>
|
||||
/// <param name="flags">
|
||||
/// An or'd combination of
|
||||
/// <see cref="CurlInitFlag" /> members.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A <see cref="CurlCode" />, hopefully
|
||||
/// <c>CurlCode.Ok</c>.
|
||||
/// </returns>
|
||||
public static CurlCode GlobalInit(CurlInitFlag flags)
|
||||
{
|
||||
_initCode = NativeMethods.curl_global_init((int)flags);
|
||||
#if USE_LIBCURLSHIM
|
||||
if (_initCode == CurlCode.Ok)
|
||||
NativeMethods.curl_shim_initialize();
|
||||
#endif
|
||||
return _initCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process-wide cleanup -- call just before exiting process.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// While it's not necessary that your program call this method
|
||||
/// before exiting, doing so will prevent leaks of native cURL resources.
|
||||
/// </remarks>
|
||||
public static void GlobalCleanup()
|
||||
{
|
||||
if (_initCode == CurlCode.Ok)
|
||||
{
|
||||
#if USE_LIBCURLSHIM
|
||||
NativeMethods.curl_shim_cleanup();
|
||||
#endif
|
||||
NativeMethods.curl_global_cleanup();
|
||||
_initCode = CurlCode.FailedInit;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// URL encode a String.
|
||||
/// </summary>
|
||||
/// <param name="url">The string to URL encode.</param>
|
||||
/// <param name="length">
|
||||
/// Input string length;
|
||||
/// use 0 for cURL to determine.
|
||||
/// </param>
|
||||
/// <returns>A new URL encoded string.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// Thrown if cURL isn't properly initialized.
|
||||
/// </exception>
|
||||
public static string Escape(string url, int length)
|
||||
{
|
||||
EnsureCurl();
|
||||
var p = NativeMethods.curl_escape(url, length);
|
||||
var s = Marshal.PtrToStringAnsi(p);
|
||||
NativeMethods.curl_free(p);
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// URL decode a String.
|
||||
/// </summary>
|
||||
/// <param name="url">The string to URL decode.</param>
|
||||
/// <param name="length">
|
||||
/// Input string length;
|
||||
/// use 0 for cURL to determine.
|
||||
/// </param>
|
||||
/// <returns>A new URL decoded string.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// Thrown if cURL isn't properly initialized.
|
||||
/// </exception>
|
||||
public static string Unescape(string url, int length)
|
||||
{
|
||||
EnsureCurl();
|
||||
var p = NativeMethods.curl_unescape(url, length);
|
||||
var s = Marshal.PtrToStringAnsi(p);
|
||||
NativeMethods.curl_free(p);
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="CurlVersionInfoData" /> object.
|
||||
/// </summary>
|
||||
/// <param name="ver">
|
||||
/// Specify a <see cref="CurlVersion" />, such as
|
||||
/// <c>CurlVersion.Now</c>.
|
||||
/// </param>
|
||||
/// <returns>A <see cref="CurlVersionInfoData" /> object.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// Thrown if cURL isn't properly initialized.
|
||||
/// </exception>
|
||||
public static CurlVersionInfoData GetVersionInfo(CurlVersion ver)
|
||||
{
|
||||
EnsureCurl();
|
||||
return new CurlVersionInfoData(ver);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by other classes to ensure valid cURL state.
|
||||
/// </summary>
|
||||
internal static void EnsureCurl()
|
||||
{
|
||||
if (_initCode != CurlCode.Ok)
|
||||
throw new InvalidOperationException("cURL not initialized");
|
||||
}
|
||||
}
|
||||
}
|
2161
src/CurlSharp/CurlEasy.cs
Normal file
388
src/CurlSharp/CurlHttpMultiPartForm.cs
Normal file
@@ -0,0 +1,388 @@
|
||||
/***************************************************************************
|
||||
*
|
||||
* CurlS#arp
|
||||
*
|
||||
* Copyright (c) 2014 Dr. Masroor Ehsan (masroore@gmail.com)
|
||||
* Portions copyright (c) 2004, 2005 Jeff Phillips (jeff@jeffp.net)
|
||||
*
|
||||
* This software is licensed as described in the file LICENSE, which you
|
||||
* should have received as part of this distribution.
|
||||
*
|
||||
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
* copies of this Software, and permit persons to whom the Software is
|
||||
* furnished to do so, under the terms of the LICENSE file.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
|
||||
* ANY KIND, either express or implied.
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This trivial class wraps the internal <c>curl_forms</c> struct.
|
||||
/// </summary>
|
||||
public sealed class CurlForms
|
||||
{
|
||||
/// <summary>The <see cref="CurlFormOption" />.</summary>
|
||||
public CurlFormOption Option;
|
||||
|
||||
/// <summary>Value for the option.</summary>
|
||||
public Object Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps a section of multipart form data to be submitted via the
|
||||
/// <see cref="CurlOption.HttpPost" /> option in the
|
||||
/// <see cref="CurlEasy.SetOpt" /> member of the <see cref="CurlEasy" /> class.
|
||||
/// </summary>
|
||||
public class CurlHttpMultiPartForm : IDisposable
|
||||
{
|
||||
// the two curlform pointers
|
||||
private readonly IntPtr[] _pItems;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// This is thrown
|
||||
/// if <see cref="Curl" /> hasn't bee properly initialized.
|
||||
/// </exception>
|
||||
public CurlHttpMultiPartForm()
|
||||
{
|
||||
Curl.EnsureCurl();
|
||||
_pItems = new IntPtr[2];
|
||||
_pItems[0] = IntPtr.Zero;
|
||||
_pItems[1] = IntPtr.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Free unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructor
|
||||
/// </summary>
|
||||
~CurlHttpMultiPartForm()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
// for CurlEasy.SetOpt()
|
||||
internal IntPtr GetHandle()
|
||||
{
|
||||
return _pItems[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-part form section.
|
||||
/// </summary>
|
||||
/// <param name="args">
|
||||
/// Argument list, as described in the remarks.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A <see cref="CurlFormCode" />, hopefully
|
||||
/// <c>CurlFormCode.Ok</c>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This is definitely the workhorse method for this class. It
|
||||
/// should be called in roughly the same manner as
|
||||
/// <c>curl_formadd()</c>, except you would omit the first two
|
||||
/// <c>struct curl_httppost**</c> arguments (<c>firstitem</c> and
|
||||
/// <c>lastitem</c>), which are wrapped in this class. So you should
|
||||
/// pass arguments in the following sequence:
|
||||
/// <para>
|
||||
/// <c>
|
||||
/// CurlHttpMultiPartForm.AddSection(option1, value1, ..., optionX, valueX,
|
||||
/// CurlFormOption.End)
|
||||
/// </c>
|
||||
/// ;
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// For a complete list of possible options, see the documentation for
|
||||
/// the <see cref="CurlFormOption" /> enumeration.
|
||||
/// </para>
|
||||
/// <note>
|
||||
/// The pointer options (<c>PtrName</c>, etc.) make an
|
||||
/// internal copy of the passed <c>byte</c> array. Therefore, any
|
||||
/// changes you make to the client copy of this array AFTER calling
|
||||
/// this method, won't be reflected internally with <c>cURL</c>. The
|
||||
/// purpose of providing the pointer options is to support the
|
||||
/// posting of non-string binary data.
|
||||
/// </note>
|
||||
/// </remarks>
|
||||
public CurlFormCode AddSection(params object[] args)
|
||||
{
|
||||
var nCount = args.Length;
|
||||
var nRealCount = nCount;
|
||||
var retCode = CurlFormCode.Ok;
|
||||
CurlForms[] aForms = null;
|
||||
|
||||
// one arg or even number of args is an error
|
||||
if ((nCount == 1) || (nCount%2 == 0))
|
||||
return CurlFormCode.Incomplete;
|
||||
|
||||
// ensure the last argument is End
|
||||
var iCode = (CurlFormOption)
|
||||
Convert.ToInt32(args.GetValue(nCount - 1));
|
||||
if (iCode != CurlFormOption.End)
|
||||
return CurlFormCode.Incomplete;
|
||||
|
||||
// walk through any passed arrays to get the true number of
|
||||
// items and ensure the child arrays are properly (and not
|
||||
// prematurely) terminated with End
|
||||
for (var i = 0; i < nCount; i += 2)
|
||||
{
|
||||
iCode = (CurlFormOption) Convert.ToInt32(args.GetValue(i));
|
||||
switch (iCode)
|
||||
{
|
||||
case CurlFormOption.Array:
|
||||
{
|
||||
aForms = args.GetValue(i + 1) as CurlForms[];
|
||||
if (aForms == null)
|
||||
return CurlFormCode.Incomplete;
|
||||
var nFormsCount = aForms.Length;
|
||||
for (var j = 0; j < nFormsCount; j++)
|
||||
{
|
||||
var pcf = aForms.GetValue(j) as CurlForms;
|
||||
if (pcf == null)
|
||||
return CurlFormCode.Incomplete;
|
||||
if (j == nFormsCount - 1)
|
||||
{
|
||||
if (pcf.Option != CurlFormOption.End)
|
||||
return CurlFormCode.Incomplete;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pcf.Option == CurlFormOption.End)
|
||||
return CurlFormCode.Incomplete;
|
||||
}
|
||||
}
|
||||
// -2 accounts for the fact that we're a) not
|
||||
// including the item with End and b) not
|
||||
// including Array in what we pass to cURL
|
||||
nRealCount += 2*(nFormsCount - 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// allocate the IntPtr array for the data
|
||||
var aPointers = new IntPtr[nRealCount];
|
||||
for (var i = 0; i < nRealCount - 1; i++)
|
||||
aPointers[i] = IntPtr.Zero;
|
||||
aPointers[nRealCount - 1] = (IntPtr) CurlFormOption.End;
|
||||
|
||||
// now we go through the args
|
||||
aForms = null;
|
||||
var formArrayPos = 0;
|
||||
var argArrayPos = 0;
|
||||
var ptrArrayPos = 0;
|
||||
Object obj = null;
|
||||
|
||||
while ((retCode == CurlFormCode.Ok) &&
|
||||
(ptrArrayPos < nRealCount))
|
||||
{
|
||||
if (aForms != null)
|
||||
{
|
||||
var pcf = aForms.GetValue(formArrayPos++)
|
||||
as CurlForms;
|
||||
if (pcf == null)
|
||||
{
|
||||
retCode = CurlFormCode.UnknownOption;
|
||||
break;
|
||||
}
|
||||
iCode = pcf.Option;
|
||||
obj = pcf.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
iCode = (CurlFormOption) Convert.ToInt32(
|
||||
args.GetValue(argArrayPos++));
|
||||
obj = (iCode == CurlFormOption.End)
|
||||
? null
|
||||
: args.GetValue(argArrayPos++);
|
||||
}
|
||||
|
||||
switch (iCode)
|
||||
{
|
||||
// handle byte-array pointer-related items
|
||||
case CurlFormOption.PtrName:
|
||||
case CurlFormOption.PtrContents:
|
||||
case CurlFormOption.BufferPtr:
|
||||
{
|
||||
var bytes = obj as byte[];
|
||||
if (bytes == null)
|
||||
retCode = CurlFormCode.UnknownOption;
|
||||
else
|
||||
{
|
||||
var nLen = bytes.Length;
|
||||
var ptr = Marshal.AllocHGlobal(nLen);
|
||||
if (ptr != IntPtr.Zero)
|
||||
{
|
||||
aPointers[ptrArrayPos++] = (IntPtr) iCode;
|
||||
// copy bytes to unmanaged buffer
|
||||
for (var j = 0; j < nLen; j++)
|
||||
Marshal.WriteByte(ptr, bytes[j]);
|
||||
aPointers[ptrArrayPos++] = ptr;
|
||||
}
|
||||
else
|
||||
retCode = CurlFormCode.Memory;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// length values
|
||||
case CurlFormOption.NameLength:
|
||||
case CurlFormOption.ContentsLength:
|
||||
case CurlFormOption.BufferLength:
|
||||
aPointers[ptrArrayPos++] = (IntPtr) iCode;
|
||||
aPointers[ptrArrayPos++] = (IntPtr)
|
||||
Convert.ToInt32(obj);
|
||||
break;
|
||||
|
||||
// strings
|
||||
case CurlFormOption.CopyName:
|
||||
case CurlFormOption.CopyContents:
|
||||
case CurlFormOption.FileContent:
|
||||
case CurlFormOption.File:
|
||||
case CurlFormOption.ContentType:
|
||||
case CurlFormOption.Filename:
|
||||
case CurlFormOption.Buffer:
|
||||
{
|
||||
aPointers[ptrArrayPos++] = (IntPtr) iCode;
|
||||
var s = obj as String;
|
||||
if (s == null)
|
||||
retCode = CurlFormCode.UnknownOption;
|
||||
else
|
||||
{
|
||||
var p = Marshal.StringToHGlobalAnsi(s);
|
||||
if (p != IntPtr.Zero)
|
||||
aPointers[ptrArrayPos++] = p;
|
||||
else
|
||||
retCode = CurlFormCode.Memory;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// array case: already handled
|
||||
case CurlFormOption.Array:
|
||||
if (aForms != null)
|
||||
retCode = CurlFormCode.IllegalArray;
|
||||
else
|
||||
{
|
||||
aForms = obj as CurlForms[];
|
||||
if (aForms == null)
|
||||
retCode = CurlFormCode.UnknownOption;
|
||||
}
|
||||
break;
|
||||
|
||||
// slist
|
||||
case CurlFormOption.ContentHeader:
|
||||
{
|
||||
aPointers[ptrArrayPos++] = (IntPtr) iCode;
|
||||
var s = obj as CurlSlist;
|
||||
if (s == null)
|
||||
retCode = CurlFormCode.UnknownOption;
|
||||
else
|
||||
aPointers[ptrArrayPos++] = s.Handle;
|
||||
break;
|
||||
}
|
||||
|
||||
// erroneous stuff
|
||||
case CurlFormOption.Nothing:
|
||||
retCode = CurlFormCode.Incomplete;
|
||||
break;
|
||||
|
||||
// end
|
||||
case CurlFormOption.End:
|
||||
if (aForms != null) // end of form
|
||||
{
|
||||
aForms = null;
|
||||
formArrayPos = 0;
|
||||
}
|
||||
else
|
||||
aPointers[ptrArrayPos++] = (IntPtr) iCode;
|
||||
break;
|
||||
|
||||
// default is unknown
|
||||
default:
|
||||
retCode = CurlFormCode.UnknownOption;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ensure we didn't come up short on parameters
|
||||
if (ptrArrayPos != nRealCount)
|
||||
retCode = CurlFormCode.Incomplete;
|
||||
|
||||
// if we're OK here, call into curl
|
||||
if (retCode == CurlFormCode.Ok)
|
||||
{
|
||||
#if USE_LIBCURLSHIM
|
||||
retCode = (CurlFormCode) NativeMethods.curl_shim_formadd(_pItems, aPointers, nRealCount);
|
||||
#else
|
||||
retCode = (CurlFormCode) NativeMethods.curl_formadd(ref _pItems[0], ref _pItems[1],
|
||||
(int) aPointers[0], aPointers[1],
|
||||
(int) aPointers[2], aPointers[3],
|
||||
(int) aPointers[4]);
|
||||
#endif
|
||||
}
|
||||
|
||||
// unmarshal native allocations
|
||||
for (var i = 0; i < nRealCount - 1; i += 2)
|
||||
{
|
||||
iCode = (CurlFormOption) (int) aPointers[i];
|
||||
switch (iCode)
|
||||
{
|
||||
case CurlFormOption.CopyName:
|
||||
case CurlFormOption.CopyContents:
|
||||
case CurlFormOption.FileContent:
|
||||
case CurlFormOption.File:
|
||||
case CurlFormOption.ContentType:
|
||||
case CurlFormOption.Filename:
|
||||
case CurlFormOption.Buffer:
|
||||
// byte buffer cases
|
||||
case CurlFormOption.PtrName:
|
||||
case CurlFormOption.PtrContents:
|
||||
case CurlFormOption.BufferPtr:
|
||||
{
|
||||
if (aPointers[i + 1] != IntPtr.Zero)
|
||||
Marshal.FreeHGlobal(aPointers[i + 1]);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return retCode;
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// clean up managed objects
|
||||
}
|
||||
|
||||
// clean up native objects
|
||||
if (_pItems[0] != IntPtr.Zero)
|
||||
NativeMethods.curl_formfree(_pItems[0]);
|
||||
_pItems[0] = IntPtr.Zero;
|
||||
_pItems[1] = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
302
src/CurlSharp/CurlMulti.cs
Normal file
@@ -0,0 +1,302 @@
|
||||
/***************************************************************************
|
||||
*
|
||||
* CurlS#arp
|
||||
*
|
||||
* Copyright (c) 2014 Dr. Masroor Ehsan (masroore@gmail.com)
|
||||
* Portions copyright (c) 2004, 2005 Jeff Phillips (jeff@jeffp.net)
|
||||
*
|
||||
* This software is licensed as described in the file LICENSE, which you
|
||||
* should have received as part of this distribution.
|
||||
*
|
||||
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
* copies of this Software, and permit persons to whom the Software is
|
||||
* furnished to do so, under the terms of the LICENSE file.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
|
||||
* ANY KIND, either express or implied.
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements the <c>curl_multi_xxx</c> API.
|
||||
/// </summary>
|
||||
public class CurlMulti : IDisposable
|
||||
{
|
||||
// private members
|
||||
private readonly Hashtable _htEasy;
|
||||
private int _maxFd;
|
||||
private CurlMultiInfo[] _multiInfo;
|
||||
private bool _bGotMultiInfo;
|
||||
#if USE_LIBCURLSHIM
|
||||
private IntPtr _fdSets;
|
||||
#else
|
||||
private NativeMethods.fd_set _fd_read, _fd_write, _fd_except;
|
||||
#endif
|
||||
private IntPtr _pMulti;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// This is thrown
|
||||
/// if <see cref="Curl" /> hasn't bee properly initialized.
|
||||
/// </exception>
|
||||
/// <exception cref="System.NullReferenceException">
|
||||
/// This is thrown if the native <c>CurlMulti</c> handle wasn't
|
||||
/// created successfully.
|
||||
/// </exception>
|
||||
public CurlMulti()
|
||||
{
|
||||
Curl.EnsureCurl();
|
||||
_pMulti = NativeMethods.curl_multi_init();
|
||||
ensureHandle();
|
||||
_maxFd = 0;
|
||||
#if USE_LIBCURLSHIM
|
||||
_fdSets = IntPtr.Zero;
|
||||
_fdSets = NativeMethods.curl_shim_alloc_fd_sets();
|
||||
#else
|
||||
_fd_read = NativeMethods.fd_set.Create();
|
||||
_fd_read = NativeMethods.fd_set.Create();
|
||||
_fd_write = NativeMethods.fd_set.Create();
|
||||
_fd_except = NativeMethods.fd_set.Create();
|
||||
#endif
|
||||
_multiInfo = null;
|
||||
_bGotMultiInfo = false;
|
||||
_htEasy = new Hashtable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Max file descriptor
|
||||
/// </summary>
|
||||
public int MaxFd
|
||||
{
|
||||
get { return _maxFd; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanup unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructor
|
||||
/// </summary>
|
||||
~CurlMulti()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
// if (disposing) // managed member cleanup
|
||||
// unmanaged cleanup
|
||||
if (_pMulti != IntPtr.Zero)
|
||||
{
|
||||
NativeMethods.curl_multi_cleanup(_pMulti);
|
||||
_pMulti = IntPtr.Zero;
|
||||
}
|
||||
|
||||
#if USE_LIBCURLSHIM
|
||||
if (_fdSets != IntPtr.Zero)
|
||||
{
|
||||
NativeMethods.curl_shim_free_fd_sets(_fdSets);
|
||||
_fdSets = IntPtr.Zero;
|
||||
}
|
||||
#else
|
||||
_fd_read.Cleanup();
|
||||
_fd_write.Cleanup();
|
||||
_fd_except.Cleanup();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureHandle()
|
||||
{
|
||||
if (_pMulti == IntPtr.Zero)
|
||||
throw new NullReferenceException("No internal multi handle");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an CurlEasy object.
|
||||
/// </summary>
|
||||
/// <param name="curlEasy">
|
||||
/// <see cref="CurlEasy" /> object to add.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A <see cref="CurlMultiCode" />, hopefully <c>CurlMultiCode.Ok</c>
|
||||
/// </returns>
|
||||
/// <exception cref="System.NullReferenceException">
|
||||
/// This is thrown if the native <c>CurlMulti</c> handle wasn't
|
||||
/// created successfully.
|
||||
/// </exception>
|
||||
public CurlMultiCode AddHandle(CurlEasy curlEasy)
|
||||
{
|
||||
ensureHandle();
|
||||
var p = curlEasy.Handle;
|
||||
_htEasy.Add(p, curlEasy);
|
||||
return NativeMethods.curl_multi_add_handle(_pMulti, p);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove an CurlEasy object.
|
||||
/// </summary>
|
||||
/// <param name="curlEasy">
|
||||
/// <see cref="CurlEasy" /> object to remove.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A <see cref="CurlMultiCode" />, hopefully <c>CurlMultiCode.Ok</c>
|
||||
/// </returns>
|
||||
/// <exception cref="System.NullReferenceException">
|
||||
/// This is thrown if the native <c>CurlMulti</c> handle wasn't
|
||||
/// created successfully.
|
||||
/// </exception>
|
||||
public CurlMultiCode RemoveHandle(CurlEasy curlEasy)
|
||||
{
|
||||
ensureHandle();
|
||||
var p = curlEasy.Handle;
|
||||
_htEasy.Remove(p);
|
||||
return NativeMethods.curl_multi_remove_handle(_pMulti, curlEasy.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a string description of an error code.
|
||||
/// </summary>
|
||||
/// <param name="errorNum">
|
||||
/// The <see cref="CurlMultiCode" /> for which to obtain the error
|
||||
/// string description.
|
||||
/// </param>
|
||||
/// <returns>The string description.</returns>
|
||||
public String StrError(CurlMultiCode errorNum)
|
||||
{
|
||||
return Marshal.PtrToStringAnsi(NativeMethods.curl_multi_strerror(errorNum));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read/write data to/from each CurlEasy object.
|
||||
/// </summary>
|
||||
/// <param name="runningObjects">
|
||||
/// The number of <see cref="CurlEasy" /> objects still in process is
|
||||
/// written by this function to this reference parameter.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A <see cref="CurlMultiCode" />, hopefully <c>CurlMultiCode.Ok</c>
|
||||
/// </returns>
|
||||
/// <exception cref="System.NullReferenceException">
|
||||
/// This is thrown if the native <c>CurlMulti</c> handle wasn't
|
||||
/// created successfully.
|
||||
/// </exception>
|
||||
public CurlMultiCode Perform(ref int runningObjects)
|
||||
{
|
||||
ensureHandle();
|
||||
return NativeMethods.curl_multi_perform(_pMulti, ref runningObjects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set internal file desriptor information before calling Select.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="CurlMultiCode" />, hopefully <c>CurlMultiCode.Ok</c>
|
||||
/// </returns>
|
||||
/// <exception cref="System.NullReferenceException">
|
||||
/// This is thrown if the native <c>CurlMulti</c> handle wasn't
|
||||
/// created successfully.
|
||||
/// </exception>
|
||||
public CurlMultiCode FdSet()
|
||||
{
|
||||
ensureHandle();
|
||||
#if USE_LIBCURLSHIM
|
||||
return NativeMethods.curl_shim_multi_fdset(_pMulti, _fdSets, ref _maxFd);
|
||||
#else
|
||||
NativeMethods.FD_ZERO(_fd_read);
|
||||
NativeMethods.FD_ZERO(_fd_write);
|
||||
NativeMethods.FD_ZERO(_fd_except);
|
||||
return NativeMethods.curl_multi_fdset(_pMulti, ref _fd_read, ref _fd_write, ref _fd_except, ref _maxFd);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call <c>select()</c> on the CurlEasy objects.
|
||||
/// </summary>
|
||||
/// <param name="timeoutMillis">
|
||||
/// The timeout for the internal <c>select()</c> call,
|
||||
/// in milliseconds.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Number or <see cref="CurlEasy" /> objects with pending reads.
|
||||
/// </returns>
|
||||
/// <exception cref="System.NullReferenceException">
|
||||
/// This is thrown if the native <c>CurlMulti</c> handle wasn't
|
||||
/// created successfully.
|
||||
/// </exception>
|
||||
public int Select(int timeoutMillis)
|
||||
{
|
||||
ensureHandle();
|
||||
#if USE_LIBCURLSHIM
|
||||
return NativeMethods.curl_shim_select(_maxFd + 1, _fdSets, timeoutMillis);
|
||||
#else
|
||||
var timeout = NativeMethods.timeval.Create(timeoutMillis);
|
||||
return NativeMethods.select(_maxFd + 1, ref _fd_read, ref _fd_write, ref _fd_except, ref timeout);
|
||||
//return NativeMethods.select2(_maxFd + 1, _fd_read, _fd_write, _fd_except, timeout);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtain status information for a CurlMulti transfer. Requires
|
||||
/// CurlSharp be compiled with the libcurlshim helper.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An array of <see cref="CurlMultiInfo" /> objects, one for each
|
||||
/// <see cref="CurlEasy" /> object child.
|
||||
/// </returns>
|
||||
/// <exception cref="System.NullReferenceException">
|
||||
/// This is thrown if the native <c>CurlMulti</c> handle wasn't
|
||||
/// created successfully.
|
||||
/// </exception>
|
||||
public CurlMultiInfo[] InfoRead()
|
||||
{
|
||||
if (_bGotMultiInfo)
|
||||
return _multiInfo;
|
||||
|
||||
_bGotMultiInfo = true;
|
||||
|
||||
#if USE_LIBCURLSHIM
|
||||
var nMsgs = 0;
|
||||
var pInfo = NativeMethods.curl_shim_multi_info_read(_pMulti, ref nMsgs);
|
||||
if (pInfo != IntPtr.Zero)
|
||||
{
|
||||
_multiInfo = new CurlMultiInfo[nMsgs];
|
||||
for (var i = 0; i < nMsgs; i++)
|
||||
{
|
||||
var msg = (CurlMessage) Marshal.ReadInt32(pInfo, i*12);
|
||||
var pEasy = Marshal.ReadIntPtr(pInfo, i*12 + 4);
|
||||
var code = (CurlCode) Marshal.ReadInt32(pInfo, i*12 + 8);
|
||||
_multiInfo[i] = new CurlMultiInfo(msg, (CurlEasy) _htEasy[pEasy], code);
|
||||
}
|
||||
NativeMethods.curl_shim_multi_info_free(pInfo);
|
||||
}
|
||||
|
||||
return _multiInfo;
|
||||
|
||||
#else
|
||||
|
||||
throw new NotImplementedException(
|
||||
"Sorry, CurlMulti.InfoRead is not implemented on this system."
|
||||
);
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
}
|
65
src/CurlSharp/CurlMultiInfo.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
/***************************************************************************
|
||||
*
|
||||
* CurlS#arp
|
||||
*
|
||||
* Copyright (c) 2013 Dr. Masroor Ehsan (masroore@gmail.com)
|
||||
* Portions copyright (c) 2004, 2005 Jeff Phillips (jeff@jeffp.net)
|
||||
*
|
||||
* This software is licensed as described in the file LICENSE, which you
|
||||
* should have received as part of this distribution.
|
||||
*
|
||||
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
* copies of this Software, and permit persons to whom the Software is
|
||||
* furnished to do so, under the terms of the LICENSE file.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
|
||||
* ANY KIND, either express or implied.
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps the <c>cURL</c> struct <c>CURLMsg</c>. This class provides
|
||||
/// status information following a <see cref="CurlMulti" /> transfer.
|
||||
/// </summary>
|
||||
public sealed class CurlMultiInfo
|
||||
{
|
||||
// private members
|
||||
private readonly CurlEasy _mCurlEasy;
|
||||
private readonly CurlMessage _msg;
|
||||
private readonly CurlCode _result;
|
||||
|
||||
internal CurlMultiInfo(CurlMessage msg, CurlEasy curlEasy, CurlCode result)
|
||||
{
|
||||
_msg = msg;
|
||||
_mCurlEasy = curlEasy;
|
||||
_result = result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the status code from the <see cref="CurlMessage" /> enumeration.
|
||||
/// </summary>
|
||||
public CurlMessage Msg
|
||||
{
|
||||
get { return _msg; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="CurlEasy" /> object for this child.
|
||||
/// </summary>
|
||||
public CurlEasy CurlEasyHandle
|
||||
{
|
||||
get { return _mCurlEasy; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the return code for the transfer, as a
|
||||
/// <see cref="CurlCode" />.
|
||||
/// </summary>
|
||||
public CurlCode Result
|
||||
{
|
||||
get { return _result; }
|
||||
}
|
||||
}
|
||||
}
|
276
src/CurlSharp/CurlShare.cs
Normal file
@@ -0,0 +1,276 @@
|
||||
/***************************************************************************
|
||||
*
|
||||
* CurlS#arp
|
||||
*
|
||||
* Copyright (c) 2013 Dr. Masroor Ehsan (masroore@gmail.com)
|
||||
* Portions copyright (c) 2004, 2005 Jeff Phillips (jeff@jeffp.net)
|
||||
*
|
||||
* This software is licensed as described in the file LICENSE, which you
|
||||
* should have received as part of this distribution.
|
||||
*
|
||||
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
* copies of this Software, and permit persons to whom the Software is
|
||||
* furnished to do so, under the terms of the LICENSE file.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
|
||||
* ANY KIND, either express or implied.
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This class provides an infrastructure for serializing access to data
|
||||
/// shared by multiple <see cref="CurlEasy" /> objects, including cookie data
|
||||
/// and Dns hosts. It implements the <c>curl_share_xxx</c> API.
|
||||
/// </summary>
|
||||
public class CurlShare : IDisposable
|
||||
{
|
||||
// private members
|
||||
private GCHandle _hThis; // for handle extraction
|
||||
private CurlShareCode _lastErrorCode;
|
||||
private string _lastErrorDescription;
|
||||
#if USE_LIBCURLSHIM
|
||||
private NativeMethods._ShimLockCallback _pDelLock; // lock delegate
|
||||
private NativeMethods._ShimUnlockCallback _pDelUnlock; // unlock delegate
|
||||
#endif
|
||||
private IntPtr _pShare; // share handle
|
||||
private CurlShareLockCallback _pfLock; // client lock delegate
|
||||
private CurlShareUnlockCallback _pfUnlock; // client unlock delegate
|
||||
private IntPtr _ptrThis; // numeric handle
|
||||
private Object _userData; // user data for delegates
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// This is thrown
|
||||
/// if <see cref="Curl" /> hasn't bee properly initialized.
|
||||
/// </exception>
|
||||
/// <exception cref="System.NullReferenceException">
|
||||
/// This is thrown if
|
||||
/// the native <c>share</c> handle wasn't created successfully.
|
||||
/// </exception>
|
||||
public CurlShare()
|
||||
{
|
||||
Curl.EnsureCurl();
|
||||
_pShare = NativeMethods.curl_share_init();
|
||||
EnsureHandle();
|
||||
LockFunction = null;
|
||||
UnlockFunction = null;
|
||||
UserData = null;
|
||||
installDelegates();
|
||||
}
|
||||
|
||||
public object UserData
|
||||
{
|
||||
get { return _userData; }
|
||||
set { _userData = value; }
|
||||
}
|
||||
|
||||
public CurlShareUnlockCallback UnlockFunction
|
||||
{
|
||||
get { return _pfUnlock; }
|
||||
set { _pfUnlock = value; }
|
||||
}
|
||||
|
||||
public CurlShareLockCallback LockFunction
|
||||
{
|
||||
get { return _pfLock; }
|
||||
set { _pfLock = value; }
|
||||
}
|
||||
|
||||
public CurlLockData Share
|
||||
{
|
||||
set { setShareOption(CurlShareOption.Share, value); }
|
||||
}
|
||||
|
||||
public CurlLockData Unshare
|
||||
{
|
||||
set { setShareOption(CurlShareOption.Unshare, value); }
|
||||
}
|
||||
|
||||
public CurlShareCode LastErrorCode
|
||||
{
|
||||
get { return _lastErrorCode; }
|
||||
}
|
||||
|
||||
public string LastErrorDescription
|
||||
{
|
||||
get { return _lastErrorDescription; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanup unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructor
|
||||
/// </summary>
|
||||
~CurlShare()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set options for this object.
|
||||
/// </summary>
|
||||
/// <param name="option">
|
||||
/// One of the values in the <see cref="CurlShareOption" />
|
||||
/// enumeration.
|
||||
/// </param>
|
||||
/// <param name="parameter">
|
||||
/// An appropriate object based on the value passed in the
|
||||
/// <c>option</c> argument. See <see cref="CurlShareOption" />
|
||||
/// for more information about the appropriate parameter type.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A <see cref="CurlShareCode" />, hopefully
|
||||
/// <c>CurlShareCode.Ok</c>.
|
||||
/// </returns>
|
||||
/// <exception cref="System.NullReferenceException">
|
||||
/// This is thrown if
|
||||
/// the native <c>share</c> handle wasn't created successfully.
|
||||
/// </exception>
|
||||
public CurlShareCode SetOpt(CurlShareOption option, Object parameter)
|
||||
{
|
||||
EnsureHandle();
|
||||
var retCode = CurlShareCode.Ok;
|
||||
|
||||
switch (option)
|
||||
{
|
||||
case CurlShareOption.LockFunction:
|
||||
var lf = parameter as CurlShareLockCallback;
|
||||
if (lf == null)
|
||||
return CurlShareCode.BadOption;
|
||||
_pfLock = lf;
|
||||
break;
|
||||
|
||||
case CurlShareOption.UnlockFunction:
|
||||
var ulf = parameter as CurlShareUnlockCallback;
|
||||
if (ulf == null)
|
||||
return CurlShareCode.BadOption;
|
||||
_pfUnlock = ulf;
|
||||
break;
|
||||
|
||||
case CurlShareOption.Share:
|
||||
case CurlShareOption.Unshare:
|
||||
{
|
||||
var opt = (CurlLockData) Convert.ToInt32(parameter);
|
||||
retCode = setShareOption(option, opt);
|
||||
break;
|
||||
}
|
||||
|
||||
case CurlShareOption.UserData:
|
||||
_userData = parameter;
|
||||
break;
|
||||
|
||||
default:
|
||||
retCode = CurlShareCode.BadOption;
|
||||
break;
|
||||
}
|
||||
return retCode;
|
||||
}
|
||||
|
||||
private void setLastError(CurlShareCode code, CurlShareOption opt)
|
||||
{
|
||||
if (_lastErrorCode == CurlShareCode.Ok && code != CurlShareCode.Ok)
|
||||
{
|
||||
_lastErrorCode = code;
|
||||
_lastErrorDescription = string.Format("Error: {0} setting option {1}", StrError(code), opt);
|
||||
}
|
||||
}
|
||||
|
||||
private CurlShareCode setShareOption(CurlShareOption option, CurlLockData value)
|
||||
{
|
||||
var retCode = (value != CurlLockData.Cookie) && (value != CurlLockData.Dns)
|
||||
? CurlShareCode.BadOption
|
||||
: NativeMethods.curl_share_setopt(_pShare, option, (IntPtr) value);
|
||||
setLastError(retCode, option);
|
||||
return retCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a String description of an error code.
|
||||
/// </summary>
|
||||
/// <param name="errorNum">
|
||||
/// The <see cref="CurlShareCode" /> for which to obtain the error
|
||||
/// string description.
|
||||
/// </param>
|
||||
/// <returns>The string description.</returns>
|
||||
public String StrError(CurlShareCode errorNum)
|
||||
{
|
||||
return Marshal.PtrToStringAnsi(NativeMethods.curl_share_strerror(errorNum));
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
// if (disposing) cleanup managed objects
|
||||
if (_pShare != IntPtr.Zero)
|
||||
{
|
||||
#if USE_LIBCURLSHIM
|
||||
NativeMethods.curl_shim_cleanup_share_delegates(_pShare);
|
||||
#endif
|
||||
NativeMethods.curl_share_cleanup(_pShare);
|
||||
_hThis.Free();
|
||||
_ptrThis = IntPtr.Zero;
|
||||
_pShare = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal IntPtr GetHandle()
|
||||
{
|
||||
return _pShare;
|
||||
}
|
||||
|
||||
private void EnsureHandle()
|
||||
{
|
||||
if (_pShare == IntPtr.Zero)
|
||||
throw new NullReferenceException("No internal share handle");
|
||||
}
|
||||
|
||||
private void installDelegates()
|
||||
{
|
||||
_hThis = GCHandle.Alloc(this);
|
||||
_ptrThis = (IntPtr)_hThis;
|
||||
#if USE_LIBCURLSHIM
|
||||
_pDelLock = LockDelegate;
|
||||
_pDelUnlock = UnlockDelegate;
|
||||
NativeMethods.curl_shim_install_share_delegates(_pShare, _ptrThis, _pDelLock, _pDelUnlock);
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void LockDelegate(int data, int access, IntPtr userPtr)
|
||||
{
|
||||
var gch = (GCHandle) userPtr;
|
||||
var share = (CurlShare) gch.Target;
|
||||
if (share == null)
|
||||
return;
|
||||
if (share.LockFunction == null)
|
||||
return;
|
||||
share.LockFunction((CurlLockData) data, (CurlLockAccess) access, share.UserData);
|
||||
}
|
||||
|
||||
internal static void UnlockDelegate(int data, IntPtr userPtr)
|
||||
{
|
||||
var gch = (GCHandle) userPtr;
|
||||
var share = (CurlShare) gch.Target;
|
||||
if (share == null)
|
||||
return;
|
||||
if (share.UnlockFunction == null)
|
||||
return;
|
||||
share.UnlockFunction((CurlLockData) data, share.UserData);
|
||||
}
|
||||
}
|
||||
}
|
97
src/CurlSharp/CurlSharp.csproj
Normal file
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{74420A79-CC16-442C-8B1E-7C1B913844F0}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>CurlSharp</RootNamespace>
|
||||
<AssemblyName>CurlSharp</AssemblyName>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;LINUX</DefineConstants>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefineConstants>LINUX</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Curl.cs" />
|
||||
<Compile Include="CurlEasy.cs" />
|
||||
<Compile Include="CurlHttpMultiPartForm.cs" />
|
||||
<Compile Include="CurlMulti.cs" />
|
||||
<Compile Include="CurlMultiInfo.cs" />
|
||||
<Compile Include="CurlShare.cs" />
|
||||
<Compile Include="CurlSlist.cs" />
|
||||
<Compile Include="CurlSslContext.cs" />
|
||||
<Compile Include="CurlVersionInfoData.cs" />
|
||||
<Compile Include="NativeMethods.cs" />
|
||||
<Compile Include="Enums\CurlClosePolicy.cs" />
|
||||
<Compile Include="Enums\CurlCode.cs" />
|
||||
<Compile Include="Enums\CurlFormCode.cs" />
|
||||
<Compile Include="Enums\CurlFormOption.cs" />
|
||||
<Compile Include="Enums\CurlFtpAuth.cs" />
|
||||
<Compile Include="Enums\CurlFtpSsl.cs" />
|
||||
<Compile Include="Enums\CurlHttpAuth.cs" />
|
||||
<Compile Include="Enums\CurlHttpVersion.cs" />
|
||||
<Compile Include="Enums\CurlInfo.cs" />
|
||||
<Compile Include="Enums\CurlInfoType.cs" />
|
||||
<Compile Include="Enums\CurlInitFlag.cs" />
|
||||
<Compile Include="Enums\CurlIoCommand.cs" />
|
||||
<Compile Include="Enums\CurlIoError.cs" />
|
||||
<Compile Include="Enums\CurlIpResolve.cs" />
|
||||
<Compile Include="Enums\CurlLockAccess.cs" />
|
||||
<Compile Include="Enums\CurlLockData.cs" />
|
||||
<Compile Include="Enums\CurlMessage.cs" />
|
||||
<Compile Include="Enums\CurlMultiCode.cs" />
|
||||
<Compile Include="Enums\CurlNetrcOption.cs" />
|
||||
<Compile Include="Enums\CurlOption.cs" />
|
||||
<Compile Include="Enums\CurlProxyType.cs" />
|
||||
<Compile Include="Enums\CurlShareCode.cs" />
|
||||
<Compile Include="Enums\CurlShareOption.cs" />
|
||||
<Compile Include="Enums\CurlSslVersion.cs" />
|
||||
<Compile Include="Enums\CurlTimeCond.cs" />
|
||||
<Compile Include="Enums\CurlVersion.cs" />
|
||||
<Compile Include="Enums\CurlVersionFeatureBitmask.cs" />
|
||||
<Compile Include="Callbacks\CurlEasyCallbacks.cs" />
|
||||
<Compile Include="Callbacks\CurlShareCallbacks.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.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
<ItemGroup />
|
||||
<ProjectExtensions>
|
||||
<MonoDevelop>
|
||||
<Properties>
|
||||
<Policies>
|
||||
<TextStylePolicy inheritsSet="VisualStudio" inheritsScope="text/plain" scope="text/x-csharp" />
|
||||
<CSharpFormattingPolicy IndentSwitchBody="True" IndentBlocksInsideExpressions="True" AnonymousMethodBraceStyle="NextLine" PropertyBraceStyle="NextLine" PropertyGetBraceStyle="NextLine" PropertySetBraceStyle="NextLine" EventBraceStyle="NextLine" EventAddBraceStyle="NextLine" EventRemoveBraceStyle="NextLine" StatementBraceStyle="NextLine" ElseNewLinePlacement="NewLine" CatchNewLinePlacement="NewLine" FinallyNewLinePlacement="NewLine" WhileNewLinePlacement="DoNotCare" ArrayInitializerWrapping="DoNotChange" ArrayInitializerBraceStyle="NextLine" BeforeMethodDeclarationParentheses="False" BeforeMethodCallParentheses="False" BeforeConstructorDeclarationParentheses="False" NewLineBeforeConstructorInitializerColon="NewLine" NewLineAfterConstructorInitializerColon="SameLine" BeforeDelegateDeclarationParentheses="False" NewParentheses="False" SpacesBeforeBrackets="False" inheritsSet="Mono" inheritsScope="text/x-csharp" scope="text/x-csharp" />
|
||||
</Policies>
|
||||
</Properties>
|
||||
</MonoDevelop>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
144
src/CurlSharp/CurlSlist.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
/***************************************************************************
|
||||
*
|
||||
* CurlS#arp
|
||||
*
|
||||
* Copyright (c) 2013 Dr. Masroor Ehsan (masroore@gmail.com)
|
||||
* Portions copyright (c) 2004, 2005 Jeff Phillips (jeff@jeffp.net)
|
||||
*
|
||||
* This software is licensed as described in the file LICENSE, which you
|
||||
* should have received as part of this distribution.
|
||||
*
|
||||
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
* copies of this Software, and permit persons to whom the Software is
|
||||
* furnished to do so, under the terms of the LICENSE file.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
|
||||
* ANY KIND, either express or implied.
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This class wraps a linked list of strings used in <c>cURL</c>. Use it
|
||||
/// to build string lists where they're required, such as when calling
|
||||
/// <see cref="CurlEasy.SetOpt" /> with <see cref="CurlOption.Quote" />
|
||||
/// as the option.
|
||||
/// </summary>
|
||||
public class CurlSlist : IDisposable
|
||||
{
|
||||
#if !USE_LIBCURLSHIM
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private class curl_slist
|
||||
{
|
||||
/// char*
|
||||
[MarshalAs(UnmanagedType.LPStr)] public string data;
|
||||
|
||||
/// curl_slist*
|
||||
public IntPtr next;
|
||||
}
|
||||
#endif
|
||||
private IntPtr _handle;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// This is thrown
|
||||
/// if <see cref="Curl" /> hasn't bee properly initialized.
|
||||
/// </exception>
|
||||
public CurlSlist()
|
||||
{
|
||||
Curl.EnsureCurl();
|
||||
_handle = IntPtr.Zero;
|
||||
}
|
||||
|
||||
public CurlSlist(IntPtr handle)
|
||||
{
|
||||
_handle = handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read-only copy of the strings stored in the SList
|
||||
/// </summary>
|
||||
public List<string> Strings
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_handle == IntPtr.Zero)
|
||||
return null;
|
||||
var strings = new List<string>();
|
||||
|
||||
#if !USE_LIBCURLSHIM
|
||||
var slist = new curl_slist();
|
||||
Marshal.PtrToStructure(_handle, slist);
|
||||
|
||||
while (true)
|
||||
{
|
||||
strings.Add(slist.data);
|
||||
if (slist.next != IntPtr.Zero)
|
||||
Marshal.PtrToStructure(slist.next, slist);
|
||||
else
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
return strings;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructor
|
||||
/// </summary>
|
||||
~CurlSlist()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Append a string to the list.
|
||||
/// </summary>
|
||||
/// <param name="str">The <c>string</c> to append.</param>
|
||||
public void Append(string str)
|
||||
{
|
||||
#if USE_LIBCURLSHIM
|
||||
_handle = NativeMethods.curl_shim_add_string_to_slist(_handle, str);
|
||||
#else
|
||||
_handle = NativeMethods.curl_slist_append(_handle, str);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Free all internal strings.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
internal IntPtr Handle
|
||||
{
|
||||
get { return _handle; }
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (_handle != IntPtr.Zero)
|
||||
{
|
||||
#if USE_LIBCURLSHIM
|
||||
NativeMethods.curl_shim_free_slist(_handle);
|
||||
#else
|
||||
NativeMethods.curl_slist_free_all(_handle);
|
||||
#endif
|
||||
_handle = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
49
src/CurlSharp/CurlSslContext.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
/***************************************************************************
|
||||
*
|
||||
* CurlS#arp
|
||||
*
|
||||
* Copyright (c) 2013 Dr. Masroor Ehsan (masroore@gmail.com)
|
||||
* Portions copyright (c) 2004, 2005 Jeff Phillips (jeff@jeffp.net)
|
||||
*
|
||||
* This software is licensed as described in the file LICENSE, which you
|
||||
* should have received as part of this distribution.
|
||||
*
|
||||
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
* copies of this Software, and permit persons to whom the Software is
|
||||
* furnished to do so, under the terms of the LICENSE file.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
|
||||
* ANY KIND, either express or implied.
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
using System;
|
||||
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// An instance of this class is passed to the delegate
|
||||
/// <see cref="CurlSslContextCallback" />, if it's implemented.
|
||||
/// Within that delegate, the code will have to make native calls to
|
||||
/// the <c>OpenSSL</c> library with the value returned from the
|
||||
/// <see cref="CurlSslContext.Context" /> property cast to an
|
||||
/// <c>SSL_CTX</c> pointer.
|
||||
/// </summary>
|
||||
public sealed class CurlSslContext
|
||||
{
|
||||
private readonly IntPtr _pvContext;
|
||||
|
||||
internal CurlSslContext(IntPtr pvContext)
|
||||
{
|
||||
_pvContext = pvContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the underlying OpenSSL context.
|
||||
/// </summary>
|
||||
public IntPtr Context
|
||||
{
|
||||
get { return _pvContext; }
|
||||
}
|
||||
}
|
||||
}
|
207
src/CurlSharp/CurlVersionInfoData.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
/***************************************************************************
|
||||
*
|
||||
* CurlS#arp
|
||||
*
|
||||
* Copyright (c) 2013 Dr. Masroor Ehsan (masroore@gmail.com)
|
||||
* Portions copyright (c) 2004, 2005 Jeff Phillips (jeff@jeffp.net)
|
||||
*
|
||||
* This software is licensed as described in the file LICENSE, which you
|
||||
* should have received as part of this distribution.
|
||||
*
|
||||
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
* copies of this Software, and permit persons to whom the Software is
|
||||
* furnished to do so, under the terms of the LICENSE file.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
|
||||
* ANY KIND, either express or implied.
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This class wraps a <c>curl_version_info_data</c> struct. An instance is
|
||||
/// obtained by calling <see cref="Curl.GetVersionInfo" />.
|
||||
/// </summary>
|
||||
public sealed class CurlVersionInfoData
|
||||
{
|
||||
private const int OFFSET_AGE = 0;
|
||||
private const int OFFSET_VERSION = 4;
|
||||
private const int OFFSET_VERSION_NUM = 8;
|
||||
private const int OFFSET_HOST = 12;
|
||||
private const int OFFSET_FEATURES = 16;
|
||||
private const int OFFSET_SSL_VERSION = 20;
|
||||
private const int OFFSET_SSL_VERSION_NUM = 24;
|
||||
private const int OFFSET_LIBZ_VERSION = 28;
|
||||
private const int OFFSET_PROTOCOLS = 32;
|
||||
private const int OFFSET_ARES_VERSION = 36;
|
||||
private const int OFFSET_ARES_VERSION_NUM = 40;
|
||||
private const int OFFSET_LIBIDN_VERSION = 44;
|
||||
|
||||
private readonly IntPtr m_pVersionInfoData;
|
||||
|
||||
internal CurlVersionInfoData(CurlVersion ver)
|
||||
{
|
||||
m_pVersionInfoData = NativeMethods.curl_version_info(ver);
|
||||
}
|
||||
#if USE_LIBCURLSHIM
|
||||
/// <summary>
|
||||
/// Age of this struct, depending on how recent the linked-in
|
||||
/// <c>libcurl</c> is, as a <see cref="CurlVersion" />.
|
||||
/// </summary>
|
||||
public CurlVersion Age
|
||||
{
|
||||
get { return (CurlVersion) NativeMethods.curl_shim_get_version_int_value(m_pVersionInfoData, OFFSET_AGE); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the internal cURL version, as a <c>string</c>.
|
||||
/// </summary>
|
||||
public string Version
|
||||
{
|
||||
get
|
||||
{
|
||||
return Marshal.PtrToStringAnsi(
|
||||
NativeMethods.curl_shim_get_version_char_ptr(m_pVersionInfoData, OFFSET_VERSION));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the internal cURL version number, a A 24-bit number created
|
||||
/// like this: [8 bits major number] | [8 bits minor number] | [8
|
||||
/// bits patch number]. For example, Version 7.12.2 is <c>0x070C02</c>.
|
||||
/// </summary>
|
||||
public int VersionNum
|
||||
{
|
||||
get { return NativeMethods.curl_shim_get_version_int_value(m_pVersionInfoData, OFFSET_VERSION_NUM); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the host information on which the underlying cURL was built.
|
||||
/// </summary>
|
||||
public string Host
|
||||
{
|
||||
get
|
||||
{
|
||||
return
|
||||
Marshal.PtrToStringAnsi(NativeMethods.curl_shim_get_version_char_ptr(m_pVersionInfoData, OFFSET_HOST));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a bitmask of features, containing bits or'd from the
|
||||
/// <see cref="CurlVersionFeatureBitmask" /> enumeration.
|
||||
/// </summary>
|
||||
public int Features
|
||||
{
|
||||
get { return NativeMethods.curl_shim_get_version_int_value(m_pVersionInfoData, OFFSET_FEATURES); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Ssl version, if it's linked in.
|
||||
/// </summary>
|
||||
public string SslVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return
|
||||
Marshal.PtrToStringAnsi(NativeMethods.curl_shim_get_version_char_ptr(m_pVersionInfoData,
|
||||
OFFSET_SSL_VERSION));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Ssl version number, if Ssl is linked in.
|
||||
/// </summary>
|
||||
public int SSLVersionNum
|
||||
{
|
||||
get { return NativeMethods.curl_shim_get_version_int_value(m_pVersionInfoData, OFFSET_SSL_VERSION_NUM); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the libz version, if libz is linked in.
|
||||
/// </summary>
|
||||
public string LibZVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return
|
||||
Marshal.PtrToStringAnsi(NativeMethods.curl_shim_get_version_char_ptr(m_pVersionInfoData,
|
||||
OFFSET_LIBZ_VERSION));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the names of the supported protocols.
|
||||
/// </summary>
|
||||
public string[] Protocols
|
||||
{
|
||||
get
|
||||
{
|
||||
var nProts = NativeMethods.curl_shim_get_number_of_protocols(
|
||||
m_pVersionInfoData, OFFSET_PROTOCOLS);
|
||||
var aProts = new String[nProts];
|
||||
for (var i = 0; i < nProts; i++)
|
||||
{
|
||||
aProts[i] =
|
||||
Marshal.PtrToStringAnsi(NativeMethods.curl_shim_get_protocol_string(m_pVersionInfoData,
|
||||
OFFSET_PROTOCOLS, i));
|
||||
}
|
||||
return aProts;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the ARes version, if ARes is linked in.
|
||||
/// </summary>
|
||||
public string ARes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Age > CurlVersion.First)
|
||||
{
|
||||
return
|
||||
Marshal.PtrToStringAnsi(NativeMethods.curl_shim_get_version_char_ptr(m_pVersionInfoData,
|
||||
OFFSET_ARES_VERSION));
|
||||
}
|
||||
return "n.a.";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the ARes version number, if ARes is linked in.
|
||||
/// </summary>
|
||||
public int AResNum
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Age > CurlVersion.First)
|
||||
{
|
||||
return NativeMethods.curl_shim_get_version_int_value(m_pVersionInfoData, OFFSET_ARES_VERSION_NUM);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the libidn version, if libidn is linked in.
|
||||
/// </summary>
|
||||
public string LibIdn
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Age > CurlVersion.Second)
|
||||
{
|
||||
return
|
||||
Marshal.PtrToStringAnsi(NativeMethods.curl_shim_get_version_char_ptr(m_pVersionInfoData,
|
||||
OFFSET_LIBIDN_VERSION));
|
||||
}
|
||||
return "n.a.";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
46
src/CurlSharp/Enums/CurlClosePolicy.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains values used to specify the order in which cached connections
|
||||
/// are closed. One of these is passed as the
|
||||
/// <see cref="CurlOption.ClosePolicy" /> option in a call
|
||||
/// to <see cref="CurlEasy.SetOpt" />
|
||||
/// </summary>
|
||||
public enum CurlClosePolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// No close policy. Never use this.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Close the oldest cached connections first.
|
||||
/// </summary>
|
||||
Oldest = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Close the least recently used connections first.
|
||||
/// </summary>
|
||||
LeastRecentlyUsed = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Close the connections with the least traffic first.
|
||||
/// </summary>
|
||||
LeastTraffic = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Close the slowest connections first.
|
||||
/// </summary>
|
||||
Slowest = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Currently unimplemented.
|
||||
/// </summary>
|
||||
Callback = 5,
|
||||
|
||||
/// <summary>
|
||||
/// End-of-enumeration marker; do not use in application code.
|
||||
/// </summary>
|
||||
Last = 6
|
||||
};
|
||||
}
|
403
src/CurlSharp/Enums/CurlCode.cs
Normal file
@@ -0,0 +1,403 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Status code returned from <see cref="CurlEasy" /> functions.
|
||||
/// </summary>
|
||||
public enum CurlCode
|
||||
{
|
||||
/// <summary>
|
||||
/// All fine. Proceed as usual.
|
||||
/// </summary>
|
||||
Ok = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Aborted by callback. An internal callback returned "abort"
|
||||
/// to libcurl.
|
||||
/// </summary>
|
||||
AbortedByCallback = 42,
|
||||
|
||||
/// <summary>
|
||||
/// Internal error. A function was called in a bad order.
|
||||
/// </summary>
|
||||
BadCallingOrder = 44,
|
||||
|
||||
/// <summary>
|
||||
/// Unrecognized transfer encoding.
|
||||
/// </summary>
|
||||
BadContentEncoding = 61,
|
||||
|
||||
/// <summary>
|
||||
/// Attempting FTP resume beyond file size.
|
||||
/// </summary>
|
||||
BadDownloadResume = 36,
|
||||
|
||||
/// <summary>
|
||||
/// Internal error. A function was called with a bad parameter.
|
||||
/// </summary>
|
||||
BadFunctionArgument = 43,
|
||||
|
||||
/// <summary>
|
||||
/// Bad password entered. An error was signaled when the password was
|
||||
/// entered. This can also be the result of a "bad password" returned
|
||||
/// from a specified password callback.
|
||||
/// </summary>
|
||||
BadPasswordEntered = 46,
|
||||
|
||||
/// <summary>
|
||||
/// Failed to connect to host or proxy.
|
||||
/// </summary>
|
||||
CouldntConnect = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Couldn't resolve host. The given remote host was not resolved.
|
||||
/// </summary>
|
||||
CouldntResolveHost = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Couldn't resolve proxy. The given proxy host could not be resolved.
|
||||
/// </summary>
|
||||
CouldntResolveProxy = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Very early initialization code failed. This is likely to be an
|
||||
/// internal error or problem.
|
||||
/// </summary>
|
||||
FailedInit = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Maximum file size exceeded.
|
||||
/// </summary>
|
||||
FilesizeExceeded = 63,
|
||||
|
||||
/// <summary>
|
||||
/// A file given with FILE:// couldn't be opened. Most likely
|
||||
/// because the file path doesn't identify an existing file. Did
|
||||
/// you check file permissions?
|
||||
/// </summary>
|
||||
FileCouldntReadFile = 37,
|
||||
|
||||
/// <summary>
|
||||
/// We were denied access when trying to login to an FTP server or
|
||||
/// when trying to change working directory to the one given in the URL.
|
||||
/// </summary>
|
||||
FtpAccessDenied = 9,
|
||||
|
||||
/// <summary>
|
||||
/// An internal failure to lookup the host used for the new
|
||||
/// connection.
|
||||
/// </summary>
|
||||
FtpCantGetHost = 15,
|
||||
|
||||
/// <summary>
|
||||
/// A bad return code on either PASV or EPSV was sent by the FTP
|
||||
/// server, preventing libcurl from being able to continue.
|
||||
/// </summary>
|
||||
FtpCantReconnect = 16,
|
||||
|
||||
/// <summary>
|
||||
/// The FTP SIZE command returned error. SIZE is not a kosher FTP
|
||||
/// command, it is an extension and not all servers support it. This
|
||||
/// is not a surprising error.
|
||||
/// </summary>
|
||||
FtpCouldntGetSize = 32,
|
||||
|
||||
/// <summary>
|
||||
/// This was either a weird reply to a 'RETR' command or a zero byte
|
||||
/// transfer complete.
|
||||
/// </summary>
|
||||
FtpCouldntRetrFile = 19,
|
||||
|
||||
/// <summary>
|
||||
/// libcurl failed to set ASCII transfer type (TYPE A).
|
||||
/// </summary>
|
||||
FtpCouldntSetAscii = 29,
|
||||
|
||||
/// <summary>
|
||||
/// Received an error when trying to set the transfer mode to binary.
|
||||
/// </summary>
|
||||
FtpCouldntSetBinary = 17,
|
||||
|
||||
/// <summary>
|
||||
/// FTP couldn't STOR file. The server denied the STOR operation.
|
||||
/// The error buffer usually contains the server's explanation to this.
|
||||
/// </summary>
|
||||
FtpCouldntStorFile = 25,
|
||||
|
||||
/// <summary>
|
||||
/// The FTP REST command returned error. This should never happen
|
||||
/// if the server is sane.
|
||||
/// </summary>
|
||||
FtpCouldntUseRest = 31,
|
||||
|
||||
/// <summary>
|
||||
/// The FTP PORT command returned error. This mostly happen when
|
||||
/// you haven't specified a good enough address for libcurl to use.
|
||||
/// See <see cref="CurlOption.FtpPort" />.
|
||||
/// </summary>
|
||||
FtpPortFailed = 30,
|
||||
|
||||
/// <summary>
|
||||
/// When sending custom "QUOTE" commands to the remote server, one
|
||||
/// of the commands returned an error code that was 400 or higher.
|
||||
/// </summary>
|
||||
FtpQuoteError = 21,
|
||||
|
||||
/// <summary>
|
||||
/// Requested FTP Ssl level failed.
|
||||
/// </summary>
|
||||
FtpSslFailed = 64,
|
||||
|
||||
/// <summary>
|
||||
/// The FTP server rejected access to the server after the password
|
||||
/// was sent to it. It might be because the username and/or the
|
||||
/// password were incorrect or just that the server is not allowing
|
||||
/// you access for the moment etc.
|
||||
/// </summary>
|
||||
FtpUserPasswordIncorrect = 10,
|
||||
|
||||
/// <summary>
|
||||
/// FTP servers return a 227-line as a response to a PASV command.
|
||||
/// If libcurl fails to parse that line, this return code is
|
||||
/// passed back.
|
||||
/// </summary>
|
||||
FtpWeird227Format = 14,
|
||||
|
||||
/// <summary>
|
||||
/// After having sent the FTP password to the server, libcurl expects
|
||||
/// a proper reply. This error code indicates that an unexpected code
|
||||
/// was returned.
|
||||
/// </summary>
|
||||
FtpWeirdPassReply = 11,
|
||||
|
||||
/// <summary>
|
||||
/// libcurl failed to get a sensible result back from the server as
|
||||
/// a response to either a PASV or a EPSV command. The server is flawed.
|
||||
/// </summary>
|
||||
FtpWeirdPasvReply = 13,
|
||||
|
||||
/// <summary>
|
||||
/// After connecting to an FTP server, libcurl expects to get a
|
||||
/// certain reply back. This error code implies that it got a strange
|
||||
/// or bad reply. The given remote server is probably not an
|
||||
/// OK FTP server.
|
||||
/// </summary>
|
||||
FtpWeirdServerReply = 8,
|
||||
|
||||
/// <summary>
|
||||
/// After having sent user name to the FTP server, libcurl expects a
|
||||
/// proper reply. This error code indicates that an unexpected code
|
||||
/// was returned.
|
||||
/// </summary>
|
||||
FtpWeirdUserReply = 12,
|
||||
|
||||
/// <summary>
|
||||
/// After a completed file transfer, the FTP server did not respond a
|
||||
/// proper "transfer successful" code.
|
||||
/// </summary>
|
||||
FtpWriteError = 20,
|
||||
|
||||
/// <summary>
|
||||
/// Function not found. A required LDAP function was not found.
|
||||
/// </summary>
|
||||
FunctionNotFound = 41,
|
||||
|
||||
/// <summary>
|
||||
/// Nothing was returned from the server, and under the circumstances,
|
||||
/// getting nothing is considered an error.
|
||||
/// </summary>
|
||||
GotNothing = 52,
|
||||
|
||||
/// <summary>
|
||||
/// This is an odd error that mainly occurs due to internal confusion.
|
||||
/// </summary>
|
||||
HttpPostError = 34,
|
||||
|
||||
/// <summary>
|
||||
/// The HTTP server does not support or accept range requests.
|
||||
/// </summary>
|
||||
HttpRangeError = 33,
|
||||
|
||||
/// <summary>
|
||||
/// This is returned if <see cref="CurlOption.FailOnError" />
|
||||
/// is set TRUE and the HTTP server returns an error code that
|
||||
/// is >= 400.
|
||||
/// </summary>
|
||||
HttpReturnedError = 22,
|
||||
|
||||
/// <summary>
|
||||
/// Interface error. A specified outgoing interface could not be
|
||||
/// used. Set which interface to use for outgoing connections'
|
||||
/// source IP address with <see cref="CurlOption.Interface" />.
|
||||
/// </summary>
|
||||
InterfaceFailed = 45,
|
||||
|
||||
/// <summary>
|
||||
/// End-of-enumeration marker; do not use in client applications.
|
||||
/// </summary>
|
||||
Last = 67,
|
||||
|
||||
/// <summary>
|
||||
/// LDAP cannot bind. LDAP bind operation failed.
|
||||
/// </summary>
|
||||
LdapCannotBind = 38,
|
||||
|
||||
/// <summary>
|
||||
/// Invalid LDAP URL.
|
||||
/// </summary>
|
||||
LdapInvalidUrl = 62,
|
||||
|
||||
/// <summary>
|
||||
/// LDAP search failed.
|
||||
/// </summary>
|
||||
LdapSearchFailed = 39,
|
||||
|
||||
/// <summary>
|
||||
/// Library not found. The LDAP library was not found.
|
||||
/// </summary>
|
||||
LibraryNotFound = 40,
|
||||
|
||||
/// <summary>
|
||||
/// Malformat user. User name badly specified. *Not currently used*
|
||||
/// </summary>
|
||||
MalformatUser = 24,
|
||||
|
||||
/// <summary>
|
||||
/// This is not an error. This used to be another error code in an
|
||||
/// old libcurl version and is currently unused.
|
||||
/// </summary>
|
||||
Obsolete = 50,
|
||||
|
||||
/// <summary>
|
||||
/// Operation timeout. The specified time-out period was reached
|
||||
/// according to the conditions.
|
||||
/// </summary>
|
||||
OperationTimeouted = 28,
|
||||
|
||||
/// <summary>
|
||||
/// Out of memory. A memory allocation request failed. This is serious
|
||||
/// badness and things are severely messed up if this ever occurs.
|
||||
/// </summary>
|
||||
OutOfMemory = 27,
|
||||
|
||||
/// <summary>
|
||||
/// A file transfer was shorter or larger than expected. This
|
||||
/// happens when the server first reports an expected transfer size,
|
||||
/// and then delivers data that doesn't match the previously
|
||||
/// given size.
|
||||
/// </summary>
|
||||
PartialFile = 18,
|
||||
|
||||
/// <summary>
|
||||
/// There was a problem reading a local file or an error returned by
|
||||
/// the read callback.
|
||||
/// </summary>
|
||||
ReadError = 26,
|
||||
|
||||
/// <summary>
|
||||
/// Failure with receiving network data.
|
||||
/// </summary>
|
||||
RecvError = 56,
|
||||
|
||||
/// <summary>
|
||||
/// Failed sending network data.
|
||||
/// </summary>
|
||||
SendError = 55,
|
||||
|
||||
/// <summary>
|
||||
/// Sending the data requires a rewind that failed.
|
||||
/// </summary>
|
||||
SendFailRewind = 65,
|
||||
|
||||
/// <summary>
|
||||
/// CurlShare is in use.
|
||||
/// </summary>
|
||||
ShareInUse = 57,
|
||||
|
||||
/// <summary>
|
||||
/// Problem with the CA cert (path? access rights?)
|
||||
/// </summary>
|
||||
SslCaCert = 60,
|
||||
|
||||
/// <summary>
|
||||
/// There's a problem with the local client certificate.
|
||||
/// </summary>
|
||||
SslCertProblem = 58,
|
||||
|
||||
/// <summary>
|
||||
/// Couldn't use specified cipher.
|
||||
/// </summary>
|
||||
SslCipher = 59,
|
||||
|
||||
/// <summary>
|
||||
/// A problem occurred somewhere in the Ssl/TLS handshake. You really
|
||||
/// want to use the <see cref="CurlEasy.CurlDebugCallback" /> delegate and read
|
||||
/// the message there as it pinpoints the problem slightly more. It
|
||||
/// could be certificates (file formats, paths, permissions),
|
||||
/// passwords, and others.
|
||||
/// </summary>
|
||||
SslConnectError = 35,
|
||||
|
||||
/// <summary>
|
||||
/// Failed to initialize Ssl engine.
|
||||
/// </summary>
|
||||
SslEngineInitFailed = 66,
|
||||
|
||||
/// <summary>
|
||||
/// The specified crypto engine wasn't found.
|
||||
/// </summary>
|
||||
SslEngineNotFound = 53,
|
||||
|
||||
/// <summary>
|
||||
/// Failed setting the selected Ssl crypto engine as default!
|
||||
/// </summary>
|
||||
SslEngineSetFailed = 54,
|
||||
|
||||
/// <summary>
|
||||
/// The remote server's Ssl certificate was deemed not OK.
|
||||
/// </summary>
|
||||
SslPeerCertificate = 51,
|
||||
|
||||
/// <summary>
|
||||
/// A telnet option string was improperly formatted.
|
||||
/// </summary>
|
||||
TelnetOptionSyntax = 49,
|
||||
|
||||
/// <summary>
|
||||
/// Too many redirects. When following redirects, libcurl hit the
|
||||
/// maximum amount. Set your limit with
|
||||
/// <see cref="CurlOption.MaxRedirs" />.
|
||||
/// </summary>
|
||||
TooManyRedirects = 47,
|
||||
|
||||
/// <summary>
|
||||
/// An option set with <see cref="CurlOption.TelnetOptions" />
|
||||
/// was not recognized/known. Refer to the appropriate documentation.
|
||||
/// </summary>
|
||||
UnknownTelnetOption = 48,
|
||||
|
||||
/// <summary>
|
||||
/// The URL you passed to libcurl used a protocol that this libcurl
|
||||
/// does not support. The support might be a compile-time option that
|
||||
/// wasn't used, it can be a misspelled protocol string or just a
|
||||
/// protocol libcurl has no code for.
|
||||
/// </summary>
|
||||
UnsupportedProtocol = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The URL was not properly formatted.
|
||||
/// </summary>
|
||||
UrlMalformat = 3,
|
||||
|
||||
/// <summary>
|
||||
/// URL user malformatted. The user-part of the URL syntax was not
|
||||
/// correct.
|
||||
/// </summary>
|
||||
UrlMalformatUser = 4,
|
||||
|
||||
/// <summary>
|
||||
/// An error occurred when writing received data to a local file,
|
||||
/// or an error was returned to libcurl from a write callback.
|
||||
/// </summary>
|
||||
WriteError = 23,
|
||||
};
|
||||
}
|
76
src/CurlSharp/Enums/CurlFormCode.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
/***************************************************************************
|
||||
*
|
||||
* Project: libcurl.NET
|
||||
*
|
||||
* Copyright (c) 2004, 2005 Jeff Phillips (jeff@jeffp.net)
|
||||
*
|
||||
* This software is licensed as described in the file LICENSE, which you
|
||||
* should have received as part of this distribution.
|
||||
*
|
||||
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
* copies of this Software, and permit persons to whom the Software is
|
||||
* furnished to do so, under the terms of the LICENSE file.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
|
||||
* ANY KIND, either express or implied.
|
||||
*
|
||||
* $Id: Enums.cs,v 1.1 2005/02/17 22:47:25 jeffreyphillips Exp $
|
||||
**************************************************************************/
|
||||
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// One of these is returned by <see cref="CurlHttpMultiPartForm.AddSection" />.
|
||||
/// </summary>
|
||||
public enum CurlFormCode
|
||||
{
|
||||
/// <summary>
|
||||
/// The section was added properly.
|
||||
/// </summary>
|
||||
Ok = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Out-of-memory when adding the section.
|
||||
/// </summary>
|
||||
Memory = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Invalid attempt to add the same option more than once to a
|
||||
/// section.
|
||||
/// </summary>
|
||||
OptionTwice = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Invalid attempt to pass a <c>null</c> string or byte array in
|
||||
/// one of the arguments.
|
||||
/// </summary>
|
||||
Null = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Invalid attempt to pass an unrecognized option in one of the
|
||||
/// arguments.
|
||||
/// </summary>
|
||||
UnknownOption = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Incomplete argument lists.
|
||||
/// </summary>
|
||||
Incomplete = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Invalid attempt to provide a nested <c>Array</c>.
|
||||
/// </summary>
|
||||
IllegalArray = 6,
|
||||
|
||||
/// <summary>
|
||||
/// This will not be returned so long as HTTP is enabled, which
|
||||
/// it always is in libcurl.NET.
|
||||
/// </summary>
|
||||
Disabled = 7,
|
||||
|
||||
/// <summary>
|
||||
/// End-of-enumeration marker; do not use in application code.
|
||||
/// </summary>
|
||||
Last = 8
|
||||
};
|
||||
}
|
142
src/CurlSharp/Enums/CurlFormOption.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// These are options available to build a multi-part form section
|
||||
/// in a call to <see cref="CurlHttpMultiPartForm.AddSection" />
|
||||
/// </summary>
|
||||
public enum CurlFormOption
|
||||
{
|
||||
/// <summary>
|
||||
/// Another possibility to send options to
|
||||
/// <see cref="CurlHttpMultiPartForm.AddSection" /> is this option, that
|
||||
/// passes a <see cref="CurlForms" /> array reference as its value.
|
||||
/// Each <see cref="CurlForms" /> array element has a
|
||||
/// <see cref="CurlFormOption" /> and a <c>string</c>. All available
|
||||
/// options can be used in an array, except the <c>Array</c>
|
||||
/// option itself! The last argument in such an array must always be
|
||||
/// <c>End</c>.
|
||||
/// </summary>
|
||||
Array = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Followed by a <c>string</c>, tells libcurl that a buffer is to be
|
||||
/// used to upload data instead of using a file.
|
||||
/// </summary>
|
||||
Buffer = 11,
|
||||
|
||||
/// <summary>
|
||||
/// Followed by an <c>int</c> with the size of the
|
||||
/// <c>BufferPtr</c> byte array, tells libcurl the length of
|
||||
/// the data to upload.
|
||||
/// </summary>
|
||||
BufferLength = 13,
|
||||
|
||||
/// <summary>
|
||||
/// Followed by a <c>byte[]</c> array, tells libcurl the address of
|
||||
/// the buffer containing data to upload (as indicated with
|
||||
/// <c>Buffer</c>). You must also use
|
||||
/// <c>BufferLength</c> to set the length of the buffer area.
|
||||
/// </summary>
|
||||
BufferPtr = 12,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies extra headers for the form POST section. This takes an
|
||||
/// <see cref="CurlSlist" /> prepared in the usual way using
|
||||
/// <see cref="CurlSlist.Append" /> and appends the list of headers to
|
||||
/// those libcurl automatically generates.
|
||||
/// </summary>
|
||||
ContentHeader = 15,
|
||||
|
||||
/// <summary>
|
||||
/// Followed by an <c>int</c> setting the length of the contents.
|
||||
/// </summary>
|
||||
ContentsLength = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Followed by a <c>string</c> with a content-type will make cURL
|
||||
/// use this given content-type for this file upload part, possibly
|
||||
/// instead of an internally chosen one.
|
||||
/// </summary>
|
||||
ContentType = 14,
|
||||
|
||||
/// <summary>
|
||||
/// Followed by a <c>string</c> is used for the contents of this part, the
|
||||
/// actual data to send away. If you'd like it to contain zero bytes,
|
||||
/// you need to set the length of the name with
|
||||
/// <c>ContentsLength</c>.
|
||||
/// </summary>
|
||||
CopyContents = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Followed by a <c>string</c> used to set the name of this part.
|
||||
/// If you'd like it to contain zero bytes, you need to set the
|
||||
/// length of the name with <c>NameLength</c>.
|
||||
/// </summary>
|
||||
CopyName = 1,
|
||||
|
||||
/// <summary>
|
||||
/// This should be the last argument to a call to
|
||||
/// <see cref="CurlHttpMultiPartForm.AddSection" />.
|
||||
/// </summary>
|
||||
End = 17,
|
||||
|
||||
/// <summary>
|
||||
/// Followed by a file name, makes this part a file upload part. It
|
||||
/// sets the file name field to the actual file name used here,
|
||||
/// it gets the contents of the file and passes as data and sets the
|
||||
/// content-type if the given file match one of the new internally
|
||||
/// known file extension. For <c>File</c> the user may send
|
||||
/// one or more files in one part by providing multiple <c>File</c>
|
||||
/// arguments each followed by the filename (and each <c>File</c>
|
||||
/// is allowed to have a <c>ContentType</c>).
|
||||
/// </summary>
|
||||
File = 10,
|
||||
|
||||
/// <summary>
|
||||
/// Followed by a file name, and does the file read: the contents
|
||||
/// will be used in as data in this part.
|
||||
/// </summary>
|
||||
FileContent = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Followed by a <c>string</c> file name, will make libcurl use the
|
||||
/// given name in the file upload part, instead of the actual file
|
||||
/// name given to <c>File</c>.
|
||||
/// </summary>
|
||||
Filename = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Followed by an <c>int</c> setting the length of the name.
|
||||
/// </summary>
|
||||
NameLength = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Not used.
|
||||
/// </summary>
|
||||
Nothing = 0,
|
||||
|
||||
/// <summary>
|
||||
/// No longer used.
|
||||
/// </summary>
|
||||
Obsolete = 9,
|
||||
|
||||
/// <summary>
|
||||
/// No longer used.
|
||||
/// </summary>
|
||||
Obsolete2 = 18,
|
||||
|
||||
/// <summary>
|
||||
/// Followed by a <c>byte[]</c> used for the contents of this part.
|
||||
/// If you'd like it to contain zero bytes, you need to set the
|
||||
/// length of the name with <c>ContentsLength</c>.
|
||||
/// </summary>
|
||||
PtrContents = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Followed by a <c>byte[]</c> used for the name of this part.
|
||||
/// If you'd like it to contain zero bytes, you need to set the
|
||||
/// length of the name with <c>NameLength</c>.
|
||||
/// </summary>
|
||||
PtrName = 2
|
||||
};
|
||||
}
|
31
src/CurlSharp/Enums/CurlFtpAuth.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This enumeration contains values used to specify the FTP Ssl
|
||||
/// authorization level using the
|
||||
/// <see cref="CurlOption.FtpSslAuth" /> option when calling
|
||||
/// <see cref="CurlEasy.SetOpt" />
|
||||
/// </summary>
|
||||
public enum CurlFtpAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Let <c>libcurl</c> decide on the authorization scheme.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Use "AUTH Ssl".
|
||||
/// </summary>
|
||||
SSL = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Use "AUTH TLS".
|
||||
/// </summary>
|
||||
TLS = 2,
|
||||
|
||||
/// <summary>
|
||||
/// End-of-enumeration marker. Do not use in a client application.
|
||||
/// </summary>
|
||||
Last = 3
|
||||
};
|
||||
}
|
37
src/CurlSharp/Enums/CurlFtpSsl.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This enumeration contains values used to specify the FTP Ssl level
|
||||
/// using the <see cref="CurlOption.FtpSsl" /> option when calling
|
||||
/// <see cref="CurlEasy.SetOpt" />
|
||||
/// </summary>
|
||||
public enum CurlFtpSsl
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't attempt to use Ssl.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Try using Ssl, proceed as normal otherwise.
|
||||
/// </summary>
|
||||
Try = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Require Ssl for the control connection or fail with
|
||||
/// <see cref="CurlCode.FtpSslFailed" />.
|
||||
/// </summary>
|
||||
Control = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Require Ssl for all communication or fail with
|
||||
/// <see cref="CurlCode.FtpSslFailed" />.
|
||||
/// </summary>
|
||||
All = 3,
|
||||
|
||||
/// <summary>
|
||||
/// End-of-enumeration marker. Do not use in a client application.
|
||||
/// </summary>
|
||||
Last = 4
|
||||
};
|
||||
}
|
65
src/CurlSharp/Enums/CurlHttpAuth.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This enumeration contains values used to specify the HTTP authentication
|
||||
/// when using the <see cref="CurlOption.HttpAuth" /> option when
|
||||
/// calling <see cref="CurlEasy.SetOpt" />
|
||||
/// </summary>
|
||||
public enum CurlHttpAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// No authentication.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// HTTP Basic authentication. This is the default choice, and the
|
||||
/// only method that is in wide-spread use and supported virtually
|
||||
/// everywhere. This is sending the user name and password over the
|
||||
/// network in plain text, easily captured by others.
|
||||
/// </summary>
|
||||
Basic = 1,
|
||||
|
||||
/// <summary>
|
||||
/// HTTP Digest authentication. Digest authentication is defined
|
||||
/// in RFC2617 and is a more secure way to do authentication over
|
||||
/// public networks than the regular old-fashioned Basic method.
|
||||
/// </summary>
|
||||
Digest = 2,
|
||||
|
||||
/// <summary>
|
||||
/// HTTP GSS-Negotiate authentication. The GSS-Negotiate (also known
|
||||
/// as plain "Negotiate") method was designed by Microsoft and is
|
||||
/// used in their web applications. It is primarily meant as a
|
||||
/// support for Kerberos5 authentication but may be also used along
|
||||
/// with another authentication methods. For more information see IETF
|
||||
/// draft draft-brezak-spnego-http-04.txt.
|
||||
/// <note>
|
||||
/// You need to use a version of libcurl.NET built with a suitable
|
||||
/// GSS-API library for this to work. This is not currently standard.
|
||||
/// </note>
|
||||
/// </summary>
|
||||
GssNegotiate = 4,
|
||||
|
||||
/// <summary>
|
||||
/// HTTP Ntlm authentication. A proprietary protocol invented and
|
||||
/// used by Microsoft. It uses a challenge-response and hash concept
|
||||
/// similar to Digest, to prevent the password from being eavesdropped.
|
||||
/// </summary>
|
||||
Ntlm = 8,
|
||||
|
||||
/// <summary>
|
||||
/// This is a convenience macro that sets all bits and thus makes
|
||||
/// libcurl pick any it finds suitable. libcurl will automatically
|
||||
/// select the one it finds most secure.
|
||||
/// </summary>
|
||||
Any = 15, // ~0
|
||||
|
||||
/// <summary>
|
||||
/// This is a convenience macro that sets all bits except Basic
|
||||
/// and thus makes libcurl pick any it finds suitable. libcurl
|
||||
/// will automatically select the one it finds most secure.
|
||||
/// </summary>
|
||||
AnySafe = 14 // ~Basic
|
||||
};
|
||||
}
|
31
src/CurlSharp/Enums/CurlHttpVersion.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains values used to specify the HTTP version level when using
|
||||
/// the <see cref="CurlOption.HttpVersion" /> option in a call
|
||||
/// to <see cref="CurlEasy.SetOpt" />
|
||||
/// </summary>
|
||||
public enum CurlHttpVersion
|
||||
{
|
||||
/// <summary>
|
||||
/// We don't care about what version the library uses. libcurl will
|
||||
/// use whatever it thinks fit.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Enforce HTTP 1.0 requests.
|
||||
/// </summary>
|
||||
Http1_0 = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Enforce HTTP 1.1 requests.
|
||||
/// </summary>
|
||||
Http1_1 = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Last entry in enumeration; do not use in application code.
|
||||
/// </summary>
|
||||
Last = 3
|
||||
};
|
||||
}
|
222
src/CurlSharp/Enums/CurlInfo.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This enumeration is used to extract information associated with an
|
||||
/// <see cref="CurlEasy" /> transfer. Specifically, a member of this
|
||||
/// enumeration is passed as the first argument to
|
||||
/// CurlEasy.GetInfo specifying the item to retrieve in the
|
||||
/// second argument, which is a reference to an <c>int</c>, a
|
||||
/// <c>double</c>, a <c>string</c>, a <c>DateTime</c> or an <c>object</c>.
|
||||
/// </summary>
|
||||
public enum CurlInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The second argument receives the elapsed time, as a <c>double</c>,
|
||||
/// in seconds, from the start until the connect to the remote host
|
||||
/// (or proxy) was completed.
|
||||
/// </summary>
|
||||
ConnectTime = 0x300005,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives, as a <c>double</c>, the content-length
|
||||
/// of the download. This is the value read from the Content-Length: field.
|
||||
/// </summary>
|
||||
ContentLengthDownload = 0x30000F,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives, as a <c>double</c>, the specified size
|
||||
/// of the upload.
|
||||
/// </summary>
|
||||
ContentLengthUpload = 0x300010,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives, as a <c>string</c>, the content-type of
|
||||
/// the downloaded object. This is the value read from the Content-Type:
|
||||
/// field. If you get <c>null</c>, it means that the server didn't
|
||||
/// send a valid Content-Type header or that the protocol used
|
||||
/// doesn't support this.
|
||||
/// </summary>
|
||||
ContentType = 0x100012,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives, as a <c>string</c>, the last
|
||||
/// used effective URL.
|
||||
/// </summary>
|
||||
EffectiveUrl = 0x100001,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives, as a <c>long</c>, the remote time
|
||||
/// of the retrieved document. You should construct a <c>DateTime</c>
|
||||
/// from this value, as shown in the <c>InfoDemo</c> sample. If you
|
||||
/// get a date in the distant
|
||||
/// past, it can be because of many reasons (unknown, the server
|
||||
/// hides it or the server doesn't support the command that tells
|
||||
/// document time etc) and the time of the document is unknown. Note
|
||||
/// that you must tell the server to collect this information before
|
||||
/// the transfer is made, by using the
|
||||
/// <see cref="CurlOption.Filetime" /> option to
|
||||
/// <see cref="CurlEasy.SetOpt" />. (Added in 7.5)
|
||||
/// </summary>
|
||||
Filetime = 0x20000E,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives an <c>int</c> specifying the total size
|
||||
/// of all the headers received.
|
||||
/// </summary>
|
||||
HeaderSize = 0x20000B,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives, as an <c>int</c>, a bitmask indicating
|
||||
/// the authentication method(s) available. The meaning of the bits is
|
||||
/// explained in the documentation of
|
||||
/// <see cref="CurlOption.HttpAuth" />. (Added in 7.10.8)
|
||||
/// </summary>
|
||||
HttpAuthAvail = 0x200017,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives an <c>int</c> indicating the numeric
|
||||
/// connect code for the HTTP request.
|
||||
/// </summary>
|
||||
HttpConnectCode = 0x200016,
|
||||
|
||||
/// <summary>
|
||||
/// End-of-enumeration marker; do not use in client applications.
|
||||
/// </summary>
|
||||
LastOne = 0x1C,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives, as a <c>double</c>, the time, in
|
||||
/// seconds it took from the start until the name resolving was
|
||||
/// completed.
|
||||
/// </summary>
|
||||
NameLookupTime = 0x300004,
|
||||
|
||||
/// <summary>
|
||||
/// Never used.
|
||||
/// </summary>
|
||||
None = 0x0,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives an <c>int</c> indicating the
|
||||
/// number of current connections. (Added in 7.13.0)
|
||||
/// </summary>
|
||||
NumConnects = 0x20001A,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives an <c>int</c> indicating the operating
|
||||
/// system error number: <c>_errro</c> or <c>GetLastError()</c>,
|
||||
/// depending on the platform. (Added in 7.12.2)
|
||||
/// </summary>
|
||||
OsErrno = 0x200019,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives, as a <c>double</c>, the time, in
|
||||
/// seconds, it took from the start until the file transfer is just about
|
||||
/// to begin. This includes all pre-transfer commands and negotiations
|
||||
/// that are specific to the particular protocol(s) involved.
|
||||
/// </summary>
|
||||
PreTransferTime = 0x300006,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives a reference to the private data
|
||||
/// associated with the <see cref="CurlEasy" /> object (set with the
|
||||
/// <see cref="CurlOption.Private" /> option to
|
||||
/// <see cref="CurlEasy.SetOpt" />. (Added in 7.10.3)
|
||||
/// </summary>
|
||||
Private = 0x100015,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives, as an <c>int</c>, a bitmask
|
||||
/// indicating the authentication method(s) available for your
|
||||
/// proxy authentication. This will be a bitmask of
|
||||
/// <see cref="CurlHttpAuth" /> enumeration constants.
|
||||
/// (Added in 7.10.8)
|
||||
/// </summary>
|
||||
ProxyAuthAvail = 0x200018,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives an <c>int</c> indicating the total
|
||||
/// number of redirections that were actually followed. (Added in 7.9.7)
|
||||
/// </summary>
|
||||
RedirectCount = 0x200014,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives, as a <c>double</c>, the total time, in
|
||||
/// seconds, for all redirection steps include name lookup, connect,
|
||||
/// pretransfer and transfer before final transaction was started.
|
||||
/// <c>RedirectTime</c> contains the complete execution
|
||||
/// time for multiple redirections. (Added in 7.9.7)
|
||||
/// </summary>
|
||||
RedirectTime = 0x300013,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives an <c>int</c> containing the total size
|
||||
/// of the issued requests. This is so far only for HTTP requests. Note
|
||||
/// that this may be more than one request if
|
||||
/// <see cref="CurlOption.FollowLocation" /> is <c>true</c>.
|
||||
/// </summary>
|
||||
RequestSize = 0x20000C,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives an <c>int</c> with the last received HTTP
|
||||
/// or FTP code. This option was known as <c>CURLINFO_HTTP_CODE</c> in
|
||||
/// libcurl 7.10.7 and earlier.
|
||||
/// </summary>
|
||||
ResponseCode = 0x200002,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives a <c>double</c> with the total amount of
|
||||
/// bytes that were downloaded. The amount is only for the latest transfer
|
||||
/// and will be reset again for each new transfer.
|
||||
/// </summary>
|
||||
SizeDownload = 0x300008,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives a <c>double</c> with the total amount
|
||||
/// of bytes that were uploaded.
|
||||
/// </summary>
|
||||
SizeUpload = 0x300007,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives a <c>double</c> with the average
|
||||
/// download speed that cURL measured for the complete download.
|
||||
/// </summary>
|
||||
SpeedDownload = 0x300009,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives a <c>double</c> with the average
|
||||
/// upload speed that libcurl measured for the complete upload.
|
||||
/// </summary>
|
||||
SpeedUpload = 0x30000A,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives an <see cref="CurlSlist" /> containing
|
||||
/// the names of the available Ssl engines.
|
||||
/// </summary>
|
||||
SslEngines = 0x40001B,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives an <c>int</c> with the result of
|
||||
/// the certificate verification that was requested (using the
|
||||
/// <see cref="CurlOption.SslVerifyPeer" /> option in
|
||||
/// <see cref="CurlEasy.SetOpt" />.
|
||||
/// </summary>
|
||||
SslVerifyResult = 0x20000D,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives a <c>double</c> specifying the time,
|
||||
/// in seconds, from the start until the first byte is just about to be
|
||||
/// transferred. This includes <c>PreTransferTime</c> and
|
||||
/// also the time the server needs to calculate the result.
|
||||
/// </summary>
|
||||
StartTransferTime = 0x300011,
|
||||
|
||||
/// <summary>
|
||||
/// The second argument receives a <c>double</c> indicating the total transaction
|
||||
/// time in seconds for the previous transfer. This time does not include
|
||||
/// the connect time, so if you want the complete operation time,
|
||||
/// you should add the <c>ConnectTime</c>.
|
||||
/// </summary>
|
||||
TotalTime = 0x300003,
|
||||
};
|
||||
}
|
50
src/CurlSharp/Enums/CurlInfoType.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// A member of this enumeration is passed as the first parameter to the
|
||||
/// <see cref="CurlEasy.CurlDebugCallback" /> delegate to which libcurl passes
|
||||
/// debug messages.
|
||||
/// </summary>
|
||||
public enum CurlInfoType
|
||||
{
|
||||
/// <summary>
|
||||
/// The data is informational text.
|
||||
/// </summary>
|
||||
Text = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The data is header (or header-like) data received from the peer.
|
||||
/// </summary>
|
||||
HeaderIn = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The data is header (or header-like) data sent to the peer.
|
||||
/// </summary>
|
||||
HeaderOut = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The data is protocol data received from the peer.
|
||||
/// </summary>
|
||||
DataIn = 3,
|
||||
|
||||
/// <summary>
|
||||
/// The data is protocol data sent to the peer.
|
||||
/// </summary>
|
||||
DataOut = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The data is Ssl-related data sent to the peer.
|
||||
/// </summary>
|
||||
SslDataIn = 5,
|
||||
|
||||
/// <summary>
|
||||
/// The data is Ssl-related data received from the peer.
|
||||
/// </summary>
|
||||
SslDataOut = 6,
|
||||
|
||||
/// <summary>
|
||||
/// End of enumeration marker, don't use in a client application.
|
||||
/// </summary>
|
||||
End = 7
|
||||
};
|
||||
}
|
34
src/CurlSharp/Enums/CurlInitFlag.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains values used to initialize libcurl internally. One of
|
||||
/// these is passed in the call to <see cref="Curl.GlobalInit" />.
|
||||
/// </summary>
|
||||
public enum CurlInitFlag
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialise nothing extra. This sets no bit.
|
||||
/// </summary>
|
||||
Nothing = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Initialize Ssl.
|
||||
/// </summary>
|
||||
Ssl = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the Win32 socket libraries.
|
||||
/// </summary>
|
||||
Win32 = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Initialize everything possible. This sets all known bits.
|
||||
/// </summary>
|
||||
All = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Equivalent to <c>All</c>.
|
||||
/// </summary>
|
||||
Default = All
|
||||
};
|
||||
}
|
27
src/CurlSharp/Enums/CurlIoCommand.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Your handler for the <see cref="CurlEasy.CurlIoctlCallback" />
|
||||
/// delegate is passed one of these values as its first parameter.
|
||||
/// Right now, the only supported value is
|
||||
/// <code>RestartRead</code>.
|
||||
/// </summary>
|
||||
public enum CurlIoCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// No IOCTL operation; we should never see this.
|
||||
/// </summary>
|
||||
Nop = 0,
|
||||
|
||||
/// <summary>
|
||||
/// When this is sent, your callback may need to, for example,
|
||||
/// rewind a local file that is being sent via FTP.
|
||||
/// </summary>
|
||||
RestartRead = 1,
|
||||
|
||||
/// <summary>
|
||||
/// End of enumeration marker, don't use in a client application.
|
||||
/// </summary>
|
||||
Last = 2
|
||||
}
|
||||
}
|
30
src/CurlSharp/Enums/CurlIoError.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Your handler for the <see cref="CurlEasy.CurlIoctlCallback" /> delegate
|
||||
/// should return a member of this enumeration.
|
||||
/// </summary>
|
||||
public enum CurlIoError
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicate that the callback processed everything okay.
|
||||
/// </summary>
|
||||
Ok = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Unknown command sent to callback. Right now, only
|
||||
/// <code>RestartRead</code> is supported.
|
||||
/// </summary>
|
||||
UnknownCommand = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Indicate to libcurl that a restart failed.
|
||||
/// </summary>
|
||||
FailRestart = 2,
|
||||
|
||||
/// <summary>
|
||||
/// End of enumeration marker, don't use in a client application.
|
||||
/// </summary>
|
||||
Last = 3
|
||||
}
|
||||
}
|
26
src/CurlSharp/Enums/CurlIpResolve.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This enumeration contains values used to specify the IP resolution
|
||||
/// method when using the <see cref="CurlOption.IpResolve" />
|
||||
/// option in a call to <see cref="CurlEasy.SetOpt" />
|
||||
/// </summary>
|
||||
public enum CurlIpResolve
|
||||
{
|
||||
/// <summary>
|
||||
/// Default, resolves addresses to all IP versions that your system
|
||||
/// allows.
|
||||
/// </summary>
|
||||
Whatever = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Resolve to ipv4 addresses.
|
||||
/// </summary>
|
||||
V4 = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Resolve to ipv6 addresses.
|
||||
/// </summary>
|
||||
V6 = 2
|
||||
};
|
||||
}
|
31
src/CurlSharp/Enums/CurlLockAccess.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Values containing the type of shared access requested when libcurl
|
||||
/// calls the <see cref="CurlShare.CurlShareLockCallback" /> delegate.
|
||||
/// </summary>
|
||||
public enum CurlLockAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Unspecified action; the delegate should never receive this.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The delegate receives this call when libcurl is requesting
|
||||
/// read access to the shared resource.
|
||||
/// </summary>
|
||||
Shared = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The delegate receives this call when libcurl is requesting
|
||||
/// write access to the shared resource.
|
||||
/// </summary>
|
||||
Single = 2,
|
||||
|
||||
/// <summary>
|
||||
/// End-of-enumeration marker; do not use in application code.
|
||||
/// </summary>
|
||||
Last = 3
|
||||
};
|
||||
}
|
48
src/CurlSharp/Enums/CurlLockData.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Members of this enumeration should be passed to
|
||||
/// <see cref="CurlShare.SetOpt" /> when it is called with the
|
||||
/// <c>CurlShare</c> or <c>Unshare</c> options
|
||||
/// provided in the <see cref="CurlShareOption" /> enumeration.
|
||||
/// </summary>
|
||||
public enum CurlLockData
|
||||
{
|
||||
/// <summary>
|
||||
/// Not used.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Used internally by libcurl.
|
||||
/// </summary>
|
||||
Share = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Cookie data will be shared across the <see cref="CurlEasy" /> objects
|
||||
/// using this shared object.
|
||||
/// </summary>
|
||||
Cookie = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Cached Dns hosts will be shared across the <see cref="CurlEasy" />
|
||||
/// objects using this shared object.
|
||||
/// </summary>
|
||||
Dns = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Not supported yet.
|
||||
/// </summary>
|
||||
SslSession = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Not supported yet.
|
||||
/// </summary>
|
||||
Connect = 5,
|
||||
|
||||
/// <summary>
|
||||
/// End-of-enumeration marker; do not use in application code.
|
||||
/// </summary>
|
||||
Last = 6
|
||||
};
|
||||
}
|
25
src/CurlSharp/Enums/CurlMessage.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// The status code associated with an <see cref="CurlEasy" /> object in a
|
||||
/// <see cref="CurlMulti" /> operation. One of these is returned in response
|
||||
/// to reading the <see cref="CurlMultiInfo.Msg" /> property.
|
||||
/// </summary>
|
||||
public enum CurlMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// First entry in the enumeration, not used.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The associated <see cref="CurlEasy" /> object completed.
|
||||
/// </summary>
|
||||
Done = 1,
|
||||
|
||||
/// <summary>
|
||||
/// End-of-enumeration marker, not used.
|
||||
/// </summary>
|
||||
Last = 2
|
||||
};
|
||||
}
|
46
src/CurlSharp/Enums/CurlMultiCode.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains return codes for many of the functions in the
|
||||
/// <see cref="CurlMulti" /> class.
|
||||
/// </summary>
|
||||
public enum CurlMultiCode
|
||||
{
|
||||
/// <summary>
|
||||
/// You should call <see cref="CurlMulti.Perform" /> again before calling
|
||||
/// <see cref="CurlMulti.Select" />.
|
||||
/// </summary>
|
||||
CallMultiPerform = -1,
|
||||
|
||||
/// <summary>
|
||||
/// The function succeded.
|
||||
/// </summary>
|
||||
Ok = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The internal <see cref="CurlMulti" /> is bad.
|
||||
/// </summary>
|
||||
BadHandle = 1,
|
||||
|
||||
/// <summary>
|
||||
/// One of the <see cref="CurlEasy" /> handles associated with the
|
||||
/// <see cref="CurlMulti" /> object is bad.
|
||||
/// </summary>
|
||||
BadEasyHandle = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Out of memory. This is a severe problem.
|
||||
/// </summary>
|
||||
OutOfMemory = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Internal error deep within the libcurl library.
|
||||
/// </summary>
|
||||
InternalError = 4,
|
||||
|
||||
/// <summary>
|
||||
/// End-of-enumeration marker, not used.
|
||||
/// </summary>
|
||||
Last = 5
|
||||
};
|
||||
}
|
43
src/CurlSharp/Enums/CurlNetrcOption.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains values used to specify the preference of libcurl between
|
||||
/// using user names and passwords from your ~/.netrc file, relative to
|
||||
/// user names and passwords in the URL supplied with
|
||||
/// <see cref="CurlOption.Url" />. This is passed when using
|
||||
/// the <see cref="CurlOption.Netrc" /> option in a call
|
||||
/// to <see cref="CurlEasy.SetOpt" />
|
||||
/// </summary>
|
||||
public enum CurlNetrcOption
|
||||
{
|
||||
/// <summary>
|
||||
/// The library will ignore the file and use only the information
|
||||
/// in the URL. This is the default.
|
||||
/// </summary>
|
||||
Ignored = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The use of your ~/.netrc file is optional, and information in the
|
||||
/// URL is to be preferred. The file will be scanned with the host
|
||||
/// and user name (to find the password only) or with the host only,
|
||||
/// to find the first user name and password after that machine,
|
||||
/// which ever information is not specified in the URL.
|
||||
/// <para>
|
||||
/// Undefined values of the option will have this effect.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
Optional = 1,
|
||||
|
||||
/// <summary>
|
||||
/// This value tells the library that use of the file is required,
|
||||
/// to ignore the information in the URL, and to search the file
|
||||
/// with the host only.
|
||||
/// </summary>
|
||||
Required = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Last entry in enumeration; do not use in application code.
|
||||
/// </summary>
|
||||
Last = 3
|
||||
};
|
||||
}
|
1603
src/CurlSharp/Enums/CurlOption.cs
Normal file
27
src/CurlSharp/Enums/CurlProxyType.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This enumeration contains values used to specify the proxy type when
|
||||
/// using the <see cref="CurlOption.Proxy" /> option when calling
|
||||
/// <see cref="CurlEasy.SetOpt" />
|
||||
/// </summary>
|
||||
public enum CurlProxyType
|
||||
{
|
||||
/// <summary>
|
||||
/// Ordinary HTTP proxy.
|
||||
/// </summary>
|
||||
Http = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Use if the proxy supports SOCKS4 user authentication. If you're
|
||||
/// unfamiliar with this, consult your network administrator.
|
||||
/// </summary>
|
||||
Socks4 = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Use if the proxy supports SOCKS5 user authentication. If you're
|
||||
/// unfamiliar with this, consult your network administrator.
|
||||
/// </summary>
|
||||
Socks5 = 5
|
||||
};
|
||||
}
|
40
src/CurlSharp/Enums/CurlShareCode.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains return codes from many of the functions in the
|
||||
/// <see cref="CurlShare" /> class.
|
||||
/// </summary>
|
||||
public enum CurlShareCode
|
||||
{
|
||||
/// <summary>
|
||||
/// The function succeeded.
|
||||
/// </summary>
|
||||
Ok = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A bad option was passed to <see cref="CurlShare.SetOpt" />.
|
||||
/// </summary>
|
||||
BadOption = 1,
|
||||
|
||||
/// <summary>
|
||||
/// An attempt was made to pass an option to
|
||||
/// <see cref="CurlShare.SetOpt" /> while the CurlShare object is in use.
|
||||
/// </summary>
|
||||
InUse = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CurlShare" /> object's internal handle is invalid.
|
||||
/// </summary>
|
||||
Invalid = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Out of memory. This is a severe problem.
|
||||
/// </summary>
|
||||
NoMem = 4,
|
||||
|
||||
/// <summary>
|
||||
/// End-of-enumeration marker; do not use in application code.
|
||||
/// </summary>
|
||||
Last = 5
|
||||
};
|
||||
}
|
53
src/CurlSharp/Enums/CurlShareOption.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// A member of this enumeration is passed to the function
|
||||
/// <see cref="CurlShare.SetOpt" /> to configure a <see cref="CurlShare" />
|
||||
/// transfer.
|
||||
/// </summary>
|
||||
public enum CurlShareOption
|
||||
{
|
||||
/// <summary>
|
||||
/// Start-of-enumeration; do not use in application code.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The parameter, which should be a member of the
|
||||
/// <see cref="CurlLockData" /> enumeration, specifies a type of
|
||||
/// data that should be shared.
|
||||
/// </summary>
|
||||
Share = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The parameter, which should be a member of the
|
||||
/// <see cref="CurlLockData" /> enumeration, specifies a type of
|
||||
/// data that should be unshared.
|
||||
/// </summary>
|
||||
Unshare = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The parameter should be a reference to a
|
||||
/// <see cref="CurlShare.CurlShareLockCallback" /> delegate.
|
||||
/// </summary>
|
||||
LockFunction = 3,
|
||||
|
||||
/// <summary>
|
||||
/// The parameter should be a reference to a
|
||||
/// <see cref="CurlShare.CurlShareUnlockCallback" /> delegate.
|
||||
/// </summary>
|
||||
UnlockFunction = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The parameter allows you to specify an object reference that
|
||||
/// will passed to the <see cref="CurlShare.CurlShareLockCallback" /> delegate and
|
||||
/// the <see cref="CurlShare.CurlShareUnlockCallback" /> delegate.
|
||||
/// </summary>
|
||||
UserData = 5,
|
||||
|
||||
/// <summary>
|
||||
/// End-of-enumeration; do not use in application code.
|
||||
/// </summary>
|
||||
Last = 6
|
||||
};
|
||||
}
|
36
src/CurlSharp/Enums/CurlSslVersion.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains values used to specify the Ssl version level when using
|
||||
/// the <see cref="CurlOption.SslVersion" /> option in a call
|
||||
/// to <see cref="CurlEasy.SetOpt" />
|
||||
/// </summary>
|
||||
public enum CurlSslVersion
|
||||
{
|
||||
/// <summary>
|
||||
/// Use whatever version the Ssl library selects.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Use TLS version 1.
|
||||
/// </summary>
|
||||
Tlsv1 = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Use Ssl version 2. This is not a good option unless it's the
|
||||
/// only version supported by the remote server.
|
||||
/// </summary>
|
||||
Sslv2 = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Use Ssl version 3. This is a preferred option.
|
||||
/// </summary>
|
||||
Sslv3 = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Last entry in enumeration; do not use in application code.
|
||||
/// </summary>
|
||||
Last = 4
|
||||
};
|
||||
}
|
39
src/CurlSharp/Enums/CurlTimeCond.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains values used to specify the time condition when using
|
||||
/// the <see cref="CurlOption.TimeCondition" /> option in a call
|
||||
/// to <see cref="CurlEasy.SetOpt" />
|
||||
/// </summary>
|
||||
public enum CurlTimeCond
|
||||
{
|
||||
/// <summary>
|
||||
/// Use no time condition.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The time condition is true if the resource has been modified
|
||||
/// since the date/time passed in
|
||||
/// <see cref="CurlOption.TimeValue" />.
|
||||
/// </summary>
|
||||
IfModSince = 1,
|
||||
|
||||
/// <summary>
|
||||
/// True if the resource has not been modified since the date/time
|
||||
/// passed in <see cref="CurlOption.TimeValue" />.
|
||||
/// </summary>
|
||||
IfUnmodSince = 2,
|
||||
|
||||
/// <summary>
|
||||
/// True if the resource's last modification date/time equals that
|
||||
/// passed in <see cref="CurlOption.TimeValue" />.
|
||||
/// </summary>
|
||||
LastMod = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Last entry in enumeration; do not use in application code.
|
||||
/// </summary>
|
||||
Last = 4
|
||||
};
|
||||
}
|
34
src/CurlSharp/Enums/CurlVersion.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// A member of this enumeration is passed to the function
|
||||
/// <see cref="Curl.GetVersionInfo" />
|
||||
/// </summary>
|
||||
public enum CurlVersion
|
||||
{
|
||||
/// <summary>
|
||||
/// Capabilities associated with the initial version of libcurl.
|
||||
/// </summary>
|
||||
First = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Capabilities associated with the second version of libcurl.
|
||||
/// </summary>
|
||||
Second = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Capabilities associated with the third version of libcurl.
|
||||
/// </summary>
|
||||
Third = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Same as <c>Third</c>.
|
||||
/// </summary>
|
||||
Now = Third,
|
||||
|
||||
/// <summary>
|
||||
/// End-of-enumeration marker; do not use in application code.
|
||||
/// </summary>
|
||||
Last = 3
|
||||
};
|
||||
}
|
71
src/CurlSharp/Enums/CurlVersionFeatureBitmask.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// A bitmask of libcurl features OR'd together as the value of the
|
||||
/// property <see cref="CurlVersionInfoData.Features" />. The feature
|
||||
/// bits are summarized in the table below.
|
||||
/// </summary>
|
||||
public enum CurlVersionFeatureBitmask
|
||||
{
|
||||
/// <summary>
|
||||
/// Supports Ipv6.
|
||||
/// </summary>
|
||||
Ipv6 = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// Supports kerberos4 (when using FTP).
|
||||
/// </summary>
|
||||
Kerberos64 = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// Supports Ssl (HTTPS/FTPS).
|
||||
/// </summary>
|
||||
Ssl = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// Supports HTTP deflate using libz.
|
||||
/// </summary>
|
||||
LibZ = 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// Supports HTTP Ntlm (added in 7.10.6).
|
||||
/// </summary>
|
||||
Ntlm = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// Supports HTTP GSS-Negotiate (added in 7.10.6).
|
||||
/// </summary>
|
||||
GssNegotiate = 0x20,
|
||||
|
||||
/// <summary>
|
||||
/// libcurl was built with extra debug capabilities built-in. This
|
||||
/// is mainly of interest for libcurl hackers. (added in 7.10.6)
|
||||
/// </summary>
|
||||
Debug = 0x40,
|
||||
|
||||
/// <summary>
|
||||
/// libcurl was built with support for asynchronous name lookups,
|
||||
/// which allows more exact timeouts (even on Windows) and less
|
||||
/// blocking when using the multi interface. (added in 7.10.7)
|
||||
/// </summary>
|
||||
AsynchDns = 0x80,
|
||||
|
||||
/// <summary>
|
||||
/// libcurl was built with support for Spnego authentication
|
||||
/// (Simple and Protected GSS-API Negotiation Mechanism, defined
|
||||
/// in RFC 2478.) (added in 7.10.8)
|
||||
/// </summary>
|
||||
Spnego = 0x100,
|
||||
|
||||
/// <summary>
|
||||
/// libcurl was built with support for large files.
|
||||
/// </summary>
|
||||
LargeFile = 0x200,
|
||||
|
||||
/// <summary>
|
||||
/// libcurl was built with support for IDNA, domain names with
|
||||
/// international letters.
|
||||
/// </summary>
|
||||
Idn = 0x400
|
||||
};
|
||||
}
|
402
src/CurlSharp/NativeMethods.cs
Normal file
@@ -0,0 +1,402 @@
|
||||
/***************************************************************************
|
||||
*
|
||||
* CurlS#arp
|
||||
*
|
||||
* Copyright (c) 2014 Dr. Masroor Ehsan (masroore@gmail.com)
|
||||
* Portions copyright (c) 2004, 2005 Jeff Phillips (jeff@jeffp.net)
|
||||
*
|
||||
* This software is licensed as described in the file LICENSE, which you
|
||||
* should have received as part of this distribution.
|
||||
*
|
||||
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
* copies of this Software, and permit persons to whom the Software is
|
||||
* furnished to do so, under the terms of the LICENSE file.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
|
||||
* ANY KIND, either express or implied.
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
//#define USE_LIBCURLSHIM
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CurlSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// P/Invoke signatures.
|
||||
/// </summary>
|
||||
internal static unsafe class NativeMethods
|
||||
{
|
||||
#if WIN64
|
||||
private const string CURL_LIB = "libcurl64.dll";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#if USE_LIBCURLSHIM
|
||||
private const string CURLSHIM_LIB = "libcurlshim64.dll";
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
#else
|
||||
#if LINUX
|
||||
private const string CURL_LIB = "libcurl";
|
||||
#else
|
||||
private const string CURL_LIB = "libcurl.dll";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#if USE_LIBCURLSHIM
|
||||
private const string CURLSHIM_LIB = "libcurlshim.dll";
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#if !USE_LIBCURLSHIM
|
||||
#if LINUX
|
||||
private const string WINSOCK_LIB = "libc";
|
||||
#else
|
||||
private const string WINSOCK_LIB = "ws2_32.dll";
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// internal delegates from cURL
|
||||
|
||||
// libcurl imports
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlCode curl_global_init (int flags);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void curl_global_cleanup ();
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
internal static extern IntPtr curl_escape (String url, int length);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
internal static extern IntPtr curl_unescape (String url, int length);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void curl_free (IntPtr p);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern IntPtr curl_version ();
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern IntPtr curl_easy_init ();
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void curl_easy_cleanup (IntPtr pCurl);
|
||||
|
||||
[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
|
||||
internal delegate int _CurlGenericCallback (IntPtr ptr, int sz, int nmemb, IntPtr userdata);
|
||||
|
||||
[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
|
||||
internal delegate int _CurlProgressCallback (
|
||||
IntPtr extraData, double dlTotal, double dlNow, double ulTotal, double ulNow);
|
||||
|
||||
[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
|
||||
internal delegate int _CurlDebugCallback (
|
||||
IntPtr ptrCurl, CurlInfoType infoType, string message, int size, IntPtr ptrUserData);
|
||||
|
||||
[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
|
||||
internal delegate int _CurlSslCtxCallback (IntPtr ctx, IntPtr parm);
|
||||
|
||||
[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
|
||||
internal delegate CurlIoError _CurlIoctlCallback (CurlIoCommand cmd, IntPtr parm);
|
||||
|
||||
// curl_easy_setopt() overloads
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlCode curl_easy_setopt (IntPtr pCurl, CurlOption opt, IntPtr parm);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlCode curl_easy_setopt (IntPtr pCurl, CurlOption opt, string parm);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlCode curl_easy_setopt (IntPtr pCurl, CurlOption opt, byte[] parm);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlCode curl_easy_setopt (IntPtr pCurl, CurlOption opt, long parm);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlCode curl_easy_setopt (IntPtr pCurl, CurlOption opt, bool parm);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl, EntryPoint = "curl_easy_setopt")]
|
||||
internal static extern CurlCode curl_easy_setopt_cb (IntPtr pCurl, CurlOption opt, _CurlGenericCallback parm);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl, EntryPoint = "curl_easy_setopt")]
|
||||
internal static extern CurlCode curl_easy_setopt_cb (IntPtr pCurl, CurlOption opt, _CurlProgressCallback parm);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl, EntryPoint = "curl_easy_setopt")]
|
||||
internal static extern CurlCode curl_easy_setopt_cb (IntPtr pCurl, CurlOption opt, _CurlDebugCallback parm);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl, EntryPoint = "curl_easy_setopt")]
|
||||
internal static extern CurlCode curl_easy_setopt_cb (IntPtr pCurl, CurlOption opt, _CurlSslCtxCallback parm);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl, EntryPoint = "curl_easy_setopt")]
|
||||
internal static extern CurlCode curl_easy_setopt_cb (IntPtr pCurl, CurlOption opt, _CurlIoctlCallback parm);
|
||||
|
||||
#if !USE_LIBCURLSHIM
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlMultiCode curl_multi_fdset (IntPtr pmulti,
|
||||
[In, Out] ref fd_set read_fd_set,
|
||||
[In, Out] ref fd_set write_fd_set,
|
||||
[In, Out] ref fd_set exc_fd_set,
|
||||
[In, Out] ref int max_fd);
|
||||
|
||||
[StructLayout (LayoutKind.Sequential)]
|
||||
internal struct fd_set
|
||||
{
|
||||
internal uint fd_count;
|
||||
//[MarshalAs(UnmanagedType.ByValArray, SizeConst = FD_SETSIZE)] internal IntPtr[] fd_array;
|
||||
internal fixed uint fd_array[FD_SETSIZE];
|
||||
|
||||
internal const int FD_SETSIZE = 64;
|
||||
|
||||
internal void Cleanup ()
|
||||
{
|
||||
//fd_array = null;
|
||||
}
|
||||
|
||||
internal static fd_set Create ()
|
||||
{
|
||||
return new fd_set {
|
||||
//fd_array = new IntPtr[FD_SETSIZE],
|
||||
fd_count = 0
|
||||
};
|
||||
}
|
||||
|
||||
internal static fd_set Create (IntPtr socket)
|
||||
{
|
||||
var handle = Create ();
|
||||
handle.fd_count = 1;
|
||||
handle.fd_array [0] = (uint)socket;
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void FD_ZERO (fd_set fds)
|
||||
{
|
||||
for (var i = 0; i < fd_set.FD_SETSIZE; i++) {
|
||||
//fds.fd_array[i] = (IntPtr) 0;
|
||||
fds.fd_array [i] = 0;
|
||||
}
|
||||
fds.fd_count = 0;
|
||||
}
|
||||
|
||||
[StructLayout (LayoutKind.Sequential)]
|
||||
internal struct timeval
|
||||
{
|
||||
/// <summary>
|
||||
/// Time interval, in seconds.
|
||||
/// </summary>
|
||||
internal int tv_sec;
|
||||
|
||||
/// <summary>
|
||||
/// Time interval, in microseconds.
|
||||
/// </summary>
|
||||
internal int tv_usec;
|
||||
|
||||
internal static timeval Create (int milliseconds)
|
||||
{
|
||||
return new timeval {
|
||||
tv_sec = milliseconds / 1000,
|
||||
tv_usec = (milliseconds % 1000) * 1000
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
[DllImport (WINSOCK_LIB, EntryPoint = "select")]
|
||||
internal static extern int select (
|
||||
int nfds, // number of sockets, (ignored in winsock)
|
||||
[In, Out] ref fd_set readfds, // read sockets to watch
|
||||
[In, Out] ref fd_set writefds, // write sockets to watch
|
||||
[In, Out] ref fd_set exceptfds, // error sockets to watch
|
||||
ref timeval timeout);
|
||||
|
||||
//[DllImport(WINSOCK_LIB, EntryPoint = "select")]
|
||||
//internal static extern int select(int ndfs, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, timeval* timeout);
|
||||
#endif
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlCode curl_easy_perform (IntPtr pCurl);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern IntPtr curl_easy_duphandle (IntPtr pCurl);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern IntPtr curl_easy_strerror (CurlCode err);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlCode curl_easy_getinfo (IntPtr pCurl, CurlInfo info, ref IntPtr pInfo);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlCode curl_easy_getinfo (IntPtr pCurl, CurlInfo info, ref double dblVal);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void curl_easy_reset (IntPtr pCurl);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern IntPtr curl_multi_init ();
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlMultiCode curl_multi_cleanup (IntPtr pmulti);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlMultiCode curl_multi_add_handle (IntPtr pmulti, IntPtr peasy);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlMultiCode curl_multi_remove_handle (IntPtr pmulti, IntPtr peasy);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern IntPtr curl_multi_strerror (CurlMultiCode errorNum);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlMultiCode curl_multi_perform (IntPtr pmulti, ref int runningHandles);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void curl_formfree (IntPtr pForm);
|
||||
|
||||
#if !USE_LIBCURLSHIM
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern int curl_formadd (ref IntPtr pHttppost, ref IntPtr pLastPost,
|
||||
int codeFirst, IntPtr bufFirst,
|
||||
int codeNext, IntPtr bufNext,
|
||||
int codeLast);
|
||||
#endif
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern IntPtr curl_share_init ();
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlShareCode curl_share_cleanup (IntPtr pShare);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern IntPtr curl_share_strerror (CurlShareCode errorCode);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlShareCode curl_share_setopt (IntPtr pShare, CurlShareOption optCode, IntPtr option);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
internal static extern IntPtr curl_slist_append (IntPtr slist, string data);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlShareCode curl_slist_free_all (IntPtr pList);
|
||||
|
||||
[DllImport (CURL_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern IntPtr curl_version_info (CurlVersion ver);
|
||||
|
||||
#if USE_LIBCURLSHIM
|
||||
|
||||
// libcurlshim imports
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void curl_shim_initialize();
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void curl_shim_cleanup();
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern IntPtr curl_shim_alloc_strings();
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl,
|
||||
CharSet = CharSet.Ansi)]
|
||||
internal static extern IntPtr curl_shim_add_string_to_slist(
|
||||
IntPtr pStrings, String str);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl,
|
||||
CharSet = CharSet.Ansi)]
|
||||
internal static extern IntPtr curl_shim_get_string_from_slist(
|
||||
IntPtr pSlist, ref IntPtr pStr);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl,
|
||||
CharSet = CharSet.Ansi)]
|
||||
internal static extern IntPtr curl_shim_add_string(IntPtr pStrings, String str);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void curl_shim_free_strings(IntPtr pStrings);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern int curl_shim_install_delegates(IntPtr pCurl, IntPtr pThis,
|
||||
_ShimWriteCallback pWrite, _ShimReadCallback pRead,
|
||||
_ShimProgressCallback pProgress, _ShimDebugCallback pDebug,
|
||||
_ShimHeaderCallback pHeader, _ShimSslCtxCallback pCtx,
|
||||
_ShimIoctlCallback pIoctl);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void curl_shim_cleanup_delegates(IntPtr pThis);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void curl_shim_get_file_time(int unixTime,
|
||||
ref int yy, ref int mm, ref int dd, ref int hh, ref int mn, ref int ss);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void curl_shim_free_slist(IntPtr p);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern IntPtr curl_shim_alloc_fd_sets();
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void curl_shim_free_fd_sets(IntPtr fdsets);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern CurlMultiCode curl_shim_multi_fdset(IntPtr multi,
|
||||
IntPtr fdsets, ref int maxFD);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern int curl_shim_select(int maxFD, IntPtr fdsets,
|
||||
int milliseconds);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern IntPtr curl_shim_multi_info_read(IntPtr multi,
|
||||
ref int nMsgs);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void curl_shim_multi_info_free(IntPtr multiInfo);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern int curl_shim_formadd(IntPtr[] ppForms, IntPtr[] pParams, int nParams);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern int curl_shim_install_share_delegates(IntPtr pShare,
|
||||
IntPtr pThis, _ShimLockCallback pLock, _ShimUnlockCallback pUnlock);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void curl_shim_cleanup_share_delegates(IntPtr pShare);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern int curl_shim_get_version_int_value(IntPtr p, int offset);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern IntPtr curl_shim_get_version_char_ptr(IntPtr p, int offset);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern int curl_shim_get_number_of_protocols(IntPtr p, int offset);
|
||||
|
||||
[DllImport(CURLSHIM_LIB, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern IntPtr curl_shim_get_protocol_string(IntPtr p, int offset, int index);
|
||||
|
||||
internal delegate void _ShimLockCallback(int data, int access, IntPtr userPtr);
|
||||
|
||||
internal delegate void _ShimUnlockCallback(int data, IntPtr userPtr);
|
||||
|
||||
internal delegate int _ShimDebugCallback(CurlInfoType infoType, IntPtr msgBuf, int msgBufSize, IntPtr parm);
|
||||
|
||||
internal delegate int _ShimHeaderCallback(IntPtr buf, int sz, int nmemb, IntPtr stream);
|
||||
|
||||
internal delegate CurlIoError _ShimIoctlCallback(CurlIoCommand cmd, IntPtr parm);
|
||||
|
||||
internal delegate int _ShimProgressCallback(IntPtr parm, double dlTotal, double dlNow, double ulTotal, double ulNow);
|
||||
|
||||
internal delegate int _ShimReadCallback(IntPtr buf, int sz, int nmemb, IntPtr parm);
|
||||
|
||||
internal delegate int _ShimSslCtxCallback(IntPtr ctx, IntPtr parm);
|
||||
|
||||
internal delegate int _ShimWriteCallback(IntPtr buf, int sz, int nmemb, IntPtr parm);
|
||||
#endif
|
||||
}
|
||||
}
|
22
src/CurlSharp/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// Information about this assembly is defined by the following attributes.
|
||||
// Change them to the values specific to your project.
|
||||
[assembly: AssemblyTitle ("CurlSharp")]
|
||||
[assembly: AssemblyDescription ("")]
|
||||
[assembly: AssemblyConfiguration ("")]
|
||||
[assembly: AssemblyCompany ("")]
|
||||
[assembly: AssemblyProduct ("")]
|
||||
[assembly: AssemblyCopyright ("max")]
|
||||
[assembly: AssemblyTrademark ("")]
|
||||
[assembly: AssemblyCulture ("")]
|
||||
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
|
||||
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
|
||||
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
|
||||
[assembly: AssemblyVersion ("1.0.*")]
|
||||
// The following attributes are used to specify the signing key for the assembly,
|
||||
// if desired. See the Mono documentation for more information about signing.
|
||||
//[assembly: AssemblyDelaySign(false)]
|
||||
//[assembly: AssemblyKeyFile("")]
|
||||
|
@@ -1,10 +1,18 @@
|
||||
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2013
|
||||
VisualStudioVersion = 12.0.31101.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jackett", "Jackett\Jackett.csproj", "{E636D5F8-68B4-4903-B4ED-CCFD9C9E899F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CurlSharp", "CurlSharp\CurlSharp.csproj", "{74420A79-CC16-442C-8B1E-7C1B913844F0}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BE7B0C8A-6144-47CD-821E-B09BA1B7BADE}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
..\LICENSE = ..\LICENSE
|
||||
..\README.md = ..\README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -15,8 +23,47 @@ Global
|
||||
{E636D5F8-68B4-4903-B4ED-CCFD9C9E899F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E636D5F8-68B4-4903-B4ED-CCFD9C9E899F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E636D5F8-68B4-4903-B4ED-CCFD9C9E899F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{74420A79-CC16-442C-8B1E-7C1B913844F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{74420A79-CC16-442C-8B1E-7C1B913844F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{74420A79-CC16-442C-8B1E-7C1B913844F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{74420A79-CC16-442C-8B1E-7C1B913844F0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(MonoDevelopProperties) = preSolution
|
||||
Policies = $0
|
||||
$0.TextStylePolicy = $1
|
||||
$1.inheritsSet = VisualStudio
|
||||
$1.inheritsScope = text/plain
|
||||
$1.scope = text/x-csharp
|
||||
$0.CSharpFormattingPolicy = $2
|
||||
$2.IndentSwitchBody = True
|
||||
$2.IndentBlocksInsideExpressions = True
|
||||
$2.AnonymousMethodBraceStyle = NextLine
|
||||
$2.PropertyBraceStyle = NextLine
|
||||
$2.PropertyGetBraceStyle = NextLine
|
||||
$2.PropertySetBraceStyle = NextLine
|
||||
$2.EventBraceStyle = NextLine
|
||||
$2.EventAddBraceStyle = NextLine
|
||||
$2.EventRemoveBraceStyle = NextLine
|
||||
$2.StatementBraceStyle = NextLine
|
||||
$2.ElseNewLinePlacement = NewLine
|
||||
$2.CatchNewLinePlacement = NewLine
|
||||
$2.FinallyNewLinePlacement = NewLine
|
||||
$2.WhileNewLinePlacement = DoNotCare
|
||||
$2.ArrayInitializerWrapping = DoNotChange
|
||||
$2.ArrayInitializerBraceStyle = NextLine
|
||||
$2.BeforeMethodDeclarationParentheses = False
|
||||
$2.BeforeMethodCallParentheses = False
|
||||
$2.BeforeConstructorDeclarationParentheses = False
|
||||
$2.NewLineBeforeConstructorInitializerColon = NewLine
|
||||
$2.NewLineAfterConstructorInitializerColon = SameLine
|
||||
$2.BeforeDelegateDeclarationParentheses = False
|
||||
$2.NewParentheses = False
|
||||
$2.SpacesBeforeBrackets = False
|
||||
$2.inheritsSet = Mono
|
||||
$2.inheritsScope = text/x-csharp
|
||||
$2.scope = text/x-csharp
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
16
src/Jackett/BrowserUtil.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett
|
||||
{
|
||||
public static class BrowserUtil
|
||||
{
|
||||
public static string ChromeUserAgent
|
||||
{
|
||||
get { return "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36"; }
|
||||
}
|
||||
}
|
||||
}
|
36
src/Jackett/CachedResult.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett
|
||||
{
|
||||
public class CachedResult
|
||||
{
|
||||
private List<ReleaseInfo> results;
|
||||
private DateTime created;
|
||||
private string query;
|
||||
|
||||
public CachedResult(string query, List<ReleaseInfo> results){
|
||||
this.results = results;
|
||||
created = DateTime.Now;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
public IReadOnlyList<ReleaseInfo> Results
|
||||
{
|
||||
get { return results.AsReadOnly(); }
|
||||
}
|
||||
|
||||
public DateTime Created
|
||||
{
|
||||
get { return created; }
|
||||
}
|
||||
|
||||
public string Query
|
||||
{
|
||||
get { return query; }
|
||||
}
|
||||
}
|
||||
}
|
45
src/Jackett/ConfigurationDataCookie.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett
|
||||
{
|
||||
|
||||
public class ConfigurationDataCookie : ConfigurationData
|
||||
{
|
||||
public StringItem Cookie { get; private set; }
|
||||
public DisplayItem CookieHint { get; private set; }
|
||||
public DisplayItem CookieExample { get; private set; }
|
||||
|
||||
public ConfigurationDataCookie()
|
||||
{
|
||||
Cookie = new StringItem { Name = "Cookie" };
|
||||
CookieHint = new DisplayItem(
|
||||
"<ol><li>Login to BeyondHD in your browser <li>Open the developer console, go the network tab <li>Find 'cookie' in the request headers <li>Copy & paste it to here</ol>")
|
||||
{
|
||||
Name = "CookieHint"
|
||||
};
|
||||
CookieExample = new DisplayItem(
|
||||
"Example cookie header (usually longer than this):<br><code>PHPSESSID=8rk27odm; ipsconnect_63ad9c=1; more_stuff=etc;</code>")
|
||||
{
|
||||
Name = "CookieExample"
|
||||
};
|
||||
}
|
||||
|
||||
public override Item[] GetItems()
|
||||
{
|
||||
return new Item[] { Cookie, CookieHint, CookieExample };
|
||||
}
|
||||
|
||||
public string CookieHeader
|
||||
{
|
||||
get
|
||||
{
|
||||
return Cookie.Value.Trim().TrimStart(new char[] { '"' }).TrimEnd(new char[] { '"' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
29
src/Jackett/ConfigurationDataUrl.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett
|
||||
{
|
||||
public class ConfigurationDataUrl : ConfigurationData
|
||||
{
|
||||
public StringItem Url { get; private set; }
|
||||
|
||||
public ConfigurationDataUrl(string defaultUrl)
|
||||
{
|
||||
Url = new StringItem { Name = "Url", Value = defaultUrl };
|
||||
}
|
||||
|
||||
public override Item[] GetItems()
|
||||
{
|
||||
return new Item[] { Url };
|
||||
}
|
||||
|
||||
public string GetFormattedHostUrl()
|
||||
{
|
||||
var uri = new Uri(Url.Value);
|
||||
return string.Format("{0}://{1}", uri.Scheme, uri.Host);
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,18 +5,53 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
namespace Jackett
|
||||
{
|
||||
public static class CookieContainerExtensions
|
||||
{
|
||||
public static void FillFromJson(this CookieContainer cookies, Uri uri, JArray json)
|
||||
|
||||
public static void FillFromJson(this CookieContainer cookies, Uri uri, JToken json)
|
||||
{
|
||||
foreach (var cookie in json)
|
||||
if (json["cookies"] != null)
|
||||
{
|
||||
var w = ((string)cookie).Split(':');
|
||||
cookies.Add(uri, new Cookie(w[0], w[1]));
|
||||
var cookieArray = (JArray)json["cookies"];
|
||||
foreach (string cookie in cookieArray)
|
||||
{
|
||||
var w = cookie.Split('=');
|
||||
if (w.Length == 1)
|
||||
{
|
||||
cookies.Add(uri, new Cookie { Name = cookie.Trim() });
|
||||
}
|
||||
else
|
||||
{
|
||||
cookies.Add(uri, new Cookie(w[0].Trim(), w[1].Trim()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (json["cookie_header"] != null)
|
||||
{
|
||||
var cfh = (string)json["cookie_header"];
|
||||
var cookieHeaders = ((string)json["cookie_header"]).Split(';');
|
||||
foreach (var c in cookieHeaders)
|
||||
{
|
||||
try
|
||||
{
|
||||
cookies.SetCookies(uri, c);
|
||||
}
|
||||
catch (CookieException ex)
|
||||
{
|
||||
Program.LoggerInstance.Info("(Non-critical) Problem loading cookie {0}, {1}, {2}", uri, c, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DumpToJson(this CookieContainer cookies, Uri uri, JToken json)
|
||||
{
|
||||
json["cookie_header"] = cookies.GetCookieHeader(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
137
src/Jackett/Curl.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using CurlSharp;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett
|
||||
{
|
||||
public class CurlHelper
|
||||
{
|
||||
private const string ChromeUserAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36";
|
||||
|
||||
public static CurlHelper Shared = new CurlHelper();
|
||||
|
||||
private BlockingCollection<CurlRequest> curlRequests;
|
||||
|
||||
public bool IsSupported { get; private set; }
|
||||
|
||||
private class CurlRequest
|
||||
{
|
||||
public TaskCompletionSource<string> TaskCompletion { get; private set; }
|
||||
|
||||
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 string PostData { get; set; }
|
||||
|
||||
public CurlRequest(HttpMethod method, string url, string cookies = null, string referer = null)
|
||||
{
|
||||
TaskCompletion = new TaskCompletionSource<string>();
|
||||
Method = method;
|
||||
Url = url;
|
||||
Cookies = cookies;
|
||||
Referer = referer;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetStringAsync(string url, string cookies = null, string referer = null)
|
||||
{
|
||||
var curlRequest = new CurlRequest(HttpMethod.Get, url, cookies, referer);
|
||||
curlRequests.Add(curlRequest);
|
||||
var result = await curlRequest.TaskCompletion.Task;
|
||||
return result;
|
||||
}
|
||||
|
||||
public CurlHelper()
|
||||
{
|
||||
try
|
||||
{
|
||||
Curl.GlobalInit(CurlInitFlag.All);
|
||||
IsSupported = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
IsSupported = false;
|
||||
}
|
||||
Task.Run((Action)CurlServicer);
|
||||
}
|
||||
|
||||
private void CurlServicer()
|
||||
{
|
||||
curlRequests = new BlockingCollection<CurlRequest>();
|
||||
foreach (var curlRequest in curlRequests.GetConsumingEnumerable())
|
||||
{
|
||||
PerformCurl(curlRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private void PerformCurl(CurlRequest curlRequest)
|
||||
{
|
||||
var headerBuffers = new List<byte[]>();
|
||||
var contentBuffers = new List<byte[]>();
|
||||
try
|
||||
{
|
||||
using (var easy = new CurlEasy())
|
||||
{
|
||||
easy.Url = curlRequest.Url;
|
||||
easy.BufferSize = 64 * 1024;
|
||||
//easy.Encoding = "UTF8";
|
||||
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);
|
||||
return size * nmemb;
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(curlRequest.Cookies))
|
||||
easy.Cookie = curlRequest.Cookies;
|
||||
|
||||
if (curlRequest.Method == HttpMethod.Post)
|
||||
{
|
||||
easy.Post = true;
|
||||
easy.PostFields = curlRequest.PostData;
|
||||
easy.PostFieldSize = Encoding.UTF8.GetByteCount(curlRequest.PostData);
|
||||
}
|
||||
|
||||
easy.Perform();
|
||||
}
|
||||
|
||||
var headerBytes = Combine(headerBuffers.ToArray());
|
||||
var headerString = Encoding.UTF8.GetString(headerBytes);
|
||||
|
||||
var contentBytes = Combine(contentBuffers.ToArray());
|
||||
var result = Encoding.UTF8.GetString(contentBytes);
|
||||
curlRequest.TaskCompletion.SetResult(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
curlRequest.TaskCompletion.TrySetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] Combine(params byte[][] arrays)
|
||||
{
|
||||
byte[] ret = new byte[arrays.Sum(x => x.Length)];
|
||||
int offset = 0;
|
||||
foreach (byte[] data in arrays)
|
||||
{
|
||||
Buffer.BlockCopy(data, 0, ret, offset, data.Length);
|
||||
offset += data.Length;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
213
src/Jackett/CurlHelper.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
using CurlSharp;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace Jackett
|
||||
{
|
||||
public static class CurlHelper
|
||||
{
|
||||
private const string ChromeUserAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36";
|
||||
|
||||
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)
|
||||
{
|
||||
Method = method;
|
||||
Url = url;
|
||||
Cookies = cookies;
|
||||
Referer = referer;
|
||||
}
|
||||
}
|
||||
|
||||
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 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 CurlResponse(List<string[]> headers, byte[] content)
|
||||
{
|
||||
Headers = new Dictionary<string, string>();
|
||||
Cookies = new Dictionary<string, string>();
|
||||
HeaderList = headers;
|
||||
Content = content;
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<CurlResponse> GetAsync(string url, string cookies = null, string referer = null)
|
||||
{
|
||||
var curlRequest = new CurlRequest(HttpMethod.Get, url, cookies, referer);
|
||||
var result = await PerformCurlAsync(curlRequest);
|
||||
var checkedResult = await FollowRedirect(url, result);
|
||||
return checkedResult;
|
||||
}
|
||||
|
||||
public static async Task<CurlResponse> PostAsync(string url, Dictionary<string, string> formData, string cookies = null, string referer = null)
|
||||
{
|
||||
var curlRequest = new CurlRequest(HttpMethod.Post, url, cookies, referer);
|
||||
curlRequest.PostData = formData;
|
||||
var result = await PerformCurlAsync(curlRequest);
|
||||
var checkedResult = await FollowRedirect(url, result);
|
||||
return checkedResult;
|
||||
}
|
||||
|
||||
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 static CurlResponse PerformCurl(CurlRequest curlRequest)
|
||||
{
|
||||
Curl.GlobalInit(CurlInitFlag.All);
|
||||
|
||||
var headerBuffers = new List<byte[]>();
|
||||
var contentBuffers = new List<byte[]>();
|
||||
|
||||
using (var easy = new CurlEasy())
|
||||
{
|
||||
easy.Url = curlRequest.Url;
|
||||
easy.BufferSize = 64 * 1024;
|
||||
easy.UserAgent = ChromeUserAgent;
|
||||
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);
|
||||
return size * nmemb;
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(curlRequest.Cookies))
|
||||
easy.Cookie = curlRequest.Cookies;
|
||||
|
||||
if (!string.IsNullOrEmpty(curlRequest.Referer))
|
||||
easy.Referer = curlRequest.Referer;
|
||||
|
||||
if (curlRequest.Method == HttpMethod.Post)
|
||||
{
|
||||
easy.Post = true;
|
||||
var postString = new FormUrlEncodedContent(curlRequest.PostData).ReadAsStringAsync().Result;
|
||||
easy.PostFields = postString;
|
||||
easy.PostFieldSize = Encoding.UTF8.GetByteCount(postString);
|
||||
}
|
||||
|
||||
easy.Perform();
|
||||
}
|
||||
|
||||
var headerBytes = Combine(headerBuffers.ToArray());
|
||||
var headerString = Encoding.UTF8.GetString(headerBytes);
|
||||
var headerParts = headerString.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var headers = new List<string[]>();
|
||||
foreach (var headerPart in headerParts.Skip(1))
|
||||
{
|
||||
var keyVal = headerPart.Split(new char[] { ':' }, 2);
|
||||
if (keyVal.Length > 1)
|
||||
{
|
||||
headers.Add(new[] { keyVal[0].ToLower().Trim(), keyVal[1].Trim() });
|
||||
}
|
||||
}
|
||||
|
||||
var contentBytes = Combine(contentBuffers.ToArray());
|
||||
var curlResponse = new CurlResponse(headers, contentBytes);
|
||||
|
||||
if (!string.IsNullOrEmpty(curlRequest.Cookies))
|
||||
curlResponse.AddCookiesFromHeaderValue(curlRequest.Cookies);
|
||||
curlResponse.AddCookiesFromHeaders(headers);
|
||||
|
||||
return curlResponse;
|
||||
|
||||
}
|
||||
|
||||
public static byte[] Combine(params byte[][] arrays)
|
||||
{
|
||||
byte[] ret = new byte[arrays.Sum(x => x.Length)];
|
||||
int offset = 0;
|
||||
foreach (byte[] data in arrays)
|
||||
{
|
||||
Buffer.BlockCopy(data, 0, ret, offset, data.Length);
|
||||
offset += data.Length;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
28
src/Jackett/HttpClientExtensions.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett
|
||||
{
|
||||
public static class HttpClientExtensions
|
||||
{
|
||||
public static async Task<string> GetStringAsync(this HttpClient client, string uri, int retries)
|
||||
{
|
||||
Exception exception = null;
|
||||
try
|
||||
{
|
||||
return await client.GetStringAsync(uri);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
if (retries > 0)
|
||||
return await client.GetStringAsync(uri, --retries);
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,22 +10,25 @@ namespace Jackett
|
||||
{
|
||||
public interface IndexerInterface
|
||||
{
|
||||
|
||||
// Invoked when the indexer configuration has been applied and verified so the cookie needs to be saved
|
||||
event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
|
||||
event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
string DisplayName { get; }
|
||||
string DisplayDescription { get; }
|
||||
Uri SiteLink { get; }
|
||||
|
||||
// Whether this indexer has been configured, verified and saved in the past and has the settings required for functioning
|
||||
bool IsConfigured { get; }
|
||||
|
||||
// Retrieved for starting setup for the indexer via web API
|
||||
Task<ConfigurationData> GetConfigurationForSetup();
|
||||
|
||||
// Called when web API wants to apply setup configuration via web API, usually this is where login and storing cookie happens
|
||||
Task ApplyConfiguration(JToken configJson);
|
||||
|
||||
// Invoked when the indexer configuration has been applied and verified so the cookie needs to be saved
|
||||
event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
|
||||
// Whether this indexer has been configured, verified and saved in the past and has the settings required for functioning
|
||||
bool IsConfigured { get; }
|
||||
|
||||
// Called on startup when initializing indexers from saved configuration
|
||||
void LoadFromSavedConfiguration(JToken jsonConfig);
|
||||
|
||||
|
@@ -43,6 +43,7 @@ namespace Jackett
|
||||
|
||||
IndexerInterface newIndexer = (IndexerInterface)Activator.CreateInstance(indexerType);
|
||||
newIndexer.OnSaveConfigurationRequested += newIndexer_OnSaveConfigurationRequested;
|
||||
newIndexer.OnResultParsingError += newIndexer_OnResultParsingError;
|
||||
|
||||
var configFilePath = GetIndexerConfigFilePath(newIndexer);
|
||||
if (File.Exists(configFilePath))
|
||||
@@ -54,6 +55,14 @@ namespace Jackett
|
||||
Indexers.Add(name, newIndexer);
|
||||
}
|
||||
|
||||
void newIndexer_OnResultParsingError(IndexerInterface indexer, string results, Exception ex)
|
||||
{
|
||||
var fileName = string.Format("Error on {0} for {1}.txt", DateTime.Now.ToString("yyyyMMddHHmmss"), indexer.DisplayName);
|
||||
var spacing = string.Join("", Enumerable.Repeat(Environment.NewLine, 5));
|
||||
var fileContents = string.Format("{0}{1}{2}", ex, spacing, results);
|
||||
File.WriteAllText(Path.Combine(Program.AppConfigDirectory, fileName), fileContents);
|
||||
}
|
||||
|
||||
string GetIndexerConfigFilePath(IndexerInterface indexer)
|
||||
{
|
||||
return Path.Combine(IndexerConfigDirectory, indexer.GetType().Name.ToLower() + ".json");
|
||||
@@ -61,7 +70,6 @@ namespace Jackett
|
||||
|
||||
void newIndexer_OnSaveConfigurationRequested(IndexerInterface indexer, JToken obj)
|
||||
{
|
||||
var name = indexer.GetType().Name.Trim().ToLower();
|
||||
var configFilePath = GetIndexerConfigFilePath(indexer);
|
||||
if (!Directory.Exists(IndexerConfigDirectory))
|
||||
Directory.CreateDirectory(IndexerConfigDirectory);
|
||||
|
253
src/Jackett/Indexers/AlphaRatio.cs
Normal file
@@ -0,0 +1,253 @@
|
||||
using CsQuery;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class AlphaRatio : IndexerInterface
|
||||
{
|
||||
public string DisplayName
|
||||
{
|
||||
get { return "AlphaRatio"; }
|
||||
}
|
||||
|
||||
public string DisplayDescription
|
||||
{
|
||||
get { return "Legendary"; }
|
||||
}
|
||||
|
||||
public Uri SiteLink
|
||||
{
|
||||
get { return new Uri(BaseUrl); }
|
||||
}
|
||||
|
||||
|
||||
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
public bool IsConfigured { get; private set; }
|
||||
|
||||
static string BaseUrl = "https://alpharatio.cc";
|
||||
|
||||
static string LoginUrl = BaseUrl + "/login.php";
|
||||
|
||||
static string SearchUrl = BaseUrl + "/ajax.php?action=browse&searchstr=";
|
||||
|
||||
static string DownloadUrl = BaseUrl + "/torrents.php?action=download&id=";
|
||||
|
||||
static string GuidUrl = BaseUrl + "/torrents.php?torrentid=";
|
||||
|
||||
static string chromeUserAgent = BrowserUtil.ChromeUserAgent;
|
||||
|
||||
CookieContainer cookies;
|
||||
HttpClientHandler handler;
|
||||
HttpClient client;
|
||||
|
||||
string cookieHeader;
|
||||
|
||||
public AlphaRatio()
|
||||
{
|
||||
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 ConfigurationDataBasicLogin();
|
||||
return Task.FromResult<ConfigurationData>(config);
|
||||
}
|
||||
|
||||
public async Task ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
var configSaveData = new JObject();
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
|
||||
var config = new ConfigurationDataBasicLogin();
|
||||
config.LoadValuesFromJson(configJson);
|
||||
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", config.Username.Value },
|
||||
{ "password", @config.Password.Value },
|
||||
{ "login", "Login" },
|
||||
{ "keeplogged", "1" }
|
||||
};
|
||||
|
||||
var content = new FormUrlEncodedContent(pairs);
|
||||
var message = CreateHttpRequest(new Uri(LoginUrl));
|
||||
message.Content = content;
|
||||
|
||||
//message.Headers.Referrer = new Uri(LoginUrl);
|
||||
string responseContent;
|
||||
|
||||
configSaveData = new JObject();
|
||||
|
||||
if (Program.IsWindows)
|
||||
{
|
||||
// If Windows use .net http
|
||||
var response = await client.SendAsync(message);
|
||||
responseContent = await response.Content.ReadAsStringAsync();
|
||||
cookies.DumpToJson(SiteLink, configSaveData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If UNIX system use curl, probably broken due to missing chromeUseragent record for CURL...cannot test
|
||||
var response = await CurlHelper.PostAsync(LoginUrl, pairs);
|
||||
responseContent = Encoding.UTF8.GetString(response.Content);
|
||||
cookieHeader = response.CookieHeader;
|
||||
configSaveData["cookie_header"] = cookieHeader;
|
||||
}
|
||||
|
||||
if (!responseContent.Contains("logout.php?"))
|
||||
{
|
||||
CQ dom = responseContent;
|
||||
dom["#loginform > table"].Remove();
|
||||
var errorMessage = dom["#loginform"].Text().Trim().Replace("\n\t", " ");
|
||||
throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
|
||||
IsConfigured = true;
|
||||
}
|
||||
}
|
||||
|
||||
HttpRequestMessage CreateHttpRequest(Uri uri)
|
||||
{
|
||||
var message = new HttpRequestMessage();
|
||||
message.Method = HttpMethod.Post;
|
||||
message.RequestUri = uri;
|
||||
message.Headers.UserAgent.ParseAdd(chromeUserAgent);
|
||||
return message;
|
||||
}
|
||||
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
cookies.FillFromJson(SiteLink, jsonConfig);
|
||||
cookieHeader = cookies.GetCookieHeader(SiteLink);
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
void FillReleaseInfoFromJson(ReleaseInfo release, JObject r)
|
||||
{
|
||||
var id = r["torrentId"];
|
||||
release.Size = (long)r["size"];
|
||||
release.Seeders = (int)r["seeders"];
|
||||
release.Peers = (int)r["leechers"] + release.Seeders;
|
||||
release.Guid = new Uri(GuidUrl + id);
|
||||
release.Comments = release.Guid;
|
||||
release.Link = new Uri(DownloadUrl + id);
|
||||
}
|
||||
|
||||
public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
foreach (var title in query.ShowTitles ?? new string[] { string.Empty })
|
||||
{
|
||||
|
||||
var searchString = title + " " + query.GetEpisodeSearchString();
|
||||
var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString);
|
||||
|
||||
string results;
|
||||
if (Program.IsWindows)
|
||||
{
|
||||
var request = CreateHttpRequest(new Uri(episodeSearchUrl));
|
||||
request.Method = HttpMethod.Get;
|
||||
var response = await client.SendAsync(request);
|
||||
results = await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
var response = await CurlHelper.GetAsync(episodeSearchUrl, cookieHeader);
|
||||
results = Encoding.UTF8.GetString(response.Content);
|
||||
}
|
||||
try
|
||||
{
|
||||
|
||||
var json = JObject.Parse(results);
|
||||
foreach (JObject r in json["response"]["results"])
|
||||
{
|
||||
DateTime pubDate = DateTime.MinValue;
|
||||
double dateNum;
|
||||
if (double.TryParse((string)r["groupTime"], out dateNum))
|
||||
pubDate = UnixTimestampToDateTime(dateNum);
|
||||
|
||||
var groupName = (string)r["groupName"];
|
||||
|
||||
if (r["torrents"] is JArray)
|
||||
{
|
||||
foreach (JObject t in r["torrents"])
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
release.PublishDate = pubDate;
|
||||
release.Title = groupName;
|
||||
release.Description = groupName;
|
||||
FillReleaseInfoFromJson(release, t);
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
release.PublishDate = pubDate;
|
||||
release.Title = groupName;
|
||||
release.Description = groupName;
|
||||
FillReleaseInfoFromJson(release, r);
|
||||
releases.Add(release);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, results, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
return releases.ToArray();
|
||||
}
|
||||
|
||||
static DateTime UnixTimestampToDateTime(double unixTime)
|
||||
{
|
||||
DateTime unixStart = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
|
||||
long unixTimeStampInTicks = (long)(unixTime * TimeSpan.TicksPerSecond);
|
||||
return new DateTime(unixStart.Ticks + unixTimeStampInTicks);
|
||||
}
|
||||
|
||||
public async Task<byte[]> Download(Uri link)
|
||||
{
|
||||
if (Program.IsWindows)
|
||||
{
|
||||
return await client.GetByteArrayAsync(link);
|
||||
}
|
||||
else
|
||||
{
|
||||
var response = await CurlHelper.GetAsync(link.ToString(), cookieHeader);
|
||||
return response.Content;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
408
src/Jackett/Indexers/AnimeBytes.cs
Normal file
@@ -0,0 +1,408 @@
|
||||
using CsQuery;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class AnimeBytes : IndexerInterface
|
||||
{
|
||||
class ConfigurationDataBasicLoginAnimeBytes : ConfigurationDataBasicLogin
|
||||
{
|
||||
public BoolItem IncludeRaw { get; private set; }
|
||||
public DisplayItem RageIdWarning { get; private set; }
|
||||
public DisplayItem DateWarning { get; private set; }
|
||||
|
||||
public ConfigurationDataBasicLoginAnimeBytes()
|
||||
: base()
|
||||
{
|
||||
IncludeRaw = new BoolItem() { Name = "IncludeRaw", Value = false };
|
||||
RageIdWarning = new DisplayItem("Ensure rageid lookup is disabled in Sonarr for this tracker.") { Name = "RageWarning" };
|
||||
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, RageIdWarning, DateWarning };
|
||||
}
|
||||
}
|
||||
|
||||
private static List<CachedResult> cache = new List<CachedResult>();
|
||||
private static readonly TimeSpan cacheTime = new TimeSpan(0, 9, 0);
|
||||
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
|
||||
static string chromeUserAgent = BrowserUtil.ChromeUserAgent;
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get { return "AnimeBytes"; }
|
||||
}
|
||||
|
||||
public string DisplayDescription
|
||||
{
|
||||
get { return "The web's best Chinese cartoons"; }
|
||||
}
|
||||
|
||||
public Uri SiteLink
|
||||
{
|
||||
get { return new Uri(BaseUrl); }
|
||||
}
|
||||
|
||||
const string BaseUrl = "https://animebytes.tv";
|
||||
const string LoginUrl = BaseUrl + "/user/login";
|
||||
const string SearchUrl = BaseUrl + "/torrents.php?filter_cat[1]=1";
|
||||
|
||||
public bool IsConfigured { get; private set; }
|
||||
public bool AllowRaws { get; private set; }
|
||||
|
||||
|
||||
CookieContainer cookieContainer;
|
||||
HttpClientHandler handler;
|
||||
HttpClient client;
|
||||
|
||||
public AnimeBytes()
|
||||
{
|
||||
IsConfigured = false;
|
||||
cookieContainer = new CookieContainer();
|
||||
handler = new HttpClientHandler
|
||||
{
|
||||
CookieContainer = cookieContainer,
|
||||
AllowAutoRedirect = false,
|
||||
UseCookies = true,
|
||||
};
|
||||
client = new HttpClient(handler);
|
||||
client.DefaultRequestHeaders.Add("User-Agent", chromeUserAgent);
|
||||
}
|
||||
|
||||
public Task<ConfigurationData> GetConfigurationForSetup()
|
||||
{
|
||||
var config = new ConfigurationDataBasicLoginAnimeBytes();
|
||||
return Task.FromResult<ConfigurationData>(config);
|
||||
}
|
||||
|
||||
public async Task ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
var config = new ConfigurationDataBasicLoginAnimeBytes();
|
||||
config.LoadValuesFromJson(configJson);
|
||||
|
||||
|
||||
// Get the login form as we need the CSRF Token
|
||||
var loginPage = await client.GetAsync(LoginUrl);
|
||||
CQ loginPageDom = await loginPage.Content.ReadAsStringAsync();
|
||||
var csrfToken = loginPageDom["input[name=\"csrf_token\"]"].Last();
|
||||
|
||||
// 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!" }
|
||||
};
|
||||
|
||||
var content = new FormUrlEncodedContent(pairs);
|
||||
|
||||
// Do the login
|
||||
var response = await client.PostAsync(LoginUrl, content);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// 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> cookies;
|
||||
if (response.Headers.TryGetValues("set-cookie", out cookies))
|
||||
{
|
||||
foreach (var c in cookies)
|
||||
{
|
||||
cookieContainer.SetCookies(new Uri(BaseUrl), c.Substring(0, c.LastIndexOf(';')));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Cookie cookie in cookieContainer.GetCookies(new Uri(BaseUrl)))
|
||||
{
|
||||
if (cookie.Name == "session")
|
||||
{
|
||||
cookie.Expires = DateTime.Now.AddDays(360);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the home page now we are logged in as AllowAutoRedirect is false as we needed to get the cookie manually.
|
||||
response = await client.GetAsync(BaseUrl);
|
||||
responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (!responseContent.Contains("/user/logout"))
|
||||
{
|
||||
throw new ExceptionWithConfigData("Failed to login, 6 failed attempts will get you banned for 6 hours.", (ConfigurationData)config);
|
||||
}
|
||||
else
|
||||
{
|
||||
AllowRaws = config.IncludeRaw.Value;
|
||||
var configSaveData = new JObject();
|
||||
cookieContainer.DumpToJson(SiteLink, configSaveData);
|
||||
configSaveData["raws"] = AllowRaws;
|
||||
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
|
||||
IsConfigured = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
cookieContainer.FillFromJson(new Uri(BaseUrl), jsonConfig);
|
||||
IsConfigured = true;
|
||||
AllowRaws = jsonConfig["raws"].Value<bool>();
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private void CleanCache()
|
||||
{
|
||||
foreach (var expired in cache.Where(i => i.Created - DateTime.Now > cacheTime).ToList())
|
||||
{
|
||||
cache.Remove(expired);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<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.SearchTerm))
|
||||
{
|
||||
var splitindex = query.SearchTerm.LastIndexOf(' ');
|
||||
if (splitindex > -1)
|
||||
query.SearchTerm = query.SearchTerm.Substring(0, splitindex);
|
||||
}
|
||||
|
||||
// The result list
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
// Check cache first so we don't query the server for each episode when searching for each episode in a series.
|
||||
lock (cache)
|
||||
{
|
||||
// Remove old cache items
|
||||
CleanCache();
|
||||
|
||||
var cachedResult = cache.Where(i => i.Query == query.SearchTerm).FirstOrDefault();
|
||||
if (cachedResult != null)
|
||||
return cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray();
|
||||
}
|
||||
|
||||
var queryUrl = SearchUrl;
|
||||
// Only include the query bit if its required as hopefully the site caches the non query page
|
||||
if (!string.IsNullOrWhiteSpace(query.SearchTerm))
|
||||
{
|
||||
|
||||
queryUrl += "&action=advanced&search_type=title&sort=time_added&way=desc&anime%5Btv_series%5D=1&searchstr=" + WebUtility.UrlEncode(query.SearchTerm);
|
||||
}
|
||||
|
||||
// Get the content from the tracker
|
||||
var response = await client.GetAsync(queryUrl);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
CQ dom = responseContent;
|
||||
|
||||
// Parse
|
||||
try
|
||||
{
|
||||
var releaseInfo = "S01";
|
||||
var root = dom.Find(".anime");
|
||||
// We may have got redirected to the series page if we have none of these
|
||||
if (root.Count() == 0)
|
||||
root = dom.Find(".torrent_table");
|
||||
|
||||
foreach (var series in root)
|
||||
{
|
||||
var seriesCq = series.Cq();
|
||||
|
||||
var synonyms = new List<string>();
|
||||
var mainTitle = seriesCq.Find(".group_title strong a").First().Text().Trim();
|
||||
|
||||
var yearStr = seriesCq.Find(".group_title strong").First().Text().Trim().Replace("]", "").Trim();
|
||||
int yearIndex = yearStr.LastIndexOf("[");
|
||||
if (yearIndex > -1)
|
||||
yearStr = yearStr.Substring(yearIndex + 1);
|
||||
|
||||
int year = 0;
|
||||
if (!int.TryParse(yearStr, out year))
|
||||
year = DateTime.Now.Year;
|
||||
|
||||
synonyms.Add(mainTitle);
|
||||
|
||||
// If the title contains a comma then we can't use the synonyms as they are comma seperated
|
||||
if (!mainTitle.Contains(","))
|
||||
{
|
||||
var symnomnNames = string.Empty;
|
||||
foreach (var e in seriesCq.Find(".group_statbox li"))
|
||||
{
|
||||
if (e.FirstChild.InnerText == "Synonyms:")
|
||||
{
|
||||
symnomnNames = e.InnerText;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(symnomnNames))
|
||||
{
|
||||
foreach (var name in symnomnNames.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var theName = name.Trim();
|
||||
if (!theName.Contains("&#") && !string.IsNullOrWhiteSpace(theName))
|
||||
{
|
||||
synonyms.Add(theName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var title in synonyms)
|
||||
{
|
||||
var releaseRows = seriesCq.Find(".torrent_group tr");
|
||||
|
||||
// Skip the first two info rows
|
||||
for (int r = 2; r < releaseRows.Count(); r++)
|
||||
{
|
||||
var row = releaseRows.Get(r);
|
||||
var rowCq = row.Cq();
|
||||
if (rowCq.HasClass("edition_info"))
|
||||
{
|
||||
releaseInfo = rowCq.Find("td").Text();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(releaseInfo))
|
||||
{
|
||||
// Single episodes alpha - Reported that this info is missing.
|
||||
// It should self correct when availible
|
||||
break;
|
||||
}
|
||||
|
||||
releaseInfo = releaseInfo.Replace("Episode ", "");
|
||||
releaseInfo = releaseInfo.Replace("Season ", "S");
|
||||
releaseInfo = releaseInfo.Trim();
|
||||
}
|
||||
else if (rowCq.HasClass("torrent"))
|
||||
{
|
||||
var links = rowCq.Find("a");
|
||||
// Protect against format changes
|
||||
if (links.Count() != 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var release = new ReleaseInfo();
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 259200;
|
||||
var downloadLink = links.Get(0);
|
||||
release.Guid = new Uri(BaseUrl + "/" + downloadLink.Attributes.GetAttribute("href") + "&nh=" + Hash(title)); // Sonarr should dedupe on this url - allow a url per name.
|
||||
release.Link = release.Guid;// We dont know this so try to fake based on the release year
|
||||
release.PublishDate = new DateTime(year, 1, 1);
|
||||
release.PublishDate = release.PublishDate.AddDays(Math.Min(DateTime.Now.DayOfYear, 365) - 1);
|
||||
|
||||
var infoLink = links.Get(1);
|
||||
release.Comments = new Uri(BaseUrl + "/" + infoLink.Attributes.GetAttribute("href"));
|
||||
|
||||
// We dont actually have a release name >.> so try to create one
|
||||
var releaseTags = infoLink.InnerText.Split("|".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
for (int i = releaseTags.Count - 1; i >= 0; i--)
|
||||
{
|
||||
releaseTags[i] = releaseTags[i].Trim();
|
||||
if (string.IsNullOrWhiteSpace(releaseTags[i]))
|
||||
releaseTags.RemoveAt(i);
|
||||
}
|
||||
|
||||
var group = releaseTags.Last();
|
||||
if (group.Contains("(") && group.Contains(")"))
|
||||
{
|
||||
// Skip raws if set
|
||||
if (group.ToLowerInvariant().StartsWith("raw") && !AllowRaws)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var start = group.IndexOf("(");
|
||||
group = "[" + group.Substring(start + 1, (group.IndexOf(")") - 1) - start) + "] ";
|
||||
}
|
||||
else
|
||||
{
|
||||
group = string.Empty;
|
||||
}
|
||||
|
||||
var infoString = "";
|
||||
|
||||
for (int i = 0; i + 1 < releaseTags.Count(); i++)
|
||||
{
|
||||
infoString += "[" + releaseTags[i] + "]";
|
||||
}
|
||||
|
||||
release.Title = string.Format("{0}{1} {2} {3}", group, title, releaseInfo, infoString);
|
||||
release.Description = title;
|
||||
|
||||
var size = rowCq.Find(".torrent_size");
|
||||
if (size.Count() > 0)
|
||||
{
|
||||
var sizeParts = size.First().Text().Split(' ');
|
||||
release.Size = ReleaseInfo.GetBytes(sizeParts[1], ParseUtil.CoerceFloat(sizeParts[0]));
|
||||
}
|
||||
|
||||
// Additional 5 hours per GB
|
||||
release.MinimumSeedTime += (release.Size / 1000000000) * 18000;
|
||||
|
||||
// Peer info
|
||||
release.Seeders = ParseUtil.CoerceInt(rowCq.Find(".torrent_seeders").Text());
|
||||
release.Peers = release.Seeders + ParseUtil.CoerceInt(rowCq.Find(".torrent_leechers").Text());
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, responseContent, ex);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
|
||||
// Add to the cache
|
||||
lock (cache)
|
||||
{
|
||||
cache.Add(new CachedResult(query.SearchTerm, releases));
|
||||
}
|
||||
|
||||
return releases.Select(s => (ReleaseInfo)s.Clone()).ToArray();
|
||||
}
|
||||
|
||||
public Task<byte[]> Download(Uri link)
|
||||
{
|
||||
return client.GetByteArrayAsync(link);
|
||||
}
|
||||
}
|
||||
}
|
180
src/Jackett/Indexers/BeyondHD.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using CsQuery;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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 BeyondHD : IndexerInterface
|
||||
{
|
||||
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get { return "BeyondHD"; }
|
||||
}
|
||||
|
||||
public string DisplayDescription
|
||||
{
|
||||
get { return "Without BeyondHD, your HDTV is just a TV"; }
|
||||
}
|
||||
|
||||
public Uri SiteLink
|
||||
{
|
||||
get { return new Uri(BaseUrl); }
|
||||
}
|
||||
|
||||
public bool IsConfigured { get; private set; }
|
||||
|
||||
const string BaseUrl = "https://beyondhd.me";
|
||||
const string SearchUrl = BaseUrl + "/browse.php?c40=1&c44=1&c48=1&c89=1&c46=1&c45=1&searchin=title&incldead=0&search={0}";
|
||||
const string DownloadUrl = BaseUrl + "/download.php?torrent={0}";
|
||||
|
||||
CookieContainer cookies;
|
||||
HttpClientHandler handler;
|
||||
HttpClient client;
|
||||
|
||||
public BeyondHD()
|
||||
{
|
||||
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 ConfigurationDataCookie();
|
||||
return Task.FromResult<ConfigurationData>(config);
|
||||
}
|
||||
|
||||
public async Task ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
var config = new ConfigurationDataCookie();
|
||||
config.LoadValuesFromJson(configJson);
|
||||
|
||||
var jsonCookie = new JObject();
|
||||
jsonCookie["cookie_header"] = config.CookieHeader;
|
||||
cookies.FillFromJson(new Uri(BaseUrl), jsonCookie);
|
||||
|
||||
var responseContent = await client.GetStringAsync(BaseUrl);
|
||||
|
||||
if (!responseContent.Contains("logout.php"))
|
||||
{
|
||||
CQ dom = responseContent;
|
||||
throw new ExceptionWithConfigData("Invalid cookie header", (ConfigurationData)config);
|
||||
}
|
||||
else
|
||||
{
|
||||
var configSaveData = new JObject();
|
||||
cookies.DumpToJson(SiteLink, configSaveData);
|
||||
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
cookies.FillFromJson(new Uri(BaseUrl), jsonConfig);
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
foreach (var title in query.ShowTitles ?? new string[] { string.Empty })
|
||||
{
|
||||
var searchString = title + " " + query.GetEpisodeSearchString();
|
||||
var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
|
||||
var results = await client.GetStringAsync(episodeSearchUrl);
|
||||
|
||||
try
|
||||
{
|
||||
CQ dom = results;
|
||||
var rows = dom["table.torrenttable > tbody > tr.browse_color"];
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
|
||||
var qRow = row.Cq();
|
||||
|
||||
var qLink = row.ChildElements.ElementAt(2).FirstChild.Cq();
|
||||
release.Link = new Uri(BaseUrl + "/" + qLink.Attr("href"));
|
||||
var torrentID = qLink.Attr("href").Split('=').Last();
|
||||
|
||||
var descCol = row.ChildElements.ElementAt(3);
|
||||
var qCommentLink = descCol.FirstChild.Cq();
|
||||
release.Title = qCommentLink.Text();
|
||||
release.Description = release.Title;
|
||||
release.Comments = new Uri(BaseUrl + "/" + qCommentLink.Attr("href"));
|
||||
release.Guid = release.Comments;
|
||||
|
||||
var dateStr = descCol.ChildElements.Last().Cq().Text().Split('|').Last().ToLowerInvariant().Replace("ago.", "").Trim();
|
||||
var dateParts = dateStr.Split(new char[] { ' ', ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var timeSpan = TimeSpan.Zero;
|
||||
for (var i = 0; i < dateParts.Length / 2; i++)
|
||||
{
|
||||
var timeVal = ParseUtil.CoerceInt(dateParts[i * 2]);
|
||||
var timeUnit = dateParts[i * 2 + 1];
|
||||
if (timeUnit.Contains("year"))
|
||||
timeSpan += TimeSpan.FromDays(365 * timeVal);
|
||||
else if (timeUnit.Contains("month"))
|
||||
timeSpan += TimeSpan.FromDays(30 * timeVal);
|
||||
else if (timeUnit.Contains("day"))
|
||||
timeSpan += TimeSpan.FromDays(timeVal);
|
||||
else if (timeUnit.Contains("hour"))
|
||||
timeSpan += TimeSpan.FromHours(timeVal);
|
||||
else if (timeUnit.Contains("min"))
|
||||
timeSpan += TimeSpan.FromMinutes(timeVal);
|
||||
}
|
||||
release.PublishDate = DateTime.SpecifyKind(DateTime.Now - timeSpan, DateTimeKind.Local);
|
||||
|
||||
var sizeEl = row.ChildElements.ElementAt(7);
|
||||
var sizeVal = ParseUtil.CoerceFloat(sizeEl.ChildNodes.First().NodeValue);
|
||||
var sizeUnit = sizeEl.ChildNodes.Last().NodeValue;
|
||||
|
||||
release.Size = ReleaseInfo.GetBytes(sizeUnit, sizeVal);
|
||||
|
||||
release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(9).Cq().Text());
|
||||
release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(10).Cq().Text()) + release.Seeders;
|
||||
|
||||
releases.Add(release);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, results, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
return releases.ToArray();
|
||||
}
|
||||
|
||||
public Task<byte[]> Download(Uri link)
|
||||
{
|
||||
return client.GetByteArrayAsync(link);
|
||||
}
|
||||
}
|
||||
}
|
@@ -14,6 +14,8 @@ namespace Jackett.Indexers
|
||||
{
|
||||
public class BitHdtv : IndexerInterface
|
||||
{
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get { return "BIT-HDTV"; }
|
||||
@@ -62,11 +64,10 @@ namespace Jackett.Indexers
|
||||
var config = new ConfigurationDataBasicLogin();
|
||||
config.LoadValuesFromJson(configJson);
|
||||
|
||||
var pairs = new Dictionary<string, string>
|
||||
{
|
||||
{ "username", config.Username.Value},
|
||||
{ "password", config.Password.Value}
|
||||
};
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", config.Username.Value },
|
||||
{ "password", config.Password.Value }
|
||||
};
|
||||
|
||||
var content = new FormUrlEncodedContent(pairs);
|
||||
|
||||
@@ -85,10 +86,7 @@ namespace Jackett.Indexers
|
||||
else
|
||||
{
|
||||
var configSaveData = new JObject();
|
||||
configSaveData["cookies"] = new JArray((
|
||||
from cookie in cookies.GetCookies(new Uri(BaseUrl)).Cast<Cookie>()
|
||||
select cookie.Name + ":" + cookie.Value
|
||||
).ToArray());
|
||||
cookies.DumpToJson(SiteLink, configSaveData);
|
||||
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
@@ -103,7 +101,7 @@ namespace Jackett.Indexers
|
||||
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
cookies.FillFromJson(new Uri(BaseUrl), (JArray)jsonConfig["cookies"]);
|
||||
cookies.FillFromJson(new Uri(BaseUrl), jsonConfig);
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
@@ -111,44 +109,51 @@ namespace Jackett.Indexers
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
|
||||
foreach (var title in query.ShowTitles ?? new string[] { string.Empty })
|
||||
{
|
||||
var searchString = title + " " + query.GetEpisodeSearchString();
|
||||
var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString);
|
||||
var results = await client.GetStringAsync(episodeSearchUrl);
|
||||
CQ dom = results;
|
||||
dom["#needseed"].Remove();
|
||||
var rows = dom["table[width='750'] > tbody"].Children();
|
||||
foreach (var row in rows.Skip(1))
|
||||
try
|
||||
{
|
||||
CQ dom = results;
|
||||
dom["#needseed"].Remove();
|
||||
var rows = dom["table[width='750'] > tbody"].Children();
|
||||
foreach (var row in rows.Skip(1))
|
||||
{
|
||||
|
||||
var release = new ReleaseInfo();
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
var qRow = row.Cq();
|
||||
var qLink = qRow.Children().ElementAt(2).Cq().Children("a").First();
|
||||
var qRow = row.Cq();
|
||||
var qLink = qRow.Children().ElementAt(2).Cq().Children("a").First();
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
release.Title = qLink.Attr("title");
|
||||
release.Description = release.Title;
|
||||
release.Guid = new Uri(BaseUrl + qLink.Attr("href"));
|
||||
release.Comments = release.Guid;
|
||||
release.Link = new Uri(string.Format(DownloadUrl, qLink.Attr("href").Split('=')[1]));
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
release.Title = qLink.Attr("title");
|
||||
release.Description = release.Title;
|
||||
release.Guid = new Uri(BaseUrl + qLink.Attr("href"));
|
||||
release.Comments = release.Guid;
|
||||
release.Link = new Uri(string.Format(DownloadUrl, qLink.Attr("href").Split('=')[1]));
|
||||
|
||||
var dateString = qRow.Children().ElementAt(5).Cq().Text().Trim();
|
||||
var pubDate = DateTime.ParseExact(dateString, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
|
||||
release.PublishDate = pubDate;
|
||||
var dateString = qRow.Children().ElementAt(5).Cq().Text().Trim();
|
||||
var pubDate = DateTime.ParseExact(dateString, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
|
||||
release.PublishDate = DateTime.SpecifyKind(pubDate, DateTimeKind.Local);
|
||||
|
||||
var sizeCol = qRow.Children().ElementAt(6);
|
||||
var sizeVal = sizeCol.ChildNodes[0].NodeValue;
|
||||
var sizeUnit = sizeCol.ChildNodes[2].NodeValue;
|
||||
release.Size = ReleaseInfo.GetBytes(sizeUnit, float.Parse(sizeVal));
|
||||
var sizeCol = qRow.Children().ElementAt(6);
|
||||
var sizeVal = sizeCol.ChildNodes[0].NodeValue;
|
||||
var sizeUnit = sizeCol.ChildNodes[2].NodeValue;
|
||||
release.Size = ReleaseInfo.GetBytes(sizeUnit, ParseUtil.CoerceFloat(sizeVal));
|
||||
|
||||
release.Seeders = int.Parse(qRow.Children().ElementAt(8).Cq().Text().Trim());
|
||||
release.Peers = int.Parse(qRow.Children().ElementAt(9).Cq().Text().Trim()) + release.Seeders;
|
||||
release.Seeders = ParseUtil.CoerceInt(qRow.Children().ElementAt(8).Cq().Text().Trim());
|
||||
release.Peers = ParseUtil.CoerceInt(qRow.Children().ElementAt(9).Cq().Text().Trim()) + release.Seeders;
|
||||
|
||||
releases.Add(release);
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, results, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,5 +164,7 @@ namespace Jackett.Indexers
|
||||
{
|
||||
return client.GetByteArrayAsync(link);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -18,8 +18,11 @@ namespace Jackett
|
||||
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 BmtvConfig()
|
||||
@@ -47,6 +50,7 @@ namespace Jackett
|
||||
HttpClient client;
|
||||
|
||||
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
public BitMeTV()
|
||||
{
|
||||
@@ -62,14 +66,16 @@ namespace Jackett
|
||||
}
|
||||
|
||||
public string DisplayName { get { return "BitMeTV"; } }
|
||||
|
||||
public string DisplayDescription { get { return "TV Episode specialty tracker"; } }
|
||||
|
||||
public Uri SiteLink { get { return new Uri(BaseUrl); } }
|
||||
|
||||
public bool IsConfigured { get; private set; }
|
||||
|
||||
public async Task<ConfigurationData> GetConfigurationForSetup()
|
||||
{
|
||||
var loginPage = await client.GetAsync(LoginUrl);
|
||||
await client.GetAsync(LoginUrl);
|
||||
var captchaImage = await client.GetByteArrayAsync(CaptchaUrl);
|
||||
var config = new BmtvConfig();
|
||||
config.CaptchaImage.Value = captchaImage;
|
||||
@@ -81,12 +87,11 @@ namespace Jackett
|
||||
var config = new BmtvConfig();
|
||||
config.LoadValuesFromJson(configJson);
|
||||
|
||||
var pairs = new Dictionary<string, string>
|
||||
{
|
||||
{ "username", config.Username.Value},
|
||||
{ "password", config.Password.Value},
|
||||
{ "secimage", config.CaptchaText.Value}
|
||||
};
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", config.Username.Value },
|
||||
{ "password", config.Password.Value },
|
||||
{ "secimage", config.CaptchaText.Value }
|
||||
};
|
||||
|
||||
var content = new FormUrlEncodedContent(pairs);
|
||||
|
||||
@@ -106,10 +111,7 @@ namespace Jackett
|
||||
else
|
||||
{
|
||||
var configSaveData = new JObject();
|
||||
configSaveData["cookies"] = new JArray((
|
||||
from cookie in cookies.GetCookies(new Uri(BaseUrl)).Cast<Cookie>()
|
||||
select cookie.Name + ":" + cookie.Value
|
||||
).ToArray());
|
||||
cookies.DumpToJson(SiteLink, configSaveData);
|
||||
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
@@ -120,7 +122,7 @@ namespace Jackett
|
||||
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
cookies.FillFromJson(new Uri(BaseUrl), (JArray)jsonConfig["cookies"]);
|
||||
cookies.FillFromJson(new Uri(BaseUrl), jsonConfig);
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
@@ -135,43 +137,55 @@ namespace Jackett
|
||||
var searchString = title + " " + query.GetEpisodeSearchString();
|
||||
var episodeSearchUrl = string.Format("{0}?search={1}&cat=0", SearchUrl, HttpUtility.UrlEncode(searchString));
|
||||
var results = await client.GetStringAsync(episodeSearchUrl);
|
||||
CQ dom = results;
|
||||
|
||||
var table = dom["tbody > tr > .latest"].Parent().Parent();
|
||||
|
||||
foreach (var row in table.Children().Skip(1))
|
||||
try
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
CQ dom = results;
|
||||
|
||||
CQ qRow = row.Cq();
|
||||
CQ qDetailsCol = row.ChildElements.ElementAt(1).Cq();
|
||||
CQ qLink = qDetailsCol.Children("a").First();
|
||||
var table = dom["tbody > tr > .latest"].Parent().Parent();
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
release.Comments = new Uri(BaseUrl + "/" + qLink.Attr("href"));
|
||||
release.Guid = release.Comments;
|
||||
release.Title = qLink.Attr("title");
|
||||
release.Description = release.Title;
|
||||
foreach (var row in table.Children().Skip(1))
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
//"Tuesday, June 11th 2013 at 03:52:53 AM" to...
|
||||
//"Tuesday June 11 2013 03:52:53 AM"
|
||||
var timestamp = qDetailsCol.Children("font").Text().Trim() + " ";
|
||||
var timeParts = new List<string>(timestamp.Replace(" at", "").Replace(",", "").Split(' '));
|
||||
timeParts[2] = Regex.Replace(timeParts[2], "[^0-9.]", "");
|
||||
var formattedTimeString = string.Join(" ", timeParts.ToArray()).Trim();
|
||||
release.PublishDate = DateTime.ParseExact(formattedTimeString, "dddd MMMM d yyyy hh:mm:ss tt", CultureInfo.InvariantCulture);
|
||||
CQ qDetailsCol = row.ChildElements.ElementAt(1).Cq();
|
||||
CQ qLink = qDetailsCol.Children("a").First();
|
||||
|
||||
release.Link = new Uri(BaseUrl + "/" + row.ChildElements.ElementAt(2).Cq().Children("a.index").Attr("href"));
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
release.Comments = new Uri(BaseUrl + "/" + qLink.Attr("href"));
|
||||
release.Guid = release.Comments;
|
||||
release.Title = qLink.Attr("title");
|
||||
release.Description = release.Title;
|
||||
|
||||
var sizeCol = row.ChildElements.ElementAt(6);
|
||||
var sizeVal = float.Parse(sizeCol.ChildNodes[0].NodeValue);
|
||||
var sizeUnit = sizeCol.ChildNodes[2].NodeValue;
|
||||
release.Size = ReleaseInfo.GetBytes(sizeUnit, sizeVal);
|
||||
//"Tuesday, June 11th 2013 at 03:52:53 AM" to...
|
||||
//"Tuesday June 11 2013 03:52:53 AM"
|
||||
var timestamp = qDetailsCol.Children("font").Text().Trim() + " ";
|
||||
var timeParts = new List<string>(timestamp.Replace(" at", "").Replace(",", "").Split(' '));
|
||||
timeParts[2] = Regex.Replace(timeParts[2], "[^0-9.]", "");
|
||||
var formattedTimeString = string.Join(" ", timeParts.ToArray()).Trim();
|
||||
var date = DateTime.ParseExact(formattedTimeString, "dddd MMMM d yyyy hh:mm:ss tt", CultureInfo.InvariantCulture);
|
||||
release.PublishDate = DateTime.SpecifyKind(date, DateTimeKind.Utc).ToLocalTime();
|
||||
|
||||
release.Seeders = int.Parse(row.ChildElements.ElementAt(8).Cq().Text());
|
||||
release.Peers = int.Parse(row.ChildElements.ElementAt(9).Cq().Text()) + release.Seeders;
|
||||
releases.Add(release);
|
||||
release.Link = new Uri(BaseUrl + "/" + row.ChildElements.ElementAt(2).Cq().Children("a.index").Attr("href"));
|
||||
|
||||
var sizeCol = row.ChildElements.ElementAt(6);
|
||||
var sizeVal = ParseUtil.CoerceFloat(sizeCol.ChildNodes[0].NodeValue);
|
||||
var sizeUnit = sizeCol.ChildNodes[2].NodeValue;
|
||||
release.Size = ReleaseInfo.GetBytes(sizeUnit, sizeVal);
|
||||
|
||||
release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(8).Cq().Text());
|
||||
release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(9).Cq().Text()) + release.Seeders;
|
||||
|
||||
//if (!release.Title.ToLower().Contains(title.ToLower()))
|
||||
// continue;
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, results, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,5 +197,6 @@ namespace Jackett
|
||||
{
|
||||
return client.GetByteArrayAsync(link);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -16,13 +16,14 @@ namespace Jackett
|
||||
{
|
||||
public class Freshon : IndexerInterface
|
||||
{
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
static string BaseUrl = "https://freshon.tv";
|
||||
static string LoginUrl = BaseUrl + "/login.php";
|
||||
static string LoginPostUrl = BaseUrl + "/login.php?action=makelogin";
|
||||
static string SearchUrl = BaseUrl + "/browse.php";
|
||||
|
||||
static string chromeUserAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36";
|
||||
static string chromeUserAgent = BrowserUtil.ChromeUserAgent;
|
||||
|
||||
CookieContainer cookies;
|
||||
HttpClientHandler handler;
|
||||
@@ -65,11 +66,10 @@ namespace Jackett
|
||||
var config = new ConfigurationDataBasicLogin();
|
||||
config.LoadValuesFromJson(configJson);
|
||||
|
||||
var pairs = new Dictionary<string, string>
|
||||
{
|
||||
{ "username", config.Username.Value},
|
||||
{ "password", config.Password.Value}
|
||||
};
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", config.Username.Value },
|
||||
{ "password", config.Password.Value }
|
||||
};
|
||||
|
||||
var content = new FormUrlEncodedContent(pairs);
|
||||
var message = CreateHttpRequest(new Uri(LoginPostUrl));
|
||||
@@ -90,10 +90,7 @@ namespace Jackett
|
||||
else
|
||||
{
|
||||
var configSaveData = new JObject();
|
||||
configSaveData["cookies"] = new JArray((
|
||||
from cookie in cookies.GetCookies(new Uri(BaseUrl)).Cast<Cookie>()
|
||||
select cookie.Name + ":" + cookie.Value
|
||||
).ToArray());
|
||||
cookies.DumpToJson(SiteLink, configSaveData);
|
||||
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
@@ -104,7 +101,7 @@ namespace Jackett
|
||||
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
cookies.FillFromJson(new Uri(BaseUrl), (JArray)jsonConfig["cookies"]);
|
||||
cookies.FillFromJson(new Uri(BaseUrl), jsonConfig);
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
@@ -136,45 +133,52 @@ namespace Jackett
|
||||
var request = CreateHttpRequest(new Uri(episodeSearchUrl));
|
||||
var response = await client.SendAsync(request);
|
||||
var results = await response.Content.ReadAsStringAsync();
|
||||
|
||||
CQ dom = results;
|
||||
|
||||
var rows = dom["#highlight > tbody > tr"];
|
||||
|
||||
foreach (var row in rows.Skip(1))
|
||||
try
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
CQ dom = results;
|
||||
|
||||
var qRow = row.Cq();
|
||||
var qLink = qRow.Find("a.torrent_name_link").First();
|
||||
var rows = dom["#highlight > tbody > tr"];
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
release.Title = qLink.Attr("title");
|
||||
release.Description = release.Title;
|
||||
release.Guid = new Uri(BaseUrl + qLink.Attr("href"));
|
||||
release.Comments = release.Guid;
|
||||
release.Link = new Uri(BaseUrl + qRow.Find("td.table_links > a").First().Attr("href"));
|
||||
foreach (var row in rows.Skip(1))
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
DateTime pubDate;
|
||||
var dateString = qRow.Find("td.table_added").Text().Trim();
|
||||
if (dateString.StartsWith("Today "))
|
||||
pubDate = (DateTime.UtcNow + TimeSpan.Parse(dateString.Split(' ')[1])).ToLocalTime();
|
||||
else if (dateString.StartsWith("Yesterday "))
|
||||
pubDate = (DateTime.UtcNow + TimeSpan.Parse(dateString.Split(' ')[1]) - TimeSpan.FromDays(1)).ToLocalTime();
|
||||
else
|
||||
pubDate = DateTime.ParseExact(dateString, "d-MMM-yyyy HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToLocalTime();
|
||||
release.PublishDate = pubDate;
|
||||
var qRow = row.Cq();
|
||||
var qLink = qRow.Find("a.torrent_name_link").First();
|
||||
|
||||
release.Seeders = int.Parse(qRow.Find("td.table_seeders").Text().Trim());
|
||||
release.Peers = int.Parse(qRow.Find("td.table_leechers").Text().Trim()) + release.Seeders;
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
release.Title = qLink.Attr("title");
|
||||
release.Description = release.Title;
|
||||
release.Guid = new Uri(BaseUrl + qLink.Attr("href"));
|
||||
release.Comments = release.Guid;
|
||||
release.Link = new Uri(BaseUrl + qRow.Find("td.table_links > a").First().Attr("href"));
|
||||
|
||||
var sizeCol = qRow.Find("td.table_size")[0];
|
||||
var sizeVal = float.Parse(sizeCol.ChildNodes[0].NodeValue.Trim());
|
||||
var sizeUnit = sizeCol.ChildNodes[2].NodeValue.Trim();
|
||||
release.Size = ReleaseInfo.GetBytes(sizeUnit, sizeVal);
|
||||
DateTime pubDate;
|
||||
var dateString = qRow.Find("td.table_added").Text().Trim();
|
||||
if (dateString.StartsWith("Today "))
|
||||
pubDate = (DateTime.UtcNow + TimeSpan.Parse(dateString.Split(' ')[1])).ToLocalTime();
|
||||
else if (dateString.StartsWith("Yesterday "))
|
||||
pubDate = (DateTime.UtcNow + TimeSpan.Parse(dateString.Split(' ')[1]) - TimeSpan.FromDays(1)).ToLocalTime();
|
||||
else
|
||||
pubDate = DateTime.ParseExact(dateString, "d-MMM-yyyy HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToLocalTime();
|
||||
release.PublishDate = pubDate;
|
||||
|
||||
releases.Add(release);
|
||||
release.Seeders = ParseUtil.CoerceInt(qRow.Find("td.table_seeders").Text().Trim());
|
||||
release.Peers = ParseUtil.CoerceInt(qRow.Find("td.table_leechers").Text().Trim()) + release.Seeders;
|
||||
|
||||
var sizeCol = qRow.Find("td.table_size")[0];
|
||||
var sizeVal = ParseUtil.CoerceFloat(sizeCol.ChildNodes[0].NodeValue.Trim());
|
||||
var sizeUnit = sizeCol.ChildNodes[2].NodeValue.Trim();
|
||||
release.Size = ReleaseInfo.GetBytes(sizeUnit, sizeVal);
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, results, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
|
243
src/Jackett/Indexers/HDTorrents.cs
Normal file
@@ -0,0 +1,243 @@
|
||||
using CsQuery;
|
||||
using Newtonsoft.Json.Linq;
|
||||
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 HDTorrents : IndexerInterface
|
||||
{
|
||||
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
const string DefaultUrl = "https://hd-torrents.org";
|
||||
string BaseUrl = DefaultUrl;
|
||||
static string chromeUserAgent = BrowserUtil.ChromeUserAgent;
|
||||
private string SearchUrl = "https://hd-torrents.org/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}";
|
||||
private static string LoginUrl = DefaultUrl + "/login.php";
|
||||
private static string LoginPostUrl = DefaultUrl + "/login.php?returnto=index.php";
|
||||
private const int MAXPAGES = 3;
|
||||
|
||||
CookieContainer cookies;
|
||||
HttpClientHandler handler;
|
||||
HttpClient client;
|
||||
|
||||
public HDTorrents()
|
||||
{
|
||||
IsConfigured = false;
|
||||
cookies = new CookieContainer();
|
||||
handler = new HttpClientHandler
|
||||
{
|
||||
CookieContainer = cookies,
|
||||
AllowAutoRedirect = true,
|
||||
UseCookies = true,
|
||||
};
|
||||
client = new HttpClient(handler);
|
||||
}
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get { return "HD-Torrents"; }
|
||||
}
|
||||
|
||||
public string DisplayDescription
|
||||
{
|
||||
get { return "HD-Torrents is a private torrent website with HD torrents and strict rules on their content."; }
|
||||
}
|
||||
|
||||
public Uri SiteLink
|
||||
{
|
||||
get { return new Uri(DefaultUrl); }
|
||||
}
|
||||
|
||||
public bool IsConfigured
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
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(chromeUserAgent);
|
||||
return message;
|
||||
}
|
||||
|
||||
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 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");
|
||||
|
||||
var response = await client.SendAsync(loginRequest);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (!responseContent.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);
|
||||
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
|
||||
IsConfigured = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
cookies.FillFromJson(SiteLink, jsonConfig);
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query, string baseUrl)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
List<string> searchurls = new List<string>();
|
||||
|
||||
foreach (var title in query.ShowTitles ?? new string[] { string.Empty })
|
||||
{
|
||||
var searchString = title + " " + query.GetEpisodeSearchString();
|
||||
for (int page = 0; page < MAXPAGES; page++)
|
||||
searchurls.Add(string.Format(SearchUrl, HttpUtility.UrlEncode(searchString.Trim()), page));
|
||||
}
|
||||
|
||||
foreach (string SearchUrl in searchurls)
|
||||
{
|
||||
var results = await client.GetStringAsync(SearchUrl);
|
||||
try
|
||||
{
|
||||
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
|
||||
{
|
||||
rowCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
release = new ReleaseInfo();
|
||||
long? size;
|
||||
|
||||
release.Title = qRow.Find("td.mainblockcontent b a").Text();
|
||||
release.Description = release.Title;
|
||||
|
||||
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))
|
||||
{
|
||||
release.Imdb = imdb;
|
||||
}
|
||||
}
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
|
||||
release.MagnetUri = new Uri(DefaultUrl + "/" + qRow.Find("td.mainblockcontent").Get(3).FirstChild.GetAttribute("href"));
|
||||
|
||||
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.Peers = peers + release.Seeders;
|
||||
}
|
||||
}
|
||||
|
||||
string fullSize = qRow.Find("td.mainblockcontent").Get(6).InnerText;
|
||||
string[] sizeSplit = fullSize.Split(' ');
|
||||
switch (sizeSplit[1].ToLower())
|
||||
{
|
||||
case "kb":
|
||||
size = ReleaseInfo.BytesFromKB(ParseUtil.CoerceFloat(sizeSplit[0]));
|
||||
break;
|
||||
case "mb":
|
||||
size = ReleaseInfo.BytesFromMB(ParseUtil.CoerceFloat(sizeSplit[0]));
|
||||
break;
|
||||
case "gb":
|
||||
size = ReleaseInfo.BytesFromGB(ParseUtil.CoerceFloat(sizeSplit[0]));
|
||||
break;
|
||||
default:
|
||||
size = null;
|
||||
break;
|
||||
}
|
||||
release.Size = size;
|
||||
|
||||
release.Link = new Uri(DefaultUrl + "/" + qRow.Find("td.mainblockcontent b a").Attr("href"));
|
||||
release.Guid = release.Link;
|
||||
|
||||
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);
|
||||
|
||||
release.Comments = new Uri(DefaultUrl + "/" + qRow.Find("td.mainblockcontent").Get(2).FirstChild.GetAttribute("href"));
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, results, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return releases.ToArray();
|
||||
}
|
||||
|
||||
public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
return await PerformQuery(query, BaseUrl);
|
||||
}
|
||||
|
||||
public Task<byte[]> Download(Uri link)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,6 +2,7 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
@@ -14,7 +15,8 @@ namespace Jackett.Indexers
|
||||
public class IPTorrents : IndexerInterface
|
||||
{
|
||||
|
||||
public event Action<IndexerInterface, Newtonsoft.Json.Linq.JToken> OnSaveConfigurationRequested;
|
||||
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
public string DisplayName { get { return "IPTorrents"; } }
|
||||
|
||||
@@ -24,8 +26,7 @@ namespace Jackett.Indexers
|
||||
|
||||
public bool IsConfigured { get; private set; }
|
||||
|
||||
static string chromeUserAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36";
|
||||
|
||||
static string chromeUserAgent = BrowserUtil.ChromeUserAgent;
|
||||
|
||||
static string BaseUrl = "https://iptorrents.com";
|
||||
|
||||
@@ -62,11 +63,10 @@ namespace Jackett.Indexers
|
||||
var config = new ConfigurationDataBasicLogin();
|
||||
config.LoadValuesFromJson(configJson);
|
||||
|
||||
var pairs = new Dictionary<string, string>
|
||||
{
|
||||
{ "username", config.Username.Value},
|
||||
{ "password", config.Password.Value}
|
||||
};
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", config.Username.Value },
|
||||
{ "password", config.Password.Value }
|
||||
};
|
||||
|
||||
var content = new FormUrlEncodedContent(pairs);
|
||||
var message = new HttpRequestMessage();
|
||||
@@ -89,10 +89,7 @@ namespace Jackett.Indexers
|
||||
else
|
||||
{
|
||||
var configSaveData = new JObject();
|
||||
configSaveData["cookies"] = new JArray((
|
||||
from cookie in cookies.GetCookies(new Uri(BaseUrl)).Cast<Cookie>()
|
||||
select cookie.Name + ":" + cookie.Value
|
||||
).ToArray());
|
||||
cookies.DumpToJson(SiteLink, configSaveData);
|
||||
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
@@ -113,7 +110,7 @@ namespace Jackett.Indexers
|
||||
|
||||
public void LoadFromSavedConfiguration(Newtonsoft.Json.Linq.JToken jsonConfig)
|
||||
{
|
||||
cookies.FillFromJson(new Uri(BaseUrl), (JArray)jsonConfig["cookies"]);
|
||||
cookies.FillFromJson(new Uri(BaseUrl), jsonConfig);
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
@@ -133,55 +130,63 @@ namespace Jackett.Indexers
|
||||
var response = await client.SendAsync(request);
|
||||
var results = await response.Content.ReadAsStringAsync();
|
||||
|
||||
CQ dom = results;
|
||||
|
||||
var rows = dom["table.torrents > tbody > tr"];
|
||||
foreach (var row in rows.Skip(1))
|
||||
try
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
CQ dom = results;
|
||||
|
||||
var qRow = row.Cq();
|
||||
var rows = dom["table.torrents > tbody > tr"];
|
||||
foreach (var row in rows.Skip(1))
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
var qTitleLink = qRow.Find("a.t_title").First();
|
||||
release.Title = qTitleLink.Text().Trim();
|
||||
release.Description = release.Title;
|
||||
release.Guid = new Uri(BaseUrl + qTitleLink.Attr("href"));
|
||||
release.Comments = release.Guid;
|
||||
var qRow = row.Cq();
|
||||
|
||||
DateTime pubDate;
|
||||
var descString = qRow.Find(".t_ctime").Text();
|
||||
var dateString = descString.Split('|').Last().Trim();
|
||||
dateString = dateString.Split(new string[] { " by " }, StringSplitOptions.None)[0];
|
||||
var dateValue = float.Parse(dateString.Split(' ')[0]);
|
||||
var dateUnit = dateString.Split(' ')[1];
|
||||
if (dateUnit.Contains("minute"))
|
||||
pubDate = DateTime.Now - TimeSpan.FromMinutes(dateValue);
|
||||
else if (dateUnit.Contains("hour"))
|
||||
pubDate = DateTime.Now - TimeSpan.FromHours(dateValue);
|
||||
else if (dateUnit.Contains("day"))
|
||||
pubDate = DateTime.Now - TimeSpan.FromDays(dateValue);
|
||||
else if (dateUnit.Contains("week"))
|
||||
pubDate = DateTime.Now - TimeSpan.FromDays(7 * dateValue);
|
||||
else if (dateUnit.Contains("month"))
|
||||
pubDate = DateTime.Now - TimeSpan.FromDays(30 * dateValue);
|
||||
else if (dateUnit.Contains("year"))
|
||||
pubDate = DateTime.Now - TimeSpan.FromDays(365 * dateValue);
|
||||
else
|
||||
pubDate = DateTime.MinValue;
|
||||
release.PublishDate = pubDate;
|
||||
var qTitleLink = qRow.Find("a.t_title").First();
|
||||
release.Title = qTitleLink.Text().Trim();
|
||||
release.Description = release.Title;
|
||||
release.Guid = new Uri(BaseUrl + qTitleLink.Attr("href"));
|
||||
release.Comments = release.Guid;
|
||||
|
||||
var qLink = row.ChildElements.ElementAt(3).Cq().Children("a");
|
||||
release.Link = new Uri(BaseUrl + qLink.Attr("href"));
|
||||
DateTime pubDate;
|
||||
var descString = qRow.Find(".t_ctime").Text();
|
||||
var dateString = descString.Split('|').Last().Trim();
|
||||
dateString = dateString.Split(new string[] { " by " }, StringSplitOptions.None)[0];
|
||||
var dateValue = ParseUtil.CoerceFloat(dateString.Split(' ')[0]);
|
||||
var dateUnit = dateString.Split(' ')[1];
|
||||
if (dateUnit.Contains("minute"))
|
||||
pubDate = DateTime.Now - TimeSpan.FromMinutes(dateValue);
|
||||
else if (dateUnit.Contains("hour"))
|
||||
pubDate = DateTime.Now - TimeSpan.FromHours(dateValue);
|
||||
else if (dateUnit.Contains("day"))
|
||||
pubDate = DateTime.Now - TimeSpan.FromDays(dateValue);
|
||||
else if (dateUnit.Contains("week"))
|
||||
pubDate = DateTime.Now - TimeSpan.FromDays(7 * dateValue);
|
||||
else if (dateUnit.Contains("month"))
|
||||
pubDate = DateTime.Now - TimeSpan.FromDays(30 * dateValue);
|
||||
else if (dateUnit.Contains("year"))
|
||||
pubDate = DateTime.Now - TimeSpan.FromDays(365 * dateValue);
|
||||
else
|
||||
pubDate = DateTime.MinValue;
|
||||
release.PublishDate = pubDate;
|
||||
|
||||
var sizeStr = row.ChildElements.ElementAt(5).Cq().Text().Trim();
|
||||
var sizeVal = float.Parse(sizeStr.Split(' ')[0]);
|
||||
var sizeUnit = sizeStr.Split(' ')[1];
|
||||
release.Size = ReleaseInfo.GetBytes(sizeUnit, sizeVal);
|
||||
var qLink = row.ChildElements.ElementAt(3).Cq().Children("a");
|
||||
release.Link = new Uri(BaseUrl + qLink.Attr("href"));
|
||||
|
||||
release.Seeders = int.Parse(qRow.Find(".t_seeders").Text().Trim());
|
||||
release.Peers = int.Parse(qRow.Find(".t_leechers").Text().Trim()) + release.Seeders;
|
||||
var sizeStr = row.ChildElements.ElementAt(5).Cq().Text().Trim();
|
||||
var sizeVal = ParseUtil.CoerceFloat(sizeStr.Split(' ')[0]);
|
||||
var sizeUnit = sizeStr.Split(' ')[1];
|
||||
release.Size = ReleaseInfo.GetBytes(sizeUnit, sizeVal);
|
||||
|
||||
releases.Add(release);
|
||||
release.Seeders = ParseUtil.CoerceInt(qRow.Find(".t_seeders").Text().Trim());
|
||||
release.Peers = ParseUtil.CoerceInt(qRow.Find(".t_leechers").Text().Trim()) + release.Seeders;
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, results, ex);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -197,5 +202,8 @@ namespace Jackett.Indexers
|
||||
var bytes = await response.Content.ReadAsByteArrayAsync();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
@@ -30,6 +31,7 @@ namespace Jackett.Indexers
|
||||
|
||||
|
||||
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
public bool IsConfigured { get; private set; }
|
||||
|
||||
@@ -47,6 +49,9 @@ namespace Jackett.Indexers
|
||||
HttpClientHandler handler;
|
||||
HttpClient client;
|
||||
|
||||
string cookieHeader;
|
||||
int retries = 3;
|
||||
|
||||
public MoreThanTV()
|
||||
{
|
||||
IsConfigured = false;
|
||||
@@ -68,22 +73,38 @@ namespace Jackett.Indexers
|
||||
|
||||
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},
|
||||
{ "login", "Log in" },
|
||||
{ "keeplogged", "1" }
|
||||
};
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", config.Username.Value },
|
||||
{ "password", config.Password.Value },
|
||||
{ "login", "Log in" },
|
||||
{ "keeplogged", "1" }
|
||||
};
|
||||
|
||||
var content = new FormUrlEncodedContent(pairs);
|
||||
|
||||
var response = await client.PostAsync(LoginUrl, content);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
string responseContent;
|
||||
|
||||
var configSaveData = new JObject();
|
||||
|
||||
if (Program.IsWindows)
|
||||
{
|
||||
// If Windows use .net http
|
||||
var response = await client.PostAsync(LoginUrl, content);
|
||||
responseContent = await response.Content.ReadAsStringAsync();
|
||||
cookies.DumpToJson(SiteLink, configSaveData);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// If UNIX system use curl
|
||||
var response = await CurlHelper.PostAsync(LoginUrl, pairs);
|
||||
responseContent = Encoding.UTF8.GetString(response.Content);
|
||||
cookieHeader = response.CookieHeader;
|
||||
configSaveData["cookie_header"] = cookieHeader;
|
||||
}
|
||||
|
||||
if (!responseContent.Contains("logout.php?"))
|
||||
{
|
||||
@@ -91,15 +112,10 @@ namespace Jackett.Indexers
|
||||
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["cookies"] = new JArray((
|
||||
from cookie in cookies.GetCookies(new Uri(BaseUrl)).Cast<Cookie>()
|
||||
select cookie.Name + ":" + cookie.Value
|
||||
).ToArray());
|
||||
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
|
||||
@@ -107,13 +123,14 @@ namespace Jackett.Indexers
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFromSavedConfiguration(Newtonsoft.Json.Linq.JToken jsonConfig)
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
cookies.FillFromJson(new Uri(BaseUrl), (JArray)jsonConfig["cookies"]);
|
||||
cookies.FillFromJson(SiteLink, jsonConfig);
|
||||
cookieHeader = cookies.GetCookieHeader(SiteLink);
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
void FillReleaseInfoFromJson(ReleaseInfo release, JObject r)
|
||||
static void FillReleaseInfoFromJson(ReleaseInfo release, JObject r)
|
||||
{
|
||||
var id = r["torrentId"];
|
||||
release.Size = (long)r["size"];
|
||||
@@ -133,36 +150,61 @@ namespace Jackett.Indexers
|
||||
|
||||
var searchString = title + " " + query.GetEpisodeSearchString();
|
||||
var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString);
|
||||
var results = await client.GetStringAsync(episodeSearchUrl);
|
||||
var json = JObject.Parse(results);
|
||||
foreach (JObject r in json["response"]["results"])
|
||||
|
||||
string results;
|
||||
if (Program.IsWindows)
|
||||
{
|
||||
results = await client.GetStringAsync(episodeSearchUrl, retries);
|
||||
}
|
||||
else
|
||||
{
|
||||
var response = await CurlHelper.GetAsync(episodeSearchUrl, cookieHeader);
|
||||
results = Encoding.UTF8.GetString(response.Content);
|
||||
}
|
||||
try
|
||||
{
|
||||
|
||||
var pubDate = UnixTimestampToDateTime(double.Parse((string)r["groupTime"]));
|
||||
var groupName = (string)r["groupName"];
|
||||
|
||||
if (r["torrents"] is JArray)
|
||||
var json = JObject.Parse(results);
|
||||
foreach (JObject r in json["response"]["results"])
|
||||
{
|
||||
foreach (JObject t in r["torrents"])
|
||||
DateTime pubDate = DateTime.MinValue;
|
||||
double dateNum;
|
||||
if (double.TryParse((string)r["groupTime"], out dateNum))
|
||||
{
|
||||
pubDate = UnixTimestampToDateTime(dateNum);
|
||||
pubDate = DateTime.SpecifyKind(pubDate, DateTimeKind.Utc).ToLocalTime();
|
||||
}
|
||||
|
||||
var groupName = (string)r["groupName"];
|
||||
|
||||
if (r["torrents"] is JArray)
|
||||
{
|
||||
foreach (JObject t in r["torrents"])
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
release.PublishDate = pubDate;
|
||||
release.Title = groupName;
|
||||
release.Description = groupName;
|
||||
FillReleaseInfoFromJson(release, t);
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
release.PublishDate = pubDate;
|
||||
release.Title = groupName;
|
||||
release.Description = groupName;
|
||||
FillReleaseInfoFromJson(release, t);
|
||||
FillReleaseInfoFromJson(release, r);
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
release.PublishDate = pubDate;
|
||||
release.Title = groupName;
|
||||
release.Description = groupName;
|
||||
FillReleaseInfoFromJson(release, r);
|
||||
releases.Add(release);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, results, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,9 +218,18 @@ namespace Jackett.Indexers
|
||||
return new DateTime(unixStart.Ticks + unixTimeStampInTicks);
|
||||
}
|
||||
|
||||
public Task<byte[]> Download(Uri link)
|
||||
public async Task<byte[]> Download(Uri link)
|
||||
{
|
||||
return client.GetByteArrayAsync(link);
|
||||
if (Program.IsWindows)
|
||||
{
|
||||
return await client.GetByteArrayAsync(link);
|
||||
}
|
||||
else
|
||||
{
|
||||
var response = await CurlHelper.GetAsync(link.ToString(), cookieHeader);
|
||||
return response.Content;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
166
src/Jackett/Indexers/Rarbg.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using CsQuery;
|
||||
using Newtonsoft.Json.Linq;
|
||||
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 : IndexerInterface
|
||||
{
|
||||
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get { return "RARBG"; }
|
||||
}
|
||||
|
||||
public string DisplayDescription
|
||||
{
|
||||
get { return DisplayName; }
|
||||
}
|
||||
|
||||
public Uri SiteLink
|
||||
{
|
||||
get { return new Uri("https://rarbg.com"); }
|
||||
}
|
||||
|
||||
public bool IsConfigured { get; private set; }
|
||||
|
||||
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";
|
||||
|
||||
static string chromeUserAgent = BrowserUtil.ChromeUserAgent;
|
||||
|
||||
string BaseUrl;
|
||||
|
||||
CookieContainer cookies;
|
||||
HttpClientHandler handler;
|
||||
HttpClient client;
|
||||
|
||||
public Rarbg()
|
||||
{
|
||||
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(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;
|
||||
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, 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(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.SearchTerm, 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)
|
||||
{
|
||||
OnResultParsingError(this, results, ex);
|
||||
}
|
||||
return releases.ToArray();
|
||||
|
||||
}
|
||||
|
||||
public Task<byte[]> Download(Uri link)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
204
src/Jackett/Indexers/SceneAccess.cs
Normal file
@@ -0,0 +1,204 @@
|
||||
using CsQuery;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
class SceneAccess : IndexerInterface
|
||||
{
|
||||
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get { return "SceneAccess"; }
|
||||
}
|
||||
|
||||
public string DisplayDescription
|
||||
{
|
||||
get { return "Your gateway to the scene"; }
|
||||
}
|
||||
|
||||
public Uri SiteLink
|
||||
{
|
||||
get { return new Uri(BaseUrl); }
|
||||
}
|
||||
|
||||
const string BaseUrl = "https://sceneaccess.eu";
|
||||
const string LoginUrl = BaseUrl + "/login";
|
||||
const string SearchUrl = BaseUrl + "/{0}?method=1&c{1}=1&search={2}";
|
||||
|
||||
|
||||
public bool IsConfigured
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
CookieContainer cookies;
|
||||
HttpClientHandler handler;
|
||||
HttpClient client;
|
||||
string cookieHeader;
|
||||
|
||||
public SceneAccess()
|
||||
{
|
||||
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 ConfigurationDataBasicLogin();
|
||||
return Task.FromResult<ConfigurationData>(config);
|
||||
}
|
||||
|
||||
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 },
|
||||
{ "submit", "come on in" }
|
||||
};
|
||||
|
||||
var content = new FormUrlEncodedContent(pairs);
|
||||
|
||||
string responseContent;
|
||||
var configSaveData = new JObject();
|
||||
|
||||
if (Program.IsWindows)
|
||||
{
|
||||
// If Windows use .net http
|
||||
var response = await client.PostAsync(LoginUrl, content);
|
||||
responseContent = await response.Content.ReadAsStringAsync();
|
||||
cookies.DumpToJson(SiteLink, configSaveData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If UNIX system use curl
|
||||
var response = await CurlHelper.PostAsync(LoginUrl, pairs);
|
||||
responseContent = Encoding.UTF8.GetString(response.Content);
|
||||
cookieHeader = response.CookieHeader;
|
||||
configSaveData["cookie_header"] = cookieHeader;
|
||||
}
|
||||
|
||||
if (!responseContent.Contains("nav_profile"))
|
||||
{
|
||||
CQ dom = responseContent;
|
||||
var messageEl = dom["#login_box_desc"];
|
||||
var errorMessage = messageEl.Text().Trim();
|
||||
throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
|
||||
IsConfigured = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
cookies.FillFromJson(new Uri(BaseUrl), jsonConfig);
|
||||
cookieHeader = cookies.GetCookieHeader(SiteLink);
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
foreach (var title in query.ShowTitles ?? new string[] { string.Empty })
|
||||
{
|
||||
var searchString = title + " " + 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);
|
||||
|
||||
string results;
|
||||
if (Program.IsWindows)
|
||||
{
|
||||
results = await client.GetStringAsync(searchUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
var response = await CurlHelper.GetAsync(searchUrl, cookieHeader);
|
||||
results = Encoding.UTF8.GetString(response.Content);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
CQ dom = results;
|
||||
var rows = dom["#torrents-table > tbody > tr.tt_row"];
|
||||
foreach (var row in rows)
|
||||
{
|
||||
CQ qRow = row.Cq();
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 129600;
|
||||
release.Title = qRow.Find(".ttr_name > a").Text();
|
||||
release.Description = release.Title;
|
||||
release.Guid = new Uri(BaseUrl + "/" + qRow.Find(".ttr_name > a").Attr("href"));
|
||||
release.Comments = release.Guid;
|
||||
release.Link = new Uri(BaseUrl + "/" + qRow.Find(".td_dl > a").Attr("href"));
|
||||
|
||||
var sizeStr = qRow.Find(".ttr_size").Contents()[0].NodeValue;
|
||||
var sizeParts = sizeStr.Split(' ');
|
||||
release.Size = ReleaseInfo.GetBytes(sizeParts[1], ParseUtil.CoerceFloat(sizeParts[0]));
|
||||
|
||||
var timeStr = qRow.Find(".ttr_added").Text();
|
||||
DateTime time;
|
||||
if (DateTime.TryParseExact(timeStr, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out time))
|
||||
{
|
||||
release.PublishDate = time;
|
||||
}
|
||||
|
||||
release.Seeders = ParseUtil.CoerceInt(qRow.Find(".ttr_seeders").Text());
|
||||
release.Peers = ParseUtil.CoerceInt(qRow.Find(".ttr_leechers").Text()) + release.Seeders;
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, results, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
return releases.ToArray();
|
||||
}
|
||||
|
||||
public async Task<byte[]> Download(Uri link)
|
||||
{
|
||||
if (Program.IsWindows)
|
||||
{
|
||||
return await client.GetByteArrayAsync(link);
|
||||
}
|
||||
else
|
||||
{
|
||||
var response = await CurlHelper.GetAsync(link.ToString(), cookieHeader);
|
||||
return response.Content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
179
src/Jackett/Indexers/SceneTime.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using CsQuery;
|
||||
using Newtonsoft.Json.Linq;
|
||||
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 SceneTime : IndexerInterface
|
||||
{
|
||||
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get { return "SceneTime"; }
|
||||
}
|
||||
|
||||
public string DisplayDescription
|
||||
{
|
||||
get { return "Always on time"; }
|
||||
}
|
||||
|
||||
public Uri SiteLink
|
||||
{
|
||||
get { return new Uri(BaseUrl); }
|
||||
}
|
||||
|
||||
public bool IsConfigured { get; private set; }
|
||||
|
||||
const string BaseUrl = "https://www.scenetime.com";
|
||||
const string LoginUrl = BaseUrl + "/takelogin.php";
|
||||
const string SearchUrl = BaseUrl + "/browse_API.php";
|
||||
const string DownloadUrl = BaseUrl + "/download.php/{0}/download.torrent";
|
||||
|
||||
CookieContainer cookies;
|
||||
HttpClientHandler handler;
|
||||
HttpClient client;
|
||||
|
||||
|
||||
public SceneTime()
|
||||
{
|
||||
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 ConfigurationDataBasicLogin();
|
||||
return Task.FromResult<ConfigurationData>(config);
|
||||
}
|
||||
|
||||
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 }
|
||||
};
|
||||
|
||||
var content = new FormUrlEncodedContent(pairs);
|
||||
|
||||
var response = await client.PostAsync(LoginUrl, content);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (!responseContent.Contains("logout.php"))
|
||||
{
|
||||
CQ dom = responseContent;
|
||||
var errorMessage = dom["td.text"].Text().Trim();
|
||||
throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
|
||||
}
|
||||
else
|
||||
{
|
||||
var configSaveData = new JObject();
|
||||
cookies.DumpToJson(SiteLink, configSaveData);
|
||||
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
|
||||
IsConfigured = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
cookies.FillFromJson(new Uri(BaseUrl), jsonConfig);
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
FormUrlEncodedContent GetSearchFormData(string searchString)
|
||||
{
|
||||
var pairs = 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)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
foreach (var title in query.ShowTitles ?? new string[] { string.Empty })
|
||||
{
|
||||
var searchString = title + " " + query.GetEpisodeSearchString();
|
||||
|
||||
var searchContent = GetSearchFormData(searchString);
|
||||
var response = await client.PostAsync(SearchUrl, searchContent);
|
||||
var results = await response.Content.ReadAsStringAsync();
|
||||
|
||||
try
|
||||
{
|
||||
CQ dom = results;
|
||||
var rows = dom["tr.browse"];
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
|
||||
var descCol = row.ChildElements.ElementAt(1);
|
||||
var qDescCol = descCol.Cq();
|
||||
var qLink = qDescCol.Find("a");
|
||||
release.Title = qLink.Text();
|
||||
release.Description = release.Title;
|
||||
release.Comments = new Uri(BaseUrl + "/" + qLink.Attr("href"));
|
||||
release.Guid = release.Comments;
|
||||
var torrentId = qLink.Attr("href").Split('=')[1];
|
||||
release.Link = new Uri(string.Format(DownloadUrl, torrentId));
|
||||
|
||||
var dateStr = descCol.ChildNodes.Last().NodeValue.Trim();
|
||||
var euDate = DateTime.ParseExact(dateStr, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
|
||||
var localDate = TimeZoneInfo.ConvertTimeToUtc(euDate, TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time")).ToLocalTime();
|
||||
release.PublishDate = localDate;
|
||||
|
||||
var sizeNodes = row.ChildElements.ElementAt(3).ChildNodes;
|
||||
var sizeVal = sizeNodes.First().NodeValue;
|
||||
var sizeUnit = sizeNodes.Last().NodeValue;
|
||||
release.Size = ReleaseInfo.GetBytes(sizeUnit, ParseUtil.CoerceFloat(sizeVal));
|
||||
|
||||
release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(4).Cq().Text().Trim());
|
||||
release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(5).Cq().Text().Trim()) + release.Seeders;
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, results, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
return releases.ToArray();
|
||||
}
|
||||
|
||||
public Task<byte[]> Download(Uri link)
|
||||
{
|
||||
return client.GetByteArrayAsync(link);
|
||||
}
|
||||
}
|
||||
}
|
175
src/Jackett/Indexers/ShowRSS.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
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;
|
||||
using System.Xml;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class ShowRSS : IndexerInterface
|
||||
{
|
||||
public event Action<IndexerInterface, Newtonsoft.Json.Linq.JToken> OnSaveConfigurationRequested;
|
||||
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get { return "ShowRSS"; }
|
||||
}
|
||||
|
||||
public string DisplayDescription
|
||||
{
|
||||
get { return "showRSS is a service that allows you to keep track of your favorite TV shows"; }
|
||||
}
|
||||
|
||||
public Uri SiteLink
|
||||
{
|
||||
get { return new Uri(DefaultUrl); }
|
||||
}
|
||||
|
||||
const string DefaultUrl = "http://showrss.info";
|
||||
const string searchAllUrl = DefaultUrl + "/feeds/all.rss";
|
||||
string BaseUrl;
|
||||
static string chromeUserAgent = BrowserUtil.ChromeUserAgent;
|
||||
|
||||
CookieContainer cookies;
|
||||
HttpClientHandler handler;
|
||||
HttpClient client;
|
||||
|
||||
public bool IsConfigured
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public ShowRSS()
|
||||
{
|
||||
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(DefaultUrl);
|
||||
return Task.FromResult<ConfigurationData>(config);
|
||||
}
|
||||
|
||||
public async Task ApplyConfiguration(Newtonsoft.Json.Linq.JToken configJson)
|
||||
{
|
||||
var config = new ConfigurationDataUrl(DefaultUrl);
|
||||
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;
|
||||
|
||||
var configSaveData = new JObject();
|
||||
configSaveData["base_url"] = BaseUrl;
|
||||
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
public void LoadFromSavedConfiguration(Newtonsoft.Json.Linq.JToken jsonConfig)
|
||||
{
|
||||
BaseUrl = (string)jsonConfig["base_url"];
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
return await PerformQuery(query, BaseUrl);
|
||||
}
|
||||
|
||||
public Task<byte[]> Download(Uri link)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private WebClient getWebClient()
|
||||
{
|
||||
WebClient wc = new WebClient();
|
||||
WebHeaderCollection headers = new WebHeaderCollection();
|
||||
headers.Add("User-Agent", chromeUserAgent);
|
||||
wc.Headers = headers;
|
||||
return wc;
|
||||
}
|
||||
|
||||
async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query, string baseUrl)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
foreach (var title in query.ShowTitles ?? new string[] { string.Empty })
|
||||
{
|
||||
var searchString = title + " " + query.GetEpisodeSearchString();
|
||||
var episodeSearchUrl = string.Format(searchAllUrl);
|
||||
|
||||
XmlDocument xmlDoc = new XmlDocument();
|
||||
string xml = string.Empty;
|
||||
WebClient wc = getWebClient();
|
||||
|
||||
try
|
||||
{
|
||||
using (wc)
|
||||
{
|
||||
xml = wc.DownloadString(episodeSearchUrl);
|
||||
xmlDoc.LoadXml(xml);
|
||||
}
|
||||
|
||||
ReleaseInfo release;
|
||||
string serie_title;
|
||||
|
||||
foreach (XmlNode node in xmlDoc.GetElementsByTagName("item"))
|
||||
{
|
||||
release = new ReleaseInfo();
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
|
||||
serie_title = node.SelectSingleNode("title").InnerText;
|
||||
release.Title = serie_title;
|
||||
|
||||
release.Comments = new Uri(node.SelectSingleNode("link").InnerText);
|
||||
release.Category = node.SelectSingleNode("title").InnerText;
|
||||
var test = node.SelectSingleNode("enclosure");
|
||||
release.Guid = new Uri(test.Attributes["url"].Value);
|
||||
release.PublishDate = DateTime.Parse(node.SelectSingleNode("pubDate").InnerText, CultureInfo.InvariantCulture);
|
||||
|
||||
release.Description = node.SelectSingleNode("description").InnerText;
|
||||
release.InfoHash = node.SelectSingleNode("description").InnerText;
|
||||
release.Size = 0;
|
||||
release.Seeders = 1;
|
||||
release.Peers = 1;
|
||||
release.MagnetUri = new Uri(node.SelectSingleNode("link").InnerText);
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, xml, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
return releases.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
158
src/Jackett/Indexers/Strike.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
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 Strike : IndexerInterface
|
||||
{
|
||||
|
||||
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get { return "Strike"; }
|
||||
}
|
||||
|
||||
public string DisplayDescription
|
||||
{
|
||||
get { return "Torrent search engine"; }
|
||||
}
|
||||
|
||||
public Uri SiteLink
|
||||
{
|
||||
get { return new Uri(DefaultUrl); }
|
||||
}
|
||||
|
||||
public bool IsConfigured { get; private set; }
|
||||
|
||||
const string DefaultUrl = "https://getstrike.net";
|
||||
|
||||
|
||||
//const string DownloadUrl = "/api/v2/torrents/download/?hash={0}";
|
||||
const string DownloadUrl = "/torrents/api/download/{0}.torrent";
|
||||
|
||||
const string SearchUrl = "/api/v2/torrents/search/?category=TV&phrase={0}";
|
||||
string BaseUrl;
|
||||
|
||||
CookieContainer cookies;
|
||||
HttpClientHandler handler;
|
||||
HttpClient client;
|
||||
|
||||
public Strike()
|
||||
{
|
||||
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(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 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;
|
||||
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
|
||||
IsConfigured = true;
|
||||
|
||||
}
|
||||
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
BaseUrl = (string)jsonConfig["base_url"];
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query, string baseUrl)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
foreach (var title in query.ShowTitles ?? new string[] { "2015" })
|
||||
{
|
||||
var searchString = title + " " + query.GetEpisodeSearchString();
|
||||
var episodeSearchUrl = baseUrl + string.Format(SearchUrl, HttpUtility.UrlEncode(searchString.Trim()));
|
||||
var results = await client.GetStringAsync(episodeSearchUrl);
|
||||
try
|
||||
{
|
||||
var jResults = JObject.Parse(results);
|
||||
foreach (JObject result in (JArray)jResults["torrents"])
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
|
||||
release.Title = (string)result["torrent_title"];
|
||||
release.Description = release.Title;
|
||||
release.Seeders = (int)result["seeds"];
|
||||
release.Peers = (int)result["leeches"] + release.Seeders;
|
||||
release.Size = (long)result["size"];
|
||||
|
||||
// "Apr 2, 2015", "Apr 12, 2015" (note the spacing)
|
||||
var dateString = string.Join(" ", ((string)result["upload_date"]).Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
|
||||
release.PublishDate = DateTime.ParseExact(dateString, "MMM d, yyyy", CultureInfo.InvariantCulture);
|
||||
|
||||
release.Guid = new Uri((string)result["page"]);
|
||||
release.Comments = release.Guid;
|
||||
|
||||
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)));
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, results, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
return releases.ToArray();
|
||||
}
|
||||
|
||||
public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
return await PerformQuery(query, BaseUrl);
|
||||
}
|
||||
|
||||
public Task<byte[]> Download(Uri link)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -16,34 +16,21 @@ namespace Jackett.Indexers
|
||||
public class ThePirateBay : IndexerInterface
|
||||
{
|
||||
|
||||
class ThePirateBayConfig : ConfigurationData
|
||||
{
|
||||
public StringItem Url { get; private set; }
|
||||
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
|
||||
public ThePirateBayConfig()
|
||||
{
|
||||
Url = new StringItem { Name = "Url", Value = "https://thepiratebay.se/" };
|
||||
}
|
||||
|
||||
public override Item[] GetItems()
|
||||
{
|
||||
return new Item[] { Url };
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<IndexerInterface, Newtonsoft.Json.Linq.JToken> OnSaveConfigurationRequested;
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
public string DisplayName { get { return "The Pirate Bay"; } }
|
||||
|
||||
public string DisplayDescription { get { return "The worlds largest bittorrent indexer"; } }
|
||||
|
||||
public Uri SiteLink { get { return new Uri("https://thepiratebay.se/"); } }
|
||||
public Uri SiteLink { get { return new Uri(DefaultUrl); } }
|
||||
|
||||
public bool IsConfigured { get; private set; }
|
||||
|
||||
static string SearchUrl = "s/?q=\"{0}\"&category=205&page=0&orderby=99";
|
||||
static string BrowserUrl = "browse/200";
|
||||
static string SwitchSingleViewUrl = "switchview.php?view=s";
|
||||
const string DefaultUrl = "https://thepiratebay.mn";
|
||||
const string SearchUrl = "/search/{0}/0/99/208";
|
||||
const string SearchUrl2 = "/search/{0}/0/99/205";
|
||||
|
||||
string BaseUrl;
|
||||
|
||||
@@ -51,9 +38,9 @@ namespace Jackett.Indexers
|
||||
HttpClientHandler handler;
|
||||
HttpClient client;
|
||||
|
||||
|
||||
public ThePirateBay()
|
||||
{
|
||||
BaseUrl = DefaultUrl;
|
||||
IsConfigured = false;
|
||||
cookies = new CookieContainer();
|
||||
handler = new HttpClientHandler
|
||||
@@ -67,24 +54,21 @@ namespace Jackett.Indexers
|
||||
|
||||
public Task<ConfigurationData> GetConfigurationForSetup()
|
||||
{
|
||||
var config = new ThePirateBayConfig();
|
||||
var config = new ConfigurationDataUrl(BaseUrl);
|
||||
return Task.FromResult<ConfigurationData>(config);
|
||||
}
|
||||
|
||||
public async Task ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
var config = new ThePirateBayConfig();
|
||||
var config = new ConfigurationDataUrl(DefaultUrl);
|
||||
config.LoadValuesFromJson(configJson);
|
||||
await TestBrowse(config.Url.Value);
|
||||
BaseUrl = new Uri(config.Url.Value).ToString();
|
||||
|
||||
var message = new HttpRequestMessage
|
||||
{
|
||||
Method = HttpMethod.Get,
|
||||
RequestUri = new Uri(BaseUrl + SwitchSingleViewUrl)
|
||||
};
|
||||
message.Headers.Referrer = new Uri(BaseUrl + BrowserUrl);
|
||||
var response = await client.SendAsync(message);
|
||||
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;
|
||||
|
||||
var configSaveData = new JObject();
|
||||
configSaveData["base_url"] = BaseUrl;
|
||||
@@ -95,15 +79,6 @@ namespace Jackett.Indexers
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
async Task TestBrowse(string url)
|
||||
{
|
||||
var result = await client.GetStringAsync(new Uri(url) + BrowserUrl);
|
||||
if (!result.Contains("<table id=\"searchResult\">"))
|
||||
{
|
||||
throw new Exception("Could not detect The Pirate Bay content");
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
BaseUrl = (string)jsonConfig["base_url"];
|
||||
@@ -111,71 +86,107 @@ namespace Jackett.Indexers
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
List<string> searchUrls = new List<string>();
|
||||
|
||||
foreach (var title in query.ShowTitles ?? new string[] { string.Empty })
|
||||
{
|
||||
var searchString = title + " " + query.GetEpisodeSearchString();
|
||||
var episodeSearchUrl = BaseUrl + string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
|
||||
var queryStr = HttpUtility.UrlEncode(searchString);
|
||||
var episodeSearchUrl = baseUrl + string.Format(SearchUrl, queryStr);
|
||||
var episodeSearchUrl2 = baseUrl + string.Format(SearchUrl2, queryStr);
|
||||
searchUrls.Add(episodeSearchUrl);
|
||||
searchUrls.Add(episodeSearchUrl2);
|
||||
}
|
||||
|
||||
var message = new HttpRequestMessage
|
||||
foreach (var episodeSearchUrl in searchUrls)
|
||||
{
|
||||
|
||||
string results;
|
||||
|
||||
if (Program.IsWindows)
|
||||
{
|
||||
Method = HttpMethod.Get,
|
||||
RequestUri = new Uri(BaseUrl + SwitchSingleViewUrl)
|
||||
};
|
||||
message.Headers.Referrer = new Uri(episodeSearchUrl);
|
||||
|
||||
var response = await client.SendAsync(message);
|
||||
var results = await response.Content.ReadAsStringAsync();
|
||||
|
||||
CQ dom = results;
|
||||
|
||||
var rows = dom["#searchResult > tbody > tr"];
|
||||
foreach (var row in rows)
|
||||
results = await client.GetStringAsync(episodeSearchUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
CQ qRow = row.Cq();
|
||||
CQ qLink = row.ChildElements.ElementAt(1).Cq().Children("a").First();
|
||||
var response = await CurlHelper.GetAsync(episodeSearchUrl, null, episodeSearchUrl);
|
||||
results = Encoding.UTF8.GetString(response.Content);
|
||||
}
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
release.Title = qLink.Text().Trim();
|
||||
release.Description = release.Title;
|
||||
release.Comments = new Uri(BaseUrl + qLink.Attr("href").TrimStart('/'));
|
||||
release.Guid = release.Comments;
|
||||
try
|
||||
{
|
||||
CQ dom = results;
|
||||
|
||||
var timeString = row.ChildElements.ElementAt(2).Cq().Text();
|
||||
if (timeString.Contains("mins ago"))
|
||||
release.PublishDate = (DateTime.Now - TimeSpan.FromMinutes(int.Parse(timeString.Split(' ')[0])));
|
||||
else if (timeString.Contains("Today"))
|
||||
release.PublishDate = (DateTime.UtcNow - TimeSpan.FromHours(2) - TimeSpan.Parse(timeString.Split(' ')[1])).ToLocalTime();
|
||||
else if (timeString.Contains("Y-day"))
|
||||
release.PublishDate = (DateTime.UtcNow - TimeSpan.FromHours(26) - TimeSpan.Parse(timeString.Split(' ')[1])).ToLocalTime();
|
||||
else if (timeString.Contains(':'))
|
||||
var rows = dom["#searchResult > tbody > tr"];
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var utc = DateTime.ParseExact(timeString, "MM-dd HH:mm", CultureInfo.InvariantCulture) - TimeSpan.FromHours(2);
|
||||
release.PublishDate = DateTime.SpecifyKind(utc, DateTimeKind.Utc).ToLocalTime();
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
CQ qRow = row.Cq();
|
||||
CQ qLink = qRow.Find(".detName > .detLink").First();
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
release.Title = qLink.Text().Trim();
|
||||
release.Description = release.Title;
|
||||
release.Comments = new Uri(baseUrl + "/" + qLink.Attr("href").TrimStart('/'));
|
||||
release.Guid = release.Comments;
|
||||
|
||||
var downloadCol = row.ChildElements.ElementAt(1).Cq().Children("a");
|
||||
release.MagnetUri = new Uri(downloadCol.Attr("href"));
|
||||
release.InfoHash = release.MagnetUri.ToString().Split(':')[3].Split('&')[0];
|
||||
|
||||
var descString = qRow.Find(".detDesc").Text().Trim();
|
||||
var descParts = descString.Split(',');
|
||||
|
||||
var timeString = descParts[0].Split(' ')[1];
|
||||
|
||||
if (timeString.Contains("mins ago"))
|
||||
{
|
||||
release.PublishDate = (DateTime.Now - TimeSpan.FromMinutes(ParseUtil.CoerceInt(timeString.Split(' ')[0])));
|
||||
}
|
||||
else if (timeString.Contains("Today"))
|
||||
{
|
||||
release.PublishDate = (DateTime.UtcNow - TimeSpan.FromHours(2) - TimeSpan.Parse(timeString.Split(' ')[1])).ToLocalTime();
|
||||
}
|
||||
else if (timeString.Contains("Y-day"))
|
||||
{
|
||||
release.PublishDate = (DateTime.UtcNow - TimeSpan.FromHours(26) - TimeSpan.Parse(timeString.Split(' ')[1])).ToLocalTime();
|
||||
}
|
||||
else if (timeString.Contains(':'))
|
||||
{
|
||||
var utc = DateTime.ParseExact(timeString, "MM-dd HH:mm", CultureInfo.InvariantCulture) - TimeSpan.FromHours(2);
|
||||
release.PublishDate = DateTime.SpecifyKind(utc, DateTimeKind.Utc).ToLocalTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
var utc = DateTime.ParseExact(timeString, "MM-dd yyyy", CultureInfo.InvariantCulture) - TimeSpan.FromHours(2);
|
||||
release.PublishDate = DateTime.SpecifyKind(utc, DateTimeKind.Utc).ToLocalTime();
|
||||
}
|
||||
|
||||
var sizeParts = descParts[1].Split(new char[] { ' ', ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var sizeVal = ParseUtil.CoerceFloat(sizeParts[1]);
|
||||
var sizeUnit = sizeParts[2];
|
||||
release.Size = ReleaseInfo.GetBytes(sizeUnit, sizeVal);
|
||||
|
||||
release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(2).Cq().Text());
|
||||
release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(3).Cq().Text()) + release.Seeders;
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
else
|
||||
{
|
||||
var utc = DateTime.ParseExact(timeString, "MM-dd yyyy", CultureInfo.InvariantCulture) - TimeSpan.FromHours(2);
|
||||
release.PublishDate = DateTime.SpecifyKind(utc, DateTimeKind.Utc).ToLocalTime();
|
||||
}
|
||||
|
||||
var downloadCol = row.ChildElements.ElementAt(3).Cq().Find("a");
|
||||
release.MagnetUrl = new Uri(downloadCol.Attr("href"));
|
||||
release.InfoHash = release.MagnetUrl.ToString().Split(':')[3].Split('&')[0];
|
||||
|
||||
var sizeString = row.ChildElements.ElementAt(4).Cq().Text().Split(' ');
|
||||
var sizeVal = float.Parse(sizeString[0]);
|
||||
var sizeUnit = sizeString[1];
|
||||
release.Size = ReleaseInfo.GetBytes(sizeUnit, sizeVal);
|
||||
|
||||
release.Seeders = int.Parse(row.ChildElements.ElementAt(5).Cq().Text());
|
||||
release.Peers = int.Parse(row.ChildElements.ElementAt(6).Cq().Text()) + release.Seeders;
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, results, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
return releases.ToArray();
|
||||
@@ -187,5 +198,7 @@ namespace Jackett.Indexers
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
195
src/Jackett/Indexers/TorrentDay.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
using CsQuery;
|
||||
using Newtonsoft.Json.Linq;
|
||||
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 TorrentDay : IndexerInterface
|
||||
{
|
||||
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get { return "TorrentDay"; }
|
||||
}
|
||||
|
||||
public string DisplayDescription
|
||||
{
|
||||
get { return DisplayName; }
|
||||
}
|
||||
|
||||
public Uri SiteLink
|
||||
{
|
||||
get { return new Uri(BaseUrl); }
|
||||
}
|
||||
|
||||
public bool IsConfigured { get; private set; }
|
||||
|
||||
const string BaseUrl = "https://torrentday.eu";
|
||||
const string StartPageUrl = BaseUrl + "/login.php";
|
||||
const string LoginUrl = BaseUrl + "/tak3login.php";
|
||||
const string SearchUrl = BaseUrl + "/browse.php?search={0}&cata=yes&c2=1&c7=1&c14=1&c24=1&c26=1&c31=1&c32=1&c33=1";
|
||||
|
||||
static string chromeUserAgent = BrowserUtil.ChromeUserAgent;
|
||||
|
||||
CookieContainer cookies;
|
||||
HttpClientHandler handler;
|
||||
HttpClient client;
|
||||
|
||||
public TorrentDay()
|
||||
{
|
||||
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 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(chromeUserAgent);
|
||||
return message;
|
||||
}
|
||||
|
||||
public async Task ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
var config = new ConfigurationDataBasicLogin();
|
||||
config.LoadValuesFromJson(configJson);
|
||||
|
||||
var startMessage = CreateHttpRequest(StartPageUrl);
|
||||
var results = await (await client.SendAsync(startMessage)).Content.ReadAsStringAsync();
|
||||
|
||||
|
||||
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"))
|
||||
{
|
||||
CQ dom = responseContent;
|
||||
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);
|
||||
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
|
||||
IsConfigured = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
cookies.FillFromJson(new Uri(BaseUrl), jsonConfig);
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
foreach (var title in query.ShowTitles ?? new string[] { string.Empty })
|
||||
{
|
||||
var searchString = title + " " + query.GetEpisodeSearchString();
|
||||
var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
|
||||
var results = await client.GetStringAsync(episodeSearchUrl);
|
||||
try
|
||||
{
|
||||
CQ dom = results;
|
||||
var rows = dom["#torrentTable > tbody > tr.browse"];
|
||||
foreach (var row in rows)
|
||||
{
|
||||
CQ qRow = row.Cq();
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
release.Title = qRow.Find(".torrentName").Text();
|
||||
release.Description = release.Title;
|
||||
release.Guid = new Uri(BaseUrl + "/" + qRow.Find(".torrentName").Attr("href"));
|
||||
release.Comments = release.Guid;
|
||||
release.Link = new Uri(BaseUrl + "/" + qRow.Find(".dlLinksInfo > a").Attr("href"));
|
||||
|
||||
var sizeStr = qRow.Find(".sizeInfo").Text().Trim();
|
||||
var sizeParts = sizeStr.Split(' ');
|
||||
release.Size = ReleaseInfo.GetBytes(sizeParts[1], ParseUtil.CoerceFloat(sizeParts[0]));
|
||||
|
||||
var dateStr = qRow.Find(".ulInfo").Text().Split('|').Last().Trim();
|
||||
var dateParts = dateStr.Split(' ');
|
||||
var dateValue = ParseUtil.CoerceInt(dateParts[0]);
|
||||
TimeSpan ts = TimeSpan.Zero;
|
||||
if (dateStr.Contains("sec"))
|
||||
ts = TimeSpan.FromSeconds(dateValue);
|
||||
else if (dateStr.Contains("min"))
|
||||
ts = TimeSpan.FromMinutes(dateValue);
|
||||
else if (dateStr.Contains("hour"))
|
||||
ts = TimeSpan.FromHours(dateValue);
|
||||
else if (dateStr.Contains("day"))
|
||||
ts = TimeSpan.FromDays(dateValue);
|
||||
else if (dateStr.Contains("week"))
|
||||
ts = TimeSpan.FromDays(dateValue * 7);
|
||||
else if (dateStr.Contains("month"))
|
||||
ts = TimeSpan.FromDays(dateValue * 30);
|
||||
else if (dateStr.Contains("year"))
|
||||
ts = TimeSpan.FromDays(dateValue * 365);
|
||||
release.PublishDate = DateTime.Now - ts;
|
||||
|
||||
release.Seeders = ParseUtil.CoerceInt(qRow.Find(".seedersInfo").Text());
|
||||
release.Peers = ParseUtil.CoerceInt(qRow.Find(".leechersInfo").Text()) + release.Seeders;
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, results, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
return releases.ToArray();
|
||||
}
|
||||
|
||||
public Task<byte[]> Download(Uri link)
|
||||
{
|
||||
return client.GetByteArrayAsync(link);
|
||||
}
|
||||
}
|
||||
}
|
178
src/Jackett/Indexers/TorrentLeech.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
using CsQuery;
|
||||
using Newtonsoft.Json.Linq;
|
||||
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 TorrentLeech : IndexerInterface
|
||||
{
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get { return "TorrentLeech"; }
|
||||
}
|
||||
|
||||
public string DisplayDescription
|
||||
{
|
||||
get { return "This is what happens when you seed"; }
|
||||
}
|
||||
|
||||
public Uri SiteLink
|
||||
{
|
||||
get { return new Uri(BaseUrl); }
|
||||
}
|
||||
|
||||
const string BaseUrl = "http://www.torrentleech.org";
|
||||
const string LoginUrl = BaseUrl + "/user/account/login/";
|
||||
const string SearchUrl = BaseUrl + "/torrents/browse/index/query/{0}/categories/2%2C26%2C27%2C32/orderby/added?";
|
||||
|
||||
public bool IsConfigured { get; private set; }
|
||||
|
||||
|
||||
CookieContainer cookies;
|
||||
HttpClientHandler handler;
|
||||
HttpClient client;
|
||||
|
||||
public TorrentLeech()
|
||||
{
|
||||
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 ConfigurationDataBasicLogin();
|
||||
return Task.FromResult<ConfigurationData>(config);
|
||||
}
|
||||
|
||||
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 },
|
||||
{ "remember_me", "on" },
|
||||
{ "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"))
|
||||
{
|
||||
CQ dom = responseContent;
|
||||
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);
|
||||
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
|
||||
IsConfigured = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
cookies.FillFromJson(new Uri(BaseUrl), jsonConfig);
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
|
||||
foreach (var title in query.ShowTitles ?? new string[] { string.Empty })
|
||||
{
|
||||
var searchString = title + " " + query.GetEpisodeSearchString();
|
||||
var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
|
||||
var results = await client.GetStringAsync(episodeSearchUrl);
|
||||
try
|
||||
{
|
||||
CQ dom = results;
|
||||
|
||||
CQ qRows = dom["#torrenttable > tbody > tr"];
|
||||
|
||||
foreach (var row in qRows)
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
var qRow = row.Cq();
|
||||
|
||||
var debug = qRow.Html();
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
|
||||
CQ qLink = qRow.Find(".title > a").First();
|
||||
release.Guid = new Uri(BaseUrl + qLink.Attr("href"));
|
||||
release.Comments = release.Guid;
|
||||
release.Title = qLink.Text();
|
||||
release.Description = release.Title;
|
||||
|
||||
release.Link = new Uri(BaseUrl + qRow.Find(".quickdownload > a").Attr("href"));
|
||||
|
||||
var dateString = qRow.Find(".name").First()[0].ChildNodes[4].NodeValue.Replace(" on", "").Trim();
|
||||
//"2015-04-25 23:38:12"
|
||||
//"yyyy-MMM-dd hh:mm:ss"
|
||||
release.PublishDate = DateTime.ParseExact(dateString, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
|
||||
|
||||
var sizeStringParts = qRow.Children().ElementAt(4).InnerText.Split(' ');
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStringParts[1], ParseUtil.CoerceFloat(sizeStringParts[0]));
|
||||
|
||||
release.Seeders = ParseUtil.CoerceInt(qRow.Find(".seeders").Text());
|
||||
release.Peers = release.Seeders + ParseUtil.CoerceInt(qRow.Find(".leechers").Text());
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, results, ex);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
return releases.ToArray();
|
||||
}
|
||||
|
||||
public Task<byte[]> Download(Uri link)
|
||||
{
|
||||
return client.GetByteArrayAsync(link);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
186
src/Jackett/Indexers/TorrentShack.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using CsQuery;
|
||||
using Newtonsoft.Json.Linq;
|
||||
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 TorrentShack : IndexerInterface
|
||||
{
|
||||
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get { return "TorrentShack"; }
|
||||
}
|
||||
|
||||
public string DisplayDescription
|
||||
{
|
||||
get { return DisplayName; }
|
||||
}
|
||||
|
||||
public Uri SiteLink
|
||||
{
|
||||
get { return new Uri(BaseUrl); }
|
||||
}
|
||||
|
||||
const string BaseUrl = "http://torrentshack.me";
|
||||
const string LoginUrl = BaseUrl + "/login.php";
|
||||
const string SearchUrl = BaseUrl + "/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 bool IsConfigured { get; private set; }
|
||||
|
||||
public TorrentShack()
|
||||
{
|
||||
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 ConfigurationDataBasicLogin();
|
||||
return Task.FromResult<ConfigurationData>(config);
|
||||
}
|
||||
|
||||
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 },
|
||||
{ "keeplogged", "1" },
|
||||
{ "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"))
|
||||
{
|
||||
CQ dom = responseContent;
|
||||
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);
|
||||
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
cookies.FillFromJson(new Uri(BaseUrl), jsonConfig);
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
foreach (var title in query.ShowTitles ?? new string[] { string.Empty })
|
||||
{
|
||||
var searchString = title + " " + query.GetEpisodeSearchString();
|
||||
var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
|
||||
var results = await client.GetStringAsync(episodeSearchUrl);
|
||||
try
|
||||
{
|
||||
CQ dom = results;
|
||||
var rows = dom["#torrent_table > tbody > tr.torrent"];
|
||||
foreach (var row in rows)
|
||||
{
|
||||
CQ qRow = row.Cq();
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
release.Title = qRow.Find(".torrent_name_link").Text();
|
||||
release.Description = release.Title;
|
||||
release.Guid = new Uri(BaseUrl + "/" + qRow.Find(".torrent_name_link").Parent().Attr("href"));
|
||||
release.Comments = release.Guid;
|
||||
release.Link = new Uri(BaseUrl + "/" + qRow.Find(".torrent_handle_links > a").First().Attr("href"));
|
||||
|
||||
var dateStr = qRow.Find(".time").Text().Trim();
|
||||
if (dateStr.ToLower().Contains("just now"))
|
||||
release.PublishDate = DateTime.Now;
|
||||
else
|
||||
{
|
||||
var dateParts = dateStr.Split(' ');
|
||||
var dateValue = ParseUtil.CoerceInt(dateParts[0]);
|
||||
TimeSpan ts = TimeSpan.Zero;
|
||||
if (dateStr.Contains("Just now"))
|
||||
ts = TimeSpan.Zero;
|
||||
else if (dateStr.Contains("sec"))
|
||||
ts = TimeSpan.FromSeconds(dateValue);
|
||||
else if (dateStr.Contains("min"))
|
||||
ts = TimeSpan.FromMinutes(dateValue);
|
||||
else if (dateStr.Contains("hour"))
|
||||
ts = TimeSpan.FromHours(dateValue);
|
||||
else if (dateStr.Contains("day"))
|
||||
ts = TimeSpan.FromDays(dateValue);
|
||||
else if (dateStr.Contains("week"))
|
||||
ts = TimeSpan.FromDays(dateValue * 7);
|
||||
else if (dateStr.Contains("month"))
|
||||
ts = TimeSpan.FromDays(dateValue * 30);
|
||||
else if (dateStr.Contains("year"))
|
||||
ts = TimeSpan.FromDays(dateValue * 365);
|
||||
release.PublishDate = DateTime.Now - ts;
|
||||
}
|
||||
|
||||
var sizeStr = qRow.Find(".size")[0].ChildNodes[0].NodeValue.Trim();
|
||||
var sizeParts = sizeStr.Split(' ');
|
||||
release.Size = ReleaseInfo.GetBytes(sizeParts[1], ParseUtil.CoerceFloat(sizeParts[0]));
|
||||
release.Seeders = ParseUtil.CoerceInt(qRow.Children().ElementAt(6).InnerText.Trim());
|
||||
release.Peers = ParseUtil.CoerceInt(qRow.Children().ElementAt(7).InnerText.Trim()) + release.Seeders;
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, results, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
return releases.ToArray();
|
||||
}
|
||||
|
||||
public Task<byte[]> Download(Uri link)
|
||||
{
|
||||
return client.GetByteArrayAsync(link);
|
||||
}
|
||||
}
|
||||
}
|
257
src/Jackett/Indexers/Torrentz.cs
Normal file
@@ -0,0 +1,257 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Windows.Forms;
|
||||
using System.Xml;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class Torrentz : IndexerInterface
|
||||
{
|
||||
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
|
||||
|
||||
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get { return "Torrentz"; }
|
||||
}
|
||||
|
||||
public string DisplayDescription
|
||||
{
|
||||
get { return "Torrentz is a meta-search engine and a Multisearch. This means we just search other search engines."; }
|
||||
}
|
||||
|
||||
public Uri SiteLink
|
||||
{
|
||||
get { return new Uri(DefaultUrl); }
|
||||
}
|
||||
|
||||
const string DefaultUrl = "https://torrentz.eu";
|
||||
const string SearchUrl = DefaultUrl + "/feed_verifiedP?f={0}";
|
||||
string BaseUrl;
|
||||
static string chromeUserAgent = BrowserUtil.ChromeUserAgent;
|
||||
|
||||
CookieContainer cookies;
|
||||
HttpClientHandler handler;
|
||||
HttpClient client;
|
||||
|
||||
public bool IsConfigured
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Torrentz()
|
||||
{
|
||||
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(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 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;
|
||||
|
||||
if (OnSaveConfigurationRequested != null)
|
||||
OnSaveConfigurationRequested(this, configSaveData);
|
||||
|
||||
IsConfigured = true;
|
||||
|
||||
}
|
||||
|
||||
private WebClient getWebClient()
|
||||
{
|
||||
WebClient wc = new WebClient();
|
||||
WebHeaderCollection headers = new WebHeaderCollection();
|
||||
headers.Add("User-Agent", chromeUserAgent);
|
||||
wc.Headers = headers;
|
||||
return wc;
|
||||
}
|
||||
|
||||
async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query, string baseUrl)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
foreach (var title in query.ShowTitles ?? new string[] { string.Empty })
|
||||
{
|
||||
var searchString = title + " " + query.GetEpisodeSearchString();
|
||||
var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString.Trim()));
|
||||
|
||||
XmlDocument xmlDoc = new XmlDocument();
|
||||
string xml = string.Empty;
|
||||
WebClient wc = getWebClient();
|
||||
|
||||
try
|
||||
{
|
||||
using (wc)
|
||||
{
|
||||
xml = wc.DownloadString(episodeSearchUrl);
|
||||
xmlDoc.LoadXml(xml);
|
||||
}
|
||||
|
||||
ReleaseInfo release;
|
||||
TorrentzHelper td;
|
||||
string serie_title;
|
||||
|
||||
foreach (XmlNode node in xmlDoc.GetElementsByTagName("item"))
|
||||
{
|
||||
release = new ReleaseInfo();
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
serie_title = node.SelectSingleNode("title").InnerText;
|
||||
release.Title = serie_title;
|
||||
|
||||
release.Comments = new Uri(node.SelectSingleNode("link").InnerText);
|
||||
release.Category = node.SelectSingleNode("category").InnerText;
|
||||
release.Guid = new Uri(node.SelectSingleNode("guid").InnerText);
|
||||
release.PublishDate = DateTime.Parse(node.SelectSingleNode("pubDate").InnerText, CultureInfo.InvariantCulture);
|
||||
|
||||
td = new TorrentzHelper(node.SelectSingleNode("description").InnerText);
|
||||
release.Description = td.Description;
|
||||
release.InfoHash = td.hash;
|
||||
release.Size = td.Size;
|
||||
release.Seeders = td.Seeders;
|
||||
release.Peers = td.Peers + release.Seeders;
|
||||
release.MagnetUri = TorrentzHelper.createMagnetLink(td.hash, serie_title);
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnResultParsingError(this, xml, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
return releases.ToArray();
|
||||
}
|
||||
|
||||
|
||||
public void LoadFromSavedConfiguration(JToken jsonConfig)
|
||||
{
|
||||
BaseUrl = (string)jsonConfig["base_url"];
|
||||
IsConfigured = true;
|
||||
}
|
||||
|
||||
public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
return await PerformQuery(query, BaseUrl);
|
||||
}
|
||||
|
||||
public Task<byte[]> Download(Uri link)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class TorrentzHelper
|
||||
{
|
||||
public TorrentzHelper(string description)
|
||||
{
|
||||
this.Description = description;
|
||||
if (null == description)
|
||||
{
|
||||
this.Description = "";
|
||||
this.Size = 0;
|
||||
this.Peers = 0;
|
||||
this.Seeders = 0;
|
||||
this.hash = "";
|
||||
}
|
||||
else
|
||||
FillProperties();
|
||||
}
|
||||
|
||||
public static Uri createMagnetLink(string hash, string title)
|
||||
{
|
||||
string MagnetLink = "magnet:?xt=urn:btih:{0}&dn={1}&tr={2}";
|
||||
string Trackers = WebUtility.UrlEncode("udp://tracker.publicbt.com:80&tr=udp://tracker.openbittorrent.com:80&tr=udp://tracker.ccc.de:80&tr=udp://tracker.istole.it:80");
|
||||
title = WebUtility.UrlEncode(title);
|
||||
|
||||
return new Uri(string.Format(MagnetLink, hash, title, Trackers));
|
||||
}
|
||||
|
||||
private void FillProperties()
|
||||
{
|
||||
string description = this.Description;
|
||||
int counter = 0;
|
||||
while (description.Contains(" "))
|
||||
{
|
||||
int nextSpace = description.IndexOf(": ") + 1;
|
||||
int secondSpace;
|
||||
if (counter != 0)
|
||||
secondSpace = description.IndexOf(" ", nextSpace + 1);
|
||||
else
|
||||
secondSpace = description.IndexOf(": ", nextSpace + 2) - description.IndexOf(" ", nextSpace);
|
||||
|
||||
string val;
|
||||
if (secondSpace == -1)
|
||||
{
|
||||
val = description.Substring(nextSpace).Trim();
|
||||
description = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = description.Substring(nextSpace, secondSpace - nextSpace).Trim();
|
||||
description = description.Substring(secondSpace);
|
||||
}
|
||||
|
||||
switch (counter)
|
||||
{
|
||||
case 0:
|
||||
this.Size = ReleaseInfo.BytesFromMB(ParseUtil.CoerceLong(val.Substring(0, val.IndexOf(" ") - 1)));
|
||||
break;
|
||||
case 1:
|
||||
this.Seeders = ParseUtil.CoerceInt(val.Contains(",") ? val.Remove(val.IndexOf(","), 1) : val);
|
||||
break;
|
||||
case 2:
|
||||
this.Peers = ParseUtil.CoerceInt(val.Contains(",") ? val.Remove(val.IndexOf(","), 1) : val);
|
||||
break;
|
||||
case 3:
|
||||
this.hash = val;
|
||||
break;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
public string Description { get; set; }
|
||||
public long Size { get; set; }
|
||||
public int Seeders { get; set; }
|
||||
public int Peers { get; set; }
|
||||
public string hash { get; set; }
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
@@ -57,15 +57,9 @@
|
||||
<Reference Include="CsQuery">
|
||||
<HintPath>..\packages\CsQuery.1.3.4\lib\net40\CsQuery.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ModernHttpClient">
|
||||
<HintPath>..\packages\modernhttpclient.2.3.0\lib\Portable-Net45+WinRT45+WP8+WPA81\ModernHttpClient.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog">
|
||||
<HintPath>..\packages\NLog.3.2.0.0\lib\net45\NLog.dll</HintPath>
|
||||
<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>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
@@ -79,29 +73,53 @@
|
||||
<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="ApiKey.cs" />
|
||||
<Compile Include="BrowserUtil.cs" />
|
||||
<Compile Include="CachedResult.cs" />
|
||||
<Compile Include="ChannelInfo.cs" />
|
||||
<Compile Include="ConfigurationData.cs" />
|
||||
<Compile Include="ConfigurationDataBasicLogin.cs" />
|
||||
<Compile Include="ConfigurationDataCookie.cs" />
|
||||
<Compile Include="ConfigurationDataUrl.cs" />
|
||||
<Compile Include="CookieContainerExtensions.cs" />
|
||||
<Compile Include="DataUrl.cs" />
|
||||
<Compile Include="ExceptionWithConfigData.cs" />
|
||||
<Compile Include="HttpClientExtensions.cs" />
|
||||
<Compile Include="IndexerInterface.cs" />
|
||||
<Compile Include="IndexerManager.cs" />
|
||||
<Compile Include="Indexers\BeyondHD.cs" />
|
||||
<Compile Include="Indexers\BitHdtv.cs" />
|
||||
<Compile Include="Indexers\BitMeTV.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\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="Main.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Main.Designer.cs">
|
||||
<DependentUpon>Main.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="ParseUtil.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
@@ -116,6 +134,8 @@
|
||||
<Compile Include="TorznabQuery.cs" />
|
||||
<Compile Include="TVRage.cs" />
|
||||
<Compile Include="WebApi.cs" />
|
||||
<Compile Include="CurlHelper.cs" />
|
||||
<Compile Include="Indexers\AlphaRatio.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
@@ -137,6 +157,36 @@
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="WebContent\custom.css">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WebContent\custom.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WebContent\logos\animebytes.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WebContent\logos\beyondhd.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WebContent\logos\hdtorrents.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WebContent\logos\sceneaccess.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WebContent\logos\scenetime.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WebContent\logos\showrss.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WebContent\logos\torrentday.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WebContent\logos\torrentshack.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="jacket_large.ico" />
|
||||
<Content Include="WebContent\animate.css">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
@@ -195,15 +245,32 @@
|
||||
<Content Include="WebContent\logos\rarbg.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WebContent\logos\strike.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WebContent\logos\thepiratebay.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WebContent\logos\torrentleech.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WebContent\logos\torrentz.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WebContent\setup_indexer.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\validator_reply.xml" />
|
||||
<Content Include="WebContent\logos\alpharatio.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CurlSharp\CurlSharp.csproj">
|
||||
<Project>{74420A79-CC16-442C-8B1E-7C1B913844F0}</Project>
|
||||
<Name>CurlSharp</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include=".NETFramework,Version=v4.5.1">
|
||||
<Visible>False</Visible>
|
||||
@@ -221,6 +288,17 @@
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<COMReference Include="IWshRuntimeLibrary">
|
||||
<Guid>{F935DC20-1CF0-11D0-ADB9-00C04FD58A0B}</Guid>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<Isolated>False</Isolated>
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.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.
|
||||
@@ -229,4 +307,14 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
<ProjectExtensions>
|
||||
<MonoDevelop>
|
||||
<Properties>
|
||||
<Policies>
|
||||
<TextStylePolicy inheritsSet="VisualStudio" inheritsScope="text/plain" scope="text/x-csharp" />
|
||||
<CSharpFormattingPolicy IndentSwitchBody="True" IndentBlocksInsideExpressions="True" AnonymousMethodBraceStyle="NextLine" PropertyBraceStyle="NextLine" PropertyGetBraceStyle="NextLine" PropertySetBraceStyle="NextLine" EventBraceStyle="NextLine" EventAddBraceStyle="NextLine" EventRemoveBraceStyle="NextLine" StatementBraceStyle="NextLine" ElseNewLinePlacement="NewLine" CatchNewLinePlacement="NewLine" FinallyNewLinePlacement="NewLine" WhileNewLinePlacement="DoNotCare" ArrayInitializerWrapping="DoNotChange" ArrayInitializerBraceStyle="NextLine" BeforeMethodDeclarationParentheses="False" BeforeMethodCallParentheses="False" BeforeConstructorDeclarationParentheses="False" NewLineBeforeConstructorInitializerColon="NewLine" NewLineAfterConstructorInitializerColon="SameLine" BeforeDelegateDeclarationParentheses="False" NewParentheses="False" SpacesBeforeBrackets="False" inheritsSet="Mono" inheritsScope="text/x-csharp" scope="text/x-csharp" />
|
||||
</Policies>
|
||||
</Properties>
|
||||
</MonoDevelop>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
7
src/Jackett/Main.Designer.cs
generated
@@ -1,4 +1,6 @@
|
||||
namespace Jackett
|
||||
#if !__MonoCS__
|
||||
|
||||
namespace Jackett
|
||||
{
|
||||
partial class Main
|
||||
{
|
||||
@@ -97,4 +99,5 @@
|
||||
private System.Windows.Forms.ToolStripMenuItem toolStripMenuItemShutdown;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -1,10 +1,12 @@
|
||||
using Microsoft.Win32;
|
||||
#if !__MonoCS__
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
@@ -37,7 +39,7 @@ namespace Jackett
|
||||
|
||||
void toolStripMenuItemShutdown_Click(object sender, EventArgs e)
|
||||
{
|
||||
Application.Exit();
|
||||
Process.GetCurrentProcess().Kill();
|
||||
}
|
||||
|
||||
void toolStripMenuItemAutoStart_CheckedChanged(object sender, EventArgs e)
|
||||
@@ -57,20 +59,38 @@ namespace Jackett
|
||||
{
|
||||
get
|
||||
{
|
||||
RegistryKey rkApp = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true);
|
||||
if (rkApp.GetValue(ProgramTitle) == null)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
return File.Exists(ShortcutPath);
|
||||
}
|
||||
set
|
||||
{
|
||||
RegistryKey rkApp = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true);
|
||||
if (value && !AutoStart)
|
||||
rkApp.SetValue(ProgramTitle, Application.ExecutablePath.ToString());
|
||||
{
|
||||
CreateShortcut();
|
||||
}
|
||||
else if (!value && AutoStart)
|
||||
rkApp.DeleteValue(ProgramTitle, false);
|
||||
{
|
||||
File.Delete(ShortcutPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ShortcutPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), "Jackett.lnk");
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateShortcut()
|
||||
{
|
||||
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
|
44
src/Jackett/ParseUtil.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett
|
||||
{
|
||||
public static class ParseUtil
|
||||
{
|
||||
public static float CoerceFloat(string str)
|
||||
{
|
||||
return float.Parse(str, NumberStyles.Any, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public static int CoerceInt(string str)
|
||||
{
|
||||
return int.Parse(str, NumberStyles.Any, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public static long CoerceLong(string str)
|
||||
{
|
||||
return long.Parse(str, NumberStyles.Any, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
|
||||
public static bool TryCoerceFloat(string str, out float result)
|
||||
{
|
||||
return float.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out result);
|
||||
}
|
||||
|
||||
public static bool TryCoerceInt(string str, out int result)
|
||||
{
|
||||
return int.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out result);
|
||||
}
|
||||
|
||||
public static bool TryCoerceLong(string str, out long result)
|
||||
{
|
||||
return long.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out result);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -1,6 +1,9 @@
|
||||
using NLog;
|
||||
using Jackett.Indexers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.Targets;
|
||||
using NLog.Windows.Forms;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -16,7 +19,7 @@ namespace Jackett
|
||||
{
|
||||
class Program
|
||||
{
|
||||
public static string AppConfigDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Jackett");
|
||||
public static string AppConfigDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Jackett");
|
||||
|
||||
public static Server ServerInstance { get; private set; }
|
||||
|
||||
@@ -26,10 +29,16 @@ namespace Jackett
|
||||
|
||||
public static ManualResetEvent ExitEvent { get; private set; }
|
||||
|
||||
public static bool IsWindows { get { return Environment.OSVersion.Platform == PlatformID.Win32NT; } }
|
||||
|
||||
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
ExitEvent = new ManualResetEvent(false);
|
||||
|
||||
MigrateSettingsDirectory();
|
||||
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(AppConfigDirectory))
|
||||
@@ -41,7 +50,7 @@ namespace Jackett
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("Could not create settings directory.");
|
||||
MessageBox.Show("Could not create settings directory. " + ex.Message);
|
||||
Application.Exit();
|
||||
return;
|
||||
}
|
||||
@@ -50,19 +59,26 @@ namespace Jackett
|
||||
|
||||
var logFile = new FileTarget();
|
||||
logConfig.AddTarget("file", logFile);
|
||||
logFile.FileName = Path.Combine(AppConfigDirectory, "log.txt");
|
||||
logFile.Layout = "${longdate} ${level} ${message} \n ${exception:format=ToString}\n";
|
||||
logFile.FileName = Path.Combine(AppConfigDirectory, "log.txt");
|
||||
logFile.ArchiveFileName = "log.{#####}.txt";
|
||||
logFile.ArchiveAboveSize = 500000;
|
||||
logFile.MaxArchiveFiles = 1;
|
||||
logFile.KeepFileOpen = false;
|
||||
logFile.ArchiveNumbering = ArchiveNumberingMode.DateAndSequence;
|
||||
var logFileRule = new LoggingRule("*", LogLevel.Debug, logFile);
|
||||
logConfig.LoggingRules.Add(logFileRule);
|
||||
|
||||
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
||||
if (Program.IsWindows)
|
||||
{
|
||||
#if !__MonoCS__
|
||||
var logAlert = new MessageBoxTarget();
|
||||
logConfig.AddTarget("alert", logAlert);
|
||||
logAlert.Layout = "${message}";
|
||||
logAlert.Caption = "Alert";
|
||||
var logAlertRule = new LoggingRule("*", LogLevel.Fatal, logAlert);
|
||||
logConfig.LoggingRules.Add(logAlertRule);
|
||||
#endif
|
||||
}
|
||||
|
||||
var logConsole = new ConsoleTarget();
|
||||
@@ -74,6 +90,8 @@ namespace Jackett
|
||||
LogManager.Configuration = logConfig;
|
||||
LoggerInstance = LogManager.GetCurrentClassLogger();
|
||||
|
||||
ReadSettingsFile();
|
||||
|
||||
var serverTask = Task.Run(async () =>
|
||||
{
|
||||
ServerInstance = new Server();
|
||||
@@ -82,20 +100,62 @@ namespace Jackett
|
||||
|
||||
try
|
||||
{
|
||||
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
||||
if (Program.IsWindows)
|
||||
{
|
||||
#if !__MonoCS__
|
||||
Application.Run(new Main());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Console.WriteLine("Running in headless mode.");
|
||||
|
||||
|
||||
|
||||
Task.WaitAll(serverTask);
|
||||
Console.WriteLine("Server thread exit");
|
||||
}
|
||||
|
||||
static void MigrateSettingsDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
string oldDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Jackett");
|
||||
if (Directory.Exists(oldDir) && !Directory.Exists(AppConfigDirectory))
|
||||
{
|
||||
Directory.Move(oldDir, AppConfigDirectory);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("ERROR could not migrate settings directory " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
static void ReadSettingsFile()
|
||||
{
|
||||
var path = Path.Combine(AppConfigDirectory, "config.json");
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
JObject f = new JObject();
|
||||
f.Add("port", Server.DefaultPort);
|
||||
f.Add("public", true);
|
||||
File.WriteAllText(path, f.ToString());
|
||||
}
|
||||
|
||||
var configJson = JObject.Parse(File.ReadAllText(path));
|
||||
int port = (int)configJson.GetValue("port");
|
||||
Server.Port = port;
|
||||
|
||||
Server.ListenPublic = (bool)configJson.GetValue("public");
|
||||
|
||||
Console.WriteLine("Config file path: " + path);
|
||||
}
|
||||
|
||||
static public void RestartAsAdmin()
|
||||
{
|
||||
var startInfo = new ProcessStartInfo(Application.ExecutablePath.ToString()) { Verb = "runas" };
|
||||
|
@@ -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("1.0.0.0")]
|
||||
[assembly: AssemblyVersion("0.4.1.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
|
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
||||
namespace Jackett
|
||||
{
|
||||
|
||||
public class ReleaseInfo
|
||||
public class ReleaseInfo: ICloneable
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public Uri Guid { get; set; }
|
||||
@@ -24,10 +24,34 @@ namespace Jackett
|
||||
public Uri ConverUrl { get; set; }
|
||||
public Uri BannerUrl { get; set; }
|
||||
public string InfoHash { get; set; }
|
||||
public Uri MagnetUrl { get; set; }
|
||||
public Uri MagnetUri { get; set; }
|
||||
public double? MinimumRatio { get; set; }
|
||||
public long? MinimumSeedTime { get; set; }
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return new ReleaseInfo()
|
||||
{
|
||||
Title = Title,
|
||||
Guid = Guid,
|
||||
Link = Link,
|
||||
Comments = Comments,
|
||||
PublishDate = PublishDate,
|
||||
Category = Category,
|
||||
Size = Size,
|
||||
Description = Description,
|
||||
RageID = RageID,
|
||||
Imdb = Imdb,
|
||||
Seeders = Seeders,
|
||||
Peers = Peers,
|
||||
ConverUrl = ConverUrl,
|
||||
BannerUrl = BannerUrl,
|
||||
InfoHash = InfoHash,
|
||||
MagnetUri = MagnetUri,
|
||||
MinimumRatio = MinimumRatio,
|
||||
MinimumSeedTime = MinimumSeedTime
|
||||
};
|
||||
}
|
||||
|
||||
public static long GetBytes(string unit, float value)
|
||||
{
|
||||
|
@@ -69,19 +69,19 @@ namespace Jackett
|
||||
select new XElement("item",
|
||||
new XElement("title", r.Title),
|
||||
new XElement("guid", r.Guid),
|
||||
new XElement("comments", r.Comments.ToString()),
|
||||
new XElement("pubDate", xmlDateFormat(r.PublishDate)),
|
||||
new XElement("size", r.Size),
|
||||
r.Comments == null ? null : new XElement("comments", r.Comments.ToString()),
|
||||
r.PublishDate == DateTime.MinValue ? null : new XElement("pubDate", xmlDateFormat(r.PublishDate)),
|
||||
r.Size == null ? null : new XElement("size", r.Size),
|
||||
new XElement("description", r.Description),
|
||||
new XElement("link", r.Link ?? r.MagnetUrl),
|
||||
new XElement("link", r.Link ?? r.MagnetUri),
|
||||
r.Category == null ? null : new XElement("category", r.Category),
|
||||
new XElement(
|
||||
"enclosure",
|
||||
new XAttribute("url", r.Link ?? r.MagnetUrl),
|
||||
new XAttribute("length", r.Size),
|
||||
new XAttribute("url", r.Link ?? r.MagnetUri),
|
||||
r.Size == null ? null : new XAttribute("length", r.Size),
|
||||
new XAttribute("type", "application/x-bittorrent")
|
||||
),
|
||||
getTorznabElement("magneturl", r.MagnetUrl),
|
||||
getTorznabElement("magneturl", r.MagnetUri),
|
||||
getTorznabElement("rageid", r.RageID),
|
||||
getTorznabElement("seeders", r.Seeders),
|
||||
getTorznabElement("peers", r.Peers),
|
||||
|
@@ -15,7 +15,9 @@ namespace Jackett
|
||||
{
|
||||
public class Server
|
||||
{
|
||||
public const int Port = 9117;
|
||||
public const int DefaultPort = 9117;
|
||||
public static int Port = DefaultPort;
|
||||
public static bool ListenPublic = true;
|
||||
|
||||
HttpListener listener;
|
||||
IndexerManager indexerManager;
|
||||
@@ -34,8 +36,6 @@ namespace Jackett
|
||||
sonarrApi = new SonarrApi();
|
||||
webApi = new WebApi(indexerManager, sonarrApi);
|
||||
|
||||
listener = new HttpListener();
|
||||
listener.Prefixes.Add("http://*:9117/");
|
||||
}
|
||||
|
||||
void LoadApiKey()
|
||||
@@ -52,51 +52,79 @@ namespace Jackett
|
||||
|
||||
public async Task Start()
|
||||
{
|
||||
Program.LoggerInstance.Info("Starting HTTP server...");
|
||||
Program.LoggerInstance.Info("Starting HTTP server on port " + Port + " listening " + (ListenPublic ? "publicly" : "privately"));
|
||||
|
||||
try
|
||||
{
|
||||
listener = new HttpListener();
|
||||
|
||||
if (ListenPublic)
|
||||
{
|
||||
listener.Prefixes.Add(string.Format("http://*:{0}/", Port));
|
||||
}
|
||||
else
|
||||
{
|
||||
listener.Prefixes.Add(string.Format("http://127.0.0.1:{0}/", Port));
|
||||
}
|
||||
|
||||
listener.Start();
|
||||
}
|
||||
catch (HttpListenerException ex)
|
||||
{
|
||||
Console.WriteLine("Server start exception: " + ex.ToString());
|
||||
var dialogResult = MessageBox.Show(
|
||||
"App must be ran as Admin for permission to use port " + Port + Environment.NewLine + "Restart app with admin privileges?",
|
||||
"Failed to open port",
|
||||
MessageBoxButtons.YesNo
|
||||
);
|
||||
if (dialogResult == DialogResult.No)
|
||||
if (ex.ErrorCode == 5)
|
||||
{
|
||||
Program.LoggerInstance.FatalException("App must be ran as Admin for permission to use port " + Port, ex);
|
||||
Application.Exit();
|
||||
return;
|
||||
var errorStr = "App must be ran as admin for permission to use port "
|
||||
+ Port + Environment.NewLine + "Restart app with admin privileges?";
|
||||
if (Program.IsWindows)
|
||||
{
|
||||
var dialogResult = MessageBox.Show(errorStr, "Error", MessageBoxButtons.YesNo);
|
||||
if (dialogResult == DialogResult.No)
|
||||
{
|
||||
Application.Exit();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Program.RestartAsAdmin();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Program.RestartAsAdmin();
|
||||
Program.LoggerInstance.FatalException("Failed to start HTTP server. " + ex.Message, ex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Server start exception: " + ex.ToString());
|
||||
Program.LoggerInstance.ErrorException("Error: " + ex.Message, ex);
|
||||
Program.LoggerInstance.ErrorException("Error starting HTTP server: " + ex.Message, ex);
|
||||
return;
|
||||
}
|
||||
Program.LoggerInstance.Info("Server started on port " + Port);
|
||||
|
||||
try
|
||||
{
|
||||
#if !DEBUG
|
||||
Process.Start("http://127.0.0.1:" + Port);
|
||||
#endif
|
||||
}
|
||||
catch (Exception) { }
|
||||
Program.LoggerInstance.Info("Server started on port " + Port);
|
||||
Program.LoggerInstance.Info("Accepting only requests from local system: " + (!ListenPublic));
|
||||
|
||||
while (true)
|
||||
{
|
||||
var context = await listener.GetContextAsync();
|
||||
ProcessHttpRequest(context);
|
||||
Exception error = null;
|
||||
try
|
||||
{
|
||||
error = null;
|
||||
var context = await listener.GetContextAsync();
|
||||
ProcessHttpRequest(context);
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
Program.LoggerInstance.ErrorException("Critical error, HTTP listener was destroyed", ex);
|
||||
Process.GetCurrentProcess().Kill();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex;
|
||||
Program.LoggerInstance.ErrorException("Error processing HTTP request", ex);
|
||||
}
|
||||
|
||||
if (error != null)
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,14 +167,18 @@ namespace Jackett
|
||||
var errorBytes = Encoding.UTF8.GetBytes(exception.Message);
|
||||
await context.Response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length);
|
||||
}
|
||||
catch (Exception) { }
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
context.Response.Close();
|
||||
}
|
||||
catch (Exception) { }
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -155,8 +187,6 @@ namespace Jackett
|
||||
|
||||
var query = HttpUtility.ParseQueryString(context.Request.Url.Query);
|
||||
var inputStream = context.Request.InputStream;
|
||||
var reader = new StreamReader(inputStream, context.Request.ContentEncoding);
|
||||
var bytes = await reader.ReadToEndAsync();
|
||||
|
||||
var indexerId = context.Request.Url.Segments[2].TrimEnd('/').ToLower();
|
||||
var indexer = indexerManager.GetIndexer(indexerId);
|
||||
@@ -172,7 +202,10 @@ namespace Jackett
|
||||
|
||||
var torznabQuery = TorznabQuery.FromHttpQuery(query);
|
||||
|
||||
torznabQuery.ShowTitles = await sonarrApi.GetShowTitle(torznabQuery.RageID);
|
||||
if (torznabQuery.RageID != 0)
|
||||
torznabQuery.ShowTitles = await sonarrApi.GetShowTitle(torznabQuery.RageID);
|
||||
else if (!string.IsNullOrEmpty(torznabQuery.SearchTerm))
|
||||
torznabQuery.ShowTitles = new string[] { torznabQuery.SearchTerm };
|
||||
|
||||
var releases = await indexer.PerformQuery(torznabQuery);
|
||||
|
||||
|
@@ -86,7 +86,15 @@ namespace Jackett
|
||||
|
||||
string SanitizeTitle(string title)
|
||||
{
|
||||
return title.Replace("(", "").Replace(")", "");
|
||||
char[] arr = title.ToCharArray();
|
||||
|
||||
arr = Array.FindAll<char>(arr, c => (char.IsLetterOrDigit(c)
|
||||
|| char.IsWhiteSpace(c)
|
||||
|| c == '-'
|
||||
|| c == '.'
|
||||
));
|
||||
title = new string(arr);
|
||||
return title;
|
||||
}
|
||||
|
||||
void LoadSettings()
|
||||
@@ -129,9 +137,9 @@ namespace Jackett
|
||||
{
|
||||
var config = new ConfigurationSonarr();
|
||||
config.LoadValuesFromJson(configJson);
|
||||
await ReloadNameMappings(config.Host.Value, int.Parse(config.Port.Value), config.ApiKey.Value);
|
||||
await ReloadNameMappings(config.Host.Value, ParseUtil.CoerceInt(config.Port.Value), config.ApiKey.Value);
|
||||
Host = "http://" + new Uri(config.Host.Value).Host;
|
||||
Port = int.Parse(config.Port.Value);
|
||||
Port = ParseUtil.CoerceInt(config.Port.Value);
|
||||
ApiKey = config.ApiKey.Value;
|
||||
SaveSettings();
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ namespace Jackett
|
||||
public int Season { get; private set; }
|
||||
public string Episode { get; private set; }
|
||||
public string[] ShowTitles { get; set; }
|
||||
public string SearchTerm { get; set; }
|
||||
|
||||
public string GetEpisodeSearchString()
|
||||
{
|
||||
@@ -33,7 +34,7 @@ namespace Jackett
|
||||
else if (string.IsNullOrEmpty(Episode))
|
||||
episodeString = string.Format("S{0:00}", Season);
|
||||
else
|
||||
episodeString = string.Format("S{0:00}E{1:00}", Season, int.Parse(Episode));
|
||||
episodeString = string.Format("S{0:00}E{1:00}", Season, ParseUtil.CoerceInt(Episode));
|
||||
|
||||
return episodeString;
|
||||
}
|
||||
@@ -44,11 +45,25 @@ namespace Jackett
|
||||
//{t=tvsearch&cat=5030%2c5040&extended=1&apikey=test&offset=0&limit=100&rid=24493&season=5&ep=1}
|
||||
var q = new TorznabQuery();
|
||||
q.QueryType = query["t"];
|
||||
q.Categories = query["cat"].Split(',');
|
||||
q.Extended = int.Parse(query["extended"]);
|
||||
q.SearchTerm = query["q"];
|
||||
if (query["cat"] != null)
|
||||
{
|
||||
q.Categories = query["cat"].Split(',');
|
||||
}
|
||||
|
||||
if (query["extended"] != null)
|
||||
{
|
||||
q.Extended = ParseUtil.CoerceInt(query["extended"]);
|
||||
}
|
||||
q.ApiKey = query["apikey"];
|
||||
q.Limit = int.Parse(query["limit"]);
|
||||
q.Offset = int.Parse(query["offset"]);
|
||||
if (query["limit"] != null)
|
||||
{
|
||||
q.Limit = ParseUtil.CoerceInt(query["limit"]);
|
||||
}
|
||||
if (query["offset"] != null)
|
||||
{
|
||||
q.Offset = ParseUtil.CoerceInt(query["offset"]);
|
||||
}
|
||||
|
||||
int temp;
|
||||
if (int.TryParse(query["rid"], out temp))
|
||||
|
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -27,17 +28,17 @@ namespace Jackett
|
||||
ApplySonarrConfig,
|
||||
TestSonarr
|
||||
}
|
||||
static Dictionary<string, WebApiMethod> WebApiMethods = new Dictionary<string, WebApiMethod>
|
||||
{
|
||||
{ "get_config_form", WebApiMethod.GetConfigForm },
|
||||
{ "configure_indexer", WebApiMethod.ConfigureIndexer },
|
||||
{ "get_indexers", WebApiMethod.GetIndexers },
|
||||
{ "test_indexer", WebApiMethod.TestIndexer },
|
||||
{ "delete_indexer", WebApiMethod.DeleteIndexer },
|
||||
{ "get_sonarr_config", WebApiMethod.GetSonarrConfig },
|
||||
{ "apply_sonarr_config", WebApiMethod.ApplySonarrConfig },
|
||||
{ "test_sonarr", WebApiMethod.TestSonarr }
|
||||
};
|
||||
|
||||
static Dictionary<string, WebApiMethod> WebApiMethods = new Dictionary<string, WebApiMethod> {
|
||||
{ "get_config_form", WebApiMethod.GetConfigForm },
|
||||
{ "configure_indexer", WebApiMethod.ConfigureIndexer },
|
||||
{ "get_indexers", WebApiMethod.GetIndexers },
|
||||
{ "test_indexer", WebApiMethod.TestIndexer },
|
||||
{ "delete_indexer", WebApiMethod.DeleteIndexer },
|
||||
{ "get_sonarr_config", WebApiMethod.GetSonarrConfig },
|
||||
{ "apply_sonarr_config", WebApiMethod.ApplySonarrConfig },
|
||||
{ "test_sonarr", WebApiMethod.TestSonarr }
|
||||
};
|
||||
|
||||
IndexerManager indexerManager;
|
||||
SonarrApi sonarrApi;
|
||||
@@ -80,7 +81,9 @@ namespace Jackett
|
||||
{
|
||||
await context.Response.OutputStream.WriteAsync(contentFile, 0, contentFile.Length);
|
||||
}
|
||||
catch (HttpListenerException) { }
|
||||
catch (HttpListenerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
async Task<JToken> ReadPostDataJson(Stream stream)
|
||||
@@ -93,8 +96,6 @@ namespace Jackett
|
||||
|
||||
async Task ProcessWebApiRequest(HttpListenerContext context, WebApiMethod method)
|
||||
{
|
||||
var query = HttpUtility.ParseQueryString(context.Request.Url.Query);
|
||||
|
||||
context.Response.ContentType = "text/json";
|
||||
context.Response.StatusCode = (int)HttpStatusCode.OK;
|
||||
|
||||
@@ -257,6 +258,7 @@ namespace Jackett
|
||||
{
|
||||
jsonReply["result"] = "success";
|
||||
jsonReply["api_key"] = ApiKey.CurrentKey;
|
||||
jsonReply["app_version"] = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
JArray items = new JArray();
|
||||
foreach (var i in indexerManager.Indexers)
|
||||
{
|
||||
|
190
src/Jackett/WebContent/custom.css
Normal file
@@ -0,0 +1,190 @@
|
||||
body {
|
||||
background-image: url("binding_dark.png");
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
#page {
|
||||
border-radius: 6px;
|
||||
background-color: white;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
}
|
||||
|
||||
#templates {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 6px;
|
||||
box-shadow: 1px 1px 5px 2px #cdcdcd;
|
||||
padding: 10px;
|
||||
width: 260px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.unconfigured-indexer {
|
||||
height: 170px;
|
||||
}
|
||||
|
||||
.indexer {
|
||||
height: 230px;
|
||||
}
|
||||
|
||||
.add-indexer {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.indexer-logo {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.indexer-logo > img {
|
||||
border: 1px solid #828282;
|
||||
}
|
||||
|
||||
.indexer-name > h3 {
|
||||
margin-top: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.indexer-buttons {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.indexer-buttons > .btn {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
||||
.indexer-button-test {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.indexer-add-content {
|
||||
color: gray;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.indexer-add-content > .glyphicon {
|
||||
font-size: 50px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.indexer-add-content > .light-text {
|
||||
margin-top: 11px;
|
||||
font-size: 18px;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
|
||||
.indexer-host > input {
|
||||
font-size: 12px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.setup-item-inputstring {
|
||||
max-width: 260px;
|
||||
}
|
||||
|
||||
.setup-item-inputbool input {
|
||||
max-width: 100px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
-webkit-animation: spin 2s infinite linear;
|
||||
-moz-animation: spin 2s infinite linear;
|
||||
-o-animation: spin 2s infinite linear;
|
||||
animation: spin 2s infinite linear;
|
||||
}
|
||||
|
||||
@-moz-keyframes spin {
|
||||
from {
|
||||
-moz-transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
-moz-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
from {
|
||||
-webkit-transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
#setup-indexer-go {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top-color: #cdcdcd;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
}
|
||||
|
||||
.input-area > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.input-area > p {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.input-header {
|
||||
font-size: 18px;
|
||||
width: 140px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.input-right {
|
||||
width: 300px;
|
||||
display: inline-block;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#sonarr-warning {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#logo {
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
#header-title {
|
||||
font-size: 34px;
|
||||
vertical-align: middle;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
#footer {
|
||||
color: #444444;
|
||||
margin: 0 auto;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
297
src/Jackett/WebContent/custom.js
Normal file
@@ -0,0 +1,297 @@
|
||||
|
||||
|
||||
reloadIndexers();
|
||||
loadSonarrInfo();
|
||||
|
||||
function loadSonarrInfo() {
|
||||
getSonarrConfig(function (data) {
|
||||
$("#sonarr-host").val("");
|
||||
var host, port, apiKey;
|
||||
for (var i = 0; i < data.config.length; i++) {
|
||||
if (data.config[i].id == "host")
|
||||
host = data.config[i].value;
|
||||
if (data.config[i].id == "port")
|
||||
port = data.config[i].value;
|
||||
if (data.config[i].id == "apikey")
|
||||
apiKey = data.config[i].value;
|
||||
}
|
||||
if (!apiKey)
|
||||
$("#sonarr-warning").show();
|
||||
else {
|
||||
$("#sonarr-warning").hide();
|
||||
$("#sonarr-host").val(host + ":" + port);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getSonarrConfig(callback) {
|
||||
var jqxhr = $.get("get_sonarr_config", function (data) {
|
||||
callback(data);
|
||||
}).fail(function () {
|
||||
doNotify("Error loading Sonarr API configuration, request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
|
||||
});
|
||||
}
|
||||
|
||||
$("#sonarr-test").click(function () {
|
||||
var jqxhr = $.get("get_indexers", function (data) {
|
||||
if (data.result == "error")
|
||||
doNotify("Test failed for Sonarr API\n" + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
else
|
||||
doNotify("Test successful for Sonarr API", "success", "glyphicon glyphicon-ok");
|
||||
}).fail(function () {
|
||||
doNotify("Error testing Sonarr, request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
|
||||
});
|
||||
});
|
||||
|
||||
$("#sonarr-settings").click(function () {
|
||||
getSonarrConfig(function (data) {
|
||||
var config = data.config;
|
||||
|
||||
var configForm = newConfigModal("Sonarr API", config);
|
||||
|
||||
var $goButton = configForm.find(".setup-indexer-go");
|
||||
$goButton.click(function () {
|
||||
var data = getConfigModalJson(configForm);
|
||||
|
||||
var originalBtnText = $goButton.html();
|
||||
$goButton.prop('disabled', true);
|
||||
$goButton.html($('#templates > .spinner')[0].outerHTML);
|
||||
|
||||
var jqxhr = $.post("apply_sonarr_config", JSON.stringify(data), function (data) {
|
||||
if (data.result == "error") {
|
||||
if (data.config) {
|
||||
populateSetupForm(data.indexer, data.name, data.config);
|
||||
}
|
||||
doNotify("Configuration failed: " + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
}
|
||||
else {
|
||||
configForm.modal("hide");
|
||||
loadSonarrInfo();
|
||||
doNotify("Successfully configured Sonarr API", "success", "glyphicon glyphicon-ok");
|
||||
}
|
||||
}).fail(function () {
|
||||
doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
|
||||
}).always(function () {
|
||||
$goButton.html(originalBtnText);
|
||||
$goButton.prop('disabled', false);
|
||||
});
|
||||
});
|
||||
|
||||
configForm.modal("show");
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function reloadIndexers() {
|
||||
$('#indexers').hide();
|
||||
$('#indexers > .indexer').remove();
|
||||
$('#unconfigured-indexers').empty();
|
||||
var jqxhr = $.get("get_indexers", function (data) {
|
||||
$("#api-key-input").val(data.api_key);
|
||||
$("#app-version").html(data.app_version);
|
||||
displayIndexers(data.items);
|
||||
}).fail(function () {
|
||||
doNotify("Error loading indexers, request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
|
||||
});
|
||||
}
|
||||
|
||||
function displayIndexers(items) {
|
||||
var indexerTemplate = Handlebars.compile($("#templates > .configured-indexer")[0].outerHTML);
|
||||
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);
|
||||
if (item.configured)
|
||||
$('#indexers').append(indexerTemplate(item));
|
||||
else
|
||||
$('#unconfigured-indexers').append($(unconfiguredIndexerTemplate(item)));
|
||||
}
|
||||
|
||||
var addIndexerButton = $("#templates > .add-indexer")[0].outerHTML;
|
||||
$('#indexers').append(addIndexerButton);
|
||||
|
||||
$('#indexers').fadeIn();
|
||||
prepareSetupButtons();
|
||||
prepareTestButtons();
|
||||
prepareDeleteButtons();
|
||||
}
|
||||
|
||||
function prepareDeleteButtons() {
|
||||
$(".indexer-button-delete").each(function (i, btn) {
|
||||
var $btn = $(btn);
|
||||
var id = $btn.data("id");
|
||||
$btn.click(function () {
|
||||
var jqxhr = $.post("delete_indexer", JSON.stringify({ indexer: id }), function (data) {
|
||||
if (data.result == "error") {
|
||||
doNotify("Delete error for " + id + "\n" + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
}
|
||||
else {
|
||||
doNotify("Deleted " + id, "success", "glyphicon glyphicon-ok");
|
||||
}
|
||||
}).fail(function () {
|
||||
doNotify("Error deleting indexer, request to Jackett server error", "danger", "glyphicon glyphicon-alert");
|
||||
}).always(function () {
|
||||
reloadIndexers();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function prepareSetupButtons() {
|
||||
$('.indexer-setup').each(function (i, btn) {
|
||||
var $btn = $(btn);
|
||||
var id = $btn.data("id");
|
||||
$btn.click(function () {
|
||||
displayIndexerSetup(id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function prepareTestButtons() {
|
||||
$(".indexer-button-test").each(function (i, btn) {
|
||||
var $btn = $(btn);
|
||||
var id = $btn.data("id");
|
||||
$btn.click(function () {
|
||||
doNotify("Test started for " + id, "info", "glyphicon glyphicon-transfer");
|
||||
var jqxhr = $.post("test_indexer", JSON.stringify({ indexer: id }), function (data) {
|
||||
if (data.result == "error") {
|
||||
doNotify("Test failed for " + data.name + "\n" + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
}
|
||||
else {
|
||||
doNotify("Test successful for " + data.name, "success", "glyphicon glyphicon-ok");
|
||||
}
|
||||
}).fail(function () {
|
||||
doNotify("Error testing indexer, request to Jackett server error", "danger", "glyphicon glyphicon-alert");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function displayIndexerSetup(id) {
|
||||
|
||||
var jqxhr = $.post("get_config_form", JSON.stringify({ indexer: id }), function (data) {
|
||||
if (data.result == "error") {
|
||||
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
return;
|
||||
}
|
||||
populateSetupForm(id, data.name, data.config);
|
||||
|
||||
}).fail(function () {
|
||||
doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
|
||||
});
|
||||
|
||||
$("#select-indexer-modal").modal("hide");
|
||||
}
|
||||
|
||||
function populateConfigItems(configForm, config) {
|
||||
var $formItemContainer = configForm.find(".config-setup-form");
|
||||
$formItemContainer.empty();
|
||||
var setupItemTemplate = Handlebars.compile($("#templates > .setup-item")[0].outerHTML);
|
||||
for (var i = 0; i < config.length; i++) {
|
||||
var item = config[i];
|
||||
var setupValueTemplate = Handlebars.compile($("#templates > .setup-item-" + item.type)[0].outerHTML);
|
||||
item.value_element = setupValueTemplate(item);
|
||||
$formItemContainer.append(setupItemTemplate(item));
|
||||
}
|
||||
}
|
||||
|
||||
function newConfigModal(title, config) {
|
||||
//config-setup-modal
|
||||
var configTemplate = Handlebars.compile($("#templates > .config-setup-modal")[0].outerHTML);
|
||||
var configForm = $(configTemplate({ title: title }));
|
||||
|
||||
$("#modals").append(configForm);
|
||||
|
||||
populateConfigItems(configForm, config);
|
||||
|
||||
return configForm;
|
||||
//modal.remove();
|
||||
}
|
||||
|
||||
function getConfigModalJson(configForm) {
|
||||
var configJson = {};
|
||||
configForm.find(".config-setup-form").children().each(function (i, el) {
|
||||
$el = $(el);
|
||||
var type = $el.data("type");
|
||||
var id = $el.data("id");
|
||||
switch (type) {
|
||||
case "inputstring":
|
||||
configJson[id] = $el.find(".setup-item-inputstring").val();
|
||||
break;
|
||||
case "inputbool":
|
||||
configJson[id] = $el.find(".setup-item-inputbool input").is(":checked");
|
||||
break;
|
||||
}
|
||||
});
|
||||
return configJson;
|
||||
}
|
||||
|
||||
function populateSetupForm(indexerId, name, config) {
|
||||
|
||||
var configForm = newConfigModal(name, config);
|
||||
|
||||
var $goButton = configForm.find(".setup-indexer-go");
|
||||
$goButton.click(function () {
|
||||
var data = { indexer: indexerId, name: name };
|
||||
data.config = getConfigModalJson(configForm);
|
||||
|
||||
var originalBtnText = $goButton.html();
|
||||
$goButton.prop('disabled', true);
|
||||
$goButton.html($('#templates > .spinner')[0].outerHTML);
|
||||
|
||||
var jqxhr = $.post("configure_indexer", JSON.stringify(data), function (data) {
|
||||
if (data.result == "error") {
|
||||
if (data.config) {
|
||||
populateConfigItems(configForm, data.config);
|
||||
}
|
||||
doNotify("Configuration failed: " + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
}
|
||||
else {
|
||||
configForm.modal("hide");
|
||||
reloadIndexers();
|
||||
doNotify("Successfully configured " + data.name, "success", "glyphicon glyphicon-ok");
|
||||
}
|
||||
}).fail(function () {
|
||||
doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
|
||||
}).always(function () {
|
||||
$goButton.html(originalBtnText);
|
||||
$goButton.prop('disabled', false);
|
||||
});
|
||||
});
|
||||
|
||||
configForm.modal("show");
|
||||
}
|
||||
|
||||
function resolveUrl(url) {
|
||||
var a = document.createElement('a');
|
||||
a.href = url;
|
||||
url = a.href;
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function doNotify(message, type, icon) {
|
||||
$.notify({
|
||||
message: message,
|
||||
icon: icon
|
||||
}, {
|
||||
element: 'body',
|
||||
type: type,
|
||||
allow_dismiss: true,
|
||||
z_index: 9000,
|
||||
mouse_over: 'pause',
|
||||
placement: {
|
||||
from: "bottom",
|
||||
align: "center"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearNotifications() {
|
||||
$('[data-notify="container"]').remove();
|
||||
}
|
||||
|
||||
$('#test').click(doNotify);
|
||||
|
@@ -1,644 +1,177 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<link rel='shortcut icon' type='image/x-icon' href='/favicon.ico' />
|
||||
|
||||
<script src="jquery-2.1.3.min.js"></script>
|
||||
<script src="handlebars-v3.0.1.js"></script>
|
||||
<script src="bootstrap/bootstrap.min.js"></script>
|
||||
<script src="bootstrap-notify.js"></script>
|
||||
|
||||
<link href="bootstrap/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="animate.css" rel="stylesheet">
|
||||
|
||||
<title>Jackett</title>
|
||||
<style>
|
||||
body {
|
||||
background-image: url("binding_dark.png");
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
#page {
|
||||
border-radius: 6px;
|
||||
background-color: white;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
}
|
||||
|
||||
#templates {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 6px;
|
||||
box-shadow: 1px 1px 5px 2px #cdcdcd;
|
||||
padding: 10px;
|
||||
width: 260px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.unconfigured-indexer {
|
||||
height: 170px;
|
||||
}
|
||||
|
||||
.indexer {
|
||||
height: 230px;
|
||||
}
|
||||
|
||||
.add-indexer {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.indexer-logo {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.indexer-logo > img {
|
||||
border: 1px solid #828282;
|
||||
}
|
||||
|
||||
.indexer-name > h3 {
|
||||
margin-top: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.indexer-buttons {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.indexer-buttons > .btn {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
||||
.indexer-button-test {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.indexer-add-content {
|
||||
color: gray;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.indexer-add-content > .glyphicon {
|
||||
font-size: 50px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.indexer-add-content > .light-text {
|
||||
margin-top: 11px;
|
||||
font-size: 18px;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
|
||||
.indexer-host > input {
|
||||
font-size: 12px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.setup-item-inputstring {
|
||||
max-width: 260px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
-webkit-animation: spin 2s infinite linear;
|
||||
-moz-animation: spin 2s infinite linear;
|
||||
-o-animation: spin 2s infinite linear;
|
||||
animation: spin 2s infinite linear;
|
||||
}
|
||||
|
||||
@-moz-keyframes spin {
|
||||
from {
|
||||
-moz-transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
-moz-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
from {
|
||||
-webkit-transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
#setup-indexer-go {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top-color: #cdcdcd;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
}
|
||||
|
||||
.input-area > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.input-area > p {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.input-header {
|
||||
font-size: 18px;
|
||||
width: 140px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.input-right {
|
||||
width: 300px;
|
||||
display: inline-block;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#sonarr-warning {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#logo {
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
#header-title {
|
||||
font-size: 34px;
|
||||
vertical-align: middle;
|
||||
padding-left: 15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<img id="logo" src="jacket_medium.png" /><span id="header-title">Jackett</span>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="input-area">
|
||||
<span class="input-header">Sonarr API Host: </span>
|
||||
<input id="sonarr-host" class="form-control input-right" type="text" readonly />
|
||||
<button id="sonarr-settings" class="btn btn-primary btn-sm">
|
||||
Settings <span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button id="sonarr-test" class="btn btn-warning btn-sm">
|
||||
Test <span class="glyphicon glyphicon-screenshot" aria-hidden="true"></span>
|
||||
</button>
|
||||
<p id="sonarr-warning" class="alert alert-danger" role="alert">
|
||||
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||
Sonarr API must be configured
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="input-area">
|
||||
<span class="input-header">Jackett API Key: </span>
|
||||
<input id="api-key-input" class="form-control input-right" type="text" value="" placeholder="API Key" readonly="">
|
||||
<p>Use this key when adding indexers to Sonarr. This key works for all indexers.</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<h3>Configured Indexers</h3>
|
||||
<div id="indexers">
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="select-indexer-modal" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<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">Select an indexer to setup</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="unconfigured-indexers">
|
||||
</div>
|
||||
<hr />
|
||||
<p>
|
||||
To add a Jackett indexer in Sonarr go to <b>Settings > Indexers > Add > Torznab > Custom</b>
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<div class="light-text">Add</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div class="configured-indexer indexer card">
|
||||
<div class="indexer-logo"><img src="logos/{{id}}.png" /></div>
|
||||
<div class="indexer-name"><h3>{{name}}</h3></div>
|
||||
<div class="indexer-buttons">
|
||||
<button class="btn btn-primary btn-sm" data-id="{{id}}">
|
||||
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm indexer-button-delete" data-id="{{id}}">
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
</button>
|
||||
<a class="btn btn-info btn-sm" target="_blank" href="{{site_link}}">
|
||||
<span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
|
||||
</a>
|
||||
<button class="btn btn-warning btn-sm indexer-button-test" data-id="{{id}}">
|
||||
Test <span class="glyphicon glyphicon-screenshot" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="indexer-host">
|
||||
<b>Torznab Host:</b>
|
||||
<input class="form-control" type="text" value="{{torznab_host}}" placeholder="Torznab Host" readonly="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="unconfigured-indexer card">
|
||||
<div class="indexer-logo"><img src="logos/{{id}}.png" /></div>
|
||||
<div class="indexer-name"><h3>{{name}}</h3></div>
|
||||
<div class="indexer-buttons">
|
||||
<a class="btn btn-info" target="_blank" href="{{site_link}}">Visit <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span></a>
|
||||
<button class="indexer-setup btn btn-success" data-id="{{id}}">Setup <span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="setup-item form-group" data-id="{{id}}" data-value="{{value}}" data-type="{{type}}">
|
||||
<div class="setup-item-label">{{name}}</div>
|
||||
<div class="setup-item-value">{{{value_element}}}</div>
|
||||
</div>
|
||||
|
||||
<input class="setup-item-inputstring form-control" type="text" value="{{{value}}}" />
|
||||
<div class="setup-item-checkbox">
|
||||
{{#if value}}
|
||||
<input type="checkbox" class="form-control" checked />
|
||||
{{else}}
|
||||
<input type="checkbox" class="form-control" />
|
||||
{{/if}}
|
||||
</div>
|
||||
<img class="setup-item-displayimage" src="{{{value}}}" />
|
||||
<div class="setup-item-displayinfo alert alert-info" role="alert">{{{value}}}</div>
|
||||
|
||||
<span class="spinner glyphicon glyphicon-refresh"></span>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
reloadIndexers();
|
||||
loadSonarrInfo();
|
||||
|
||||
function loadSonarrInfo() {
|
||||
getSonarrConfig(function (data) {
|
||||
$("#sonarr-host").val("");
|
||||
var host, port, apiKey;
|
||||
for (var i = 0; i < data.config.length; i++) {
|
||||
if (data.config[i].id == "host")
|
||||
host = data.config[i].value;
|
||||
if (data.config[i].id == "port")
|
||||
port = data.config[i].value;
|
||||
if (data.config[i].id == "apikey")
|
||||
apiKey = data.config[i].value;
|
||||
}
|
||||
if (!apiKey)
|
||||
$("#sonarr-warning").show();
|
||||
else {
|
||||
$("#sonarr-warning").hide();
|
||||
$("#sonarr-host").val(host + ":" + port);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getSonarrConfig(callback) {
|
||||
var jqxhr = $.get("get_sonarr_config", function (data) {
|
||||
callback(data);
|
||||
}).fail(function () {
|
||||
doNotify("Error loading Sonarr API configuration, request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
|
||||
});
|
||||
}
|
||||
|
||||
$("#sonarr-test").click(function () {
|
||||
var jqxhr = $.get("get_indexers", function (data) {
|
||||
if (data.result == "error")
|
||||
doNotify("Test failed for Sonarr API\n" + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
else
|
||||
doNotify("Test successful for Sonarr API", "success", "glyphicon glyphicon-ok");
|
||||
}).fail(function () {
|
||||
doNotify("Error testing Sonarr, request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
|
||||
});
|
||||
});
|
||||
|
||||
$("#sonarr-settings").click(function () {
|
||||
getSonarrConfig(function (data) {
|
||||
var config = data.config;
|
||||
|
||||
var configForm = newConfigModal("Sonarr API", config);
|
||||
|
||||
var $goButton = configForm.find(".setup-indexer-go");
|
||||
$goButton.click(function () {
|
||||
var data = getConfigModalJson(configForm);
|
||||
|
||||
var originalBtnText = $goButton.html();
|
||||
$goButton.prop('disabled', true);
|
||||
$goButton.html($('#templates > .spinner')[0].outerHTML);
|
||||
|
||||
var jqxhr = $.post("apply_sonarr_config", JSON.stringify(data), function (data) {
|
||||
if (data.result == "error") {
|
||||
if (data.config) {
|
||||
populateSetupForm(data.indexer, data.name, data.config);
|
||||
}
|
||||
doNotify("Configuration failed: " + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
}
|
||||
else {
|
||||
configForm.modal("hide");
|
||||
loadSonarrInfo();
|
||||
doNotify("Successfully configured Sonarr API", "success", "glyphicon glyphicon-ok");
|
||||
}
|
||||
}).fail(function () {
|
||||
doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
|
||||
}).always(function () {
|
||||
$goButton.html(originalBtnText);
|
||||
$goButton.prop('disabled', false);
|
||||
});
|
||||
});
|
||||
|
||||
configForm.modal("show");
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function reloadIndexers() {
|
||||
$('#indexers').hide();
|
||||
$('#indexers > .indexer').remove();
|
||||
$('#unconfigured-indexers').empty();
|
||||
var jqxhr = $.get("get_indexers", function (data) {
|
||||
$("#api-key-input").val(data.api_key);
|
||||
displayIndexers(data.items);
|
||||
}).fail(function () {
|
||||
doNotify("Error loading indexers, request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
|
||||
});
|
||||
}
|
||||
|
||||
function displayIndexers(items) {
|
||||
var indexerTemplate = Handlebars.compile($("#templates > .configured-indexer")[0].outerHTML);
|
||||
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);
|
||||
if (item.configured)
|
||||
$('#indexers').append(indexerTemplate(item));
|
||||
else
|
||||
$('#unconfigured-indexers').append($(unconfiguredIndexerTemplate(item)));
|
||||
}
|
||||
|
||||
var addIndexerButton = $("#templates > .add-indexer")[0].outerHTML;
|
||||
$('#indexers').append(addIndexerButton);
|
||||
|
||||
$('#indexers').fadeIn();
|
||||
prepareSetupButtons();
|
||||
prepareTestButtons();
|
||||
prepareDeleteButtons();
|
||||
}
|
||||
|
||||
function prepareDeleteButtons() {
|
||||
$(".indexer-button-delete").each(function (i, btn) {
|
||||
var $btn = $(btn);
|
||||
var id = $btn.data("id");
|
||||
$btn.click(function () {
|
||||
var jqxhr = $.post("delete_indexer", JSON.stringify({ indexer: id }), function (data) {
|
||||
if (data.result == "error") {
|
||||
doNotify("Delete error for " + id + "\n" + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
}
|
||||
else {
|
||||
doNotify("Deleted " + id, "success", "glyphicon glyphicon-ok");
|
||||
}
|
||||
}).fail(function () {
|
||||
doNotify("Error deleting indexer, request to Jackett server error", "danger", "glyphicon glyphicon-alert");
|
||||
}).always(function () {
|
||||
reloadIndexers();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function prepareSetupButtons() {
|
||||
$('.indexer-setup').each(function (i, btn) {
|
||||
var $btn = $(btn);
|
||||
var id = $btn.data("id");
|
||||
$btn.click(function () {
|
||||
displayIndexerSetup(id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function prepareTestButtons() {
|
||||
$(".indexer-button-test").each(function (i, btn) {
|
||||
var $btn = $(btn);
|
||||
var id = $btn.data("id");
|
||||
$btn.click(function () {
|
||||
doNotify("Test started for " + id, "info", "glyphicon glyphicon-transfer");
|
||||
var jqxhr = $.post("test_indexer", JSON.stringify({ indexer: id }), function (data) {
|
||||
if (data.result == "error") {
|
||||
doNotify("Test failed for " + data.name + "\n" + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
}
|
||||
else {
|
||||
doNotify("Test successful for " + data.name, "success", "glyphicon glyphicon-ok");
|
||||
}
|
||||
}).fail(function () {
|
||||
doNotify("Error testing indexer, request to Jackett server error", "danger", "glyphicon glyphicon-alert");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function displayIndexerSetup(id) {
|
||||
|
||||
var jqxhr = $.post("get_config_form", JSON.stringify({ indexer: id }), function (data) {
|
||||
if (data.result == "error") {
|
||||
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
return;
|
||||
}
|
||||
populateSetupForm(id, data.name, data.config);
|
||||
|
||||
}).fail(function () {
|
||||
doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
|
||||
});
|
||||
|
||||
$("#select-indexer-modal").modal("hide");
|
||||
}
|
||||
|
||||
function populateConfigItems(configForm, config) {
|
||||
var $formItemContainer = configForm.find(".config-setup-form");
|
||||
$formItemContainer.empty();
|
||||
var setupItemTemplate = Handlebars.compile($("#templates > .setup-item")[0].outerHTML);
|
||||
for (var i = 0; i < config.length; i++) {
|
||||
var item = config[i];
|
||||
var setupValueTemplate = Handlebars.compile($("#templates > .setup-item-" + item.type)[0].outerHTML);
|
||||
item.value_element = setupValueTemplate(item);
|
||||
$formItemContainer.append(setupItemTemplate(item));
|
||||
}
|
||||
}
|
||||
|
||||
function newConfigModal(title, config) {
|
||||
//config-setup-modal
|
||||
var configTemplate = Handlebars.compile($("#templates > .config-setup-modal")[0].outerHTML);
|
||||
var configForm = $(configTemplate({ title: title }));
|
||||
|
||||
$("#modals").append(configForm);
|
||||
|
||||
populateConfigItems(configForm, config);
|
||||
|
||||
return configForm;
|
||||
//modal.remove();
|
||||
}
|
||||
|
||||
function getConfigModalJson(configForm) {
|
||||
var configJson = {};
|
||||
configForm.find(".config-setup-form").children().each(function (i, el) {
|
||||
$el = $(el);
|
||||
var type = $el.data("type");
|
||||
var id = $el.data("id");
|
||||
switch (type) {
|
||||
case "inputstring":
|
||||
configJson[id] = $el.find(".setup-item-inputstring").val();
|
||||
break;
|
||||
case "inputbool":
|
||||
configJson[id] = $el.find(".setup-item-checkbox").val();
|
||||
break;
|
||||
}
|
||||
});
|
||||
return configJson;
|
||||
}
|
||||
|
||||
function populateSetupForm(indexerId, name, config) {
|
||||
|
||||
var configForm = newConfigModal(name, config);
|
||||
|
||||
var $goButton = configForm.find(".setup-indexer-go");
|
||||
$goButton.click(function () {
|
||||
var data = { indexer: indexerId, name: name };
|
||||
data.config = getConfigModalJson(configForm);
|
||||
|
||||
var originalBtnText = $goButton.html();
|
||||
$goButton.prop('disabled', true);
|
||||
$goButton.html($('#templates > .spinner')[0].outerHTML);
|
||||
|
||||
var jqxhr = $.post("configure_indexer", JSON.stringify(data), function (data) {
|
||||
if (data.result == "error") {
|
||||
if (data.config) {
|
||||
populateConfigItems(configForm, data.config);
|
||||
}
|
||||
doNotify("Configuration failed: " + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
}
|
||||
else {
|
||||
configForm.modal("hide");
|
||||
reloadIndexers();
|
||||
doNotify("Successfully configured " + data.name, "success", "glyphicon glyphicon-ok");
|
||||
}
|
||||
}).fail(function () {
|
||||
doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
|
||||
}).always(function () {
|
||||
$goButton.html(originalBtnText);
|
||||
$goButton.prop('disabled', false);
|
||||
});
|
||||
});
|
||||
|
||||
configForm.modal("show");
|
||||
}
|
||||
|
||||
function resolveUrl(url) {
|
||||
var a = document.createElement('a');
|
||||
a.href = url;
|
||||
url = a.href;
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function doNotify(message, type, icon) {
|
||||
$.notify({
|
||||
message: message,
|
||||
icon: icon
|
||||
}, {
|
||||
element: 'body',
|
||||
type: type,
|
||||
allow_dismiss: true,
|
||||
z_index: 9000,
|
||||
mouse_over: 'pause',
|
||||
placement: {
|
||||
from: "bottom",
|
||||
align: "center"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearNotifications() {
|
||||
$('[data-notify="container"]').remove();
|
||||
}
|
||||
|
||||
$('#test').click(doNotify);
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<link rel='shortcut icon' type='image/x-icon' href='/favicon.ico' />
|
||||
|
||||
<script src="jquery-2.1.3.min.js"></script>
|
||||
<script src="handlebars-v3.0.1.js"></script>
|
||||
<script src="bootstrap/bootstrap.min.js"></script>
|
||||
<script src="bootstrap-notify.js"></script>
|
||||
|
||||
<link href="bootstrap/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="animate.css" rel="stylesheet">
|
||||
<link href="custom.css" rel="stylesheet">
|
||||
|
||||
<title>Jackett</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<img id="logo" src="jacket_medium.png" /><span id="header-title">Jackett</span>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="input-area">
|
||||
<span class="input-header">Sonarr API Host: </span>
|
||||
<input id="sonarr-host" class="form-control input-right" type="text" readonly />
|
||||
<button id="sonarr-settings" class="btn btn-primary btn-sm">
|
||||
Settings <span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button id="sonarr-test" class="btn btn-warning btn-sm">
|
||||
Test <span class="glyphicon glyphicon-screenshot" aria-hidden="true"></span>
|
||||
</button>
|
||||
<p id="sonarr-warning" class="alert alert-danger" role="alert">
|
||||
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||
Sonarr API must be configured
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="input-area">
|
||||
<span class="input-header">Jackett API Key: </span>
|
||||
<input id="api-key-input" class="form-control input-right" type="text" value="" placeholder="API Key" readonly="">
|
||||
<p>Use this key when adding indexers to Sonarr. This key works for all indexers.</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<h3>Configured Indexers</h3>
|
||||
<div id="indexers">
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div id="footer">
|
||||
Jackett Version <span id="app-version"></span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div id="select-indexer-modal" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<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">Select an indexer to setup</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="unconfigured-indexers">
|
||||
</div>
|
||||
<hr />
|
||||
<p>
|
||||
To add a Jackett indexer in Sonarr go to <b>Settings > Indexers > Add > Torznab > Custom</b>
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<div class="light-text">Add</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div class="configured-indexer indexer card">
|
||||
<div class="indexer-logo"><img src="logos/{{id}}.png" /></div>
|
||||
<div class="indexer-name"><h3>{{name}}</h3></div>
|
||||
<div class="indexer-buttons">
|
||||
<button class="btn btn-primary btn-sm indexer-setup" data-id="{{id}}">
|
||||
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm indexer-button-delete" data-id="{{id}}">
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
</button>
|
||||
<a class="btn btn-info btn-sm" target="_blank" href="{{site_link}}">
|
||||
<span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
|
||||
</a>
|
||||
<button class="btn btn-warning btn-sm indexer-button-test" data-id="{{id}}">
|
||||
Test <span class="glyphicon glyphicon-screenshot" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="indexer-host">
|
||||
<b>Torznab Host:</b>
|
||||
<input class="form-control" type="text" value="{{torznab_host}}" placeholder="Torznab Host" readonly="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="unconfigured-indexer card">
|
||||
<div class="indexer-logo"><img src="logos/{{id}}.png" /></div>
|
||||
<div class="indexer-name"><h3>{{name}}</h3></div>
|
||||
<div class="indexer-buttons">
|
||||
<a class="btn btn-info" target="_blank" href="{{site_link}}">Visit <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span></a>
|
||||
<button class="indexer-setup btn btn-success" data-id="{{id}}">Setup <span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="setup-item form-group" data-id="{{id}}" data-value="{{value}}" data-type="{{type}}">
|
||||
<div class="setup-item-label">{{name}}</div>
|
||||
<div class="setup-item-value">{{{value_element}}}</div>
|
||||
</div>
|
||||
|
||||
<input class="setup-item-inputstring form-control" type="text" value="{{{value}}}" />
|
||||
<div class="setup-item-inputbool">
|
||||
{{#if value}}
|
||||
<input type="checkbox" data-id="{{id}}" class="form-control" checked />
|
||||
{{else}}
|
||||
<input type="checkbox" data-id="{{id}}" class="form-control" />
|
||||
{{/if}}
|
||||
</div>
|
||||
<img class="setup-item-displayimage" src="{{{value}}}" />
|
||||
<div class="setup-item-displayinfo alert alert-info" role="alert">{{{value}}}</div>
|
||||
|
||||
<span class="spinner glyphicon glyphicon-refresh"></span>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<script src="custom.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
BIN
src/Jackett/WebContent/logos/alpharatio.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
src/Jackett/WebContent/logos/animebytes.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
src/Jackett/WebContent/logos/beyondhd.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
src/Jackett/WebContent/logos/hdtorrents.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
src/Jackett/WebContent/logos/sceneaccess.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
src/Jackett/WebContent/logos/scenetime.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
src/Jackett/WebContent/logos/showrss.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
src/Jackett/WebContent/logos/strike.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
src/Jackett/WebContent/logos/torrentday.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
src/Jackett/WebContent/logos/torrentleech.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
src/Jackett/WebContent/logos/torrentshack.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
src/Jackett/WebContent/logos/torrentz.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="CsQuery" version="1.3.4" targetFramework="net451" />
|
||||
<package id="modernhttpclient" version="2.3.0" targetFramework="net451" />
|
||||
<package id="Newtonsoft.Json" version="6.0.8" targetFramework="net451" />
|
||||
<package id="NLog" version="3.2.0.0" 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" />
|
||||
</packages>
|