diff --git a/README.md b/README.md index f955e9e44..83ba1a42a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This project is a new fork and is recruiting development help. If you are able to help out please contact us. -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. +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. Developer note: The software implements the [Torznab](https://github.com/Sonarr/Sonarr/wiki/Implementing-a-Torznab-indexer) (with [nZEDb](https://github.com/nZEDb/nZEDb/blob/dev/docs/newznab_api_specification.txt) category numbering) and [TorrentPotato](https://github.com/RuudBurger/CouchPotatoServer/wiki/Couchpotato-torrent-provider) APIs. @@ -18,6 +18,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/ * AlphaRatio * Andraste * AnimeBytes + * AnimeTorrents * Avistaz * BakaBT * bB @@ -90,7 +91,7 @@ Jackett can also be run from the command line using JackettConsole.exe if you wo * 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 `Jackett.Binaries.Mono.tar.gz` release from the [releases page](https://github.com/Jackett/Jackett/releases) and run Jackett using mono with the command `mono JackettConsole.exe`. - + Detailed instructions for [Ubuntu 14.x](http://www.htpcguides.com/install-jackett-on-ubuntu-14-x-for-custom-torrents-in-sonarr/) and [Ubuntu 15.x](http://www.htpcguides.com/install-jackett-ubuntu-15-x-for-custom-torrents-in-sonarr/) #### Installation on Synology diff --git a/src/Jackett/Content/logos/animetorrents.png b/src/Jackett/Content/logos/animetorrents.png new file mode 100644 index 000000000..24c836e4b Binary files /dev/null and b/src/Jackett/Content/logos/animetorrents.png differ diff --git a/src/Jackett/Indexers/AnimeTorrents.cs b/src/Jackett/Indexers/AnimeTorrents.cs new file mode 100644 index 000000000..120d58ee3 --- /dev/null +++ b/src/Jackett/Indexers/AnimeTorrents.cs @@ -0,0 +1,176 @@ +using CsQuery; +using Jackett.Models; +using Jackett.Services; +using Jackett.Utils; +using Jackett.Utils.Clients; +using Newtonsoft.Json.Linq; +using NLog; +using System; +using System.Collections.Generic; +using System.IO; +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; +using System.Globalization; + +namespace Jackett.Indexers +{ + public class AnimeTorrents : BaseIndexer, IIndexer + { + private string LoginUrl { get { return SiteLink + "login.php"; } } + private string SearchUrl { get { return SiteLink + "ajax/torrents_data.php"; } } + private string SearchUrlReferer { get { return SiteLink + "torrents.php?cat=0&searchin=filename&search="; } } + + new ConfigurationDataBasicLogin configData + { + get { return (ConfigurationDataBasicLogin)base.configData; } + set { base.configData = value; } + } + + public AnimeTorrents(IIndexerManagerService i, HttpWebClient c, Logger l, IProtectionService ps) + : base(name: "AnimeTorrents", + description: "Definitive source for anime and manga", + link: "http://animetorrents.me/", + caps: new TorznabCapabilities(), + manager: i, + client: c, // Forced HTTP client for custom headers + logger: l, + p: ps, + configData: new ConfigurationDataBasicLogin()) + { + AddCategoryMapping(1, TorznabCatType.MoviesSD); // Anime Movie + AddCategoryMapping(6, TorznabCatType.MoviesHD); // Anime Movie HD + AddCategoryMapping(2, TorznabCatType.TVAnime); // Anime Series + AddCategoryMapping(7, TorznabCatType.TVAnime); // Anime Series HD + AddCategoryMapping(5, TorznabCatType.XXXDVD); // Hentai (censored) + AddCategoryMapping(9, TorznabCatType.XXXDVD); // Hentai (censored) HD + AddCategoryMapping(4, TorznabCatType.XXXDVD); // Hentai (un-censored) + AddCategoryMapping(8, TorznabCatType.XXXDVD); // Hentai (un-censored) HD + AddCategoryMapping(13, TorznabCatType.BooksForeign); // Light Novel + AddCategoryMapping(3, TorznabCatType.BooksComics); // Manga + AddCategoryMapping(10, TorznabCatType.BooksComics); // Manga 18+ + AddCategoryMapping(11, TorznabCatType.TVAnime); // OVA + AddCategoryMapping(12, TorznabCatType.TVAnime); // OVA HD + AddCategoryMapping(14, TorznabCatType.BooksComics); // Doujin Anime + AddCategoryMapping(15, TorznabCatType.XXXDVD); // Doujin Anime 18+ + AddCategoryMapping(16, TorznabCatType.AudioForeign); // Doujin Music + AddCategoryMapping(17, TorznabCatType.BooksComics); // Doujinshi + AddCategoryMapping(18, TorznabCatType.BooksComics); // Doujinshi 18+ + AddCategoryMapping(19, TorznabCatType.Audio); // OST + } + + public async Task 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); + 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/Jackett.csproj b/src/Jackett/Jackett.csproj index cbdcdcf4f..5a9436492 100644 --- a/src/Jackett/Jackett.csproj +++ b/src/Jackett/Jackett.csproj @@ -164,6 +164,7 @@ + diff --git a/src/Jackett/Utils/Clients/HttpWebClient.cs b/src/Jackett/Utils/Clients/HttpWebClient.cs index c3b4a2124..3de1db66b 100644 --- a/src/Jackett/Utils/Clients/HttpWebClient.cs +++ b/src/Jackett/Utils/Clients/HttpWebClient.cs @@ -183,7 +183,7 @@ namespace Jackett.Utils.Clients var nameSplit = value.IndexOf('='); if (nameSplit > -1) { - responseCookies.Add(new Tuple(value.Substring(0, nameSplit), value.Substring(0, value.IndexOf(';') + 1))); + responseCookies.Add(new Tuple(value.Substring(0, nameSplit), value.Substring(0, value.IndexOf(';') == -1 ? value.Length : (value.IndexOf(';') + 1)))); } }