From 11e41485df0939352767a8eec1373961a52eb7c7 Mon Sep 17 00:00:00 2001 From: KZ Date: Sun, 23 Aug 2015 22:33:26 +0100 Subject: [PATCH 01/11] Fix display issue with the readme --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index cc30745d4..8c38e4ce8 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,7 @@ We were previously focused on TV but are working on extending searches to allow * Debian/Ubunutu: apt-get install libcurl-dev * Redhat/Fedora: yum install libcurl-devel * For other distros see the [Curl docs](http://curl.haxx.se/dlwiz/?type=devel). -3. Download and extract the latest ```.tar.bz2``` release from the [website](http://jackett.net/Download) and run Jackett using mono with the command "mono JackettConsole.exe". - + 3. Download and extract the latest ```.tar.bz2``` release from the [website](http://jackett.net/Download) and run Jackett using mono with the command "mono JackettConsole.exe". #### Installation on Windows From a2611a5797d3d4f2415d48e867c0ca6090cba597 Mon Sep 17 00:00:00 2001 From: KZ Date: Mon, 24 Aug 2015 20:46:11 +0100 Subject: [PATCH 02/11] Add metatags to the website --- src/Website/Views/Shared/_Layout.cshtml | 98 +++++++++++++------------ 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/src/Website/Views/Shared/_Layout.cshtml b/src/Website/Views/Shared/_Layout.cshtml index d7373becc..bf531c4bf 100644 --- a/src/Website/Views/Shared/_Layout.cshtml +++ b/src/Website/Views/Shared/_Layout.cshtml @@ -1,48 +1,52 @@ - - - - - - - - - - - - - - - - - - - - Jackett - - -
- - Jackett - -
- - @RenderBody() -
- -
- -
- - - - - + + + + + + + + + + + + + + + + + + + + + + + + Jackett Torznab Indexer + + +
+ + Jackett + +
+ + @RenderBody() +
+ +
+ +
+ + + + + \ No newline at end of file From c7656971a2a9e0e6da20e88faf4ba39e88d5bc6a Mon Sep 17 00:00:00 2001 From: KZ Date: Tue, 25 Aug 2015 21:30:32 +0100 Subject: [PATCH 03/11] Implement animetorrents #150 --- README.md | 193 +-- src/Jackett/Content/logos/animetorrents.png | Bin 0 -> 8409 bytes src/Jackett/Indexers/AnimeTorrents.cs | 176 +++ src/Jackett/Indexers/BaseIndexer.cs | 903 +++++++------- src/Jackett/Jackett.csproj | 1208 ++++++++++--------- src/Jackett/JackettModule.cs | 163 +-- src/Jackett/Utils/Clients/HttpWebClient.cs | 228 ++-- src/Jackett/Utils/Clients/WebRequest.cs | 184 +-- 8 files changed, 1626 insertions(+), 1429 deletions(-) create mode 100644 src/Jackett/Content/logos/animetorrents.png create mode 100644 src/Jackett/Indexers/AnimeTorrents.cs diff --git a/README.md b/README.md index 8c38e4ce8..cc8422dd4 100644 --- a/README.md +++ b/README.md @@ -1,97 +1,98 @@ -## Jackett - -#### Download -Downloads on the [Releases page](https://github.com/zone117x/Jackett/releases) - - -#### Overview -This software creates a [Torznab](https://github.com/Sonarr/Sonarr/wiki/Implementing-a-Torznab-indexer) (with [nZEDb](https://github.com/nZEDb/nZEDb/blob/master/docs/newznab_api_specification.txt) category numbering) and [TorrentPotato](https://github.com/RuudBurger/CouchPotatoServer/wiki/Couchpotato-torrent-provider) API server on your machine. Torznab enables software such as [Sonarr](https://sonarr.tv) to access data from your favorite indexers in a similar fashion to rss but with added features such as searching. TorrentPotato is an interface accessible to [CouchPotato](https://couchpota.to/). - -Jackett works as a proxy server: it translates queries from apps (Sonarr, SickRage, CouchPotato, Mylar, etc) into tracker-site-specific http queries, parses the html response, then sends results back to the requesting software. This allows for getting recent uploads (like RSS) and performing searches. Jackett is a single repository of maintained indexer scraping & translation logic - removing the burden from other apps. - -We were previously focused on TV but are working on extending searches to allow for searching other items such as movies, comics, and music. - - -#### Supported Systems -* Windows using .NET 4.5 -* Linux and OSX using Mono 4 (v3 should work but you may experience crashes). - - -#### Supported Trackers - * [AlphaRatio](https://alpharatio.cc/) - * [AnimeBytes](https://animebytes.tv/) - * [Avistaz](https://avistaz.to/) - * [BakaBT](http://bakabt.me/) - * [bB](http://reddit.com/r/baconbits) - * [BeyondHD](https://beyondhd.me/) - * [BIT-HDTV](https://www.bit-hdtv.com) - * [BitMeTV](http://www.bitmetv.org/) - * [Demonoid](http://www.demonoid.pw/) - * [EuTorrents](https://eutorrents.to/) - * [FileList](http://filelist.ro/) - * [FrenchTorrentDb](http://www.frenchtorrentdb.com/) - * [Freshon](https://freshon.tv/) - * [HD-Space](https://hd-space.org/) - * [HD-Torrents.org](https://hd-torrents.org/) - * [Immortalseed.me](http://immortalseed.me) - * [IPTorrents](https://iptorrents.com/) - * [MoreThan.tv](https://morethan.tv/) - * [pretome](https://pretome.info) - * [PrivateHD](https://privatehd.to/) - * [RARGB](https://rarbg.to/) - * [RuTor](http://rutor.org/) - * [SceneAccess](https://sceneaccess.eu/login) - * [SceneTime](https://www.scenetime.com/) - * [ShowRSS](https://showrss.info/) - * [Strike](https://getstrike.net/) - * [T411](http://www.t411.io/) - * [The Pirate Bay](https://thepiratebay.se/) - * [TorrentBytes](https://www.torrentbytes.net/) - * [TorrentDay](https://torrentday.eu/) - * [TorrentLeech](http://www.torrentleech.org/) - * [TorrentShack](http://torrentshack.me/) - * [Torrentz](https://torrentz.eu/) - * [TV Chaos UK](https://tvchaosuk.com/) - -#### Installation on Linux/OSX - 1. Install [Mono 4](http://www.mono-project.com/download/) or better - 2. Install libcurl: - * Debian/Ubunutu: apt-get install libcurl-dev - * Redhat/Fedora: yum install libcurl-devel - * For other distros see the [Curl docs](http://curl.haxx.se/dlwiz/?type=devel). - 3. Download and extract the latest ```.tar.bz2``` release from the [website](http://jackett.net/Download) and run Jackett using mono with the command "mono JackettConsole.exe". - - -#### Installation on Windows - -Grab the latest release from the [web site](http://jackett.net/Download). - -We recommend you install Jackett as a Windows service using the supplied installer. When installed as a service the tray icon acts as a way to open/start/stop Jackett. If you opted to not install it as a service then Jackett will run its web server from the tray tool. - -Jackett can also be run from the command line using JackettConsole.exe if you would like to see log messages (Ensure the server isn't already running from the tray/service). - - -#### Troubleshooting - -* Command line switches - -You can pass various options when running via the command line, see --help for details. - -* Unable to connect to certain trackers on Linux - -Try running with the "--SSLFix true" if you are on Redhat/Fedora/NNS based libcurl. If the tracker is currently configured try removing it and adding it again. Alternatively try running with a different client via --UseClient (Warning: safecurl just executes curl and your details may be seen from the process list). - -* Enable logging - -You can get additional logging with the switches "-t -l". Please post logs if you are unable to resolve your issue with these switches ensuring to remove your username/password/cookies. - - -### Additional Trackers -Jackett's framework allows our team (and any other volunteering dev) to implement new trackers in an hour or two. If you'd like support for a new tracker then feel free to leave a request on the [issues page](https://github.com/zone117x/Jackett/issues) or contact us on IRC (see below). Pull requests must be made to the develop branch. - -### Contact & Support -Use the github issues pages or talk to us directly at: [irc.freenode.net#jackett](http://webchat.freenode.net/?channels=#jackett). - -### Screenshots - +## Jackett + +#### Download +Downloads on the [Releases page](https://github.com/zone117x/Jackett/releases) + + +#### Overview +This software creates a [Torznab](https://github.com/Sonarr/Sonarr/wiki/Implementing-a-Torznab-indexer) (with [nZEDb](https://github.com/nZEDb/nZEDb/blob/master/docs/newznab_api_specification.txt) category numbering) and [TorrentPotato](https://github.com/RuudBurger/CouchPotatoServer/wiki/Couchpotato-torrent-provider) API server on your machine. Torznab enables software such as [Sonarr](https://sonarr.tv) to access data from your favorite indexers in a similar fashion to rss but with added features such as searching. TorrentPotato is an interface accessible to [CouchPotato](https://couchpota.to/). + +Jackett works as a proxy server: it translates queries from apps (Sonarr, SickRage, CouchPotato, Mylar, etc) into tracker-site-specific http queries, parses the html response, then sends results back to the requesting software. This allows for getting recent uploads (like RSS) and performing searches. Jackett is a single repository of maintained indexer scraping & translation logic - removing the burden from other apps. + +We were previously focused on TV but are working on extending searches to allow for searching other items such as movies, comics, and music. + + +#### Supported Systems +* Windows using .NET 4.5 +* Linux and OSX using Mono 4 (v3 should work but you may experience crashes). + + +#### Supported Trackers + * [AlphaRatio](https://alpharatio.cc/) + * [AnimeBytes](https://animebytes.tv/) + * [AnimeTorrents](http://animetorrents.me/) + * [Avistaz](https://avistaz.to/) + * [BakaBT](http://bakabt.me/) + * [bB](http://reddit.com/r/baconbits) + * [BeyondHD](https://beyondhd.me/) + * [BIT-HDTV](https://www.bit-hdtv.com) + * [BitMeTV](http://www.bitmetv.org/) + * [Demonoid](http://www.demonoid.pw/) + * [EuTorrents](https://eutorrents.to/) + * [FileList](http://filelist.ro/) + * [FrenchTorrentDb](http://www.frenchtorrentdb.com/) + * [Freshon](https://freshon.tv/) + * [HD-Space](https://hd-space.org/) + * [HD-Torrents.org](https://hd-torrents.org/) + * [Immortalseed.me](http://immortalseed.me) + * [IPTorrents](https://iptorrents.com/) + * [MoreThan.tv](https://morethan.tv/) + * [pretome](https://pretome.info) + * [PrivateHD](https://privatehd.to/) + * [RARGB](https://rarbg.to/) + * [RuTor](http://rutor.org/) + * [SceneAccess](https://sceneaccess.eu/login) + * [SceneTime](https://www.scenetime.com/) + * [ShowRSS](https://showrss.info/) + * [Strike](https://getstrike.net/) + * [T411](http://www.t411.io/) + * [The Pirate Bay](https://thepiratebay.se/) + * [TorrentBytes](https://www.torrentbytes.net/) + * [TorrentDay](https://torrentday.eu/) + * [TorrentLeech](http://www.torrentleech.org/) + * [TorrentShack](http://torrentshack.me/) + * [Torrentz](https://torrentz.eu/) + * [TV Chaos UK](https://tvchaosuk.com/) + +#### Installation on Linux/OSX + 1. Install [Mono 4](http://www.mono-project.com/download/) or better + 2. Install libcurl: + * Debian/Ubunutu: apt-get install libcurl-dev + * Redhat/Fedora: yum install libcurl-devel + * For other distros see the [Curl docs](http://curl.haxx.se/dlwiz/?type=devel). + 3. Download and extract the latest ```.tar.bz2``` release from the [website](http://jackett.net/Download) and run Jackett using mono with the command "mono JackettConsole.exe". + + +#### Installation on Windows + +Grab the latest release from the [web site](http://jackett.net/Download). + +We recommend you install Jackett as a Windows service using the supplied installer. When installed as a service the tray icon acts as a way to open/start/stop Jackett. If you opted to not install it as a service then Jackett will run its web server from the tray tool. + +Jackett can also be run from the command line using JackettConsole.exe if you would like to see log messages (Ensure the server isn't already running from the tray/service). + + +#### Troubleshooting + +* Command line switches + +You can pass various options when running via the command line, see --help for details. + +* Unable to connect to certain trackers on Linux + +Try running with the "--SSLFix true" if you are on Redhat/Fedora/NNS based libcurl. If the tracker is currently configured try removing it and adding it again. Alternatively try running with a different client via --UseClient (Warning: safecurl just executes curl and your details may be seen from the process list). + +* Enable logging + +You can get additional logging with the switches "-t -l". Please post logs if you are unable to resolve your issue with these switches ensuring to remove your username/password/cookies. + + +### Additional Trackers +Jackett's framework allows our team (and any other volunteering dev) to implement new trackers in an hour or two. If you'd like support for a new tracker then feel free to leave a request on the [issues page](https://github.com/zone117x/Jackett/issues) or contact us on IRC (see below). Pull requests must be made to the develop branch. + +### Contact & Support +Use the github issues pages or talk to us directly at: [irc.freenode.net#jackett](http://webchat.freenode.net/?channels=#jackett). + +### Screenshots + ![screenshot](http://i.imgur.com/t1sVva6.png "screenshot") \ No newline at end of file diff --git a/src/Jackett/Content/logos/animetorrents.png b/src/Jackett/Content/logos/animetorrents.png new file mode 100644 index 0000000000000000000000000000000000000000..24c836e4b073bd8d0af47a33b90dc5d8a822e2ab GIT binary patch literal 8409 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 ApplyConfiguration(JToken configJson) + { + configData.LoadValuesFromJson(configJson); + var pairs = new Dictionary { + { "username", configData.Username.Value }, + { "password", configData.Password.Value }, + { "form", "login" }, + { "rememberme[]", "1" } + }; + + var loginPage = await RequestStringWithCookiesAndRetry(LoginUrl, null, null); + + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, SearchUrl, SiteLink); + await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () => + { + CQ dom = result.Content; + var errorMessage = dom[".ui-state-error"].Text().Trim(); + throw new ExceptionWithConfigData(errorMessage, configData); + }); + + return IndexerConfigurationStatus.RequiresTesting; + } + + public async Task> PerformQuery(TorznabQuery query) + { + var releases = new List(); + var searchString = query.GetQueryString(); + var searchUrl = SearchUrl; + var queryCollection = new NameValueCollection(); + + queryCollection.Add("total", "146"); // Not sure what this is about but its required! + + var cat = "0"; + var queryCats = MapTorznabCapsToTrackers(query); + if (queryCats.Count == 1) + { + cat = queryCats.First().ToString(); + } + + queryCollection.Add("cat", cat); + queryCollection.Add("searchin", "filename"); + queryCollection.Add("search", searchString); + queryCollection.Add("page", "1"); + searchUrl += "?" + queryCollection.GetQueryString(); + + var extraHeaders = new Dictionary() + { + { "X-Requested-With", "XMLHttpRequest" } + }; + + var response = await RequestStringWithCookiesAndRetry(searchUrl, null, SearchUrlReferer, extraHeaders); + + var results = response.Content; + try + { + CQ dom = results; + + var rows = dom["tr"]; + foreach (var row in rows.Skip(1)) + { + var release = new ReleaseInfo(); + var qRow = row.Cq(); + var qTitleLink = qRow.Find("td:eq(1) a:eq(0)").First(); + release.Title = qTitleLink.Find("strong").Text().Trim(); + + // If we search an get no results, we still get a table just with no info. + if (string.IsNullOrWhiteSpace(release.Title)) + { + break; + } + + release.Description = release.Title; + release.Guid = new Uri(qTitleLink.Attr("href")); + release.Comments = release.Guid; + + var dateString = qRow.Find("td:eq(4)").Text(); + release.PublishDate = DateTime.ParseExact(dateString, "dd MMM yy", CultureInfo.InvariantCulture); + + var qLink = qRow.Find("td:eq(2) a"); + release.Link = new Uri(qLink.Attr("href")); + + var sizeStr = qRow.Find("td:eq(5)").Text(); + release.Size = ReleaseInfo.GetBytes(sizeStr); + + var connections = qRow.Find("td:eq(7)").Text().Trim().Split("/".ToCharArray(),StringSplitOptions.RemoveEmptyEntries); + + release.Seeders = ParseUtil.CoerceInt(connections[0].Trim()); + release.Peers = ParseUtil.CoerceInt(connections[1].Trim()) + release.Seeders; + + var rCat = row.Cq().Find("td:eq(0) a").First().Attr("href"); + var rCatIdx = rCat.IndexOf("cat="); + if (rCatIdx > -1) + { + rCat = rCat.Substring(rCatIdx + 4); + } + + release.Category = MapTrackerCatToNewznab(rCat); + + releases.Add(release); + } + } + catch (Exception ex) + { + OnParseError(results, ex); + } + + return releases; + } + } +} diff --git a/src/Jackett/Indexers/BaseIndexer.cs b/src/Jackett/Indexers/BaseIndexer.cs index 71cf91979..2b7efef21 100644 --- a/src/Jackett/Indexers/BaseIndexer.cs +++ b/src/Jackett/Indexers/BaseIndexer.cs @@ -1,451 +1,452 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Jackett.Models; -using Newtonsoft.Json.Linq; -using NLog; -using Jackett.Services; -using Jackett.Utils; -using Jackett.Utils.Clients; -using AutoMapper; -using System.Threading; -using Jackett.Models.IndexerConfig; - -namespace Jackett.Indexers -{ - public abstract class BaseIndexer - { - public string SiteLink { get; private set; } - public string DisplayDescription { get; private set; } - public string DisplayName { get; private set; } - public string ID { get { return GetIndexerID(GetType()); } } - - public bool IsConfigured { get; protected set; } - public TorznabCapabilities TorznabCaps { get; private set; } - protected Logger logger; - protected IIndexerManagerService indexerService; - protected static List cache = new List(); - protected static readonly TimeSpan cacheTime = new TimeSpan(0, 9, 0); - protected IWebClient webclient; - protected IProtectionService protectionService; - protected readonly string downloadUrlBase = ""; - - protected string CookieHeader - { - get { return configData.CookieHeader.Value; } - set { configData.CookieHeader.Value = value; } - } - - - - protected ConfigurationData configData; - - private List categoryMapping = new List(); - - public BaseIndexer(string name, string link, string description, IIndexerManagerService manager, IWebClient client, Logger logger, ConfigurationData configData, IProtectionService p, TorznabCapabilities caps = null, string downloadBase = null) - { - if (!link.EndsWith("/")) - throw new Exception("Site link must end with a slash."); - - DisplayName = name; - DisplayDescription = description; - SiteLink = link; - this.logger = logger; - indexerService = manager; - webclient = client; - protectionService = p; - this.downloadUrlBase = downloadBase; - - this.configData = configData; - - if (caps == null) - caps = TorznabUtil.CreateDefaultTorznabTVCaps(); - TorznabCaps = caps; - - } - - public IEnumerable CleanLinks(IEnumerable releases) - { - if (string.IsNullOrEmpty(downloadUrlBase)) - return releases; - foreach (var release in releases) - { - if (release.Link.ToString().StartsWith(downloadUrlBase)) - { - release.Link = new Uri(release.Link.ToString().Substring(downloadUrlBase.Length), UriKind.Relative); - } - } - - return releases; - } - - public Uri UncleanLink(Uri link) - { - return new Uri(downloadUrlBase + link.ToString(), UriKind.RelativeOrAbsolute); - } - - protected int MapTrackerCatToNewznab(string input) - { - if (null != input) - { - input = input.ToLowerInvariant(); - var mapping = categoryMapping.Where(m => m.TrackerCategory.ToLowerInvariant() == input.ToLowerInvariant()).FirstOrDefault(); - if (mapping != null) - { - return mapping.NewzNabCategory; - } - } - return 0; - } - - public static string GetIndexerID(Type type) - { - return StringUtil.StripNonAlphaNumeric(type.Name.ToLowerInvariant()); - } - - public virtual Task GetConfigurationForSetup() - { - return Task.FromResult(configData); - } - - public virtual void ResetBaseConfig() - { - CookieHeader = string.Empty; - IsConfigured = false; - } - - protected virtual void SaveConfig() - { - indexerService.SaveConfig(this as IIndexer, configData.ToJson(protectionService, forDisplay: false)); - } - - protected void OnParseError(string results, Exception ex) - { - var fileName = string.Format("Error on {0} for {1}.txt", DateTime.Now.ToString("yyyyMMddHHmmss"), DisplayName); - var spacing = string.Join("", Enumerable.Repeat(Environment.NewLine, 5)); - var fileContents = string.Format("{0}{1}{2}", ex, spacing, results); - logger.Error(fileName + fileContents); - } - - protected void CleanCache() - { - foreach (var expired in cache.Where(i => i.Created - DateTime.Now > cacheTime).ToList()) - { - cache.Remove(expired); - } - } - - protected async Task FollowIfRedirect(WebClientStringResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null) - { - var byteResult = new WebClientByteResult(); - // Map to byte - Mapper.Map(response, byteResult); - await FollowIfRedirect(byteResult, referrer, overrideRedirectUrl, overrideCookies); - // Map to string - Mapper.Map(byteResult, response); - } - - protected async Task FollowIfRedirect(WebClientByteResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null) - { - // Follow up to 5 redirects - for (int i = 0; i < 5; i++) - { - if (!response.IsRedirect) - break; - await DoFollowIfRedirect(response, referrer, overrideRedirectUrl, overrideCookies); - } - } - - private async Task DoFollowIfRedirect(WebClientByteResult incomingResponse, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null) - { - if (incomingResponse.IsRedirect) - { - // Do redirect - var redirectedResponse = await webclient.GetBytes(new WebRequest() - { - Url = overrideRedirectUrl ?? incomingResponse.RedirectingTo, - Referer = referrer, - Cookies = overrideCookies ?? CookieHeader - }); - Mapper.Map(redirectedResponse, incomingResponse); - } - } - - - protected void LoadLegacyCookieConfig(JToken jsonConfig) - { - string legacyCookieHeader = (string)jsonConfig["cookie_header"]; - if (!string.IsNullOrEmpty(legacyCookieHeader)) - { - CookieHeader = legacyCookieHeader; - } - else - { - // Legacy cookie key - var jcookies = jsonConfig["cookies"]; - if (jcookies is JArray) - { - var array = (JArray)jcookies; - legacyCookieHeader = string.Empty; - for (int i = 0; i < array.Count; i++) - { - if (i != 0) - legacyCookieHeader += "; "; - legacyCookieHeader += array[i]; - } - CookieHeader = legacyCookieHeader; - } - else if (jcookies != null) - { - CookieHeader = (string)jcookies; - } - } - } - - public virtual void LoadFromSavedConfiguration(JToken jsonConfig) - { - if (jsonConfig is JArray) - { - configData.LoadValuesFromJson(jsonConfig, protectionService); - IsConfigured = true; - } - // read and upgrade old settings file format - else if (jsonConfig is Object) - { - LoadLegacyCookieConfig(jsonConfig); - SaveConfig(); - IsConfigured = true; - } - } - - public async virtual Task Download(Uri link) - { - var response = await RequestBytesWithCookiesAndRetry(link.ToString()); - return response.Content; - } - - protected async Task RequestBytesWithCookiesAndRetry(string url, string cookieOverride = null) - { - Exception lastException = null; - for (int i = 0; i < 3; i++) - { - try - { - return await RequestBytesWithCookies(url, cookieOverride); - } - catch (Exception e) - { - logger.Error(string.Format("On attempt {0} downloading from {1}: {2}", (i + 1), DisplayName, e.Message)); - lastException = e; - } - await Task.Delay(500); - } - - throw lastException; - } - - protected async Task RequestStringWithCookies(string url, string cookieOverride = null, string referer = null) - { - var request = new Utils.Clients.WebRequest() - { - Url = url, - Type = RequestType.GET, - Cookies = CookieHeader, - Referer = referer - }; - - if (cookieOverride != null) - request.Cookies = cookieOverride; - return await webclient.GetString(request); - } - - protected async Task RequestStringWithCookiesAndRetry(string url, string cookieOverride = null, string referer = null) - { - Exception lastException = null; - for (int i = 0; i < 3; i++) - { - try - { - return await RequestStringWithCookies(url, cookieOverride, referer); - } - catch (Exception e) - { - logger.Error(string.Format("On attempt {0} checking for results from {1}: {2}", (i + 1), DisplayName, e.Message)); - lastException = e; - } - await Task.Delay(500); - } - - throw lastException; - } - - protected async Task RequestBytesWithCookies(string url, string cookieOverride = null) - { - var request = new Utils.Clients.WebRequest() - { - Url = url, - Type = RequestType.GET, - Cookies = cookieOverride ?? CookieHeader - }; - - if (cookieOverride != null) - request.Cookies = cookieOverride; - return await webclient.GetBytes(request); - } - - protected async Task PostDataWithCookies(string url, IEnumerable> data, string cookieOverride = null) - { - var request = new Utils.Clients.WebRequest() - { - Url = url, - Type = RequestType.POST, - Cookies = cookieOverride ?? CookieHeader, - PostData = data - }; - return await webclient.GetString(request); - } - - protected async Task PostDataWithCookiesAndRetry(string url, IEnumerable> data, string cookieOverride = null) - { - Exception lastException = null; - for (int i = 0; i < 3; i++) - { - try - { - return await PostDataWithCookies(url, data, cookieOverride); - } - catch (Exception e) - { - logger.Error(string.Format("On attempt {0} checking for results from {1}: {2}", (i + 1), DisplayName, e.Message)); - lastException = e; - } - await Task.Delay(500); - } - - throw lastException; - } - - protected async Task RequestLoginAndFollowRedirect(string url, IEnumerable> data, string cookies, bool returnCookiesFromFirstCall, string redirectUrlOverride = null, string referer = null) - { - var request = new Utils.Clients.WebRequest() - { - Url = url, - Type = RequestType.POST, - Cookies = cookies, - Referer = referer, - PostData = data - }; - var response = await webclient.GetString(request); - var firstCallCookies = response.Cookies; - - if (response.IsRedirect) - { - await FollowIfRedirect(response, request.Url, null, response.Cookies); - } - - if (returnCookiesFromFirstCall) - { - response.Cookies = firstCallCookies; - } - - return response; - } - - protected async Task ConfigureIfOK(string cookies, bool isLoggedin, Func onError) - { - if (isLoggedin) - { - CookieHeader = cookies; - SaveConfig(); - IsConfigured = true; - } - else - { - await onError(); - } - } - - public virtual IEnumerable FilterResults(TorznabQuery query, IEnumerable results) - { - foreach (var result in results) - { - if (query.Categories.Length == 0 || query.Categories.Contains(result.Category) || result.Category == 0 || TorznabCatType.QueryContainsParentCategory(query.Categories, result.Category)) - { - yield return result; - } - } - } - - protected void AddCategoryMapping(string trackerCategory, int newznabCategory) - { - categoryMapping.Add(new CategoryMapping(trackerCategory, newznabCategory)); - } - - protected void AddCategoryMapping(int trackerCategory, TorznabCategory newznabCategory) - { - categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory.ID)); - if (!TorznabCaps.Categories.Contains(newznabCategory)) - TorznabCaps.Categories.Add(newznabCategory); - } - - protected void AddCategoryMapping(string trackerCategory, TorznabCategory newznabCategory) - { - categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory.ID)); - if (!TorznabCaps.Categories.Contains(newznabCategory)) - TorznabCaps.Categories.Add(newznabCategory); - } - - protected void AddCategoryMapping(int trackerCategory, int newznabCategory) - { - categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory)); - } - - protected void AddMultiCategoryMapping(TorznabCategory newznabCategory, params int[] trackerCategories) - { - foreach (var trackerCat in trackerCategories) - { - categoryMapping.Add(new CategoryMapping(trackerCat.ToString(), newznabCategory.ID)); - } - } - - protected void AddMultiCategoryMapping(int trackerCategory, params TorznabCategory[] newznabCategories) - { - foreach (var newznabCat in newznabCategories) - { - categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCat.ID)); - } - } - - protected List MapTorznabCapsToTrackers(TorznabQuery query, bool mapChildrenCatsToParent = false) - { - var result = new List(); - foreach (var cat in query.Categories) - { - var queryCats = new List { cat }; - var newznabCat = TorznabCatType.AllCats.FirstOrDefault(c => c.ID == cat); - if (newznabCat != null) - { - queryCats.AddRange(newznabCat.SubCategories.Select(c => c.ID)); - } - - if (mapChildrenCatsToParent) - { - var parentNewznabCat = TorznabCatType.AllCats.FirstOrDefault(c => c.SubCategories.Contains(newznabCat)); - if (parentNewznabCat != null) - { - queryCats.Add(parentNewznabCat.ID); - } - } - - foreach (var mapping in categoryMapping.Where(c => queryCats.Contains(c.NewzNabCategory))) - { - result.Add(mapping.TrackerCategory); - } - } - - return result.Distinct().ToList(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Jackett.Models; +using Newtonsoft.Json.Linq; +using NLog; +using Jackett.Services; +using Jackett.Utils; +using Jackett.Utils.Clients; +using AutoMapper; +using System.Threading; +using Jackett.Models.IndexerConfig; + +namespace Jackett.Indexers +{ + public abstract class BaseIndexer + { + public string SiteLink { get; private set; } + public string DisplayDescription { get; private set; } + public string DisplayName { get; private set; } + public string ID { get { return GetIndexerID(GetType()); } } + + public bool IsConfigured { get; protected set; } + public TorznabCapabilities TorznabCaps { get; private set; } + protected Logger logger; + protected IIndexerManagerService indexerService; + protected static List cache = new List(); + protected static readonly TimeSpan cacheTime = new TimeSpan(0, 9, 0); + protected IWebClient webclient; + protected IProtectionService protectionService; + protected readonly string downloadUrlBase = ""; + + protected string CookieHeader + { + get { return configData.CookieHeader.Value; } + set { configData.CookieHeader.Value = value; } + } + + + + protected ConfigurationData configData; + + private List categoryMapping = new List(); + + public BaseIndexer(string name, string link, string description, IIndexerManagerService manager, IWebClient client, Logger logger, ConfigurationData configData, IProtectionService p, TorznabCapabilities caps = null, string downloadBase = null) + { + if (!link.EndsWith("/")) + throw new Exception("Site link must end with a slash."); + + DisplayName = name; + DisplayDescription = description; + SiteLink = link; + this.logger = logger; + indexerService = manager; + webclient = client; + protectionService = p; + this.downloadUrlBase = downloadBase; + + this.configData = configData; + + if (caps == null) + caps = TorznabUtil.CreateDefaultTorznabTVCaps(); + TorznabCaps = caps; + + } + + public IEnumerable CleanLinks(IEnumerable releases) + { + if (string.IsNullOrEmpty(downloadUrlBase)) + return releases; + foreach (var release in releases) + { + if (release.Link.ToString().StartsWith(downloadUrlBase)) + { + release.Link = new Uri(release.Link.ToString().Substring(downloadUrlBase.Length), UriKind.Relative); + } + } + + return releases; + } + + public Uri UncleanLink(Uri link) + { + return new Uri(downloadUrlBase + link.ToString(), UriKind.RelativeOrAbsolute); + } + + protected int MapTrackerCatToNewznab(string input) + { + if (null != input) + { + input = input.ToLowerInvariant(); + var mapping = categoryMapping.Where(m => m.TrackerCategory.ToLowerInvariant() == input.ToLowerInvariant()).FirstOrDefault(); + if (mapping != null) + { + return mapping.NewzNabCategory; + } + } + return 0; + } + + public static string GetIndexerID(Type type) + { + return StringUtil.StripNonAlphaNumeric(type.Name.ToLowerInvariant()); + } + + public virtual Task GetConfigurationForSetup() + { + return Task.FromResult(configData); + } + + public virtual void ResetBaseConfig() + { + CookieHeader = string.Empty; + IsConfigured = false; + } + + protected virtual void SaveConfig() + { + indexerService.SaveConfig(this as IIndexer, configData.ToJson(protectionService, forDisplay: false)); + } + + protected void OnParseError(string results, Exception ex) + { + var fileName = string.Format("Error on {0} for {1}.txt", DateTime.Now.ToString("yyyyMMddHHmmss"), DisplayName); + var spacing = string.Join("", Enumerable.Repeat(Environment.NewLine, 5)); + var fileContents = string.Format("{0}{1}{2}", ex, spacing, results); + logger.Error(fileName + fileContents); + } + + protected void CleanCache() + { + foreach (var expired in cache.Where(i => i.Created - DateTime.Now > cacheTime).ToList()) + { + cache.Remove(expired); + } + } + + protected async Task FollowIfRedirect(WebClientStringResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null) + { + var byteResult = new WebClientByteResult(); + // Map to byte + Mapper.Map(response, byteResult); + await FollowIfRedirect(byteResult, referrer, overrideRedirectUrl, overrideCookies); + // Map to string + Mapper.Map(byteResult, response); + } + + protected async Task FollowIfRedirect(WebClientByteResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null) + { + // Follow up to 5 redirects + for (int i = 0; i < 5; i++) + { + if (!response.IsRedirect) + break; + await DoFollowIfRedirect(response, referrer, overrideRedirectUrl, overrideCookies); + } + } + + private async Task DoFollowIfRedirect(WebClientByteResult incomingResponse, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null) + { + if (incomingResponse.IsRedirect) + { + // Do redirect + var redirectedResponse = await webclient.GetBytes(new WebRequest() + { + Url = overrideRedirectUrl ?? incomingResponse.RedirectingTo, + Referer = referrer, + Cookies = overrideCookies ?? CookieHeader + }); + Mapper.Map(redirectedResponse, incomingResponse); + } + } + + + protected void LoadLegacyCookieConfig(JToken jsonConfig) + { + string legacyCookieHeader = (string)jsonConfig["cookie_header"]; + if (!string.IsNullOrEmpty(legacyCookieHeader)) + { + CookieHeader = legacyCookieHeader; + } + else + { + // Legacy cookie key + var jcookies = jsonConfig["cookies"]; + if (jcookies is JArray) + { + var array = (JArray)jcookies; + legacyCookieHeader = string.Empty; + for (int i = 0; i < array.Count; i++) + { + if (i != 0) + legacyCookieHeader += "; "; + legacyCookieHeader += array[i]; + } + CookieHeader = legacyCookieHeader; + } + else if (jcookies != null) + { + CookieHeader = (string)jcookies; + } + } + } + + public virtual void LoadFromSavedConfiguration(JToken jsonConfig) + { + if (jsonConfig is JArray) + { + configData.LoadValuesFromJson(jsonConfig, protectionService); + IsConfigured = true; + } + // read and upgrade old settings file format + else if (jsonConfig is Object) + { + LoadLegacyCookieConfig(jsonConfig); + SaveConfig(); + IsConfigured = true; + } + } + + public async virtual Task Download(Uri link) + { + var response = await RequestBytesWithCookiesAndRetry(link.ToString()); + return response.Content; + } + + protected async Task RequestBytesWithCookiesAndRetry(string url, string cookieOverride = null) + { + Exception lastException = null; + for (int i = 0; i < 3; i++) + { + try + { + return await RequestBytesWithCookies(url, cookieOverride); + } + catch (Exception e) + { + logger.Error(string.Format("On attempt {0} downloading from {1}: {2}", (i + 1), DisplayName, e.Message)); + lastException = e; + } + await Task.Delay(500); + } + + throw lastException; + } + + protected async Task RequestStringWithCookies(string url, string cookieOverride = null, string referer = null, Dictionary headers = null) + { + var request = new Utils.Clients.WebRequest() + { + Url = url, + Type = RequestType.GET, + Cookies = CookieHeader, + Referer = referer, + Headers = headers + }; + + if (cookieOverride != null) + request.Cookies = cookieOverride; + return await webclient.GetString(request); + } + + protected async Task RequestStringWithCookiesAndRetry(string url, string cookieOverride = null, string referer = null, Dictionary headers = null) + { + Exception lastException = null; + for (int i = 0; i < 3; i++) + { + try + { + return await RequestStringWithCookies(url, cookieOverride, referer, headers); + } + catch (Exception e) + { + logger.Error(string.Format("On attempt {0} checking for results from {1}: {2}", (i + 1), DisplayName, e.Message)); + lastException = e; + } + await Task.Delay(500); + } + + throw lastException; + } + + protected async Task RequestBytesWithCookies(string url, string cookieOverride = null) + { + var request = new Utils.Clients.WebRequest() + { + Url = url, + Type = RequestType.GET, + Cookies = cookieOverride ?? CookieHeader + }; + + if (cookieOverride != null) + request.Cookies = cookieOverride; + return await webclient.GetBytes(request); + } + + protected async Task PostDataWithCookies(string url, IEnumerable> data, string cookieOverride = null) + { + var request = new Utils.Clients.WebRequest() + { + Url = url, + Type = RequestType.POST, + Cookies = cookieOverride ?? CookieHeader, + PostData = data + }; + return await webclient.GetString(request); + } + + protected async Task PostDataWithCookiesAndRetry(string url, IEnumerable> data, string cookieOverride = null) + { + Exception lastException = null; + for (int i = 0; i < 3; i++) + { + try + { + return await PostDataWithCookies(url, data, cookieOverride); + } + catch (Exception e) + { + logger.Error(string.Format("On attempt {0} checking for results from {1}: {2}", (i + 1), DisplayName, e.Message)); + lastException = e; + } + await Task.Delay(500); + } + + throw lastException; + } + + protected async Task RequestLoginAndFollowRedirect(string url, IEnumerable> data, string cookies, bool returnCookiesFromFirstCall, string redirectUrlOverride = null, string referer = null) + { + var request = new Utils.Clients.WebRequest() + { + Url = url, + Type = RequestType.POST, + Cookies = cookies, + Referer = referer, + PostData = data + }; + var response = await webclient.GetString(request); + var firstCallCookies = response.Cookies; + + if (response.IsRedirect) + { + await FollowIfRedirect(response, request.Url, null, response.Cookies); + } + + if (returnCookiesFromFirstCall) + { + response.Cookies = firstCallCookies; + } + + return response; + } + + protected async Task ConfigureIfOK(string cookies, bool isLoggedin, Func onError) + { + if (isLoggedin) + { + CookieHeader = cookies; + SaveConfig(); + IsConfigured = true; + } + else + { + await onError(); + } + } + + public virtual IEnumerable FilterResults(TorznabQuery query, IEnumerable results) + { + foreach (var result in results) + { + if (query.Categories.Length == 0 || query.Categories.Contains(result.Category) || result.Category == 0 || TorznabCatType.QueryContainsParentCategory(query.Categories, result.Category)) + { + yield return result; + } + } + } + + protected void AddCategoryMapping(string trackerCategory, int newznabCategory) + { + categoryMapping.Add(new CategoryMapping(trackerCategory, newznabCategory)); + } + + protected void AddCategoryMapping(int trackerCategory, TorznabCategory newznabCategory) + { + categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory.ID)); + if (!TorznabCaps.Categories.Contains(newznabCategory)) + TorznabCaps.Categories.Add(newznabCategory); + } + + protected void AddCategoryMapping(string trackerCategory, TorznabCategory newznabCategory) + { + categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory.ID)); + if (!TorznabCaps.Categories.Contains(newznabCategory)) + TorznabCaps.Categories.Add(newznabCategory); + } + + protected void AddCategoryMapping(int trackerCategory, int newznabCategory) + { + categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory)); + } + + protected void AddMultiCategoryMapping(TorznabCategory newznabCategory, params int[] trackerCategories) + { + foreach (var trackerCat in trackerCategories) + { + categoryMapping.Add(new CategoryMapping(trackerCat.ToString(), newznabCategory.ID)); + } + } + + protected void AddMultiCategoryMapping(int trackerCategory, params TorznabCategory[] newznabCategories) + { + foreach (var newznabCat in newznabCategories) + { + categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCat.ID)); + } + } + + protected List MapTorznabCapsToTrackers(TorznabQuery query, bool mapChildrenCatsToParent = false) + { + var result = new List(); + foreach (var cat in query.Categories) + { + var queryCats = new List { cat }; + var newznabCat = TorznabCatType.AllCats.FirstOrDefault(c => c.ID == cat); + if (newznabCat != null) + { + queryCats.AddRange(newznabCat.SubCategories.Select(c => c.ID)); + } + + if (mapChildrenCatsToParent) + { + var parentNewznabCat = TorznabCatType.AllCats.FirstOrDefault(c => c.SubCategories.Contains(newznabCat)); + if (parentNewznabCat != null) + { + queryCats.Add(parentNewznabCat.ID); + } + } + + foreach (var mapping in categoryMapping.Where(c => queryCats.Contains(c.NewzNabCategory))) + { + result.Add(mapping.TrackerCategory); + } + } + + return result.Distinct().ToList(); + } + } +} diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj index b84a20994..71a193b74 100644 --- a/src/Jackett/Jackett.csproj +++ b/src/Jackett/Jackett.csproj @@ -1,609 +1,613 @@ - - - - - Debug - AnyCPU - {E636D5F8-68B4-4903-B4ED-CCFD9C9E899F} - Library - Properties - Jackett - Jackett - v4.5 - 512 - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - ..\packages\Autofac.3.5.2\lib\net40\Autofac.dll - True - - - ..\packages\Autofac.Owin.3.1.0\lib\net45\Autofac.Integration.Owin.dll - True - - - ..\packages\Autofac.WebApi2.3.4.0\lib\net45\Autofac.Integration.WebApi.dll - True - - - ..\packages\Autofac.WebApi2.Owin.3.2.0\lib\net45\Autofac.Integration.WebApi.Owin.dll - True - - - ..\packages\AutoMapper.4.0.4\lib\net45\AutoMapper.dll - True - - - ..\packages\CsQuery.1.3.4\lib\net40\CsQuery.dll - True - - - ..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll - True - - - ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - True - - - ..\packages\Microsoft.Owin.FileSystems.3.0.1\lib\net45\Microsoft.Owin.FileSystems.dll - True - - - ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll - True - - - ..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - True - - - ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll - True - - - ..\packages\Microsoft.Owin.StaticFiles.3.0.1\lib\net45\Microsoft.Owin.StaticFiles.dll - True - - - ..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll - True - - - ..\packages\NLog.4.0.1\lib\net45\NLog.dll - True - - - ..\packages\NLog.Windows.Forms.2.0.0.0\lib\net35\NLog.Windows.Forms.dll - True - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - - - - - - ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll - True - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True - - - ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll - True - - - - - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True - - - ..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll - True - - - ..\packages\Microsoft.AspNet.WebApi.Tracing.5.2.3\lib\net45\System.Web.Http.Tracing.dll - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - TorznabCatType.tt - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - - - - - - - - - Designer - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - Always - - - Designer - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - TextTemplatingFileGenerator - TorznabCatType.generated.cs - - - - PreserveNewest - - - - - {74420A79-CC16-442C-8B1E-7C1B913844F0} - CurlSharp - - - - - False - Microsoft .NET Framework 4.5.1 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - - - - + + + + + Debug + AnyCPU + {E636D5F8-68B4-4903-B4ED-CCFD9C9E899F} + Library + Properties + Jackett + Jackett + v4.5 + 512 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + ..\packages\Autofac.3.5.2\lib\net40\Autofac.dll + True + + + ..\packages\Autofac.Owin.3.1.0\lib\net45\Autofac.Integration.Owin.dll + True + + + ..\packages\Autofac.WebApi2.3.4.0\lib\net45\Autofac.Integration.WebApi.dll + True + + + ..\packages\Autofac.WebApi2.Owin.3.2.0\lib\net45\Autofac.Integration.WebApi.Owin.dll + True + + + ..\packages\AutoMapper.4.0.4\lib\net45\AutoMapper.dll + True + + + ..\packages\CsQuery.1.3.4\lib\net40\CsQuery.dll + True + + + ..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll + True + + + ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\packages\Microsoft.Owin.FileSystems.3.0.1\lib\net45\Microsoft.Owin.FileSystems.dll + True + + + ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll + True + + + ..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + True + + + ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll + True + + + ..\packages\Microsoft.Owin.StaticFiles.3.0.1\lib\net45\Microsoft.Owin.StaticFiles.dll + True + + + ..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\NLog.4.0.1\lib\net45\NLog.dll + True + + + ..\packages\NLog.Windows.Forms.2.0.0.0\lib\net35\NLog.Windows.Forms.dll + True + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll + True + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll + True + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll + True + + + + + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll + True + + + ..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll + True + + + ..\packages\Microsoft.AspNet.WebApi.Tracing.5.2.3\lib\net45\System.Web.Http.Tracing.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + TorznabCatType.tt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + + + + + + + + + + + Designer + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + Always + + + Designer + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + TextTemplatingFileGenerator + TorznabCatType.generated.cs + + + + PreserveNewest + + + + + {74420A79-CC16-442C-8B1E-7C1B913844F0} + CurlSharp + + + + + False + Microsoft .NET Framework 4.5.1 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + + + + - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - + --> + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + \ No newline at end of file diff --git a/src/Jackett/JackettModule.cs b/src/Jackett/JackettModule.cs index cccbbe192..988b61238 100644 --- a/src/Jackett/JackettModule.cs +++ b/src/Jackett/JackettModule.cs @@ -1,81 +1,82 @@ -using Autofac; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Autofac.Integration.WebApi; -using Jackett.Indexers; -using Jackett.Utils; -using Jackett.Utils.Clients; -using AutoMapper; -using Jackett.Models; - -namespace Jackett -{ - public class JackettModule : Module - { - protected override void Load(ContainerBuilder builder) - { - // Just register everything! - var thisAssembly = typeof(JackettModule).Assembly; - builder.RegisterAssemblyTypes(thisAssembly).Except().AsImplementedInterfaces().SingleInstance(); - builder.RegisterApiControllers(thisAssembly).InstancePerRequest(); - - // Register the best web client for the platform or the override - switch (Startup.ClientOverride) - { - case "httpclient": - builder.RegisterType().As(); - break; - case "safecurl": - builder.RegisterType().As(); - break; - case "libcurl": - builder.RegisterType().As(); - break; - case "automatic": - default: - if (System.Environment.OSVersion.Platform == PlatformID.Unix) - { - builder.RegisterType().As(); - } - else - { - builder.RegisterType().As(); - } - break; - } - - // Register indexers - foreach (var indexer in thisAssembly.GetTypes() - .Where(p => typeof(IIndexer).IsAssignableFrom(p) && !p.IsInterface) - .ToArray()) - { - builder.RegisterType(indexer).Named(BaseIndexer.GetIndexerID(indexer)); - } - - Mapper.CreateMap().ForMember(x => x.Content, opt => opt.Ignore()).AfterMap((be, str) => - { - str.Content = Encoding.UTF8.GetString(be.Content); - }); - - Mapper.CreateMap().ForMember(x => x.Content, opt => opt.Ignore()).AfterMap((str, be) => - { - if (!string.IsNullOrEmpty(str.Content)) - { - be.Content = Encoding.UTF8.GetBytes(str.Content); - } - }); - - Mapper.CreateMap(); - Mapper.CreateMap(); - Mapper.CreateMap(); - - Mapper.CreateMap().AfterMap((r, t) => - { - t.CategoryDesc = TorznabCatType.GetCatDesc(r.Category); - }); - } - } -} +using Autofac; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Autofac.Integration.WebApi; +using Jackett.Indexers; +using Jackett.Utils; +using Jackett.Utils.Clients; +using AutoMapper; +using Jackett.Models; + +namespace Jackett +{ + public class JackettModule : Module + { + protected override void Load(ContainerBuilder builder) + { + // Just register everything! + var thisAssembly = typeof(JackettModule).Assembly; + builder.RegisterAssemblyTypes(thisAssembly).Except().AsImplementedInterfaces().SingleInstance(); + builder.RegisterApiControllers(thisAssembly).InstancePerRequest(); + builder.RegisterType(); + + // Register the best web client for the platform or the override + switch (Startup.ClientOverride) + { + case "httpclient": + builder.RegisterType().As(); + break; + case "safecurl": + builder.RegisterType().As(); + break; + case "libcurl": + builder.RegisterType().As(); + break; + case "automatic": + default: + if (System.Environment.OSVersion.Platform == PlatformID.Unix) + { + builder.RegisterType().As(); + } + else + { + builder.RegisterType().As(); + } + break; + } + + // Register indexers + foreach (var indexer in thisAssembly.GetTypes() + .Where(p => typeof(IIndexer).IsAssignableFrom(p) && !p.IsInterface) + .ToArray()) + { + builder.RegisterType(indexer).Named(BaseIndexer.GetIndexerID(indexer)); + } + + Mapper.CreateMap().ForMember(x => x.Content, opt => opt.Ignore()).AfterMap((be, str) => + { + str.Content = Encoding.UTF8.GetString(be.Content); + }); + + Mapper.CreateMap().ForMember(x => x.Content, opt => opt.Ignore()).AfterMap((str, be) => + { + if (!string.IsNullOrEmpty(str.Content)) + { + be.Content = Encoding.UTF8.GetBytes(str.Content); + } + }); + + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + + Mapper.CreateMap().AfterMap((r, t) => + { + t.CategoryDesc = TorznabCatType.GetCatDesc(r.Category); + }); + } + } +} diff --git a/src/Jackett/Utils/Clients/HttpWebClient.cs b/src/Jackett/Utils/Clients/HttpWebClient.cs index 89e7eed01..5387dd932 100644 --- a/src/Jackett/Utils/Clients/HttpWebClient.cs +++ b/src/Jackett/Utils/Clients/HttpWebClient.cs @@ -1,110 +1,118 @@ -using AutoMapper; -using Jackett.Models; -using NLog; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; - -namespace Jackett.Utils.Clients -{ - class HttpWebClient : IWebClient - { - private Logger logger; - - public HttpWebClient(Logger l) - { - logger = l; - } - - - public void Init() - { - } - - public async Task GetBytes(WebRequest request) - { - logger.Debug(string.Format("WindowsWebClient:GetBytes(Url:{0})", request.Url)); - var result = await Run(request); - logger.Debug(string.Format("WindowsWebClient: Returning {0} => {1} bytes", result.Status, (result.Content == null ? "" : result.Content.Length.ToString()))); - return result; - } - - public async Task GetString(WebRequest request) - { - logger.Debug(string.Format("WindowsWebClient:GetString(Url:{0})", request.Url)); - var result = await Run(request); - logger.Debug(string.Format("WindowsWebClient: Returning {0} => {1}", result.Status, (result.Content == null ? "" : Encoding.UTF8.GetString(result.Content)))); - return Mapper.Map(result); - } - - private async Task Run(WebRequest request) - { - var cookies = new CookieContainer(); - if (!string.IsNullOrEmpty(request.Cookies)) - { - var uri = new Uri(request.Url); - foreach (var c in request.Cookies.Split(';')) - { - try - { - cookies.SetCookies(uri, c); - } - catch (CookieException ex) - { - logger.Info("(Non-critical) Problem loading cookie {0}, {1}, {2}", uri, c, ex.Message); - } - } - } - - var client = new HttpClient(new HttpClientHandler - { - CookieContainer = cookies, - AllowAutoRedirect = false, // Do not use this - Bugs ahoy! Lost cookies and more. - UseCookies = true, - }); - - client.DefaultRequestHeaders.Add("User-Agent", BrowserUtil.ChromeUserAgent); - HttpResponseMessage response = null; - - if (request.Type == RequestType.POST) - { - var content = new FormUrlEncodedContent(request.PostData); - response = await client.PostAsync(request.Url, content); - } - else - { - response = await client.GetAsync(request.Url); - } - - var result = new WebClientByteResult(); - result.Content = await response.Content.ReadAsByteArrayAsync(); - if (response.Headers.Location != null) - { - result.RedirectingTo = response.Headers.Location.ToString(); - } - result.Status = response.StatusCode; - - // Compatiblity issue between the cookie format and httpclient - // Pull it out manually ignoring the expiry date then set it manually - // http://stackoverflow.com/questions/14681144/httpclient-not-storing-cookies-in-cookiecontainer - IEnumerable cookieHeaders; - if (response.Headers.TryGetValues("set-cookie", out cookieHeaders)) - { - var cookieBuilder = new StringBuilder(); - foreach (var c in cookieHeaders) - { - cookieBuilder.AppendFormat("{0} ", c.Substring(0, c.IndexOf(';') + 1)); - } - - result.Cookies = cookieBuilder.ToString().TrimEnd(); - } - - ServerUtil.ResureRedirectIsFullyQualified(request, result); - return result; - } - } -} +using AutoMapper; +using Jackett.Models; +using NLog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Utils.Clients +{ + public class HttpWebClient : IWebClient + { + private Logger logger; + + public HttpWebClient(Logger l) + { + logger = l; + } + + + public void Init() + { + } + + public async Task GetBytes(WebRequest request) + { + logger.Debug(string.Format("WindowsWebClient:GetBytes(Url:{0})", request.Url)); + var result = await Run(request); + logger.Debug(string.Format("WindowsWebClient: Returning {0} => {1} bytes", result.Status, (result.Content == null ? "" : result.Content.Length.ToString()))); + return result; + } + + public async Task GetString(WebRequest request) + { + logger.Debug(string.Format("WindowsWebClient:GetString(Url:{0})", request.Url)); + var result = await Run(request); + logger.Debug(string.Format("WindowsWebClient: Returning {0} => {1}", result.Status, (result.Content == null ? "" : Encoding.UTF8.GetString(result.Content)))); + return Mapper.Map(result); + } + + private async Task Run(WebRequest request) + { + var cookies = new CookieContainer(); + if (!string.IsNullOrEmpty(request.Cookies)) + { + var uri = new Uri(request.Url); + foreach (var c in request.Cookies.Split(';')) + { + try + { + cookies.SetCookies(uri, c); + } + catch (CookieException ex) + { + logger.Info("(Non-critical) Problem loading cookie {0}, {1}, {2}", uri, c, ex.Message); + } + } + } + + var client = new HttpClient(new HttpClientHandler + { + CookieContainer = cookies, + AllowAutoRedirect = false, // Do not use this - Bugs ahoy! Lost cookies and more. + UseCookies = true, + }); + + client.DefaultRequestHeaders.Add("User-Agent", BrowserUtil.ChromeUserAgent); + HttpResponseMessage response = null; + + if (request.Headers != null) + { + foreach (var header in request.Headers) + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + + if (request.Type == RequestType.POST) + { + var content = new FormUrlEncodedContent(request.PostData); + response = await client.PostAsync(request.Url, content); + } + else + { + response = await client.GetAsync(request.Url); + } + + var result = new WebClientByteResult(); + result.Content = await response.Content.ReadAsByteArrayAsync(); + if (response.Headers.Location != null) + { + result.RedirectingTo = response.Headers.Location.ToString(); + } + result.Status = response.StatusCode; + + // Compatiblity issue between the cookie format and httpclient + // Pull it out manually ignoring the expiry date then set it manually + // http://stackoverflow.com/questions/14681144/httpclient-not-storing-cookies-in-cookiecontainer + IEnumerable cookieHeaders; + if (response.Headers.TryGetValues("set-cookie", out cookieHeaders)) + { + var cookieBuilder = new StringBuilder(); + foreach (var c in cookieHeaders) + { + cookieBuilder.AppendFormat("{0} ", c.Substring(0, c.IndexOf(';') + 1)); + } + + result.Cookies = cookieBuilder.ToString().TrimEnd(); + } + + ServerUtil.ResureRedirectIsFullyQualified(request, result); + return result; + } + } +} diff --git a/src/Jackett/Utils/Clients/WebRequest.cs b/src/Jackett/Utils/Clients/WebRequest.cs index 2d8862b18..a9ae22d6e 100644 --- a/src/Jackett/Utils/Clients/WebRequest.cs +++ b/src/Jackett/Utils/Clients/WebRequest.cs @@ -1,89 +1,95 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; - -namespace Jackett.Utils.Clients -{ - public class WebRequest - { - public WebRequest() - { - PostData = new List>(); - Type = RequestType.GET; - } - - public WebRequest(string url) - { - PostData = new List>(); - Type = RequestType.GET; - Url = url; - } - - public string Url { get; set; } - public IEnumerable> PostData { get; set; } - public string Cookies { get; set; } - public string Referer { get; set; } - public RequestType Type { get; set; } - - - public override bool Equals(System.Object obj) - { - if (obj is WebRequest) - { - var other = obj as WebRequest; - var postDataSame = PostData == null && other.PostData == null; - if (!postDataSame) - { - if (!(PostData == null || other.PostData == null)) - { - var ok = PostData.Count() == other.PostData.Count(); - foreach (var i in PostData) - { - if (!other.PostData.Any(item => item.Key == i.Key)) - { - ok = false; - break; - } - - if (PostData.FirstOrDefault(item => item.Key == i.Key).Value != other.PostData.FirstOrDefault(item => item.Key == i.Key).Value) - { - ok = false; - break; - } - } - - if (ok) - { - postDataSame = true; - } - } - } - - return other.Url == Url && - other.Referer == Referer && - other.Cookies == Cookies && - other.Type == Type && - postDataSame; - - } - else - { - return false; - } - } - - public override int GetHashCode() - { - return base.GetHashCode(); - } - } - - public enum RequestType - { - GET, - POST - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Utils.Clients +{ + public class WebRequest + { + public WebRequest() + { + PostData = new List>(); + Type = RequestType.GET; + Headers = new Dictionary(); + } + + public WebRequest(string url) + { + PostData = new List>(); + Type = RequestType.GET; + Url = url; + Headers = new Dictionary(); + } + + public string Url { get; set; } + public IEnumerable> PostData { get; set; } + public string Cookies { get; set; } + public string Referer { get; set; } + public RequestType Type { get; set; } + + /// + /// Warning this is only implemented on HTTPWebClient currently! + /// + public Dictionary Headers { get; set; } + + public override bool Equals(System.Object obj) + { + if (obj is WebRequest) + { + var other = obj as WebRequest; + var postDataSame = PostData == null && other.PostData == null; + if (!postDataSame) + { + if (!(PostData == null || other.PostData == null)) + { + var ok = PostData.Count() == other.PostData.Count(); + foreach (var i in PostData) + { + if (!other.PostData.Any(item => item.Key == i.Key)) + { + ok = false; + break; + } + + if (PostData.FirstOrDefault(item => item.Key == i.Key).Value != other.PostData.FirstOrDefault(item => item.Key == i.Key).Value) + { + ok = false; + break; + } + } + + if (ok) + { + postDataSame = true; + } + } + } + + return other.Url == Url && + other.Referer == Referer && + other.Cookies == Cookies && + other.Type == Type && + postDataSame; + + } + else + { + return false; + } + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } + + public enum RequestType + { + GET, + POST + } +} From 61a0c34aef1de9bce0e0912e587546ae8ba0fa32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Simonsen?= Date: Sun, 23 Aug 2015 17:15:41 +0200 Subject: [PATCH 04/11] Since CouchPotato will not drop a release that matches by name, even if the IMDB id doesn't match, I've added to Jackett that it doesn't return releases that have an IMDB that doesn't match the one we're searching for. This should prevent CouchPotato from grabbing wrong movies (for example downloading San Andreas Quake instead of San Andreas). Updated nxtgn indexer to implement the changed interface in develop compared to master. Implemented missing imdb id on TorrentPotato, and changed JsonResponse to not include null values (to prevent imdb property being shown when we don't know it) Added thetvdb and imdb id to torznab, as they are part of the torznab specification, albeit optional. Added imdb ID for movies. Will now only look at the first 3 pages when searching. To prevent flooding server when searching for a common term. A category is now assigned to releases. Changed nxtgn banner to only be copied if newer upon build. Added support for nxtgn.org # Conflicts: # src/Jackett/Jackett.csproj --- src/Jackett/Content/logos/nxtgn.png | Bin 0 -> 28946 bytes src/Jackett/Controllers/PotatoController.cs | 3 +- src/Jackett/Indexers/NxtGn.cs | 213 ++++++++++++++++++ src/Jackett/Jackett.csproj | 4 + src/Jackett/Models/ReleaseInfo.cs | 1 + src/Jackett/Models/ResultPage.cs | 2 + .../Models/TorrentPotatoResponseItem.cs | 2 +- src/Jackett/Utils/JsonContent.cs | 2 +- src/Jackett/Utils/TorznabCapsUtil.cs | 10 + 9 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 src/Jackett/Content/logos/nxtgn.png create mode 100644 src/Jackett/Indexers/NxtGn.cs diff --git a/src/Jackett/Content/logos/nxtgn.png b/src/Jackett/Content/logos/nxtgn.png new file mode 100644 index 0000000000000000000000000000000000000000..74d7e1ce806fda9c4b2a04d6cca1ddd39c0fcf40 GIT binary patch literal 28946 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 ApplyConfiguration(JToken configJson) + { + var loginPage = await RequestStringWithCookies(LoginUrl); + CQ loginDom = loginPage.Content; + var loginPostUrl = loginDom["#login"].Attr("action"); + + configData.LoadValuesFromJson(configJson); + var pairs = new Dictionary { + { "username", configData.Username.Value }, + { "password", configData.Password.Value } + }; + // Get inital cookies + CookieHeader = string.Empty; + var response = await RequestLoginAndFollowRedirect(SiteLink + loginPostUrl, pairs, CookieHeader, true, null, LoginUrl); + + await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("Velkommen tilbage"), () => + { + CQ dom = response.Content; + var messageEl = dom["inputs"]; + var errorMessage = messageEl.Text().Trim(); + throw new ExceptionWithConfigData(errorMessage, configData); + }); + + var profilePage = await RequestStringWithCookies(ProfileUrl, response.Cookies); + CQ profileDom = profilePage.Content; + var passKey = profileDom["input[name=resetkey]"].Parent().Text(); + passKey = passKey.Substring(0, passKey.IndexOf(' ')); + configData.RSSKey.Value = passKey; + SaveConfig(); + return IndexerConfigurationStatus.RequiresTesting; + } + + public async Task> PerformQuery(TorznabQuery query) + { + var releases = new List(); + var breakWhile = false; + var page = 0; + while (page < 3) + { + string episodeSearchUrl; + if (string.IsNullOrEmpty(query.GetQueryString())) + { + episodeSearchUrl = SearchUrl + "?page=" + page; + breakWhile = true; + } + else + { + var cats = MapTorznabCapsToTrackers(query); + var catsUrlPart = string.Join("&", cats.Select(c => $"c{c}=1")); + episodeSearchUrl = string.Format("{0}?search={1}&cat=0&incldead=0&{2}&page={3}", SearchUrl, HttpUtility.UrlEncode(query.GetQueryString()), catsUrlPart, page); + } + page++; + var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl); + try + { + CQ dom = results.Content; + + var rows = dom["#torrent-table-wrapper > div"]; + + foreach (var row in rows.Skip(1)) + { + var release = new ReleaseInfo(); + + var qRow = row.Cq(); + var qLink = qRow.Find("#torrent-udgivelse2-users > a").First(); + var qDesc = qRow.Find("#torrent-udgivelse2-users > p").FirstOrDefault(); + + var moviesCats = new[] { 47, 38, 5, 23, 22, 33, 17, 9 }; + var seriesCats = new[] { 46, 26, 43 }; + var catUrl = qRow.Find(".torrent-icon > a").Attr("href"); + var cat = catUrl.Substring(catUrl.LastIndexOf('=') + 1); + var catNo = int.Parse(cat); + if (moviesCats.Contains(catNo)) + release.Category = TorznabCatType.Movies.ID; + else if (seriesCats.Contains(catNo)) + release.Category = TorznabCatType.TV.ID; + else + continue; + + releases.Add(release); + + var torrentUrl = qLink.Attr("href"); + var torrentId = torrentUrl.Substring(torrentUrl.LastIndexOf('=') + 1); + + release.MinimumRatio = 1; + release.MinimumSeedTime = 172800; + release.Title = qLink.Attr("title"); + release.Description = qDesc != null ? qDesc.InnerText : release.Title; + release.Guid = new Uri(SiteLink + torrentUrl); + release.Comments = new Uri(release.Guid + "#startcomments"); + + var downloadUrl = $"{SiteLink}download.php?id={torrentId}&rss&passkey={configData.RSSKey.Value}"; + release.Link = new Uri(downloadUrl); + + var qAdded = qRow.Find("#torrent-added").First(); + var addedStr = qAdded.Text().Trim(); + release.PublishDate = DateTime.ParseExact(addedStr, "dd-MM-yyyyHH:mm:ss", CultureInfo.InvariantCulture); + + release.Seeders = ParseUtil.CoerceInt(qRow.Find("#torrent-seeders").Text().Trim()); + release.Peers = ParseUtil.CoerceInt(qRow.Find("#torrent-leechers").Text().Trim()) + release.Seeders; + + var sizeStr = qRow.Find("#torrent-size").First().Text(); + release.Size = ReleaseInfo.GetBytes(sizeStr); + + var infoLink = qRow.Find("#infolink"); + var linkContainer = infoLink.Children().First().Children().First(); + var url = linkContainer.Attr("href"); + var img = linkContainer.Children().First(); + var imgUrl = img.Attr("src"); + if (imgUrl == "/pic/imdb.png") + { + release.Imdb = long.Parse(url.Substring(url.LastIndexOf('t') + 1)); + } + else if (imgUrl == "/pic/TV.png") + { + release.TheTvDbId = long.Parse(url.Substring(url.LastIndexOf('=') + 1)); + } + } + var nextPage = dom["#torrent-table-wrapper + p[align=center]"].Children().Last(); + if (!nextPage.Is("a")) + breakWhile = true; + } + catch (Exception ex) + { + OnParseError(results.Content, ex); + } + if (breakWhile) + break; + } + return releases; + } + } +} diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj index 71a193b74..f01f8b875 100644 --- a/src/Jackett/Jackett.csproj +++ b/src/Jackett/Jackett.csproj @@ -185,6 +185,7 @@ + @@ -448,6 +449,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/src/Jackett/Models/ReleaseInfo.cs b/src/Jackett/Models/ReleaseInfo.cs index 4c3318075..8484ab651 100644 --- a/src/Jackett/Models/ReleaseInfo.cs +++ b/src/Jackett/Models/ReleaseInfo.cs @@ -21,6 +21,7 @@ namespace Jackett.Models public long? Size { get; set; } public string Description { get; set; } public long? RageID { get; set; } + public long? TheTvDbId { get; set; } public long? Imdb { get; set; } public int? Seeders { get; set; } public int? Peers { get; set; } diff --git a/src/Jackett/Models/ResultPage.cs b/src/Jackett/Models/ResultPage.cs index 7593ea0b6..4b70cb29b 100644 --- a/src/Jackett/Models/ResultPage.cs +++ b/src/Jackett/Models/ResultPage.cs @@ -81,6 +81,8 @@ namespace Jackett.Models ), getTorznabElement("magneturl", r.MagnetUri), getTorznabElement("rageid", r.RageID), + getTorznabElement("thetvdb", r.TheTvDbId), + getTorznabElement("imdb", r.Imdb), getTorznabElement("seeders", r.Seeders), getTorznabElement("peers", r.Peers), getTorznabElement("infohash", r.InfoHash), diff --git a/src/Jackett/Models/TorrentPotatoResponseItem.cs b/src/Jackett/Models/TorrentPotatoResponseItem.cs index 809ada2b6..9c860610a 100644 --- a/src/Jackett/Models/TorrentPotatoResponseItem.cs +++ b/src/Jackett/Models/TorrentPotatoResponseItem.cs @@ -12,7 +12,7 @@ namespace Jackett.Models public string torrent_id { get; set; } public string details_url { get; set; } public string download_url { get; set; } - // public string imdb_id { get; set; } + public string imdb_id { get; set; } public bool freeleech { get; set; } public string type { get; set; } public long size { get; set; } diff --git a/src/Jackett/Utils/JsonContent.cs b/src/Jackett/Utils/JsonContent.cs index 412962bda..17132f029 100644 --- a/src/Jackett/Utils/JsonContent.cs +++ b/src/Jackett/Utils/JsonContent.cs @@ -25,7 +25,7 @@ namespace Jackett.Utils protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) { - var json = JsonConvert.SerializeObject(_value, Formatting.Indented); + var json = JsonConvert.SerializeObject(_value, Formatting.Indented, new JsonSerializerSettings {NullValueHandling = NullValueHandling.Ignore}); var writer = new StreamWriter(stream); writer.Write(json); await writer.FlushAsync(); diff --git a/src/Jackett/Utils/TorznabCapsUtil.cs b/src/Jackett/Utils/TorznabCapsUtil.cs index 6fc28e8b5..338feea56 100644 --- a/src/Jackett/Utils/TorznabCapsUtil.cs +++ b/src/Jackett/Utils/TorznabCapsUtil.cs @@ -44,6 +44,16 @@ namespace Jackett.Utils return filteredResults; } + public static IEnumerable FilterResultsToImdb(IEnumerable results, string imdb) + { + if (string.IsNullOrWhiteSpace(imdb)) + return results; + // Filter out releases that do have a valid imdb ID, that is not equal to the one we're searching for. + return + results.Where( + result => !result.Imdb.HasValue || result.Imdb.Value == 0 || ("tt" + result.Imdb.Value).Equals(imdb)); + } + private static string CleanTitle(string title) { title = title.Replace(':', ' ').Replace('.', ' ').Replace('-', ' ').Replace('_', ' ').Replace('+', ' '); From 116a9026a0a90e282406a587725ada9e5be66b7c Mon Sep 17 00:00:00 2001 From: KZ Date: Wed, 26 Aug 2015 17:33:43 +0100 Subject: [PATCH 05/11] Fix nxtgn logo size --- src/Jackett/Content/logos/nxtgn.png | Bin 28946 -> 18831 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/Jackett/Content/logos/nxtgn.png b/src/Jackett/Content/logos/nxtgn.png index 74d7e1ce806fda9c4b2a04d6cca1ddd39c0fcf40..7a26136d40b1304bed0c84af69b625a913c7e300 100644 GIT binary patch literal 18831 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 WQQKwO)Rk-i0000XB) z7WX1s{PsU{Ki#>>Br{1SXL6n=iPBJ0eDaw3F#rHhl$GQ(|J9rSiUb7oPm+J! z`|n=`d#j}R3IP0A0RSBa0N4Li=p6v?RA_WrM64LE0Bg0OYt;uGS)XaPq$MGkSKQ{u1Us`ePb5bO&W z61*fl#nTYAeDDe4m=buk-jJ4&E@;S#bf}g-q^$)7Up@JKNO$K=$kw95 z5+EK**F_m{EJP3diVWB$`L>EReD$ZY_!b+kln9d!0ppP{|KEn6BZ5=WO2NRGt9QAp zd5(%4P$;I;{+1rV=QLqgtBCgZdt_5TV|`;SsA%S>i*h>_{VP3s+LO!m{|a?K7k%;x^y@*~-pC=CFBJpaE-N9(P9p)bxmpMC&Jv?Pg{SZTwijcl*!Q#rezrD^j z{+D=DKQ+1exakmkYIwr%OKPkk~a-;m;v0bu^A1eH|%hk z0r-2kvICkmqBj#C=e^_7j!;yE*&rq7CsFrrrmVaWkmC(t!2hCDTub8f*Q_^Gs#F#t(;dzFCGoKUIeGZwkI^9((ytROG=G$F zeu6~F1M%W{Q1vxyUDr)YbGz$dKc0W$`uX10f0b2u zvMX{AE+j2qKEGdA?!O}A?-aLtY7p|iBwAJw?nrR}evXe%jsKjwTQf9#t7l}b$5f$M z#C0Rul`~_N^e47%?03f(D}OzPEE)wQjrbS1=wGI*dc_UQH?#Nk$uq1lNUDix8+EYV zY5kabp)5sq?(xZ^{NqA_O)4q7*vE~Ao2xOM=l)L5cznm)bDEv#M8o~!7$@00<-&|j zZZF@i1U3(D>xgxFE?t|*q?_gt9{OdlAzLY|5^QqyFHsYZWVUh7!ffVR4xaruiWyOx zxR<}jgj{ylq({?3#e_48m})(ixL{Ec zrQiMS!e^h3`zTIh4k`ZrNM`t>O0T5qc75B6`ha6a%tyVIJaH%P-5sIF0WvegQ78g~ z@L-U~PltDGcts^ZX3R)J00xTe0!fLcfb#AycjFFU)An@VWe;_82hSlOj1ZjEPc0`l z1+^O)U?ngyRg$!v92CjO{SyuGXJZgqeIQwfkwo(5sw+nLch4cP!m-g-0nnt(pPh!IW%7Uq5|3mz1BGP= zo?a7p-!g*nI^t&iRXDXG_E05xU#k|cyk;+fT~0aHFj z1Ea*<`BX$iL@UM{iXvt-hhT+luhq|@Nc)xDPLpC9oqu)&V4Twa1Ndo(iWOVmP(V20 zXaEiTO?H<4yBjLyg7r7p1pnR1aGKk+{tB|3E=HMpB~_4_3k`$;2K!2iSLUBn9(UtM z^AGz*6av^-*vMzYlPTDHB_MJctwM|D5vhwXL7SaO znJ1I3A{{o;DMdS>5X-?D=(w)1?{-26})H01^uW=R?z-AwYU11Plv4?LGoY%Zo+7)`_N?6H==v zcW#1$u~MwS%%pMvsBiPF3>b++E;sR|50?}vEkiBZnG4zSjH{r`b~mg@;^u zH2FU`@#TV)CY>-kI@l=?JI!CGzk9;*J&kzC@n3;^V6foKaM7@?kyyy;4OB*3TRmXs zYYk^b+f4;(SRbD;Y;D2cDgm)7Zf|eDziV-O5{^Hr;N?>#B{Kg#LJ>;K_~;Cm+&FNr zkErcIKl2t7xvT!WzSR|!TG8I8U%F!QmKh6F2m{b@WsIPhep<2*@Ns%FGz^9XgS$YP zhpFSxdg(<~xB&7tZ}AZTE&o;4*&@wO7^0HtRjd6wyq*lGFR6H5beX&$B7^)sWjBLsv4CB zM-1dEe}R&MJ|jrOWf08BDv3?D$`HOJEKMb52r`EyZ*gAIcVom?zNO55niLuef$`jq^ZSOcC}@S(%X7 zN*63dVF1ogOF$pAae9L};>Rr*^mlgbCu#Ez&W6l2fD-fHNL>B?HFA&p^TD`K+l4$V zDbxkH9HH0H3vtnitSE_yz~yLndamZ>Y;@MZ8x6r;5B9r5Z}kP~w%@0$Uo+g%z-8oS z41N+2U}-Jx2^qG-D1p#C7+xOc^NiC%OT|7&gA%lHvj_l*O4wNCuRaKXa1F`faHQ1T z3?xr=`Jmj9|6><3z#3f_Kb(KQfkf<=2`mNRgs@Pe-?|#H>p{s0M}ilWPexwK(_~OV ze>R>&LO>xS-AqW5fe?*Umcj9(Ma%^U1jdbl;QctQ4}nE0SP-rn5YjUPxTXmQVWEFv zj7ldYJ)y}gRgXu;@M9pevjx#&?Sa;oFO;8KahFVng2|E4E*PweRNdH|oKyjbgGh(e z-md+(*&R=6WkwE2lVAY~rVy-!D|QAV!U){fZtXCF9`uj>@^7}^v9YFMg(+hcF@cqH z4uY3nUJly6*2MEI9~XGELBv|xuDg?O?*fk_qH1A=0#aerK7 zz**68RzVVYJK1|>E|jY-xPe`Ie+*vFx#XU}r1ULi z%oLDdQgZ2jWXr@(xwFCkC$55bxkSx9n8S z?*TDrB}yb1jM4}LNTa#_TseH$b|+?(fs#X1s2pR7M)D!4=qe(E(m9i$yVtq5JFU*` z@lXv)Few%zf6Szo?u_jAxRl5GvhMxuIFn34s(U;TMU^h{L_-PC2+pgT6rm$LTUhq> z-~ZP3hW5qX^ya>>(@K-)qa@R-jJ1I7exkM@6ZmeoIX|qYdK_S=`|;8Y8*r?9Odwa& zhpagVfhohn$@0C&{vFWyRiX~WmWn+oYu{TQtTa6$5N`SO@U_^ZF@=J+->*%NA&K$k za^?V0z7k$(Pk7VJ-r40uH*scPI3UBxY4(hd)9h-VRs~6#2qOW&a(5{w>*{59r$i)S zP&BG=#Yxnl&rr-ge;+nN(XzCf`lGf1J;*5n#}pzrdEV7cm5-+g3= zjDDa^*^_uJ!h~F1rht&kLQSE2E*Nh{)%_rN!sC}H=W-7+&l-@y!(Q7STPrR zL@ZB@#$YgK`S1S7j(g|`oB02F;z;mpGg}tC1%rplMM8fETu|BUe&y|+Eb##oC*~$C z6xGav4@419spJuPCwh*&=13%tObCp6=G)%vI`NF9Q+|0XHJJ}sz@|JE3RY%PG(?|Dp&)?i&kg+; zuJf;MN%<69dje3GoJv@E{R5$f3}9iUHH@d%rBKk`>}&4hQAhcN5{$|Sot@ZQY>DLj zCJO-a2vrE@t!Xlp5i1`|o;bE=P&y?pA>n{oLKPYrK^|d~cTNLDWpTh~<9w?qRagqk z7c#t6?Csz_8<4Z853>vs&p7^=B83&y4Zs<5Us0T8{)+)eq~p7Z8R5^KhJC_S97eEc z){1PG&i@d1?!0Use^>L1x3I_7WhoPm&kX^YK z@4-<3EK(M$;MY)@&)JH7?)5T-y2-mgGjk@5tMUdBP|9#X8PG#pMonOMagakzbp|by z)NvHMb&!umZey#WBdwf+wabQV%#ZaTP-tX7E*W!`4-x0aJBLZ*+8`~x27Dy03LNvm?%73`i&_ zV_`!zo;}59AAl;^!WiDHK|@$h5Jxk|0o&mTFCMIe&J~kTBwaTxONY0MmACalHv>U7 zrIXO=svji9gE!Ha!S%OM!f3gdK|WWZ_yog;1}CicDk!1H&)D zAyC?v<&hBIe~~@0Q$y=m^MVC97z_KiwY<*H_Ivu6F~6mvIeLf&BRfc*Ip-w^dfeqN zYqP&-W2D``a}posx9i2n!vGG>gQ3AtjYTC>3uIE!8S=6IGt!MN;$R%rsY1j{9Buyy zbm25VqD!*M8V}v0QeDbUTqzKIGPxk3vq)r-A`gU-R-f>ToJ$Jm;dDW4sowsLa)Qcf zp`(>|MGRL&%r+EKgd%#C`RE)g$~hpl>%sBJl<7Ad;r51EXnq=kM4PV*J0dvwVW>o% zxrUZIN#qx+M<*=)rdVq*blrI}7O~5}9pD9U5(S*tYXGI-R0=3mj*T-3v}SsyF)9y` z@jZ`BY&$053?_!575u6Y!Kg4KfW@dHKwQw+2Ua~$sjhF(k#(u>45~Vt-Ym}Xxs7UE z^(mgPf$jagHSE0Dth~RPmt^AqF$x z8?ZZMeYD!{es}GUqP5RoYnO3beFXRu!D#i+%HXgU<^}D0ptLC}^j%aN919kMkRu;Q z6);%ReK+m{i9ha{rGC}DxMs&Z^xgNA82uYglBE11R+t#h zXCSfR&KuVaYy&TRlll1wjc0HIjo{i_-#*}Vl+tZNSfuQ5#pyQjLt$&UT4HRiX z`BmUR)9|30!iR@~0yNS!{XDa=(+fWUpuwf}J^c|Y~q9`Y=SNN&ZSjw%4e1`C-sV*ZZf2zV-?-74nPm9<{#GPp%1}USI1CCtxjTDAPVjR8Z$Rv$hqGt> z%#oaJEBSBWTSiNRWe&v^h}f^)SXgpEsl+&2c_EY&YjQo-=#df~nFq)~u5bQDUTs(X(r=yY$ z_itLoE%_3dxK^>P>p>*OE&YFBP{NU<34{H$mzPBl7%`kNA7W#bhlp3D4Y9_P0p&rf zaV`dH8E$T^B}M7zI{uD26A)hZS#VAtIK~kOn`_^!}Fx&Al~cKepQSxN{6lmX=9WO#)B- zlP7pzJuesZXN490c$T$&HsiEiK4*NhHP^AM_G=p9%mo8Wthz#!JPsrtqoGeXG~azI zu+{cAoQ)Z(qZ0f~jLSoAPkcI}{E1%8%Gh9kX&V<8w_$|EMMh5PTgK|J0YLeLNZ#gf zJ)$<%b87De=IeNcQ!YD4v7J8c6AhUU@+gOpvkEU28m}BVXxcaqzy~3#??~ zDN(k)W8cEeOO2VXX48Xsq6*aCUB-AOq9H0#ys+0AP>2RSC9&T7OeB)yX^7S=c3;y1 z+_9~SNQO4j(bV;z#BcqNe!h);QS0LFyS-oIw+jq+_d%z*cdzBlFTbSlGDP6~4*J*@ zjs6Q2WMb~;BzTiv#HW?kPa}0d5kAExd7oq*_&1bcr1+ln8rv3^FcWlY z(s8wy@HRG9XmoYafj_cF&B5^n0}t0de{QpCFT+p73*LSU2|2O-`{l|z1^=^ihP%s7 zC(5|!d;}j~eNFvC@5AoH-|O<;Q2Bl|$CJDDnBs=T-(+pRp4EY0jUU+vFMZcbs~_{t zvH+97*J>$vp3CzDoY9PyWeY)5z<6PGU_*wH^C%orR))GRj~2k?arFp=mx#?CXY>@5 zRvZ^?TT8|=v~JP&rKNN~^zMeVjKWz%0{r900fys8um|C3)N_i?^ZicOz4d`;7`t+B zQ%=G+SM!hA3bZtkH{?Z?TzE-}#5zr!6kS~+94eL@0;6R_WvNWkAcBgh$%*U!%D}6w z?9hizsq3E(IsO?nY1jl15QyYQfjJXo5Q|t83^uc3mZQT*?v|xOi@t7~X1`4fTbQ0L zL@h`DVwS1!=X3JBu578me5xC|P}XR8)TlNRO%hR&6h-yZ%lJcI__Cfqj` zvl2m13DxP-!r# zg-s*-ciuYabj|ed*nVZs;-aZZ5}biPEITMf6E+WwL}|{7Z?~f}l(4(5JZM#GYHI!) zO!OhuQSrVBVEYH%5OiP6=_=r2LK|%afUquh`)sL;zV?EG!wCB8p2~;v*~_ol%7m;N zB`0V3CDB^Nvn1hodGibNbGZ35k2{Q`L}4xF(P?LuqYaA%J_xK?8A6JRNl9BY1ZIbw0Ne< zAcbH^aQ0n6uYaSE;ZP7p_vh{6@!h4#L+W_Y1<^y^f{Fj-f_2b+deGI5=|i^n>Wk%O z9*D~BCi^{s5b~DF(2oEI&`3lN;>Y}OWhKN8FE03;73Va@mfYR_I-O@grNSscHwmZE z(Ve{X|M z?B)3uT75^c)LDNG@C5N8=EWug2eW-{R{mXyaE`siHFf5kj>UBh_9tCUAtAG3-zl}U zxE|Y|$-P!(RJfY_cjRqX%J+4Jbqv0ywIDV`2pNCeL}Q_n%f7Sz;Z*9WdU-%tJ;e<56bv0fNC zsmjTozxF6J5l57^QrF#2)49tp zZ=`P5F_Gv~5iLbIS`Zx6-bn$)$_%a42b5^A#a`8_kqYZsP>ub8TW+dLoX__%_+8(I zKbid9=HBh-w?zI2UB-@z!VF-gbuQ&ZPxuaKrP(`)4`G!QxIDtjTs5VZdE z?kU^j%fij$`}WR2%;o5ku+wNnZ|=>7iU04Rm@{}?Yus)fEx~*Fs8&VMC@*;ywRu-T z9wYr3`StyedUb0KLbz7t^`7;U*L|5&b55(=+sR7UMbrG*H-TScBY6v?<$*yn$t*v@ z>={WrY`Kll%2GXhAY*J>c{Z~4eWH`#V#Q_h#C!(Aw(d{#)KSZD%YW@xyC%0Z2~syn z&FPv|HIgEHHZrIs`Oi`L%tEfQXcA^EW6pvnS3UxC^j9sMgK-hq(OQL9(%7dKgg@@5*}be*(Y(M5>U% zVaHP_j6#H8Y_^4+;=-J>!hqdudu3wXtZ^=I${P{veJLpA^Hs}J=s<;yC7X8x;`p)w)?qX zb&Q|T#|2;FlKc_t&)ke6UbC}gfK5-ogAREuq{p5oV4I`!VJSH~>+V14#xHw&QRDY+ z4}DZ@gI_YKvctt9%*V8kND>)oOxDM%-HksWDl*I!i1~uBT!8wioJ`b7N%{ zId2w3>T8-OOdK)8q3sV#Qg=U1Zq{Ssro;SZ4=v)nj%P1QzguuJ{NsSd=uBF@9IdS@ zJ8Rsw4n*ntYp<3ULlRRofwpM~1E-4qFo$U@Yc&$J=rPu>;Jq~3Z|-FM6haR8>aWYz zH8y7ToxWB#T9umTIFos1kaiBB;d)!Q41IIKDGG4%AKA&$7y@j;mYz1Pd-^7gfp1lI_m(ugC3wxBiilozu2elt^LN9$wlg*>kD3V6V(3Ha*s0 zB-S@$;p9u5l(Jc}bZy(OeANvL4!5B}e-iFXA8sF(S0s~gp&#;7R#x)4Uuy=Jp04k7 zrOJQm`>qNhh76LVMa$tNeRq{C7tz%JPqu1>^Zu_Qcjw>wpzqc2DG=#-s*CpsawX;(X-l2R}AwwiJia8P|dHnlC_sEv}6G=v!1_R6LHK-D?A45MM z+|%5v|1VGHT`I=-!4>Su^IBum5@kR2Z~t^`+F02_jPp42&1-@V2XKWesWc=-w$_Ze zHqtkZ##%dX{~YJ|-fFtPZLRn1VILh%)Tu_1@M<|I-UMA$KJ5Tm^F8WbllqEt%M>o^Ysof2}V$^Q#1ttBJW(W$}GBrj;z(L#jhVj?$|nYX-E7zRx#US=^%) z+seZ1RQXK_^Mdpl>HzJET+zF8iVkGCgF!9U+2a0O@zZUUVspQ{&Ca&#MGV1vg<3p7 zy2rH{7Xs&p`ddRBk|@renFHoXRg3jDdBU|lJ^QCrCh4qdFNtP{qrQ*7+ZDc_jVnFA z8+y3I!9Aw?O4(MGM^cEGkQe2!W+GfVaDcu=`u_J}=kvDI^}$p^(4G1{B5L&h!hTA= zLh#v-ZfxuA2I`v@mm;aU6o+i7sWxzt_v$EX|GSjX+Q&T(ZZ5p53907C-}AqeKP^N! zDcH*1Y_vBTw_dK!Fuxp(njfa@$I@zjOTE(OM!SzjbBEi3%%$UD5ko30(PHTkDar7;p_4Z-qAD?R{p#iEGF!@ZVeKdZ|7$?#zV*( zK;N0lk+5c8NIfvzXDjCT9#tCOUpC(V#_Ze4MSrTKlC}2i|65jK2j37eQhnd8ll0#W z#{2tT>Hv>aPTfb{%9Wd}pa+Jd<0tp$Cbv>D7Qlh)req`pe2j&}SNh0&mzr82hUvb6 z5mrqZy;{s3jZkx2e)lV`bdT&VDdCg4>TNc~&vtLeADTn28SWeoqkHd+QqMi74Hoj( zrRjxdvm|o}iD0DE-Fed+&nEL+g=5di5&+@#u z%`~|S?_N6^GsR#YZh~^asjNw_)y%ZCwX@Z`#?esyp}N4oFFO~+J=vpCN?XZo#`Bac z$|nPr4<{?f@A?rkfOUC_pn`2n&3`F)v(b@4)FD%Slp+ux=f>GtxjY2BtUdsdH;Q9p z&0V-6g(%fRdP8MeFi+u#SXO&x<*jFN)Rgr}B;W#cHg$O6a~C~vDzb*oB)8f2zeDOC z43Y^G1cDAi3-7Ka}8@aU^ z_gKvA>)#Q2FUp6Gj5b%Ton735yU>F}lq|@*v!<7+2uhSTiCcRP6Ta)y%j`~Q`-(R7 zH!UGK=}ZBfG|N;dF`UVtjFizwhgh=^D*EgRp7mo$}ZkE|uvtjnM zta|_MKM@r}l(vXb+Qwt_uuX4SGfidL<({u7-S!t!e=Pzqy?vv|;*8UxP$t?mMCTa~ zca1xLl5|P-+!r|9JmMuj+mGE!7qlr=nIivYew{qkf5{U&&>yalfvXlG)(b2oIz`E7 zG=XqRZF)x__z{>-MJ6eRE43DSYtGMoL?h{IcG0&xBPbFSxZmzgG)$eO?ILfKpey{b zgvx}9c86ILMXKT)@-y(yLc!N#$^UBny`Aq)ipt|s74r9*N661Yuy~_%8_w#CT)-@E ze72^pEc`C|rQ*9ebn!mmK0?Y+>P^opV?FeEsTM;mWVM%Y+ z69d>v1YyyUgF8^4g(4Xx2m8m8;B)DK-@kJG4l)yrjef{f+t#}MzVW>q z_oH*nc7E@*omohkLy$45 zK!mtw@tNk~Q#Li?-75hMTzT2ShHH%5N^#QzN-Qv`M2x+q@Sn#tf^$wXF%kvPhSY-T z0fOS7ydvri2hFec{f>YepiftkD+Ue%G#0kowJ)ZeG^&awObO z=LM!Y_w4LyjDv?I>e%YXapSAV|73S6a}k*(dEKS?b^r7Ihp(5)oo{0v{`(rsYTj+> znc_`IoGkE&P+Rgzw;2+>V{;U*az6C&?4;;nC+KwGq4Mr_?fq=@T9d&@h8(_zDWlwW zK*54}Hdh|+Xlhxjj%$_Is)Hc?!^zd;CeOp)f$korbb7ImGvbX$EFm+8ciU1|R~v>w zu0F>KovCuR<8_{rc&^rTd$ySj!D>GWQ3W$Q6`U$k(OF+MsUV5YYIb!sjGC+^F$ zT%ee{{l@T+B+$6L^%UE!9`OP3i+T^na1-}n;}C)Qd2_EI%K~ha0~*UCY@Y6=4+n}; zMi;ZI&5g6 zn-<1=9=Nx!0*#2_kMQ=KOrqjxncTW8)8B)U#9hPKsdY7%D%yTpIdl;kqXSb4hktf6 z_Ce{MaRd3+!5yz*SpAEd#*EFH@3(}rY@9Xn+^Kzn{=iO<{PrX>LRew({-7gv& zX?Eq=lAbt^t|y60WRPN`ou@<(Q{5|=)xX2JtUWv1?9nC53X5PX`1Hs8!7kS~G8c_w zN3Y7B&Zz6Q?psUU#dMyoNL{Db?jraRh#n|dAfMztI+hcXU%`TKT&H>%p3KRQ0Z&T-?sz6tG#zIV{V4b9P;O|1q|I)SXt^ zr2K!~e4B3}0Sx{E2t@2gYLzqrC@;UR(b$K2>XQdbBo ze~J6iAJo;ex zfaL8D-->|BDdVM85!WLE1>&a_j*h6>E;Mi_gD#fsdLJ@ZaAr_G`ZgT?x?De}W7+S&uRC;>}6fO1IY$q}>9;cb_*jVI+|rM%PWXfg7K{>j=Mq zJ#vF~>h*n|Z~xP4{?3r;PtD}HHH|Vhiba1aBUV0?5>%i^6ICY%Z8C^08Y6>*x+Qvy zn@HY_46HT>O2sdM4gQ>-tfVIxKUlq?6@q-j937i+>E>x?!df~YAG!kT%v{n z{t&{x&)_&4?5~Zkr@uNiCin6cOh&9CyoJPvviFa*wZD>HYv87RSv#Gj<>zb%(V5=bOfNqS z?#QjQU;Za__}W@hfNLfsAY71olS|(A?cwsS;ha;F(~Fy36XO>*=XqN0I?wk5P{~~U zM)b>pXD%28rkIxU`0p=Iq-{qJrUbsm@SBYMQrLU8S0Ux#w{%ehOCZJzx9`1L!31g% zV$vK#B70y4SqNo)7^h<>Z)hYKI~J`?>}O5Co9!oJI`y?LpKvvzKQ{Wbdqy`PFp^pI zd!Uq$MpgG0aBz~x2f@^6JxGR)?_+7GwYy<#cX}bZn^#C(;;y;!0N=` zcFB}^U_mN$9q?)`Mmc)RosW+k%KP9!d-}zW4bZ0eMUCIMx9C z)#I(_&BoVS20O1YIs$tg)TpO1VR!mBLd?(G(ojqwDF-;UFHVOEWto#My-OK-#%I;R z{!%zx!TE6HY}uePglT*bhWH|Jy?fVaa(cGziu05hwJQ?!K^1BQC6UHb0G}IBKGGsL z+@^ooI2N_{x6X0ueRM$>7ZK*D<1FCx-wz8WjO(A9ChmY{YtIi22Ar@SP)HYAx{t$y zguEdKNC&!*iRa@l?qs}g4ko5?DF(;129J$8YY=;R&z-439b_}L>xp3-g~6^`+Sqg9 z<`M8%&`x3-D^!EFCOKUyX#J03WzfF5y;iony1?W?&k&h3{lcF-7q;AY4c;3E^%R}N z;l9kY5IGtEgolGA@@hP&Lr6zXN6?#v70;~MxNXyI0 z$>e=~MC4*W2z<8=<*0nH5fRIvJv{O74s~0*@+dE0F_s@?FaIBDn+#%l(*htuhLuD$}hxK(Vf1<+7SPJuLPUe@6 z_!hxsuv$b>)@T}$Cik$fS*;aYG>LA6_?)xzr^Wx|X2z^-W6Hy*`^_sSNbqCYO4#j< zX0#Zeun;&MwOtFq=`Q&gW1G`-@<~n1QdqbLa7cMdbJDN8mXk7c%PIM98IBteQ)T>XF_beq?>tw&j0c%;@bqEc8g&Xu(4 z{oQ@;vbTm=nM+w$G*3UO%@urmAMdB!fRl*htfA<-sK+{s_sj)Kyp;#-)4ni&3`NR` zOdgI782@dH5sT@04be>Ry?cSREaHDZ>CW@X?^gDBrTOJ=Dy^Jt$>~uq=HGjESd2ja zU`;PQBuN^tLH&*2`af9e1>^GFtZ>1g)K%1XvvMgHf$^98YMNqaJ50TyrxW(a+zijE zpzh2tgicNUfK+yNhu+rPFOHc@h!HFAptku=_o~L^!iioYskXuIBKEeV3Z%$&7u7;i zMZPK#Lt=65n}yN4yWmg9s3!j)?Wg^SCg5m^;Pl8KLzQzEiU&mjjav-K~)+p z4=1%#1?{02<0$i?0};Y8dE(5p`tJ0Lt>LJd!?<3<#siDtesj!LYh2@a?!W&LMmjO4 zs<>*9OU$u_o@F+@*1_=fXI4$}?hwP>%1s)!<93YK!l0jhX}7*9v*&W-6QHha5PBS9 z`F^JSxwNVvJ>9{Re@uOal?b)b!|L&^N$y?N1MHQ0joot;b+JM3JC87eWO(JU{Ogx5 zT}9hkZ?;U-oI0dUL$}|#O&c$9XR7eJiti>iNgwSa;Rf=F9jlI}86AtBQ_i-+6NPprihS3JNR9#pZapGn& zeN0$O&XZ#0$(@Sag?9(L9&gW7$yRS_gI125TJ|J$-E)4IJN4ce2MTnp{bXx_aDHLR zD{O9PH~Dor5G1O))%bAU8FX8lQ1aau#covkaIiOnv8r8|8t-u7{q*%{%NK{0<)ap4N7P_N z^4L=>cdLV)GrY6jFz1{;&0($aev~X&X^n}!Gc<}^VXfL`>IMhU`twr`lO(&AgAzE ziN?eb@AHaN^V)%j@mNW{SD7~7VzVGU1;L)}??X$yMdxuN9x>-$4 zH?wgJ_wp~=l2{x{L5c0<0c!&GHs$T!s1grVdE;ybKmrkEnOZhEz z-B`PFtge!t6Bt6XFS-w+o~;wH6_3rjdop#v#rpI#U$t+Qeokz?W^OjQmVgtX!j}JW z^a4MNNa{s}s}R*|7Y*GjpAk+n4f`7p`bHhKoN9SbpM5oQ`RH=@aBH<%wY))H?Cu&O z73ec$*~hF>^>_IyI;D9GV)cS_%f2`MLG^dPBW?_{l6_N|GlfeKzfZDHcW1-@*yyNw zg|m}0X!xQM%d+3?ow=*-T>)osJ&LRKwJo9*b7mMIk;PTADE00XHS=a6`t4>woRTM3 z9<_ZjeWqA@hVGlWFA)7=(iQ*X)T?fqRYFA1kVBH(kV>&hnzk{ic&j+Zo2aHx+s)yb zcEy{0%%#)MQR}m|s>9^;DR&+plYO^$$HOx;x)nDIs&U@$b&VZaSig(i{CoG`kO$4z z7+_>rZQY6?c3a?0%#TSD^yga5%LC(aM{oGjIiLsolt00tdFDt!Q;s-EdO9p>LOZEg z!BV+zb!m9f@4rEwH&t&ItKn`r%ZAoj4ngWM?z3V%7L$lwB;=)gFg)4=QWA9e=iBRC zYEJO?7MF{?%-YS>iPoU2i^GU<&)K=itV0oYBJxmg1zWn@<>gPCp~J8E_-NVkH`I5W&tyO`$ zSHFM1y)u_*^JHO(ezm9-9pQ~TXFR&ydp|o=)=uxx)kW-wU<>u1`!U*|+O7Q&O7 z=z<3~6Ml9J#?t7U^)%A$zS?zYKn&+sP@KtE55`RDXgijB&&J^>Hk1KP6&wsrW#L6h zPErlNr*0oTw~~FoY+v0b_UDQOsIWaFX}WTGw>cy5M44%7)N9$u6~vwU*{3zbE>WlT zH4ZsM;a^WZL&Gdg<9L60Ex4q{QhxfNlH_&I{mvzs?tvn)qHV!~)7&i1wno^aM_&=q z#Da-tnsB8r+2Qx$1EWI`+bOKH*0FH0$-@8UjsuQ^l=hlo6uoAt+xCVu`2tr-3_T?AvmU5hsE8274|&W!+#=SbokE zzTDlCWgEf&{4sw{HJEudA^Ahh)u#uc7-pmj?tirm_qR;BxBZ3~96L$L!I=B&s8Or4 z*||W>+E^9`+xSk^M{<-e#?;F>-$4mJW3n52_SLfT14P5oNosPrL#t#ytVe{#0m0U2 z-RjIF4wfl=%c=kvy~6Q-V|gc8`YoT=zPvX7!_4S&lrVF3;S=RlI|J_8r=ZoHxJ|v?5>tG1Xwy2SwGswTC&d~zFxSmU=>;XSqah@w{%_uKwf1Z-J}$>IohSOTS;OIZQL{qAIKYfgNMTz&?TweS zpmzvbIVEf{RsMed`FKU=N%cPh>sJJ>`^XOsLoU;n?a~j#b=Hct>xai?F=~~|%T=?v*xAbVlW57x6Ow6MrQ+xSGp6O(5Rd zlVyyF=jIbOw5|4RSLtb!9sie4{Y_l+7-{r~L}h&Rk7-x4eF7gz`X}B_y&gJAQ?ya; z7H;C=ZKq=i<5FbaG#mwO##~DviMxf)JHwKsEq=$!2A$l%z4tl= z>kivCQkB~%aYHFzKfO~9o`9R*;cR|~R%Lxyz}JiNr}p-A3@q|_0{dQm`!+R`Z;@P! zd4HKzi$e<*e8@{#*;kB=94~Z1GLVlj{n=-XJ|y;Cv%d<4PVRkN#ivK(;u6X}b-W45 z8CO7Q4SePzq|7F#W6$_Mb-e{pTtU|@I=I6?umC|OxVyW%yJv6)cXxsXhu{vu-8}^N z;4XpS3==#Adwl=B@2k4??yH%au5-Fqcb`6|rcdu)d+ozp4_byywU_ywn*-bXiMZY% zJ$eF}+6(kGH^Ka{H|u105-l{nO%iW}e>}yD&xyO{ek4^~f9lG6yK}y8(^l^m%t`eF zP~74*BZt1Syn>QH`2e3Z@c_S=)`XpWMP`huQN4A1$5JI%(Btu122-{s@16UkyWNM} zsviUTLb8`>6TZ?9(tcZ#mAE-HZF8Dh9G8ex>@G%8QrbTtlQTATf z%D180-}?HK*~&405>6k!7)m}5IHkD8CXx+h(X6&51}=vAkpz$ib}lEU9ygr!npJG@ zgj4kNf1Y#MJoY-*WfK&()ovc_%>lF(xjdCn^vpf!QM^XJmI@O8KFA*`4V=O_)p&h6 zp>sBVAWXKjJ3Q?|jtR|cRV;BQSBI;xNLipck@{1$cH*_n=UI~Osk?V)Z}Y4i{8((4 zhxe|%%dR@PmRE}z$GQKy*NIMXzN5W~5m84&u>m*%=5^pRS}#qHnqTLRA$X5m0sYYl zvTGHlL)R=b_+odHl7F!&nho`8TTf2@1Ub{|5m~#S+rG8sV)tX3S}}#+endlTLYKy# zcUkXnAi>&aFZt~u@ZW3jXjT7%xojP)S)g+S3xNyagZ9wmX|X=PM!=qmI#Q z4!9Mkn0BA#R!^eS=<8K;C%mI^!addWRI3Yq%80FDFy>2n@!`j~mf#~J?|aU(_c1EM zAqMoK%F!}n-{V!hYnYa`aPs{ARb)Mf{Cq?syR|Fs$JaOzNO3K^K_@$eAW9zIC3Chs z-Wp*QfhM|x7Dm#fBAR3%=D>|lpzXvw7dPZi{;&)c9P_#0Pd_;FQhqK<`#S z8UmwiWq@Ni4t!kkyhenAW}KWEgXLr&@Um3#=m;Z+SFJ@)^#BeX7AfP9Cl2Hj>bbuA zbCFK3(Jl8#>nLkbJ!jXfE2xx>yWM%k8@ggH6QN);P3UiP%^*R46v?5{oSg1U&ZC<$ zgQlbqQe0P?!u{#@!^=!-2I2mhpiV_g;U`MjD@xvo(qsde)Ss~d-*Wsm&;*@t&$H$0 z_^hE-DJy6zUV#nUN%~>nd`9SYPdpiC%(R3^g4)ZIhn&5kg1>^i!mH7}H$UD4IPY3A z$4GRoen7h@y{j(qC-#~145F<4M$w+MuBI!hvGyPK5tHSvz}3KSK&*q@@wls7%47OV zpvq$-h7K1>yW+8zIPBjv7i78WejYPA;e9uSLII)A2o9x%1rbmk99Y@U&=*ip@1!R^l+4S&Vsu z6VC%3b9B{-6U4aTExx|soBfQcVM+=dn^Zefo#k~GCc6Ky(+BFDB(g{Wy|CQO7UUDZrz@5{_~xeDa8fzXOx3;`sL6T zA41U9nBbJUf1Lb-2Oen|4ZTG+6RAY`h&n16G0!r`+qP5RUQ|0(8U#X0Y=y1C#|6=j z#wt~4-tmSH;vL={b|0iZn5xZt9Gi$t>;NWT?@{v#%C7eNy9XSJG{}gj?N_1`!=up- zyzwL=S8%PX4IW*NkWtK*W}2mTo!WUmd|2C!RR07(`+_Kl#~HRx%q1%Lg}b)xc5cht zSRiIUQ=5eTa|N2lp;SffH70o)5p@ZoNfJrABoGGC9@1DA@oa1jbk&xtF~XNj6=ucu zGJV8$wTnO_;F3rJ3nwhJ%v~5FQ~%rxzDT8bD4Px1yZUHT;Cp&W9b4DqEd#+aPR+sb zu9H)Lj|G?-2xq544I_-8adx*%W2qj0$%{4A9vgYELHY;Gy&e9&Ez+>uY21J;i3&i6 zL;Hv{U@_zr;O%m~l$t2KumEWuG%2**e~Kib3W1Lypd)+87U2lRm*GJ9BPpE!#iM>- zB87R)mdDvF5cbBSxJoT}OwUrQ;hRtg?%22G7!2wyZ9(+zzwz<4v)ZD<^T2V&Y0wv~ zcHVFMxJ(kLv&Ftlpe_7CTGt%K$S7F#IUR~o;@j?O8ygmk+Koql#y=eFM)9s+E(!E- zFEjVJhhErwxBA+@t|xZaGOMToTsXWr%2H2iIIEIr@ftj+LTmaxX_L}Y82}X9z3a9u zV}@H>(R?IdIe6nI{ISdKrOdFCBr~V4!b?&U#Yn-9SW82N(ZV*8TdmV@1IHI1Y;G-u zeVegi{C>th;@Gk-ljz%7%t~X%z>|ME#4^jLI z^WSi`CG6)N9xD@f0{2lg$ug6;Si74kU24I5!{SwLKKIk|s(Z%MLOAi=s3FXUx zR1+XXiUx_|vk5eNM@`t76-L`^?{LGqJbWAxfG&D>6(v;@> zE8dO)9krTXkIT(KDys}G0-lr0$sZ@31D<;~V6{%QET6kIReOF%OYk%MG6N)tTPO`+ zaOt!$ty4CSuHGifoC4?0d60HP11%2eRl&ewf?Iui-Bo$P=Yg4NqrolziKRwF?63HYoIq3wN|*NbPGZ>C1*!HisKe%# zF^i7yC-g;NakM*;4hI)WT6Iq_B?ax-$zg{Cg&%z$+r@U#$Lk&S4jdwWYX5pE{VymVJGSDV*v8J-YF- z6`CkeD#E(Uo!aGK^I%jaX2sGVbAXdtP~>$?=5lG*R(G95o9WDB4Fx6=JuNjI=E~)N zla+YCaq?$7eU+UFj20V?SDOWAztHdJb8RRZP@ep+RZh%mJ*P006O{omqaClHuAuAs zbVYZO9@^4d_>MM&JHCVU{$iWy+rn1&!`)h{4`*A^+)hwWr`N~{qDJ-z)73p}lkrf7 z%Z>^q$=>E@{pQ$E=v~-%=Dmt4YxF03u3y%K9*YUT+2IWB@(ed}$u7Pm-bII}2x^S{ z`_Dq^j>0X4+wlUQzodcL0o<}|xk?zGx7iWULp0%0Ln?tb5t4J>Pn zw+WE!joWAy;8QppUQfd>6HyTnBf*x1{iR5^yfbr_20sS-oB@*C@prXcwcsJCx+qtT zXn$+6wb+nN8f`#B$}Rm}z#tXFA&TE;A~%LTp4lGgFm)cDJVTbmM*vwgSya z5q0etq?^BIP<>_5{+^FLcAR_6vQPzhQd=*FhJ_6?&#siE#nmV+9!eUZj~BKu_zG9n z?<&c+qU2x$u~~@Ms+xmv^XZpGT?-x)N}EJ?IgkjRrRg}G$)s3Sh~vj2zvDewbI0K_ zmmmA{`(JgiMur?y4pZLk?T=;N0!DOHOv$Oe9%#KsPpz&YRF0yrV*j)5+^=5)%gc8* zV)qYQc|zWG{W8V?)=oWQR{@G>IPpqrCdXFw?}yD`A6rb^2{4C=!45Ow!|uy%$%gZ^ zQ@@Nlup=Z^XNXyeb`7xmevg_*wNXOaRBo0MG~*<@F?>cwVvU&G+#%Y_5X+^fM%jSi~}gzG6> ziE0kmX5PzN+gXq2K@m zOgcj1W#dqBJMyuv!Z)J7M&e4pJ8ZtR^uXuuXsRI`b`Y+-8usAG)+HEK!B(!JH0bZ; z%fj-C9fT|l=3V|8@bTg1UwwbRb6wZ8+!+!<%Z|+PHYsb6_-*NOjh2D*PZ*VR?~iEi{>AbaCY=P|p7!rOvA0VN2M zGUZQDlT&ImGTiH|;|vIFo@=(6>%Q);5*{-_-Mr#<37s0n`d{0yo5zwyD?AFhDG|+d zkg4UdMpjZEgPJxt9CetOCkbVKzYqJF@+(>b172Jlo8Gi%9+@guWmcL$?*dnmLo`?g3#OBxmN=dCgW;bDwq(jKvJAqn3~%MhIq1-#r6=`U*oB2!9uCdKWVK3h zk2o_NNBMXyBLT@1b2T=5;wCoxi_vkw=wGrxtfCS1(CP7X0%c)~?q0=; zn(6TRRh@GiMT1DVFfBwyq(jXUeig&G2h_r!mg#V~q8+ZiYepwdx#QmT=Y7|1T#>r< zdOlP0)C-!rt<9KJ#Hi?!CckUI={I#-0zQ}N-rGKBTLFE?w~uk4;LBm>?aoV9^IOq( zS134Tq|@{eU}62FpQsEjjJe1y!Onh~GG{0`W&uE)q``nJJx_@r@YAql zIXT(h$$divY^i~yK-N<1W5R$2*wX()`F3V+4?Ww$XsYwYi^i5?l84cxR{R(y_zT3CYzb4ndh^8kV4XmH3Ds(d5)g zKbry;imBuE!xv@xa?A8*Xa$wJ<8N$_Xe$}dWZM|Oa{-xc*pO|)@8hjgapBqN%NS%Q ze-By*b1C_ee-~f-1%hXbEz#%UrrqU69x_+=s6Z8AWtiu;h5JA~(6V!S)O@yIA{1}^ zb(mJy{QJnUmIujC^QLvjj8wQ)VP1MbC|nxI9KIg^A8=y)i~Q}s4_I#t;AjggEN%2$ zD%m<7K^bgmP{{(Z&7cT=MmEh!c5m>}-M{6S)p)1gXSeF~Qm9uNb!v%JcP4H8Hw1!H zk~Lg7do5LW*#&4l5@*aD^-Es+cE{C>kvGh^U={_jRsQdqwIYCkhg4x1xLD`q#mf;* ztZy&(8DcOcd;bi^9O&$-aj3M2#j8PsPk}GX#QzA#-WU$F%BI&hlNY!;FmL@dwiFwf zk{Qj+U~mlaw$^;vxp7EE&J#z>vBFHacXEUz9?M9zs^6b6^J$NyA*qu^U^ZQjRgP&JDb26M zlgwp!l0^ra5XiAK9hkRk&eYsv%6>!Vjo3dFw>C?kS(9DY(l|m?d!BIXHFu-)j^#b@lWjg_n8CI41g(Xgx%uzaf|LPG@bA!3U$(YNwaeY z$BK9RyHW9(+&{r-kS}<(Xthkp54+X^J9#sscI6gdSP%E@Ij%)fE4U%-iY6IKy5LVo zgdac35H^N}RikzE7=Ia&yLS*Z!k+(CaEgEo`0f$d+uAi=SbbvI=)D2JKnS<~An`GN zPkZ8xeoFYzL+>_GJabxQcYihUJ}&|oofZN>{2nqeDzB42i=FTtO%_Xe1C)F$Pl~k% zV9|3YsO*?yw;MzN;VvGWn`eZfV527pqyf`$^5Nm=c~Ohg^lIKGVOfL#xhwFfIg~@l zWzdiTF}M=rn_4u6MUYd*XlyDpO%!}>*Ph%egvs}UsH3fe$4MV>KZe(5QNn99@ zz1nourtlcKJ!(*e>g9c{7^w4!l7Wgl;9B3d;N8y)=JNP<%36vYsoGer**q?9GyVmN zjY60N7@sNA%O9HF+Vx)PfYekaV$u%R*Q6u_Ax|h2M2)_u?WGf%ngUvN z@J^`&946%?6-5ofkU=gtSVL3Sv$w$kA`ezf1TP#g@i)B3IJjA;QD*(N{_CL(do<{`z0Ob!ewkkRRAXacaL!=m-|B5f#a6sj=Y8h*>f zh*O3~6yerH6yb1{3B*zAer*)j8C8GJfR+cZ_FIFwXdxM}SPI{uy7z5fF_}`}hi0W_ zWv;e_hC&y0RdMv&JH8;*7aHJRlX_#G}Ybd9{=g`*U8Jp=p%q@>rT51pu#NvTKzYTyRzj zvMjLKEg7J0$bm;yi+z19@<7X$^?Bpr`2O zk^MKUc0uT75?z^VR>52;$TY=^UWbw{zQ*6pzptB4hd^fTFeX)3KRR@cOP>^*!m zM;eaYZ~(pnNtp&FDjDcotQ6WZ9QQwH>H9`Pihv(!h|GbW+#-l=cJ0ZxyhP*@!+2o> zA?M&fV@0~zB`r5$Agr|Ef&3g55zXC@QrXn-XydM?*v7i>!etsIc13ubnvlS{Iax%w z`ROG=bT4kC(w@x9snU4Wx(;>5_%5u0;en#oR!MX7G8L1lO)AgjHj9$TERtkuyrP7f z(#o$VHH@d!8g@dm)V~1eAEhgS0)$~S2v&g09}~nts>Scc>i99u1j&Zkq7ul!FnJ$= zRTg*<92_+*jgp@Yw&4jq2!zxKhAqycKMSFRAyZM_P%4KcTZinf*}f}EES5$LYcN!G zw~(Nw9R_`ss_|St0hx=Ep*Z8ld}ph-v_su45MTcw-1pJ_XxAd6-cwJcm4f0v zCm}BkTJ}U6$)uLh)<}U5$y3R@7mOcQH%~-GM+-$K3?z@43O z*5}@)zv)#ah<|aasZG~tLQUaaPcR@E56ki#Dq^gjfAde-s73l|D~<}JFE+2x^*fXr z%jQ9m1MiK6*2j_IOO#UU7t9XZLX_i=B^&mnBH;MJn9*Dwglh=8eZ}-Y==ZA7r*t&3 zxZTm+Zdx(ys)qqHOQCO!+COQs;u`PAARlP5WOljlW@r^gV%_vUPe@jiW=;zUs(iFr;i#0j12wDN}<{3_v)6hz@=xrP5 zu{G2gEQC>TbtBg4wGoqLOE@?!F^R)*RHIv@sXu=QGT;I5w3!XXsVSo+4efiaBuaqD z09ySSB-tTumE@ngJkdZPI;)*uqTFBMOg5!bxUVH25`8mTB0>O)m48w@2VnbY`?!tQ zgCFlUO$_eoYnRBQ)K%#AxAN}pthpu?&j4`rcf0RvG>1Z&EjDev+lxdh<&#jl=$0&HxpEs+W0C8~(tUk_}1y%2?@&~>Yy)6=^TXLqgHWE#OP9Jxk z>MnQ+UKdAOF$R`3r0@G4r>l3K2g=w2-k5=B)vtpTLv~-Nca>^MF`W6>M7*x19$?SI zD>}KV#+PNUyDRswi@r-evxl)G(dH&sN#S-~-dNw4H_3=^Q5B1t4`5HoV{^eH_ynPu z_z$1y>*Z4Wrd;rC1{XmbOIqmKz}$WRCG_s!>Fn!ieNG6^73j0CPTxJ<&ee>_EtA*| z?5@~u$zi1ZO)+9WT`&43icQQN8daa@<+W7Vs`k`s*70!Hy4CH?7X0)vQRM08MXgah z_l%qztmpFIsMy1>?X_h#E$yyQd)*c}H1MgU;ORN|vc3!QtQPc=y2KBwsgZ0qiR<}$ zQ15~|`}}ltvD4LfJ=1MD#WgD`WWrq6`+Qg_N73tZq9Jhuk9u<&x5IKIV+p2E7w1SJ z!o&_mcvH#yeBjdppp4({s;J1s#5lt*me2+n!hb|17XLe*IdM<$p(%+OiwP|y1ibhq z4BTq4th>O`%t0PCp5c{)kLrUAw7XkDDtWS{bVAb{yc~)_sX>oWjQE)9Aqbtb-IW3W zBpov|5>`0kKSx&u6S&1GWgW7CUbj5jnC}kW%u)@(Q zSQr;{wE*ZhnJ2lah zGr{pDw&zx}K>wqQ^xntzi*3=smtPO&SEH|c1u*E2NRR(n|1;YwTYcZneAe~Rg6=!x z+n;jzCSsmfP>Pp7wJSRTu;ut`3cupUHbR<{D6y9}5B*y+gTN=|?zJ+ad_^I&i2vBR z|E^(_n^e%N^3CKfD13%j^88F6YBFzE(Dn2?hAyA~;cqG(tBC8rn91^jo`BP51KBz40M$e2#|3OLs<@Q|ZygnHi(O2C6r{E@AMpxai_=$*uy) zP6DhtldfnT@EY*+(O<&o?FG6JTGcHF7K`_F#f-Mht$Ymj6e&B%ZvCeGR0@O7+F$O( z9&KLF$LZJ*#yCS5L02zN7Jn)Af07teN=SN8;qPQ8yi~>B|@Gj z?(Tfp!vXBXUK~OlVj-PoCyvkyGAI-qZufTyk#mnfMTjmh+tVMM7cxKq9MJsiro@#C zo)1>*`FiXfd=%IJ2g6@3@$35@*W0|oL8;>!y9Mb&(|*2yM<|f~q7H*rf^PThW!>+q z^sI5vru1Af^R~}p-W!F;kS0;|wXvK$cHEO5YP=)Q3dKoy zvtVU$*+exzq{Dh@%FlU%&acgsNor{qAKP-5X~ef2+oOd1qhKZ?_csgI0hbpzeH6J& zDI>k@L2?3!W!YHG?$3nl6CXY})_B0SJK-E2bDq2PcCwBA-Q7GIQUWUUzl_{FIIRg)YxYz*!$`1mES|rqZ#vx6sn5FqvsnhZw(WC=6Hk)>AWn z8Cqzo(vZ*8NG?RglSNHpNUgoo!|ztl^^P02g}rPU@kMn%{_~OlGmDI-ObNh54W%w* z7Z<;z4nk|HPX}K`oWE(TukWKF%zBi>S>jNUFq8l_B5%r)H6+n{bTfk3!GbEWiTR~DX zPkgI*ZXBK>iTO!^DW!kkDQk*Ln>z(JHgY~dTLDco=%A^|e->tcp8|LTVaxvM)odcq z5qWyR#qoDG7X&MM`N7#ygRM@mCGTxGsYFqaAlS?HG$~&WG@)sCg2b=dsCJs#sn)pw z(z382VGOZu>*Gf49<`H`1&qf-g#jFVth#?go##86o+C8Tp0f`TliP)ez^N%c-aM#UfTaJ5nD>a#Yh1wtm_PUFF8Fz8XluE|M(yPoVpc|tDCA$U z@-QL<6R+Xbee{OO=3U#|tdAU&vnlBe2HYL*C$7yW1U)Sm9Q>hp-RdW*+1L=%7YjTb zKPq^A@izSpY)e0^LiLpSZt3B|1CUE5+3bIe-+6RILj5uYv}-2AB7qmX`MaR7+!O4< z{vq+|q{4J+NGLw(l=$Z$O4NA|g|=oxu2P8xv4r%WfdHbgvL#bV1qpqyzD5P=amVN< z1%zsD_Bi?AUWjsLyd2&+N4OvW9?=Z#@6K~?@O66q%jLzzj2kFkQr7Hi*7BaQDL%kM z_|5w^lEOF+!2HmGkU$AG+Dx9{bp614TAY?M5Q4BOQI#2>Du-Xz%5@oclwok$hB)p7UF`vpBImHC8+ z#3(1{XttpH`QO=!**~x4GfUTHg_MPI-ZnnJ{WTX;g65G;n|V-qhE# zcB?*t_8zviVqlrXr}s$5$E)h(pqhYOveCXk9sA`Q{o%T4@Y#m~BP}F)`!C9ba*yp! z6wr&l`+bT6VP8(~ptkP)(|=RrwduFphj*WuuSn~AIVO%=tLL~~gRQI_{hJwScOFg) zjP!(#x(zG>-LI!C5-IoY`=6HVxm$GJHavBCm)qLa{?7MZh3nZzQO(AB({1;=bX_iy+bj8rVdXJMX#}XcnQp+AGaLVYM!UhR7a?HeQCF5 z&`6kz04Zw&LmGJ26^)VV2+dH8iuB9IJAs)Q2u+D_%<#o&OJKn~0J$2H%#Y~EeR|o_ zt~ya*i@7hr{&E0U5+JUr(AHM9fkp7%qlir9Y7R)M014!Z;hNlo3*in2pi{j=_sgMg zC046eAdeL^F@n_9t5t6bq8P$qq{MpQ%ekf>y(z|%HgFcWKs%dO9pv(`-w8-(s@VzJ zIytN;-_B7jVhC@~M11bDnIQzTJEFU5m9V(Os8mVa`;*K+2_FvF`fjY-n%mYos<&rs zFvfyt%!wr^ZO6i;7L#>>VN7waW)?@wn`7}(zYvnnbC@3l@2J@KJ!D}RZaL3v=%<6 zX@j}%UHkQ-@nMp=`Fy_54%{Jbfi%@hglF?VBbRYz&w^i-cp`wi)`bYJtq%2dSA)+t zo-BOr!u?+=A?u=vsqh@O_SySEHZ$KGPug~%RK*NkMz+~)5RPx7POADx&5$3^ur|;fSF8Uy~!V(e$A43=RQpe8%*{eZt>KNgFPeo z&bs~}yztN9GLjptGZk<#cwBOVcL(#MqtevP9|% z>N>mR(bB)6pZs5kl?*ofNLh7dk{DF%6ZdPoYl73oaw8(@|Lb# zGKsSXFTJI4Ma6N&7%D>s$AzD{-zAtM)W2~=?#^_*n`LSWqlx8a%p8F(>|dTR)c{ag zT>v%cWx}ojdo{roqZs zUf6|IVL1j;cDUF$iQ4a3*_7FhP1S$gv}|wgFgbl52Ae5H+qWvByT;;D4IL^I{l2^W z^mrNmw}rp|=OQ`IBeA{dS6L#gVN5EAt6u?~Uu1Y6Tp$1LGEd&OKQu(Wher>DkLuod zF5mGwgYM#@^O1gT5lfh|SgR`+?xOC1w_5zCWz3W0!KI-t>ASv&YU_p`aTv`rouGm0 zE>B>*1k}n`Ml5zu;t_GC(iu^J0|DH^Sf-TWviy0}$yO6VpB^{le2-27bRuNs9cF?b zMi1M~MAS@`CDI(eD$R@$%GlA^{yw2j6`-+o)zWFklm50(%>A?1kz36I{nzi)Q*cZy zWy*2ByHjgtUzb09kg?c6-i&&$`F9C7K7E*sN3;kTFci<89q6@;Qb*NFmM zaw`>!yqCSi3;vAEew&&dgw@7>5F+vT(I`2r^*UXZm66E?8XTNNwF2l>d3O-8ELW7h zx;t7Ct_$ZorY$##uhY*R4^J^OcmHT*c(UhYO5k+r>Z94q!b^-;kvYWz)2o+Yx7X=X~_lr=YsJJ!zCTW}X@8@now!(`|=g>@nL5TV$Q}?`bn4RM31UkdzL` z`{n}IGvzB4TU8a&6=9AvuINdsb0w#0@<(wmh0EI)Ip0E(^&r!Gc9!|>>0v=~F0Y=} z8&bmi>GE1F2Ztg+xWiVH#q=0{*tQeP{`yLWIy#&d6cSzxE=A_(pkGqN?+~&HVLyeT z!2gW)`H4vA-rwQ#I@Mw(Ccs(6UHLwj&Qo&=Mcs6b$&@^Q@q{QOn9kNGb+kSx?ccus zFBz*wMw+LrrO_;q9uD-{+4sJ&DY&Vo_eE947=pu%utG9w*6g#VU6q<&PH@>E<80@*taBn7>I1AB$N0pYE zP8%l+Z2$XkM{hQH_kQ&G-{f;KObgZNkRkGy1NdiGkd^sxZt;~)1%6Q)YW<{M_~Kr^ z`R9kcrwI(CSsN0s4-0I4@}+LC_EGL5HvU^?RLWzTJtjU9t72kK@hCfeajo zAN~!i!P+UrI1+VZ=r4Gf(6o>7A{VvVXN(L^MWa(`mRL)Z=%%G^4Z^z_0|ZOO(ORG` zUa3t(JrAceQGL#uT5&}eY%%`%9m1KDIu~EL%0J{*$a7!Sh6YW_j-V;uY@Pk~fsmh% zvReUAgGa@8w$0EAsXh)XGaq3_EF#CNC?d0ULw_}>nNVn*r2+AJ>5nDRD?wE%v5nAg zt}W2~q#_D?L5V^|G+?A(krwe{#MaN@Ob0&BL|~$P#&c_?pZHfiEqRUy?neG{VJMD< z%O+HEp*4f=Bf=+IZrf1)Mf>u6a4CpcF&M2R!j!?2RaM_41{F)!tY2M}NptB$)qDQP z4n4PdoB0QbTf%i5e`FOad~VVwsc^v$8usfPQ}xZ6U&3Nyf1q5U?5d>9tWI-5N3GIR z`^`i|GnwInaD8KZD>G~sD@`}9T2&&ur%2B=9o`a$WNsyFA6;F2c5tB6ZG2aK-FzL_ ze0?3ngIT@QV92UF8RWUlM)Ze$s2yYF33r<+mS5~QgfG@k=ms}|KiQq`9()Qv z*O6LoyO)F(dq%#AbPo&*M65!lMtglPEfmU5x|)-RN%1E&25%eaHv&1<`O{q^a(GP) zjjhqDBH<+&1ozddgOMVc;p$k;DG5UOBQ+thI>SaiUnMG7#lcJDS!bId?N1^u=eJ1- zK=Un_HilSn43)C3bQy(T*TEKR#}TIjs)~a|nSX|6$tPj5O+^5jNsVqJw5l*KM6q7V zFW<~;^QFfoQKvK$UDDc^7iljo2F!ZMbtTnA8hK=*#I|qkK4#&tk*3MonU=z`KzGlG zWjLk8kXjK<`Fqden^qy+r+=MD(LwdogXcM3=$A)-_u1Y=x|)OLx<8u^jWj-JnY;(01^(?mY Date: Wed, 26 Aug 2015 19:38:50 +0100 Subject: [PATCH 06/11] Fix torrentless magnet links failing on the torrentpotato endpoint #157 --- src/Jackett/Controllers/PotatoController.cs | 330 ++++++++++---------- 1 file changed, 167 insertions(+), 163 deletions(-) diff --git a/src/Jackett/Controllers/PotatoController.cs b/src/Jackett/Controllers/PotatoController.cs index 34cccbcbf..e10662652 100644 --- a/src/Jackett/Controllers/PotatoController.cs +++ b/src/Jackett/Controllers/PotatoController.cs @@ -1,163 +1,167 @@ -using AutoMapper; -using Jackett.Models; -using Jackett.Services; -using Jackett.Utils; -using Jackett.Utils.Clients; -using Newtonsoft.Json.Linq; -using NLog; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using System.Web; -using System.Web.Http; - -namespace Jackett.Controllers -{ - [AllowAnonymous] - [JackettAPINoCache] - public class PotatoController : ApiController - { - private IIndexerManagerService indexerService; - private Logger logger; - private IServerService serverService; - private ICacheService cacheService; - private IWebClient webClient; - - public static int[] MOVIE_CATS - { - get - { - var torznabQuery = new TorznabQuery() - { - Categories = new int[1] { TorznabCatType.Movies.ID }, - }; - - torznabQuery.ExpandCatsToSubCats(); - return torznabQuery.Categories; - } - } - - public PotatoController(IIndexerManagerService i, Logger l, IServerService s, ICacheService c, IWebClient w) - { - indexerService = i; - logger = l; - serverService = s; - cacheService = c; - webClient = w; - } - - [HttpGet] - public async Task Call(string indexerID, [FromUri]TorrentPotatoRequest request) - { - var indexer = indexerService.GetIndexer(indexerID); - - var allowBadApiDueToDebug = false; -#if DEBUG - allowBadApiDueToDebug = Debugger.IsAttached; -#endif - - if (!allowBadApiDueToDebug && !string.Equals(request.passkey, serverService.Config.APIKey, StringComparison.InvariantCultureIgnoreCase)) - { - logger.Warn(string.Format("A request from {0} was made with an incorrect API key.", Request.GetOwinContext().Request.RemoteIpAddress)); - return Request.CreateResponse(HttpStatusCode.Forbidden, "Incorrect API key"); - } - - if (!indexer.IsConfigured) - { - logger.Warn(string.Format("Rejected a request to {0} which is unconfigured.", indexer.DisplayName)); - return Request.CreateResponse(HttpStatusCode.Forbidden, "This indexer is not configured."); - } - - if (!indexer.TorznabCaps.Categories.Select(c => c.ID).Any(i => MOVIE_CATS.Contains(i))){ - logger.Warn(string.Format("Rejected a request to {0} which does not support searching for movies.", indexer.DisplayName)); - return Request.CreateResponse(HttpStatusCode.Forbidden, "This indexer does not support movies."); - } - - var year = 0; - - if (string.IsNullOrWhiteSpace(request.search)) - { - // We are searching by IMDB id so look up the name - var response = await webClient.GetString(new Utils.Clients.WebRequest("http://www.omdbapi.com/?type=movie&i=" + request.imdbid)); - if (response.Status == HttpStatusCode.OK) - { - JObject result = JObject.Parse(response.Content); - if (result["Title"] != null) - { - request.search = result["Title"].ToString(); - year = ParseUtil.CoerceInt(result["Year"].ToString()); - } - } - } - - var torznabQuery = new TorznabQuery() - { - ApiKey = request.passkey, - Categories = MOVIE_CATS, - SearchTerm = request.search - }; - - IEnumerable releases = new List(); - - if (!string.IsNullOrWhiteSpace(torznabQuery.SanitizedSearchTerm)) - { - releases = await indexer.PerformQuery(torznabQuery); - releases = indexer.CleanLinks(releases); - } - - // Cache non query results - if (string.IsNullOrEmpty(torznabQuery.SanitizedSearchTerm)) - { - cacheService.CacheRssResults(indexer, releases); - } - - releases = indexer.FilterResults(torznabQuery, releases); - var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port); - var potatoResponse = new TorrentPotatoResponse(); - - releases = TorznabUtil.FilterResultsToTitle(releases, torznabQuery.SanitizedSearchTerm, year); - releases = TorznabUtil.FilterResultsToImdb(releases, request.imdbid); - - foreach (var r in releases) - { - var release = Mapper.Map(r); - release.Link = serverService.ConvertToProxyLink(release.Link, serverUrl, indexerID); - - potatoResponse.results.Add(new TorrentPotatoResponseItem() - { - release_name = release.Title + "[" + indexer.DisplayName + "]", // Suffix the indexer so we can see which tracker we are using in CPS as it just says torrentpotato >.> - torrent_id = release.Guid.ToString(), - details_url = release.Comments.ToString(), - download_url = release.Link.ToString(), - imdb_id = release.Imdb.HasValue ? "tt" + release.Imdb : null, - freeleech = false, - type = "movie", - size = (long)release.Size/ (1024 * 1024), // This is in MB - leechers = (int)release.Peers - (int)release.Seeders, - seeders = (int)release.Seeders - }); - } - - // Log info - if (string.IsNullOrWhiteSpace(torznabQuery.SanitizedSearchTerm)) - { - logger.Info(string.Format("Found {0} torrentpotato releases from {1}", releases.Count(), indexer.DisplayName)); - } - else - { - logger.Info(string.Format("Found {0} torrentpotato releases from {1} for: {2}", releases.Count(), indexer.DisplayName, torznabQuery.GetQueryString())); - } - - // Force the return as Json - return new HttpResponseMessage() - { - Content = new JsonContent(potatoResponse) - }; - } - } -} +using AutoMapper; +using Jackett.Models; +using Jackett.Services; +using Jackett.Utils; +using Jackett.Utils.Clients; +using Newtonsoft.Json.Linq; +using NLog; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using System.Web.Http; + +namespace Jackett.Controllers +{ + [AllowAnonymous] + [JackettAPINoCache] + public class PotatoController : ApiController + { + private IIndexerManagerService indexerService; + private Logger logger; + private IServerService serverService; + private ICacheService cacheService; + private IWebClient webClient; + + public static int[] MOVIE_CATS + { + get + { + var torznabQuery = new TorznabQuery() + { + Categories = new int[1] { TorznabCatType.Movies.ID }, + }; + + torznabQuery.ExpandCatsToSubCats(); + return torznabQuery.Categories; + } + } + + public PotatoController(IIndexerManagerService i, Logger l, IServerService s, ICacheService c, IWebClient w) + { + indexerService = i; + logger = l; + serverService = s; + cacheService = c; + webClient = w; + } + + [HttpGet] + public async Task Call(string indexerID, [FromUri]TorrentPotatoRequest request) + { + var indexer = indexerService.GetIndexer(indexerID); + + var allowBadApiDueToDebug = false; +#if DEBUG + allowBadApiDueToDebug = Debugger.IsAttached; +#endif + + if (!allowBadApiDueToDebug && !string.Equals(request.passkey, serverService.Config.APIKey, StringComparison.InvariantCultureIgnoreCase)) + { + logger.Warn(string.Format("A request from {0} was made with an incorrect API key.", Request.GetOwinContext().Request.RemoteIpAddress)); + return Request.CreateResponse(HttpStatusCode.Forbidden, "Incorrect API key"); + } + + if (!indexer.IsConfigured) + { + logger.Warn(string.Format("Rejected a request to {0} which is unconfigured.", indexer.DisplayName)); + return Request.CreateResponse(HttpStatusCode.Forbidden, "This indexer is not configured."); + } + + if (!indexer.TorznabCaps.Categories.Select(c => c.ID).Any(i => MOVIE_CATS.Contains(i))){ + logger.Warn(string.Format("Rejected a request to {0} which does not support searching for movies.", indexer.DisplayName)); + return Request.CreateResponse(HttpStatusCode.Forbidden, "This indexer does not support movies."); + } + + var year = 0; + + if (string.IsNullOrWhiteSpace(request.search)) + { + // We are searching by IMDB id so look up the name + var response = await webClient.GetString(new Utils.Clients.WebRequest("http://www.omdbapi.com/?type=movie&i=" + request.imdbid)); + if (response.Status == HttpStatusCode.OK) + { + JObject result = JObject.Parse(response.Content); + if (result["Title"] != null) + { + request.search = result["Title"].ToString(); + year = ParseUtil.CoerceInt(result["Year"].ToString()); + } + } + } + + var torznabQuery = new TorznabQuery() + { + ApiKey = request.passkey, + Categories = MOVIE_CATS, + SearchTerm = request.search + }; + + IEnumerable releases = new List(); + + if (!string.IsNullOrWhiteSpace(torznabQuery.SanitizedSearchTerm)) + { + releases = await indexer.PerformQuery(torznabQuery); + releases = indexer.CleanLinks(releases); + } + + // Cache non query results + if (string.IsNullOrEmpty(torznabQuery.SanitizedSearchTerm)) + { + cacheService.CacheRssResults(indexer, releases); + } + + releases = indexer.FilterResults(torznabQuery, releases); + var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port); + var potatoResponse = new TorrentPotatoResponse(); + + releases = TorznabUtil.FilterResultsToTitle(releases, torznabQuery.SanitizedSearchTerm, year); + releases = TorznabUtil.FilterResultsToImdb(releases, request.imdbid); + + foreach (var r in releases) + { + var release = Mapper.Map(r); + release.Link = serverService.ConvertToProxyLink(release.Link, serverUrl, indexerID); + + // Only accept torrent links, magnet is not supported + if (release.Link != null) + { + potatoResponse.results.Add(new TorrentPotatoResponseItem() + { + release_name = release.Title + "[" + indexer.DisplayName + "]", // Suffix the indexer so we can see which tracker we are using in CPS as it just says torrentpotato >.> + torrent_id = release.Guid.ToString(), + details_url = release.Comments.ToString(), + download_url = release.Link.ToString(), + imdb_id = release.Imdb.HasValue ? "tt" + release.Imdb : null, + freeleech = false, + type = "movie", + size = (long)release.Size / (1024 * 1024), // This is in MB + leechers = (int)release.Peers - (int)release.Seeders, + seeders = (int)release.Seeders + }); + } + } + + // Log info + if (string.IsNullOrWhiteSpace(torznabQuery.SanitizedSearchTerm)) + { + logger.Info(string.Format("Found {0} torrentpotato releases from {1}", releases.Count(), indexer.DisplayName)); + } + else + { + logger.Info(string.Format("Found {0} torrentpotato releases from {1} for: {2}", releases.Count(), indexer.DisplayName, torznabQuery.GetQueryString())); + } + + // Force the return as Json + return new HttpResponseMessage() + { + Content = new JsonContent(potatoResponse) + }; + } + } +} From 1e3de38db77679753eca6477dcfb5b7415399b7a Mon Sep 17 00:00:00 2001 From: KZ Date: Mon, 31 Aug 2015 20:52:15 +0100 Subject: [PATCH 07/11] CouchPotato - Change prefilter to allow results with no year #158 --- src/Jackett/Utils/TorznabCapsUtil.cs | 149 ++++++++++++++++----------- 1 file changed, 86 insertions(+), 63 deletions(-) diff --git a/src/Jackett/Utils/TorznabCapsUtil.cs b/src/Jackett/Utils/TorznabCapsUtil.cs index 338feea56..d4b593750 100644 --- a/src/Jackett/Utils/TorznabCapsUtil.cs +++ b/src/Jackett/Utils/TorznabCapsUtil.cs @@ -1,63 +1,86 @@ -using Jackett.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace Jackett.Utils -{ - public class TorznabUtil - { - static Regex reduceSpacesRegex = new Regex("\\s{2,}", RegexOptions.Compiled); - - public static TorznabCapabilities CreateDefaultTorznabTVCaps() - { - var caps = new TorznabCapabilities(); - caps.Categories.AddRange(new[] { - TorznabCatType.TV, - TorznabCatType.TVSD, - TorznabCatType.TVHD - }); - return caps; - } - - public static IEnumerable FilterResultsToTitle(IEnumerable results, string name, int year) - { - if (string.IsNullOrWhiteSpace(name)) - return results; - - name = CleanTitle(name); - var filteredResults = new List(); - foreach (var result in results) - { - if (result.Title == null) - continue; - if (CleanTitle(result.Title).Contains(name) && - (year ==0 || result.Title.Contains(year.ToString()))) - { - filteredResults.Add(result); - } - } - - return filteredResults; - } - - public static IEnumerable FilterResultsToImdb(IEnumerable results, string imdb) - { - if (string.IsNullOrWhiteSpace(imdb)) - return results; - // Filter out releases that do have a valid imdb ID, that is not equal to the one we're searching for. - return - results.Where( - result => !result.Imdb.HasValue || result.Imdb.Value == 0 || ("tt" + result.Imdb.Value).Equals(imdb)); - } - - private static string CleanTitle(string title) - { - title = title.Replace(':', ' ').Replace('.', ' ').Replace('-', ' ').Replace('_', ' ').Replace('+', ' '); - return reduceSpacesRegex.Replace(title, " ").ToLowerInvariant(); - } - } -} +using Jackett.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Jackett.Utils +{ + public class TorznabUtil + { + static Regex reduceSpacesRegex = new Regex("\\s{2,}", RegexOptions.Compiled); + + static Regex findYearRegex = new Regex(@"(?<=\[|\(|\s)(\d{4})(?=\]|\)|\s)", RegexOptions.Compiled); + + public static TorznabCapabilities CreateDefaultTorznabTVCaps() + { + var caps = new TorznabCapabilities(); + caps.Categories.AddRange(new[] { + TorznabCatType.TV, + TorznabCatType.TVSD, + TorznabCatType.TVHD + }); + return caps; + } + + private static int GetYearFromTitle(string title) + { + var match = findYearRegex.Match(title); + if (match.Success) + { + var year = ParseUtil.CoerceInt(match.Value); + if(year>1850 && year < 2100) + { + return year; + } + } + + return 0; + } + + public static IEnumerable FilterResultsToTitle(IEnumerable results, string name, int imdbYear) + { + if (string.IsNullOrWhiteSpace(name)) + return results; + + name = CleanTitle(name); + var filteredResults = new List(); + foreach (var result in results) + { + if (result.Title == null) + continue; + + // Match on title + if (CleanTitle(result.Title).Contains(name)) + { + // Match on year + var titleYear = GetYearFromTitle(result.Title); + if (imdbYear == 0 || titleYear == 0 || titleYear == imdbYear) + { + filteredResults.Add(result); + } + } + } + + return filteredResults; + } + + public static IEnumerable FilterResultsToImdb(IEnumerable results, string imdb) + { + if (string.IsNullOrWhiteSpace(imdb)) + return results; + // Filter out releases that do have a valid imdb ID, that is not equal to the one we're searching for. + return + results.Where( + result => !result.Imdb.HasValue || result.Imdb.Value == 0 || ("tt" + result.Imdb.Value).Equals(imdb)); + } + + private static string CleanTitle(string title) + { + title = title.Replace(':', ' ').Replace('.', ' ').Replace('-', ' ').Replace('_', ' ').Replace('+', ' '); + return reduceSpacesRegex.Replace(title, " ").ToLowerInvariant(); + } + } +} From b39740848da67003bb4991739226dd2dd30f58bd Mon Sep 17 00:00:00 2001 From: KZ Date: Mon, 31 Aug 2015 20:59:35 +0100 Subject: [PATCH 08/11] Torznab removes aprostrophies but filtering is done with them causing items to be incorrectly removed #158 --- src/Jackett/Utils/TorznabCapsUtil.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jackett/Utils/TorznabCapsUtil.cs b/src/Jackett/Utils/TorznabCapsUtil.cs index d4b593750..954355d33 100644 --- a/src/Jackett/Utils/TorznabCapsUtil.cs +++ b/src/Jackett/Utils/TorznabCapsUtil.cs @@ -79,7 +79,7 @@ namespace Jackett.Utils private static string CleanTitle(string title) { - title = title.Replace(':', ' ').Replace('.', ' ').Replace('-', ' ').Replace('_', ' ').Replace('+', ' '); + title = title.Replace(':', ' ').Replace('.', ' ').Replace('-', ' ').Replace('_', ' ').Replace('+', ' ').Replace("'", ""); return reduceSpacesRegex.Replace(title, " ").ToLowerInvariant(); } } From cf0f35596091bcd87ff60925bf8028a5711c4811 Mon Sep 17 00:00:00 2001 From: KZ Date: Fri, 4 Sep 2015 19:16:40 +0100 Subject: [PATCH 09/11] Fix torrentday non local login --- src/Jackett/Indexers/TorrentDay.cs | 425 +++++++++++++++-------------- 1 file changed, 213 insertions(+), 212 deletions(-) diff --git a/src/Jackett/Indexers/TorrentDay.cs b/src/Jackett/Indexers/TorrentDay.cs index c67779d83..2943dd772 100644 --- a/src/Jackett/Indexers/TorrentDay.cs +++ b/src/Jackett/Indexers/TorrentDay.cs @@ -1,212 +1,213 @@ -using CsQuery; -using Jackett.Models; -using Jackett.Services; -using Jackett.Utils; -using Jackett.Utils.Clients; -using Newtonsoft.Json.Linq; -using NLog; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using System.Web; -using Jackett.Models.IndexerConfig; -using System.Collections.Specialized; - -namespace Jackett.Indexers -{ - public class TorrentDay : BaseIndexer, IIndexer - { - private string StartPageUrl { get { return SiteLink + "login.php"; } } - private string LoginUrl { get { return SiteLink + "tak3login.php"; } } - private string SearchUrl { get { return SiteLink + "browse.php"; } } - - new ConfigurationDataRecaptchaLogin configData - { - get { return (ConfigurationDataRecaptchaLogin)base.configData; } - set { base.configData = value; } - } - - public TorrentDay(IIndexerManagerService i, Logger l, IWebClient wc, IProtectionService ps) - : base(name: "TorrentDay", - description: "TorrentDay", - link: "https://torrentday.eu/", - caps: TorznabUtil.CreateDefaultTorznabTVCaps(), - manager: i, - client: wc, - logger: l, - p: ps, - configData: new ConfigurationDataRecaptchaLogin()) - { - - AddCategoryMapping(29, TorznabCatType.TVAnime); - AddCategoryMapping(28, TorznabCatType.PC); - AddCategoryMapping(28, TorznabCatType.AudioAudiobook); - AddCategoryMapping(20, TorznabCatType.Books); - AddCategoryMapping(30, TorznabCatType.TVDocumentary); - //Freelech - //Mac - - AddCategoryMapping(25, TorznabCatType.MoviesSD); - AddCategoryMapping(11, TorznabCatType.MoviesHD); - AddCategoryMapping(5, TorznabCatType.MoviesHD); - AddCategoryMapping(3, TorznabCatType.MoviesSD); - AddCategoryMapping(21, TorznabCatType.MoviesSD); - AddCategoryMapping(22, TorznabCatType.MoviesForeign); - // Movie packs - AddCategoryMapping(44, TorznabCatType.MoviesSD); - AddCategoryMapping(1, TorznabCatType.MoviesSD); - - // Music foreign - // Music packs - // Music videos - - AddCategoryMapping(4, TorznabCatType.PCGames); - // ps3 - // psp - // wii - // 360 - - AddCategoryMapping(24, TorznabCatType.TVSD); - AddCategoryMapping(32, TorznabCatType.TVHD); - AddCategoryMapping(31, TorznabCatType.TVSD); - AddCategoryMapping(33, TorznabCatType.TVSD); - AddCategoryMapping(14, TorznabCatType.TVHD); - AddCategoryMapping(26, TorznabCatType.TVSD); - AddCategoryMapping(7, TorznabCatType.TVHD); - AddCategoryMapping(2, TorznabCatType.TVSD); - - AddCategoryMapping(6, TorznabCatType.XXX); - AddCategoryMapping(15, TorznabCatType.XXX); - } - - public override async Task GetConfigurationForSetup() - { - var loginPage = await RequestStringWithCookies(StartPageUrl, string.Empty); - CQ cq = loginPage.Content; - var result = new ConfigurationDataRecaptchaLogin(); - result.CookieHeader.Value = loginPage.Cookies; - result.Captcha.SiteKey = cq.Find(".g-recaptcha").Attr("data-sitekey"); - return result; - } - - public async Task ApplyConfiguration(JToken configJson) - { - configData.LoadValuesFromJson(configJson); - var pairs = new Dictionary { - { "username", configData.Username.Value }, - { "password", configData.Password.Value }, - { "g-recaptcha-response", configData.Captcha.Value } - }; - - if (!string.IsNullOrWhiteSpace(configData.Captcha.Cookie)) - { - // Cookie was manually supplied - CookieHeader = configData.Captcha.Cookie; - try - { - var results = await PerformQuery(new TorznabQuery()); - if (results.Count() == 0) - { - throw new Exception("Your cookie did not work"); - } - - SaveConfig(); - IsConfigured = true; - } - catch (Exception e) - { - IsConfigured = false; - throw new Exception("Your cookie did not work: " + e.Message); - } - } - - var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, configData.CookieHeader.Value, true, SiteLink, LoginUrl); - await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () => - { - CQ dom = result.Content; - var messageEl = dom["#login"]; - messageEl.Children("form").Remove(); - var errorMessage = messageEl.Text().Trim(); - - if (string.IsNullOrWhiteSpace(errorMessage)) - { - errorMessage = dom.Text(); - } - - throw new ExceptionWithConfigData(errorMessage, configData); - }); - return IndexerConfigurationStatus.RequiresTesting; - } - - public async Task> PerformQuery(TorznabQuery query) - { - var releases = new List(); - var searchString = query.GetQueryString(); - var queryUrl = SearchUrl; - var queryCollection = new NameValueCollection(); - - if (!string.IsNullOrWhiteSpace(searchString)) - queryCollection.Add("search", searchString); - - foreach (var cat in MapTorznabCapsToTrackers(query)) - queryCollection.Add("c" + cat, "1"); - - if (queryCollection.Count > 0) - queryUrl += "?" + queryCollection.GetQueryString(); - - var results = await RequestStringWithCookiesAndRetry(queryUrl); - - // Check for being logged out - if (results.IsRedirect) - throw new AuthenticationException(); - - try - { - CQ dom = results.Content; - 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(SiteLink + qRow.Find(".torrentName").Attr("href")); - release.Comments = release.Guid; - release.Link = new Uri(SiteLink + qRow.Find(".dlLinksInfo > a").Attr("href")); - - var sizeStr = qRow.Find(".sizeInfo").Text(); - release.Size = ReleaseInfo.GetBytes(sizeStr); - - var dateStr = qRow.Find(".ulInfo").Text().Split('|').Last().Trim(); - var agoIdx = dateStr.IndexOf("ago"); - if (agoIdx > -1) - { - dateStr = dateStr.Substring(0, agoIdx); - } - release.PublishDate = DateTimeUtil.FromTimeAgo(dateStr); - - release.Seeders = ParseUtil.CoerceInt(qRow.Find(".seedersInfo").Text()); - release.Peers = ParseUtil.CoerceInt(qRow.Find(".leechersInfo").Text()) + release.Seeders; - - var cat = qRow.Find("td:eq(0) a").First().Attr("href").Substring(15);//browse.php?cat=24 - release.Category = MapTrackerCatToNewznab(cat); - - releases.Add(release); - } - } - catch (Exception ex) - { - OnParseError(results.Content, ex); - } - return releases; - } - } -} +using CsQuery; +using Jackett.Models; +using Jackett.Services; +using Jackett.Utils; +using Jackett.Utils.Clients; +using Newtonsoft.Json.Linq; +using NLog; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Jackett.Models.IndexerConfig; +using System.Collections.Specialized; + +namespace Jackett.Indexers +{ + public class TorrentDay : BaseIndexer, IIndexer + { + private string StartPageUrl { get { return SiteLink + "login.php"; } } + private string LoginUrl { get { return SiteLink + "tak3login.php"; } } + private string SearchUrl { get { return SiteLink + "browse.php"; } } + + new ConfigurationDataRecaptchaLogin configData + { + get { return (ConfigurationDataRecaptchaLogin)base.configData; } + set { base.configData = value; } + } + + public TorrentDay(IIndexerManagerService i, Logger l, IWebClient wc, IProtectionService ps) + : base(name: "TorrentDay", + description: "TorrentDay", + link: "https://torrentday.eu/", + caps: TorznabUtil.CreateDefaultTorznabTVCaps(), + manager: i, + client: wc, + logger: l, + p: ps, + configData: new ConfigurationDataRecaptchaLogin()) + { + + AddCategoryMapping(29, TorznabCatType.TVAnime); + AddCategoryMapping(28, TorznabCatType.PC); + AddCategoryMapping(28, TorznabCatType.AudioAudiobook); + AddCategoryMapping(20, TorznabCatType.Books); + AddCategoryMapping(30, TorznabCatType.TVDocumentary); + //Freelech + //Mac + + AddCategoryMapping(25, TorznabCatType.MoviesSD); + AddCategoryMapping(11, TorznabCatType.MoviesHD); + AddCategoryMapping(5, TorznabCatType.MoviesHD); + AddCategoryMapping(3, TorznabCatType.MoviesSD); + AddCategoryMapping(21, TorznabCatType.MoviesSD); + AddCategoryMapping(22, TorznabCatType.MoviesForeign); + // Movie packs + AddCategoryMapping(44, TorznabCatType.MoviesSD); + AddCategoryMapping(1, TorznabCatType.MoviesSD); + + // Music foreign + // Music packs + // Music videos + + AddCategoryMapping(4, TorznabCatType.PCGames); + // ps3 + // psp + // wii + // 360 + + AddCategoryMapping(24, TorznabCatType.TVSD); + AddCategoryMapping(32, TorznabCatType.TVHD); + AddCategoryMapping(31, TorznabCatType.TVSD); + AddCategoryMapping(33, TorznabCatType.TVSD); + AddCategoryMapping(14, TorznabCatType.TVHD); + AddCategoryMapping(26, TorznabCatType.TVSD); + AddCategoryMapping(7, TorznabCatType.TVHD); + AddCategoryMapping(2, TorznabCatType.TVSD); + + AddCategoryMapping(6, TorznabCatType.XXX); + AddCategoryMapping(15, TorznabCatType.XXX); + } + + public override async Task GetConfigurationForSetup() + { + var loginPage = await RequestStringWithCookies(StartPageUrl, string.Empty); + CQ cq = loginPage.Content; + var result = new ConfigurationDataRecaptchaLogin(); + result.CookieHeader.Value = loginPage.Cookies; + result.Captcha.SiteKey = cq.Find(".g-recaptcha").Attr("data-sitekey"); + return result; + } + + public async Task ApplyConfiguration(JToken configJson) + { + configData.LoadValuesFromJson(configJson); + var pairs = new Dictionary { + { "username", configData.Username.Value }, + { "password", configData.Password.Value }, + { "g-recaptcha-response", configData.Captcha.Value } + }; + + if (!string.IsNullOrWhiteSpace(configData.Captcha.Cookie)) + { + // Cookie was manually supplied + CookieHeader = configData.Captcha.Cookie; + try + { + var results = await PerformQuery(new TorznabQuery()); + if (results.Count() == 0) + { + throw new Exception("Your cookie did not work"); + } + + SaveConfig(); + IsConfigured = true; + return IndexerConfigurationStatus.Completed; + } + catch (Exception e) + { + IsConfigured = false; + throw new Exception("Your cookie did not work: " + e.Message); + } + } + + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, configData.CookieHeader.Value, true, SiteLink, LoginUrl); + await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () => + { + CQ dom = result.Content; + var messageEl = dom["#login"]; + messageEl.Children("form").Remove(); + var errorMessage = messageEl.Text().Trim(); + + if (string.IsNullOrWhiteSpace(errorMessage)) + { + errorMessage = dom.Text(); + } + + throw new ExceptionWithConfigData(errorMessage, configData); + }); + return IndexerConfigurationStatus.RequiresTesting; + } + + public async Task> PerformQuery(TorznabQuery query) + { + var releases = new List(); + var searchString = query.GetQueryString(); + var queryUrl = SearchUrl; + var queryCollection = new NameValueCollection(); + + if (!string.IsNullOrWhiteSpace(searchString)) + queryCollection.Add("search", searchString); + + foreach (var cat in MapTorznabCapsToTrackers(query)) + queryCollection.Add("c" + cat, "1"); + + if (queryCollection.Count > 0) + queryUrl += "?" + queryCollection.GetQueryString(); + + var results = await RequestStringWithCookiesAndRetry(queryUrl); + + // Check for being logged out + if (results.IsRedirect) + throw new AuthenticationException(); + + try + { + CQ dom = results.Content; + 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(SiteLink + qRow.Find(".torrentName").Attr("href")); + release.Comments = release.Guid; + release.Link = new Uri(SiteLink + qRow.Find(".dlLinksInfo > a").Attr("href")); + + var sizeStr = qRow.Find(".sizeInfo").Text(); + release.Size = ReleaseInfo.GetBytes(sizeStr); + + var dateStr = qRow.Find(".ulInfo").Text().Split('|').Last().Trim(); + var agoIdx = dateStr.IndexOf("ago"); + if (agoIdx > -1) + { + dateStr = dateStr.Substring(0, agoIdx); + } + release.PublishDate = DateTimeUtil.FromTimeAgo(dateStr); + + release.Seeders = ParseUtil.CoerceInt(qRow.Find(".seedersInfo").Text()); + release.Peers = ParseUtil.CoerceInt(qRow.Find(".leechersInfo").Text()) + release.Seeders; + + var cat = qRow.Find("td:eq(0) a").First().Attr("href").Substring(15);//browse.php?cat=24 + release.Category = MapTrackerCatToNewznab(cat); + + releases.Add(release); + } + } + catch (Exception ex) + { + OnParseError(results.Content, ex); + } + return releases; + } + } +} From 372bd46c4700e0d5820e8df70709a5c98b056830 Mon Sep 17 00:00:00 2001 From: KZ Date: Fri, 4 Sep 2015 19:26:00 +0100 Subject: [PATCH 10/11] Remove shields from website as its slow! --- src/Website/Views/Home/Index.cshtml | 114 ++++++++++++++-------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/src/Website/Views/Home/Index.cshtml b/src/Website/Views/Home/Index.cshtml index 17e7def08..0e93e5864 100644 --- a/src/Website/Views/Home/Index.cshtml +++ b/src/Website/Views/Home/Index.cshtml @@ -1,58 +1,58 @@ -@{ - ViewBag.Title = "Home Page"; -} - -

What is Jackett?

-

- Jackett creates a Torznab (with nZEDb category numbering) and TorrentPotato API server on your machine. This enables supporting software to access your favorite torrent indexers in a similar fashion to rss but with added features such as searching. To see the list of the trackers we support see our Github page. -

- - -

We are on Github

-
-
-
-
- Follow development, report issues and contribute on our Github page. -
-

- -
-
-
- Github -
- -
-
-

Applications with support for Jackett

- -
-
- - Sonarr - -
-
- - Couchpotato - -
-
- - Mylar - -
+@{ + ViewBag.Title = "Home Page"; +} + +

What is Jackett?

+

+ Jackett creates a Torznab (with nZEDb category numbering) and TorrentPotato API server on your machine. This enables supporting software to access your favorite torrent indexers in a similar fashion to rss but with added features such as searching. To see the list of the trackers we support see our Github page. +

+ + +

We are on Github

+
+
+
+
+ Follow development, report issues and contribute on our Github page. +
+

+ +
+
+
+ Github +
+ +
+
+

Applications with support for Jackett

+ +
+
+ + Sonarr + +
+
+ + Couchpotato + +
+
+ + Mylar + +
\ No newline at end of file From f8469968414ec1ee2e2175971bc65b6ff473d698 Mon Sep 17 00:00:00 2001 From: KZ Date: Sun, 6 Sep 2015 22:52:02 +0100 Subject: [PATCH 11/11] Bump version 0.6.4 --- src/Jackett/Properties/AssemblyInfo.cs | 72 +++++++++++++------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/Jackett/Properties/AssemblyInfo.cs b/src/Jackett/Properties/AssemblyInfo.cs index 6e876de18..879a02a76 100644 --- a/src/Jackett/Properties/AssemblyInfo.cs +++ b/src/Jackett/Properties/AssemblyInfo.cs @@ -1,36 +1,36 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Jackett")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Jackett")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("5881fb69-3cb2-42b7-a744-2c1e537176bd")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.6.3.0")] -[assembly: AssemblyFileVersion("0.6.3.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Jackett")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Jackett")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5881fb69-3cb2-42b7-a744-2c1e537176bd")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.6.4.0")] +[assembly: AssemblyFileVersion("0.6.4.0")]