diff --git a/README.md b/README.md index 01de7cdb1..88a8f370c 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/ * TransmitheNet * TV Chaos UK * World-In-HD + * x264 * XSpeeds * Xthor diff --git a/src/Jackett/Content/custom.js b/src/Jackett/Content/custom.js index 1c9cd6572..c18e7f5e8 100644 --- a/src/Jackett/Content/custom.js +++ b/src/Jackett/Content/custom.js @@ -200,9 +200,35 @@ function populateConfigItems(configForm, config) { var template = setupItemTemplate(item); $formItemContainer.append(template); if (item.type === 'recaptcha') { - grecaptcha.render($('.jackettrecaptcha')[0], { - 'sitekey': item.sitekey - }); + var jackettrecaptcha = $('.jackettrecaptcha'); + jackettrecaptcha.data("version", item.version); + switch (item.version) { + case "1": + // The v1 reCAPTCHA code uses document.write() calls to write the CAPTCHA to the location where the script was loaded. + // As it's loaded async this doesn't work. + // We use an iframe to work around this problem. + var html = ''; + var frame = document.createElement('iframe'); + frame.id = "jackettrecaptchaiframe"; + frame.style.height = "145px"; + frame.style.weight = "326px"; + frame.style.border = "none"; + frame.onload = function () { + // auto resize iframe to content + frame.style.height = frame.contentWindow.document.body.scrollHeight + 'px'; + frame.style.width = frame.contentWindow.document.body.scrollWidth + 'px'; + } + jackettrecaptcha.append(frame); + frame.contentDocument.open(); + frame.contentDocument.write(html); + frame.contentDocument.close(); + break; + case "2": + grecaptcha.render(jackettrecaptcha[0], { + 'sitekey': item.sitekey + }); + break; + } } } } @@ -235,7 +261,18 @@ function getConfigModalJson(configForm) { break; case "recaptcha": if (window.jackettIsLocal) { - itemEntry.value = $('.g-recaptcha-response').val(); + var version = $el.find('.jackettrecaptcha').data("version"); + switch (version) { + case "1": + var frameDoc = $("#jackettrecaptchaiframe")[0].contentDocument; + itemEntry.version = version; + itemEntry.challenge = $("#recaptcha_challenge_field", frameDoc).val() + itemEntry.value = $("#recaptcha_response_field", frameDoc).val() + break; + case "2": + itemEntry.value = $('.g-recaptcha-response').val(); + break; + } } else { itemEntry.cookie = $el.find(".setup-item-recaptcha input").val(); } diff --git a/src/Jackett/Content/logos/x264.png b/src/Jackett/Content/logos/x264.png new file mode 100644 index 000000000..c48002c90 Binary files /dev/null and b/src/Jackett/Content/logos/x264.png differ diff --git a/src/Jackett/Indexers/x264.cs b/src/Jackett/Indexers/x264.cs new file mode 100644 index 000000000..584a42e22 --- /dev/null +++ b/src/Jackett/Indexers/x264.cs @@ -0,0 +1,180 @@ +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.Linq; +using System.Threading.Tasks; +using Jackett.Models.IndexerConfig; +using System.Collections.Specialized; + +namespace Jackett.Indexers +{ + public class x264 : BaseIndexer, IIndexer + { + private string SearchUrl { get { return SiteLink + "browse.php"; } } + private string LoginUrl { get { return SiteLink + "login.php"; } } + private string SubmitLoginUrl { get { return SiteLink + "takelogin.php"; } } + + new ConfigurationDataRecaptchaLogin configData + { + get { return (ConfigurationDataRecaptchaLogin)base.configData; } + set { base.configData = value; } + } + + public x264(IIndexerManagerService i, Logger l, IWebClient w, IProtectionService ps) + : base(name: "x264", + description: "A movie/TV tracker", + link: "https://x264.me/", + caps: new TorznabCapabilities(), + manager: i, + client: w, + logger: l, + p: ps, + configData: new ConfigurationDataRecaptchaLogin()) + { + AddCategoryMapping(20, TorznabCatType.Movies); // Movies&TV/Sources + AddCategoryMapping(53, TorznabCatType.MoviesHD); // Movies/1080p + AddCategoryMapping(30, TorznabCatType.MoviesHD); // Movies/576p + AddCategoryMapping(50, TorznabCatType.MoviesHD); // Movies/720p + AddCategoryMapping(33, TorznabCatType.MoviesSD); // Movies/SD + AddCategoryMapping(54, TorznabCatType.TVHD); // TV/1080p + AddCategoryMapping(31, TorznabCatType.TVHD); // TV/576p + AddCategoryMapping(51, TorznabCatType.TVHD); // TV/720p + AddCategoryMapping(25, TorznabCatType.TVSD); // TV/SD + } + + public override async Task GetConfigurationForSetup() + { + var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty); + CQ dom = loginPage.Content; + CQ recaptchaScript = dom.Find("script").First(); + + string recaptchaSiteKey = recaptchaScript.Attr("src").Split('=')[1]; + var result = new ConfigurationDataRecaptchaLogin(); + result.CookieHeader.Value = loginPage.Cookies; + result.Captcha.SiteKey = recaptchaSiteKey; + result.Captcha.Version = "1"; + return result; + } + + public async Task ApplyConfiguration(JToken configJson) + { + configData.LoadValuesFromJson(configJson); + var pairs = new Dictionary { + { "username", configData.Username.Value }, + { "password", configData.Password.Value }, + { "recaptcha_challenge_field", configData.Captcha.Challenge }, + { "recaptcha_response_field", 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.Any()) + { + 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(SubmitLoginUrl, pairs, configData.CookieHeader.Value, true, null, LoginUrl); + await ConfigureIfOK(result.Cookies, result.Content.Contains("logout.php"), () => + { + var errorMessage = result.Content; + throw new ExceptionWithConfigData(errorMessage, configData); + }); + return IndexerConfigurationStatus.RequiresTesting; + } + + public async Task> PerformQuery(TorznabQuery query) + { + List releases = new List(); + + var searchString = query.GetQueryString(); + var searchUrl = SearchUrl; + var queryCollection = new NameValueCollection(); + queryCollection.Add("incldead", "1"); + queryCollection.Add("xtype", "0"); + queryCollection.Add("stype", "0"); + + if (!string.IsNullOrWhiteSpace(searchString)) + { + queryCollection.Add("search", searchString); + } + + foreach (var cat in MapTorznabCapsToTrackers(query)) + { + queryCollection.Add("c" + cat, "1"); + } + + searchUrl += "?" + queryCollection.GetQueryString(); + + var results = await RequestStringWithCookiesAndRetry(searchUrl); + try + { + CQ dom = results.Content; + var rows = dom["table > tbody > tr[height=36]"]; + foreach (var row in rows) + { + var release = new ReleaseInfo(); + release.MinimumRatio = 1; + release.MinimumSeedTime = 7 * 24 * 60 * 60; + + var qRow = row.Cq(); + var qCatLink = qRow.Find("a[href^=?cat]").First(); + var qDetailsLink = qRow.Find("a[href^=details.php]").First(); + var qSeeders = qRow.Find("td:eq(8)"); + var qLeechers = qRow.Find("td:eq(9)"); + var qDownloadLink = qRow.Find("a[href^=\"download.php\"]").First(); + var qImdbLink = qRow.Find("a[href^=/redir.php?url=http://www.imdb.com]"); + var qSize = qRow.Find("td:eq(6)"); + + var catStr = qCatLink.Attr("href").Split('=')[1]; + release.Category = MapTrackerCatToNewznab(catStr); + + release.Link = new Uri(SiteLink + qDownloadLink.Attr("href")); + release.Title = qDetailsLink.Text().Trim(); + release.Comments = new Uri(SiteLink + qDetailsLink.Attr("href")); + release.Guid = release.Link; + + var sizeStr = qSize.Text(); + release.Size = ReleaseInfo.GetBytes(sizeStr); + + if(qImdbLink.Length == 1) { + var ImdbId = qImdbLink.Attr("href").Split('/').Last().Substring(2); + release.Imdb = ParseUtil.CoerceLong(ImdbId); + } + + release.Seeders = ParseUtil.CoerceInt(qSeeders.Text()); + release.Peers = ParseUtil.CoerceInt(qLeechers.Text()) + release.Seeders; + + releases.Add(release); + } + } + catch (Exception ex) + { + OnParseError(results.Content, ex); + } + + return releases; + } + } +} diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj index 39a51f6f9..7a341ff80 100644 --- a/src/Jackett/Jackett.csproj +++ b/src/Jackett/Jackett.csproj @@ -163,6 +163,7 @@ + @@ -437,6 +438,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/src/Jackett/Models/IndexerConfig/ConfigurationData.cs b/src/Jackett/Models/IndexerConfig/ConfigurationData.cs index 134968542..ce3479ed7 100644 --- a/src/Jackett/Models/IndexerConfig/ConfigurationData.cs +++ b/src/Jackett/Models/IndexerConfig/ConfigurationData.cs @@ -73,6 +73,8 @@ namespace Jackett.Models.IndexerConfig case ItemType.Recaptcha: ((RecaptchaItem)item).Value = arrItem.Value("value"); ((RecaptchaItem)item).Cookie = arrItem.Value("cookie"); + ((RecaptchaItem)item).Version = arrItem.Value("version"); + ((RecaptchaItem)item).Challenge = arrItem.Value("challenge"); break; } } @@ -92,6 +94,7 @@ namespace Jackett.Models.IndexerConfig { case ItemType.Recaptcha: jObject["sitekey"] = ((RecaptchaItem)item).SiteKey; + jObject["version"] = ((RecaptchaItem)item).Version; break; case ItemType.InputString: case ItemType.HiddenData: @@ -177,8 +180,11 @@ namespace Jackett.Models.IndexerConfig public class RecaptchaItem : StringItem { + public string Version { get; set; } + public string Challenge { get; set; } public RecaptchaItem() { + this.Version = "2"; ItemType = ConfigurationData.ItemType.Recaptcha; } }