diff --git a/src/Jackett/Content/logos/rutor.png b/src/Jackett/Content/logos/rutor.png new file mode 100644 index 000000000..603114c3f Binary files /dev/null and b/src/Jackett/Content/logos/rutor.png differ diff --git a/src/Jackett/Controllers/TorznabController.cs b/src/Jackett/Controllers/TorznabController.cs index 621468a3a..515e62d44 100644 --- a/src/Jackett/Controllers/TorznabController.cs +++ b/src/Jackett/Controllers/TorznabController.cs @@ -89,7 +89,7 @@ namespace Jackett.Controllers } if (!string.IsNullOrWhiteSpace(torznabQuery.SanitizedSearchTerm)) { - logBuilder.AppendFormat(" for: {2} {3}", torznabQuery.SanitizedSearchTerm, torznabQuery.GetEpisodeSearchString()); + logBuilder.AppendFormat(" for: {0} {1}", torznabQuery.SanitizedSearchTerm, torznabQuery.GetEpisodeSearchString()); } logger.Info(logBuilder.ToString()); diff --git a/src/Jackett/Indexers/BaseIndexer.cs b/src/Jackett/Indexers/BaseIndexer.cs index ede51bec1..7c951004e 100644 --- a/src/Jackett/Indexers/BaseIndexer.cs +++ b/src/Jackett/Indexers/BaseIndexer.cs @@ -89,7 +89,7 @@ namespace Jackett.Indexers IsConfigured = false; } - protected void SaveConfig() + protected virtual void SaveConfig() { indexerService.SaveConfig(this as IIndexer, configData.ToJson(forDisplay: false)); } diff --git a/src/Jackett/Indexers/RUTor.cs b/src/Jackett/Indexers/RUTor.cs new file mode 100644 index 000000000..e43cd2248 --- /dev/null +++ b/src/Jackett/Indexers/RUTor.cs @@ -0,0 +1,225 @@ +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.Utils; +using System.Net; +using System.Net.Http; +using CsQuery; +using System.Web; +using Jackett.Services; +using Jackett.Utils.Clients; +using System.Text.RegularExpressions; +using Jackett.Models.IndexerConfig; +using System.Globalization; +using Newtonsoft.Json; + +namespace Jackett.Indexers +{ + public class RuTor : BaseIndexer, IIndexer + { + private string SearchUrl { get { return SiteLink + "search/0/{0}/000/0/{1}"; } } + private string BrowseUrl { get { return SiteLink + "browse/0/{0}/0/0"; } } + readonly static string defaultSiteLink = "http://rutor.org/"; + + new ConfigurationDataRuTor configData + { + get { return (ConfigurationDataRuTor)base.configData; } + set { base.configData = value; } + } + + public RuTor(IIndexerManagerService i, Logger l, IWebClient wc) + : base(name: "RUTor", + description: "Свободный торрент трекер", + link: "http://rutor.org/", + caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(), + manager: i, + client: wc, + logger: l, + configData: new ConfigurationDataRuTor(defaultSiteLink)) + { + TorznabCaps.Categories.Add(TorznabCatType.Anime); + TorznabCaps.Categories.Add(TorznabCatType.Movies); + TorznabCaps.Categories.Add(TorznabCatType.Audio); + TorznabCaps.Categories.Add(TorznabCatType.Books); + } + + public async Task ApplyConfiguration(JToken configJson) + { + configData.LoadValuesFromJson(configJson); + var oldConfig = configData; + var releases = await PerformQuery(new TorznabQuery()); + + await ConfigureIfOK(string.Empty, releases.Count() > 0, () => + { + configData = oldConfig; + throw new Exception("Could not find releases from this URL"); + }); + } + + + protected override void SaveConfig() + { + indexerService.SaveConfig(this as IIndexer, JsonConvert.SerializeObject(configData)); + } + + // Override to load legacy config format + public override void LoadFromSavedConfiguration(JToken jsonConfig) + { + var json = jsonConfig.ToString(); + configData = JsonConvert.DeserializeObject(json); + IsConfigured = true; + } + + private readonly int CAT_ANY = 0; + private readonly int CAT_FOREIGN_MOVIE = 1; + // private readonly int CAT_OUR_MOVIES = 5; + // private readonly int CAT_POP_SCIFI_MOVIES = 12; + private readonly int CAT_TV_SERIES = 4; + // private readonly int CAT_TV = 6; + // private readonly int CAT_ANIMATION = 7; + private readonly int CAT_ANIME = 10; + private readonly int CAT_MUSIC = 2; + // private readonly int CAT_GAMES = 8; + // private readonly int CAT_SOFTWARE = 9; + // private readonly int CAT_SPORTS_HEALTH = 13; + // private readonly int CAT_HUMOR = 15; + // private readonly int CAT_ECONOMY_LIFE = 14; + private readonly int CAT_BOOKS = 11; + // private readonly int CAT_OTHER = 3; + + public async Task> PerformQuery(TorznabQuery query) + { + var releases = new List(); + var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); + var searchCategory = CAT_ANY; + + if(query.Categories.Contains(TorznabCatType.TV.ID) || + query.Categories.Contains(TorznabCatType.TVHD.ID)|| + query.Categories.Contains(TorznabCatType.TVSD.ID)){ + searchCategory = CAT_TV_SERIES; + } + + if ((searchCategory == CAT_ANY) && + (query.Categories.Contains(TorznabCatType.Movies.ID) || + query.Categories.Contains(TorznabCatType.MoviesForeign.ID) || + query.Categories.Contains(TorznabCatType.MoviesHD.ID) || + query.Categories.Contains(TorznabCatType.MoviesSD.ID))) + { + searchCategory = CAT_FOREIGN_MOVIE; + } + + if ((searchCategory == CAT_ANY) && + (query.Categories.Contains(TorznabCatType.Anime.ID))) + { + searchCategory = CAT_ANIME; + } + + if ((searchCategory == CAT_ANY) && + (query.Categories.Contains(TorznabCatType.Books.ID))) + { + searchCategory = CAT_BOOKS; + } + + if ((searchCategory == CAT_ANY) && + (query.Categories.Contains(TorznabCatType.Audio.ID) || + query.Categories.Contains(TorznabCatType.AudioLossless.ID) || + query.Categories.Contains(TorznabCatType.AudioLossy.ID))) + { + searchCategory = CAT_MUSIC; + } + + string queryUrl = string.Empty; + if (string.IsNullOrWhiteSpace(searchString)) + { + queryUrl = string.Format(BrowseUrl, searchCategory); + } else + { + queryUrl = string.Format(SearchUrl, searchCategory, HttpUtility.UrlEncode(searchString.Trim())); + } + + var results = await RequestStringWithCookiesAndRetry(queryUrl, string.Empty); + try + { + CQ dom = results.Content; + var rows = dom["#index table tr"]; + foreach (var row in rows.Skip(1)) + { + var release = new ReleaseInfo(); + + release.MinimumRatio = 1; + release.MinimumSeedTime = 172800; + + var date = StringUtil.StripNonAlphaNumeric(row.Cq().Find("td:eq(0)").Text().Trim() + .Replace("Янв", "01") + .Replace("Фев", "02") + .Replace("Мар", "03") + .Replace("Апр", "04") + .Replace("Май", "05") + .Replace("Июн", "06") + .Replace("Июл", "07") + .Replace("Авг", "08") + .Replace("Сен", "09") + .Replace("Окт", "10") + .Replace("Ноя", "11") + .Replace("Дек", "12")); + + release.PublishDate = DateTime.ParseExact(date, "ddMMyy", CultureInfo.InvariantCulture); + + release.Title = row.Cq().Find("td:eq(1)").Text().Trim(); + + if (configData.StripRussian.Value) + { + var split = release.Title.IndexOf('/'); + if (split > -1) + { + release.Title = release.Title.Substring(split + 1).Trim(); + } + } + + release.Description = release.Title; + + var hasComments = row.Cq().Find("td:eq(2) img").Length > 0; + var sizeCol = 2; + + if (hasComments) + sizeCol++; + + var sizeStr = StringUtil.StripRegex(row.Cq().Find("td:eq("+ sizeCol + ")").Text(), "[^a-zA-Z0-9\\. -]", " ").Trim(); + string[] sizeSplit = sizeStr.Split(' '); + release.Size = ReleaseInfo.GetBytes(sizeSplit[1].ToLower(), ParseUtil.CoerceFloat(sizeSplit[0])); + + release.Seeders = ParseUtil.CoerceInt(row.Cq().Find(".green").Text().Trim()); + release.Peers = ParseUtil.CoerceInt(row.Cq().Find(".red").Text().Trim()) + release.Seeders; + + release.Guid = new Uri(configData.Url.Value + row.Cq().Find("td:eq(1) a:eq(1)").Attr("href").Substring(1)); + release.Comments = release.Guid; + + var hasTorrent = row.Cq().Find("td:eq(1) a").Length == 3; + + if (hasTorrent) + { + release.Link = new Uri(row.Cq().Find("td:eq(1) a:eq(0)").Attr("href")); + release.MagnetUri = new Uri(row.Cq().Find("td:eq(1) a:eq(1)").Attr("href")); + } + else + { + release.MagnetUri = new Uri(row.Cq().Find("td:eq(1) a:eq(0)").Attr("href")); + } + + 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 83ae8fbeb..ad391b285 100644 --- a/src/Jackett/Jackett.csproj +++ b/src/Jackett/Jackett.csproj @@ -190,6 +190,7 @@ + @@ -213,6 +214,7 @@ + @@ -241,7 +243,7 @@ - + @@ -418,6 +420,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/src/Jackett/Models/IndexerConfig/ConfigurationDataRuTor.cs b/src/Jackett/Models/IndexerConfig/ConfigurationDataRuTor.cs new file mode 100644 index 000000000..49c1024ea --- /dev/null +++ b/src/Jackett/Models/IndexerConfig/ConfigurationDataRuTor.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Models.IndexerConfig +{ + public class ConfigurationDataRuTor : ConfigurationData + { + [JsonProperty] + public StringItem Url { get; private set; } + [JsonProperty] + public BoolItem StripRussian { get; private set; } + + public ConfigurationDataRuTor() + { + } + + public ConfigurationDataRuTor(string defaultUrl) + { + Url = new StringItem { Name = "Url", Value = defaultUrl }; + StripRussian = new BoolItem() { Name = "StripRusNamePrefix", Value = true }; + } + } +} diff --git a/src/Jackett/Utils/StringUtil.cs b/src/Jackett/Utils/StringUtil.cs index a8d045e35..ad024effa 100644 --- a/src/Jackett/Utils/StringUtil.cs +++ b/src/Jackett/Utils/StringUtil.cs @@ -13,10 +13,15 @@ namespace Jackett.Utils { public static class StringUtil { - public static string StripNonAlphaNumeric(string str) + public static string StripNonAlphaNumeric(string str, string replacement = "") { - Regex rgx = new Regex("[^a-zA-Z0-9 -]"); - str = rgx.Replace(str, ""); + return StripRegex(str, "[^a-zA-Z0-9 -]", replacement); + } + + public static string StripRegex(string str, string regex, string replacement = "") + { + Regex rgx = new Regex(regex); + str = rgx.Replace(str, replacement); return str; }