diff --git a/.gitignore b/.gitignore index 57a1574c4..eb5a9e709 100644 --- a/.gitignore +++ b/.gitignore @@ -194,3 +194,5 @@ FakesAssemblies/ # Visual Studio 6 workspace options file *.opt +/Build.mono +/Build.windows diff --git a/src/Jackett/Content/logos/torrentbytes.png b/src/Jackett/Content/logos/torrentbytes.png new file mode 100644 index 000000000..245c86aea Binary files /dev/null and b/src/Jackett/Content/logos/torrentbytes.png differ diff --git a/src/Jackett/Controllers/APIController.cs b/src/Jackett/Controllers/APIController.cs index 064bdc133..836aad9bc 100644 --- a/src/Jackett/Controllers/APIController.cs +++ b/src/Jackett/Controllers/APIController.cs @@ -78,7 +78,7 @@ namespace Jackett.Controllers } else { - logger.Info(string.Format("Found {0} releases from {1} for: {2}", releases.Count(), indexer.DisplayName, torznabQuery.SanitizedSearchTerm)); + logger.Info(string.Format("Found {0} releases from {1} for: {2} {3}", releases.Count(), indexer.DisplayName, torznabQuery.SanitizedSearchTerm, torznabQuery.GetEpisodeSearchString())); } var severUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port); diff --git a/src/Jackett/Indexers/IPTorrents.cs b/src/Jackett/Indexers/IPTorrents.cs index 165fe845b..7e405ae06 100644 --- a/src/Jackett/Indexers/IPTorrents.cs +++ b/src/Jackett/Indexers/IPTorrents.cs @@ -128,7 +128,7 @@ namespace Jackett.Indexers if (queryCollection.Count > 0) { - searchUrl += "?" + string.Join("&", queryCollection.AllKeys.Select(a => a + "=" + HttpUtility.UrlEncode(queryCollection[a]))); + searchUrl += "?" + queryCollection.GetQueryString(); } var response = await RequestStringWithCookiesAndRetry(searchUrl, null, BrowseUrl); diff --git a/src/Jackett/Indexers/TorrentBytes.cs b/src/Jackett/Indexers/TorrentBytes.cs new file mode 100644 index 000000000..f9fc9c7ab --- /dev/null +++ b/src/Jackett/Indexers/TorrentBytes.cs @@ -0,0 +1,179 @@ +using CsQuery; +using Jackett.Models; +using Jackett.Services; +using Jackett.Utils; +using Jackett.Utils.Clients; +using Newtonsoft.Json.Linq; +using NLog; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Web; + +namespace Jackett.Indexers +{ + public class TorrentBytes : BaseIndexer, IIndexer + { + private string BrowseUrl { get { return SiteLink + "browse.php"; } } + private string LoginUrl { get { return SiteLink + "takelogin.php"; } } + + public TorrentBytes(IIndexerManagerService i, IWebClient wc, Logger l) + : base(name: "TorrentBytes", + description: "A decade of torrentbytes", + link: "https://www.torrentbytes.net/", + caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), + manager: i, + client: wc, + logger: l) + { + + AddCategoryMapping(41, TorznabCatType.TV); + AddCategoryMapping(33, TorznabCatType.TVSD); + AddCategoryMapping(38, TorznabCatType.TVHD); + AddCategoryMapping(32, TorznabCatType.TVSD); + AddCategoryMapping(37, TorznabCatType.TVSD); + AddCategoryMapping(44, TorznabCatType.TVSD); + + AddCategoryMapping(40, TorznabCatType.Movies); + AddCategoryMapping(19, TorznabCatType.MoviesSD); + AddCategoryMapping(5, TorznabCatType.MoviesHD); + AddCategoryMapping(20, TorznabCatType.MoviesSD); + AddCategoryMapping(28, TorznabCatType.MoviesForeign); + AddCategoryMapping(45, TorznabCatType.MoviesSD); + + AddCategoryMapping(43, TorznabCatType.Audio); + AddCategoryMapping(48, TorznabCatType.AudioLossless); + AddCategoryMapping(6, TorznabCatType.AudioLossy); + AddCategoryMapping(46, TorznabCatType.Movies); + + AddCategoryMapping(1, TorznabCatType.Apps); + AddCategoryMapping(2, TorznabCatType.Apps); + AddCategoryMapping(23, TorznabCatType.Anime); + AddCategoryMapping(21, TorznabCatType.XXX); + AddCategoryMapping(9, TorznabCatType.XXXSD); + AddCategoryMapping(39, TorznabCatType.XXXHD); + AddCategoryMapping(29, TorznabCatType.XXXSD); + AddCategoryMapping(24, TorznabCatType.XXXImg); + } + + public Task GetConfigurationForSetup() + { + return Task.FromResult(new ConfigurationDataBasicLogin()); + } + + public async Task ApplyConfiguration(JToken configJson) + { + var incomingConfig = new ConfigurationDataBasicLogin(); + incomingConfig.LoadValuesFromJson(configJson); + var pairs = new Dictionary { + { "username", incomingConfig.Username.Value }, + { "password", incomingConfig.Password.Value }, + { "returnto", "/" }, + { "login", "Log in!" } + }; + + var loginPage = await RequestStringWithCookies(SiteLink, string.Empty); + + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, SiteLink, SiteLink); + ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () => + { + CQ dom = result.Content; + var messageEl = dom["body > div"].First(); + var errorMessage = messageEl.Text().Trim(); + throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig); + }); + } + + public async Task> PerformQuery(TorznabQuery query) + { + var releases = new List(); + var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); + var searchUrl = BrowseUrl; + var trackerCats = MapTorznabCapsToTrackers(query); + var queryCollection = new NameValueCollection(); + + // Tracker can only search OR return things in categories + if (!string.IsNullOrWhiteSpace(searchString)) + { + queryCollection.Add("search", searchString); + queryCollection.Add("cat", "0"); + queryCollection.Add("sc", "1"); + } + else + { + foreach (var cat in MapTorznabCapsToTrackers(query)) + { + queryCollection.Add("c" + cat, "1"); + } + + queryCollection.Add("incldead", "0"); + } + + searchUrl += "?" + queryCollection.GetQueryString(); + + // 15 results per page - really don't want to call the server twice but only 15 results per page is a bit crap! + await ProcessPage(releases, searchUrl); + await ProcessPage(releases, searchUrl + "&page=1"); + + return releases; + } + + private async Task ProcessPage(List releases, string searchUrl) + { + + var response = await RequestStringWithCookiesAndRetry(searchUrl, null, BrowseUrl); + + var results = response.Content; + try + { + CQ dom = results; + + var rows = dom["#content table:eq(4) tr"]; + foreach (var row in rows.Skip(1)) + { + var release = new ReleaseInfo(); + + var link = row.Cq().Find("td:eq(1) a:eq(1)").First(); + release.Guid = new Uri(SiteLink + link.Attr("href")); + release.Comments = release.Guid; + release.Title = link.Text().Trim(); + release.Description = release.Title; + + // If we search an get no results, we still get a table just with no info. + if (string.IsNullOrWhiteSpace(release.Title)) + { + break; + } + + var cat = row.Cq().Find("td:eq(0) a").First().Attr("href").Substring(15); + release.Category = MapTrackerCatToNewznab(cat); + + + var qLink = row.Cq().Find("td:eq(1) a").First(); + release.Link = new Uri(SiteLink + qLink.Attr("href")); + + var added = row.Cq().Find("td:eq(4)").First().Text().Trim(); + release.PublishDate = DateTimeUtil.FromTimeAgo(added); + + var sizeStr = row.Cq().Find("td:eq(6)").First().Text().Trim(); + release.Size = ReleaseInfo.GetBytes(sizeStr); + + release.Seeders = ParseUtil.CoerceInt(row.Cq().Find("td:eq(8)").First().Text().Trim()); + release.Peers = ParseUtil.CoerceInt(row.Cq().Find("td:eq(9)").First().Text().Trim()) + release.Seeders; + + releases.Add(release); + } + } + catch (Exception ex) + { + OnParseError(results, ex); + } + } + } +} diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj index 6af3a040a..21bc6aeac 100644 --- a/src/Jackett/Jackett.csproj +++ b/src/Jackett/Jackett.csproj @@ -186,6 +186,7 @@ + @@ -406,6 +407,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/src/Jackett/Models/TorznabCatType.cs b/src/Jackett/Models/TorznabCatType.cs index c52c63cb7..f78fb3995 100644 --- a/src/Jackett/Models/TorznabCatType.cs +++ b/src/Jackett/Models/TorznabCatType.cs @@ -27,6 +27,10 @@ namespace Jackett.Models cats.Add(3000, "Audio"); cats.Add(3040, "Audio/Lossless"); cats.Add(3010, "Audio/MP3"); + cats.Add(6000, "XXX"); + cats.Add(6040, "XXX/x264"); + cats.Add(6010, "XXX/DVD"); + cats.Add(6060, "XXX/Imageset"); } public static bool QueryContainsParentCategory(int[] queryCats, int releaseCat) @@ -142,5 +146,25 @@ namespace Jackett.Models { get { return GetCat(3010); } } + + public static TorznabCategory XXX + { + get { return GetCat(6000); } + } + + public static TorznabCategory XXXHD + { + get { return GetCat(6040); } + } + + public static TorznabCategory XXXSD + { + get { return GetCat(6010); } + } + + public static TorznabCategory XXXImg + { + get { return GetCat(6060); } + } } } diff --git a/src/Jackett/Utils/StringUtil.cs b/src/Jackett/Utils/StringUtil.cs index 3b7d4b861..6fa5864e4 100644 --- a/src/Jackett/Utils/StringUtil.cs +++ b/src/Jackett/Utils/StringUtil.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Web; namespace Jackett.Utils { @@ -56,5 +58,9 @@ namespace Jackett.Utils return String.Join("\n", fields); } + public static string GetQueryString(this NameValueCollection collection) + { + return string.Join("&", collection.AllKeys.Select(a => a + "=" + HttpUtility.UrlEncode(collection[a]))); + } } }