diff --git a/README.md b/README.md index b82168956cd5874fb90eae0c0da4a3c02365c732..cc30745d46ec61ff3a12bb5d1d00c38c4833095f 100644 --- a/README.md +++ b/README.md @@ -14,18 +14,20 @@ We were previously focused on TV but are working on extending searches to allow #### Supported Systems * Windows using .NET 4.5 -* Linux and OSX using Mono 4 +* 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/) @@ -36,6 +38,7 @@ We were previously focused on TV but are working on extending searches to allow * [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/) @@ -55,21 +58,19 @@ We were previously focused on TV but are working on extending searches to allow 2. Install libcurl: * Debian/Ubunutu: apt-get install libcurl-dev * Redhat/Fedora: yum install libcurl-devel - * Or see the [Curl docs](http://curl.haxx.se/dlwiz/?type=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). -#### Installation on Linux/OSX - -Run Jackett using mono with the command "mono JackettConsole.exe". - - #### Troubleshooting @@ -87,11 +88,11 @@ You can get additional logging with the switches "-t -l". Please post logs if y ### 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). +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 - + \ No newline at end of file diff --git a/src/Jackett/Content/logos/avistaz.png b/src/Jackett/Content/logos/avistaz.png new file mode 100644 index 0000000000000000000000000000000000000000..4944a926ef10f918dc52c4eaf7893c957a21769b Binary files /dev/null and b/src/Jackett/Content/logos/avistaz.png differ diff --git a/src/Jackett/Content/logos/eutorrents.png b/src/Jackett/Content/logos/eutorrents.png new file mode 100644 index 0000000000000000000000000000000000000000..3c3983696c5d78e289952627942c8ea96ac04b54 Binary files /dev/null and b/src/Jackett/Content/logos/eutorrents.png differ diff --git a/src/Jackett/Content/logos/rarbg.png b/src/Jackett/Content/logos/rarbg.png new file mode 100644 index 0000000000000000000000000000000000000000..da8bebbbc75833e41f4835d2262de87c6d7b7f3e Binary files /dev/null and b/src/Jackett/Content/logos/rarbg.png differ diff --git a/src/Jackett/Controllers/AdminController.cs b/src/Jackett/Controllers/AdminController.cs index 47d49fa868d0a9ef276a36089fc10ba61d95b66e..3a091f2abf7a2344d49db72327a1f2b85e244b9c 100644 --- a/src/Jackett/Controllers/AdminController.cs +++ b/src/Jackett/Controllers/AdminController.cs @@ -67,7 +67,7 @@ namespace Jackett.Controllers result.Content = new StreamContent(stream); result.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeMapping.GetMimeMapping(mappedPath)); - + return result; } @@ -86,7 +86,7 @@ namespace Jackett.Controllers [AllowAnonymous] public async Task<HttpResponseMessage> Dashboard() { - if(Request.RequestUri.Query!=null && Request.RequestUri.Query.Contains("logout")) + if (Request.RequestUri.Query != null && Request.RequestUri.Query.Contains("logout")) { var file = GetFile("login.html"); securityService.Logout(file); @@ -98,16 +98,18 @@ namespace Jackett.Controllers { return GetFile("index.html"); - } else + } + else { var formData = await Request.Content.ReadAsFormDataAsync(); - - if (formData!=null && securityService.HashPassword(formData["password"]) == serverService.Config.AdminPassword) + + if (formData != null && securityService.HashPassword(formData["password"]) == serverService.Config.AdminPassword) { var file = GetFile("index.html"); securityService.Login(file); return file; - } else + } + else { return GetFile("login.html"); } @@ -180,8 +182,15 @@ namespace Jackett.Controllers string indexerString = (string)postData["indexer"]; indexer = indexerService.GetIndexer((string)postData["indexer"]); jsonReply["name"] = indexer.DisplayName; - await indexer.ApplyConfiguration(postData["config"]); - await indexerService.TestIndexer((string)postData["indexer"]); + var configurationResult = await indexer.ApplyConfiguration(postData["config"]); + if (configurationResult == IndexerConfigurationStatus.RequiresTesting) + { + await indexerService.TestIndexer((string)postData["indexer"]); + } + else if (configurationResult == IndexerConfigurationStatus.Failed) + { + throw new Exception("Configuration Failed"); + } jsonReply["result"] = "success"; } catch (Exception ex) @@ -194,10 +203,11 @@ namespace Jackett.Controllers if (ex is ExceptionWithConfigData) { jsonReply["config"] = ((ExceptionWithConfigData)ex).ConfigData.ToJson(null); - } else + } + else { logger.Error(ex, "Exception in Configure"); - } + } } return Json(jsonReply); } @@ -293,12 +303,13 @@ namespace Jackett.Controllers cfg["external"] = serverService.Config.AllowExternal; cfg["api_key"] = serverService.Config.APIKey; cfg["blackholedir"] = serverService.Config.BlackholeDir; - cfg["password"] = string.IsNullOrEmpty(serverService.Config.AdminPassword )? string.Empty:serverService.Config.AdminPassword.Substring(0,10); + cfg["password"] = string.IsNullOrEmpty(serverService.Config.AdminPassword) ? string.Empty : serverService.Config.AdminPassword.Substring(0, 10); jsonReply["config"] = cfg; jsonReply["app_version"] = config.GetVersion(); jsonReply["result"] = "success"; - }catch (Exception ex) + } + catch (Exception ex) { logger.Error(ex, "Exception in get_jackett_config"); jsonReply["result"] = "error"; @@ -360,7 +371,8 @@ namespace Jackett.Controllers } } - (new Thread(() => { + (new Thread(() => + { Thread.Sleep(500); serverService.Stop(); Engine.BuildContainer(); @@ -370,7 +382,7 @@ namespace Jackett.Controllers } - if(saveDir != Engine.Server.Config.BlackholeDir) + if (saveDir != Engine.Server.Config.BlackholeDir) { if (!string.IsNullOrEmpty(saveDir)) { @@ -435,12 +447,12 @@ namespace Jackett.Controllers var query = new TorznabQuery() { SearchTerm = value.Query, - Categories = value.Category ==0?new int[0]: new int[1] { value.Category } + Categories = value.Category == 0 ? new int[0] : new int[1] { value.Category } }; query.ExpandCatsToSubCats(); - var trackers = indexerService.GetAllIndexers().Where(t=>t.IsConfigured).ToList(); + var trackers = indexerService.GetAllIndexers().Where(t => t.IsConfigured).ToList(); if (!string.IsNullOrWhiteSpace(value.Tracker)) { trackers = trackers.Where(t => t.ID == value.Tracker).ToList(); @@ -453,7 +465,8 @@ namespace Jackett.Controllers Parallel.ForEach(trackers.ToList(), indexer => { - try { + try + { var searchResults = indexer.PerformQuery(query).Result; cacheService.CacheRssResults(indexer, searchResults); searchResults = indexer.FilterResults(query, searchResults); @@ -470,7 +483,7 @@ namespace Jackett.Controllers } } } - catch(Exception e) + catch (Exception e) { logger.Error(e, "An error occured during manual search on " + indexer.DisplayName + ": " + e.Message); } @@ -483,10 +496,10 @@ namespace Jackett.Controllers results = results.OrderByDescending(d => d.PublishDate).ToList(); } - var manualResult = new ManualSearchResult() + var manualResult = new ManualSearchResult() { Results = results, - Indexers = trackers.Select(t=>t.DisplayName).ToList() + Indexers = trackers.Select(t => t.DisplayName).ToList() }; diff --git a/src/Jackett/Indexers/Abstract/AvistazTracker.cs b/src/Jackett/Indexers/Abstract/AvistazTracker.cs new file mode 100644 index 0000000000000000000000000000000000000000..72ef2a29eef5ca7341a12ac0cf9a59a740da7539 --- /dev/null +++ b/src/Jackett/Indexers/Abstract/AvistazTracker.cs @@ -0,0 +1,136 @@ +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; + +namespace Jackett.Indexers +{ + public abstract class AvistazTracker : BaseIndexer + { + private string LoginUrl { get { return SiteLink + "auth/login"; } } + private string SearchUrl { get { return SiteLink + "torrents?in=1&type={0}&search={1}"; } } + + new ConfigurationDataBasicLogin configData + { + get { return (ConfigurationDataBasicLogin)base.configData; } + set { base.configData = value; } + } + + public AvistazTracker(IIndexerManagerService indexerManager, IWebClient webClient, Logger logger, IProtectionService protectionService, string name, string desc, string link) + : base(name: name, + description: desc, + link: link, + caps: TorznabUtil.CreateDefaultTorznabTVCaps(), + manager: indexerManager, + client: webClient, + logger: logger, + p: protectionService, + configData: new ConfigurationDataBasicLogin()) + { + AddCategoryMapping(1, TorznabCatType.Movies); + AddCategoryMapping(1, TorznabCatType.MoviesForeign); + AddCategoryMapping(1, TorznabCatType.MoviesHD); + AddCategoryMapping(1, TorznabCatType.MoviesSD); + AddCategoryMapping(2, TorznabCatType.TV); + AddCategoryMapping(3, TorznabCatType.Audio); + } + + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) + { + configData.LoadValuesFromJson(configJson); + var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty); + var token = new Regex("Avz.CSRF_TOKEN = '(.*?)';").Match(loginPage.Content).Groups[1].ToString(); + var pairs = new Dictionary<string, string> { + { "_token", token }, + { "username_email", configData.Username.Value }, + { "password", configData.Password.Value }, + { "remember", "on" } + }; + + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, null, LoginUrl); + await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("auth/logout"), () => + { + CQ dom = result.Content; + var messageEl = dom[".form-error"]; + var errorMessage = messageEl.Text().Trim(); + throw new ExceptionWithConfigData(errorMessage, configData); + }); + + return IndexerConfigurationStatus.RequiresTesting; + } + + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) + { + var releases = new List<ReleaseInfo>(); + + var categoryMapping = MapTorznabCapsToTrackers(query).Distinct(); + string category = "0"; // Aka all + if (categoryMapping.Count() == 1) + { + category = categoryMapping.First(); + } + + + var episodeSearchUrl = string.Format(SearchUrl, category, HttpUtility.UrlEncode(query.GetQueryString())); + + var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl); + + try + { + CQ dom = response.Content; + var rows = dom["table > tbody > tr"]; + foreach (var row in rows) + { + CQ qRow = row.Cq(); + var release = new ReleaseInfo(); + + release.MinimumRatio = 1; + release.MinimumSeedTime = 172800; + + var qLink = row.ChildElements.ElementAt(1).FirstElementChild.Cq(); + release.Title = qLink.Text().Trim(); + release.Comments = new Uri(qLink.Attr("href")); + release.Guid = release.Comments; + + var qDownload = row.ChildElements.ElementAt(3).FirstElementChild.Cq(); + release.Link = new Uri(qDownload.Attr("href")); + + var dateStr = row.ChildElements.ElementAt(5).Cq().Text().Trim(); + release.PublishDate = DateTimeUtil.FromTimeAgo(dateStr); + + var sizeStr = row.ChildElements.ElementAt(6).Cq().Text(); + release.Size = ReleaseInfo.GetBytes(sizeStr); + + release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(8).Cq().Text()); + release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(9).Cq().Text()) + release.Seeders; + + var cat = row.Cq().Find("td:eq(0) i").First().Attr("class") + .Replace("gi gi-film", "1") + .Replace("gi gi-tv", "2") + .Replace("gi gi-music", "3") + .Replace("text-pink", string.Empty); + release.Category = MapTrackerCatToNewznab(cat.Trim()); + releases.Add(release); + } + } + catch (Exception ex) + { + OnParseError(response.Content, ex); + } + return releases; + } + } +} \ No newline at end of file diff --git a/src/Jackett/Indexers/AlphaRatio.cs b/src/Jackett/Indexers/AlphaRatio.cs index f466a50a2b5cb3e573938b3f4115e852d7ea8982..2b1e825f40b3b04367054124b109184ba467e39a 100644 --- a/src/Jackett/Indexers/AlphaRatio.cs +++ b/src/Jackett/Indexers/AlphaRatio.cs @@ -56,7 +56,7 @@ namespace Jackett.Indexers AddCategoryMapping(23, TorznabCatType.Audio); } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { var incomingConfig = new ConfigurationDataBasicLogin(); incomingConfig.LoadValuesFromJson(configJson); @@ -76,6 +76,7 @@ namespace Jackett.Indexers var errorMessage = dom["#loginform"].Text().Trim().Replace("\n\t", " "); throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig); }); + return IndexerConfigurationStatus.RequiresTesting; } void FillReleaseInfoFromJson(ReleaseInfo release, JObject r) diff --git a/src/Jackett/Indexers/AnimeBytes.cs b/src/Jackett/Indexers/AnimeBytes.cs index 71bc47b5a6274af8d75dcb3bb69972d1d7cd4767..6875324da1c16bba661ce9ae2375f60fd22cdc1a 100644 --- a/src/Jackett/Indexers/AnimeBytes.cs +++ b/src/Jackett/Indexers/AnimeBytes.cs @@ -49,10 +49,10 @@ namespace Jackett.Indexers p: ps, configData: new ConfigurationDataAnimeBytes()) { - + } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); @@ -99,6 +99,8 @@ namespace Jackett.Indexers // Their login page appears to be broken and just gives a 500 error. throw new ExceptionWithConfigData("Failed to login, 6 failed attempts will get you banned for 6 hours.", configData); }); + + return IndexerConfigurationStatus.RequiresTesting; } // Override to load legacy config format diff --git a/src/Jackett/Indexers/Avistaz.cs b/src/Jackett/Indexers/Avistaz.cs new file mode 100644 index 0000000000000000000000000000000000000000..ea99df3a06404a9538a2864b842c0af874294b86 --- /dev/null +++ b/src/Jackett/Indexers/Avistaz.cs @@ -0,0 +1,35 @@ +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; + +namespace Jackett.Indexers +{ + public class Avistaz : AvistazTracker, IIndexer + { + public Avistaz(IIndexerManagerService indexerManager, IWebClient webClient, Logger logger, IProtectionService protectionService) + : base(name: "Avistaz", + desc: "Aka AsiaTorrents", + link: "https://avistaz.to/", + indexerManager: indexerManager, + logger: logger, + protectionService: protectionService, + webClient: webClient + ) + { + } + } +} \ No newline at end of file diff --git a/src/Jackett/Indexers/BB.cs b/src/Jackett/Indexers/BB.cs index 8a657bfe92a7e18752de928ce82b0f0070de12b0..0f6b30549f2e20a1cace88b3953299f409359a9a 100644 --- a/src/Jackett/Indexers/BB.cs +++ b/src/Jackett/Indexers/BB.cs @@ -37,7 +37,7 @@ namespace Jackett.Indexers : base(name: "bB", description: "bB", link: "http://www.reddit.com/r/baconbits/", - caps: TorznabUtil.CreateDefaultTorznabTVCaps(), + caps: new TorznabCapabilities(), manager: i, client: w, logger: l, @@ -57,7 +57,7 @@ namespace Jackett.Indexers AddCategoryMapping(11, TorznabCatType.PCGames); } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { @@ -80,8 +80,9 @@ namespace Jackett.Indexers } var message = string.Join(" ", messages); throw new ExceptionWithConfigData(message, configData); - }); + + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) diff --git a/src/Jackett/Indexers/BakaBT.cs b/src/Jackett/Indexers/BakaBT.cs index 4112dd18a24cca2e45eea94d1ec1445f99fcc7b2..5c89fd31c51f56ebef8eb0677a8252a898cd032e 100644 --- a/src/Jackett/Indexers/BakaBT.cs +++ b/src/Jackett/Indexers/BakaBT.cs @@ -41,7 +41,7 @@ namespace Jackett.Indexers { } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); @@ -66,6 +66,8 @@ namespace Jackett.Indexers var errorMessage = messageEl.Text().Trim(); throw new ExceptionWithConfigData(errorMessage, configData); }); + + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) diff --git a/src/Jackett/Indexers/BaseIndexer.cs b/src/Jackett/Indexers/BaseIndexer.cs index dd528f4b6bf7285068c1d579215d7169a15bf6c2..71cf91979ca9e1ae4527521fe12420d02f8b2991 100644 --- a/src/Jackett/Indexers/BaseIndexer.cs +++ b/src/Jackett/Indexers/BaseIndexer.cs @@ -38,7 +38,7 @@ namespace Jackett.Indexers set { configData.CookieHeader.Value = value; } } - + protected ConfigurationData configData; @@ -70,7 +70,7 @@ namespace Jackett.Indexers { if (string.IsNullOrEmpty(downloadUrlBase)) return releases; - foreach(var release in releases) + foreach (var release in releases) { if (release.Link.ToString().StartsWith(downloadUrlBase)) { @@ -91,7 +91,7 @@ namespace Jackett.Indexers if (null != input) { input = input.ToLowerInvariant(); - var mapping = categoryMapping.Where(m => m.TrackerCategory == input).FirstOrDefault(); + var mapping = categoryMapping.Where(m => m.TrackerCategory.ToLowerInvariant() == input.ToLowerInvariant()).FirstOrDefault(); if (mapping != null) { return mapping.NewzNabCategory; @@ -127,7 +127,6 @@ namespace Jackett.Indexers var spacing = string.Join("", Enumerable.Repeat(Environment.NewLine, 5)); var fileContents = string.Format("{0}{1}{2}", ex, spacing, results); logger.Error(fileName + fileContents); - throw ex; } protected void CleanCache() @@ -403,12 +402,44 @@ namespace Jackett.Indexers categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory)); } - protected List<string> MapTorznabCapsToTrackers(TorznabQuery query) + 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<string> MapTorznabCapsToTrackers(TorznabQuery query, bool mapChildrenCatsToParent = false) { var result = new List<string>(); foreach (var cat in query.Categories) { - foreach (var mapping in categoryMapping.Where(c => c.NewzNabCategory == cat)) + var queryCats = new List<int> { 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); } diff --git a/src/Jackett/Indexers/BeyondHD.cs b/src/Jackett/Indexers/BeyondHD.cs index 81f472c197c981a123f225b98d9e6259a72fb1b0..f0f5a635b0d01529971d9548b727048df6a21c3f 100644 --- a/src/Jackett/Indexers/BeyondHD.cs +++ b/src/Jackett/Indexers/BeyondHD.cs @@ -33,36 +33,52 @@ namespace Jackett.Indexers : base(name: "BeyondHD", description: "Without BeyondHD, your HDTV is just a TV", link: "https://beyondhd.me/", - caps: TorznabUtil.CreateDefaultTorznabTVCaps(), + caps: new TorznabCapabilities(), manager: i, client: w, logger: l, p: ps, configData: new ConfigurationDataCookie()) { - AddCategoryMapping("40,44,48,89,46,45", TorznabCatType.TV); - AddCategoryMapping("40", TorznabCatType.TVHD); - AddCategoryMapping("44", TorznabCatType.TVHD); - AddCategoryMapping("48", TorznabCatType.TVHD); - AddCategoryMapping("46", TorznabCatType.TVHD); - AddCategoryMapping("45", TorznabCatType.TVHD); - AddCategoryMapping("44", TorznabCatType.TVSD); - AddCategoryMapping("46", TorznabCatType.TVSD); - AddCategoryMapping("45", TorznabCatType.TVSD); - - AddCategoryMapping("41,77,71,94,78,37,54,17", TorznabCatType.Movies); - AddCategoryMapping("77", TorznabCatType.MoviesHD); - AddCategoryMapping("71", TorznabCatType.Movies3D); - AddCategoryMapping("78", TorznabCatType.MoviesHD); - AddCategoryMapping("37", TorznabCatType.MoviesBluRay); - AddCategoryMapping("54", TorznabCatType.MoviesHD); - - AddCategoryMapping("55,56,42,36,69", TorznabCatType.Audio); - AddCategoryMapping("36", TorznabCatType.AudioLossless); - AddCategoryMapping("69", TorznabCatType.AudioMP3); + AddCategoryMapping(37, TorznabCatType.MoviesBluRay); // Movie / Blu-ray + AddMultiCategoryMapping(TorznabCatType.Movies3D, + 71, // Movie / 3D + 83 // FraMeSToR 3D + ); + AddMultiCategoryMapping(TorznabCatType.MoviesHD, + 77, // Movie / 1080p/i + 94, // Movie / 4K + 78, // Movie / 720p + 54, // Movie / MP4 + 17, // Movie / Remux + 50, // Internal / FraMeSToR 1080p + 75, // Internal / FraMeSToR 720p + 49, // Internal / FraMeSToR REMUX + 61, // Internal / HDX REMUX + 86 // Internal / SC4R + ); + + AddMultiCategoryMapping(TorznabCatType.TVHD, + 40, // TV Show / Blu-ray + 44, // TV Show / Encodes + 48, // TV Show / HDTV + 89, // TV Show / Packs + 46, // TV Show / Remux + 45 // TV Show / WEB-DL + ); + + AddCategoryMapping(36, TorznabCatType.AudioLossless); // Music / Lossless + AddCategoryMapping(69, TorznabCatType.AudioMP3); // Music / MP3 + AddMultiCategoryMapping(TorznabCatType.AudioVideo, + 55, // Music / 1080p/i + 56, // Music / 720p + 42 // Music / Blu-ray + ); + + } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); @@ -72,18 +88,19 @@ namespace Jackett.Indexers Cookies = configData.Cookie.Value }); - await ConfigureIfOK(CookieHeader, response.Content.Contains("logout.php"), () => + await ConfigureIfOK(configData.Cookie.Value, response.Content.Contains("logout.php"), () => { CQ dom = response.Content; throw new ExceptionWithConfigData("Invalid cookie header", configData); }); + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { List<ReleaseInfo> releases = new List<ReleaseInfo>(); - var searchString = query.GetQueryString(); + var searchString = query.GetQueryString(); var searchUrl = SearchUrl; var queryCollection = new NameValueCollection(); @@ -92,12 +109,7 @@ namespace Jackett.Indexers queryCollection.Add("search", searchString); } - var cats = new List<string>(); foreach (var cat in MapTorznabCapsToTrackers(query)) - { - cats.AddRange(cat.Split(',')); - } - foreach (var cat in cats.Distinct()) { queryCollection.Add("c" + cat, "1"); } diff --git a/src/Jackett/Indexers/BitHdtv.cs b/src/Jackett/Indexers/BitHdtv.cs index b01f3f37a6b87db0186b209abbaed9231e06eb95..bdead64ecf109500e5fddd09efcb3c09adb76370 100644 --- a/src/Jackett/Indexers/BitHdtv.cs +++ b/src/Jackett/Indexers/BitHdtv.cs @@ -15,13 +15,14 @@ using System.Text; using System.Threading.Tasks; using System.Web; using Jackett.Models.IndexerConfig; +using System.Collections.Specialized; namespace Jackett.Indexers { public class BitHdtv : BaseIndexer, IIndexer { private string LoginUrl { get { return SiteLink + "takelogin.php"; } } - private string SearchUrl { get { return SiteLink + "torrents.php?cat=0&search="; } } + private string SearchUrl { get { return SiteLink + "torrents.php?"; } } private string DownloadUrl { get { return SiteLink + "download.php?/{0}/dl.torrent"; } } new ConfigurationDataBasicLogin configData @@ -34,16 +35,26 @@ namespace Jackett.Indexers : base(name: "BIT-HDTV", description: "Home of high definition invites", link: "https://www.bit-hdtv.com/", - caps: TorznabUtil.CreateDefaultTorznabTVCaps(), + caps: new TorznabCapabilities(), manager: i, client: w, logger: l, p: ps, configData: new ConfigurationDataBasicLogin()) { + AddCategoryMapping(1, TorznabCatType.TVAnime); // Anime + AddCategoryMapping(2, TorznabCatType.MoviesBluRay); // Blu-ray + AddCategoryMapping(4, TorznabCatType.TVDocumentary); // Documentaries + AddCategoryMapping(6, TorznabCatType.AudioLossless); // HQ Audio + AddCategoryMapping(7, TorznabCatType.Movies); // Movies + AddCategoryMapping(8, TorznabCatType.AudioVideo); // Music Videos + AddCategoryMapping(5, TorznabCatType.TVSport); // Sports + AddCategoryMapping(10, TorznabCatType.TV); // TV + AddCategoryMapping(12, TorznabCatType.TV); // TV/Seasonpack + AddCategoryMapping(11, TorznabCatType.XXX); // XXX } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); @@ -62,13 +73,25 @@ namespace Jackett.Indexers var errorMessage = messageEl.Text().Trim(); throw new ExceptionWithConfigData(errorMessage, configData); }); + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { var releases = new List<ReleaseInfo>(); - var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(query.GetQueryString()); - var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl); + var searchString = query.GetQueryString(); + var queryCollection = new NameValueCollection(); + + if (!string.IsNullOrWhiteSpace(searchString)) + { + queryCollection.Add("search", searchString); + } + + var searchUrl = SearchUrl + queryCollection.GetQueryString(); + + var trackerCats = MapTorznabCapsToTrackers(query, mapChildrenCatsToParent: true); + + var results = await RequestStringWithCookiesAndRetry(searchUrl); try { CQ dom = results.Content; @@ -86,10 +109,18 @@ namespace Jackett.Indexers release.MinimumSeedTime = 172800; release.Title = qLink.Attr("title"); release.Description = release.Title; - release.Guid = new Uri(SiteLink + qLink.Attr("href")); + release.Guid = new Uri(SiteLink + qLink.Attr("href").TrimStart('/')); release.Comments = release.Guid; release.Link = new Uri(string.Format(DownloadUrl, qLink.Attr("href").Split('=')[1])); + var catUrl = qRow.Children().ElementAt(1).FirstElementChild.Cq().Attr("href"); + var catNum = catUrl.Split(new char[] { '=', '&' })[1]; + release.Category = MapTrackerCatToNewznab(catNum); + + // This tracker cannot search multiple cats at a time, so search all cats then filter out results from different cats + if (trackerCats.Count > 0 && !trackerCats.Contains(catNum)) + continue; + var dateString = qRow.Children().ElementAt(5).Cq().Text().Trim(); var pubDate = DateTime.ParseExact(dateString, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); release.PublishDate = DateTime.SpecifyKind(pubDate, DateTimeKind.Local); diff --git a/src/Jackett/Indexers/BitMeTV.cs b/src/Jackett/Indexers/BitMeTV.cs index 8be342237ad61d550ea127066c50ee002e58f1e3..bc117d070420ab6c6172e8c21296343ae384a4b6 100644 --- a/src/Jackett/Indexers/BitMeTV.cs +++ b/src/Jackett/Indexers/BitMeTV.cs @@ -58,7 +58,7 @@ namespace Jackett.Indexers return configData; } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); @@ -80,6 +80,7 @@ namespace Jackett.Indexers configData.CaptchaCookie.Value = captchaImage.Cookies; throw new ExceptionWithConfigData(errorMessage, configData); }); + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) diff --git a/src/Jackett/Indexers/Demonoid.cs b/src/Jackett/Indexers/Demonoid.cs index c99fcbcbec175f0b1681c646829d1ee9a00d8284..53400d26fdaaa59f13d7935f301a108d7daf8b6e 100644 --- a/src/Jackett/Indexers/Demonoid.cs +++ b/src/Jackett/Indexers/Demonoid.cs @@ -40,7 +40,7 @@ namespace Jackett.Indexers { } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { @@ -58,6 +58,7 @@ namespace Jackett.Indexers var errorMessage = dom[".red"].ElementAt(1).Cq().Text().Trim(); throw new ExceptionWithConfigData(errorMessage, configData); }); + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) diff --git a/src/Jackett/Indexers/EuTorrents.cs b/src/Jackett/Indexers/EuTorrents.cs new file mode 100644 index 0000000000000000000000000000000000000000..af0affe692860043d3b29e9bc33f336aaa5a4dd4 --- /dev/null +++ b/src/Jackett/Indexers/EuTorrents.cs @@ -0,0 +1,35 @@ +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; + +namespace Jackett.Indexers +{ + public class EuTorrents : AvistazTracker, IIndexer + { + public EuTorrents(IIndexerManagerService indexerManager, IWebClient webClient, Logger logger, IProtectionService protectionService) + : base(name: "EuTorrents", + desc: "Part of the Avistaz network.", + link: "https://eutorrents.to/", + indexerManager: indexerManager, + logger: logger, + protectionService: protectionService, + webClient: webClient + ) + { + } + } +} \ No newline at end of file diff --git a/src/Jackett/Indexers/FileList.cs b/src/Jackett/Indexers/FileList.cs index 0b03887f58bfa36e38cc191d3f82dd4667638bc5..9ca4e219fc465d8ede4d9d1e281b13746cd3facf 100644 --- a/src/Jackett/Indexers/FileList.cs +++ b/src/Jackett/Indexers/FileList.cs @@ -66,7 +66,7 @@ namespace Jackett.Indexers AddCategoryMapping(7, TorznabCatType.XXX); } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { @@ -81,6 +81,7 @@ namespace Jackett.Indexers var errorMessage = dom[".main"].Text().Trim(); throw new ExceptionWithConfigData(errorMessage, configData); }); + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) @@ -99,7 +100,7 @@ namespace Jackett.Indexers if (!string.IsNullOrWhiteSpace(searchString) || cat != "0") searchUrl += string.Format("?search={0}&cat={1}&searchin=0&sort=0", HttpUtility.UrlEncode(searchString), cat); - + var response = await RequestStringWithCookiesAndRetry(searchUrl, null, BrowseUrl); var results = response.Content; diff --git a/src/Jackett/Indexers/FrenchTorrentDb.cs b/src/Jackett/Indexers/FrenchTorrentDb.cs index 00d1e835ab035335d0b846bbf77d4ae0d9a4d692..da7538d93b3f8666af62ee58c5f9d6c23be858c4 100644 --- a/src/Jackett/Indexers/FrenchTorrentDb.cs +++ b/src/Jackett/Indexers/FrenchTorrentDb.cs @@ -37,7 +37,7 @@ namespace Jackett.Indexers { } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var response = await webclient.GetString(new Utils.Clients.WebRequest() @@ -51,6 +51,7 @@ namespace Jackett.Indexers { throw new ExceptionWithConfigData("Failed to login", configData); }); + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) diff --git a/src/Jackett/Indexers/Freshon.cs b/src/Jackett/Indexers/Freshon.cs index 0b298543b26c6c09a6bda42e4e821c490d8cc332..62a4ee61f13c4237765c52443161c8990b16cdfe 100644 --- a/src/Jackett/Indexers/Freshon.cs +++ b/src/Jackett/Indexers/Freshon.cs @@ -46,7 +46,7 @@ namespace Jackett.Indexers { } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { @@ -65,6 +65,7 @@ namespace Jackett.Indexers var errorMessage = messageEl.Text().Trim(); throw new ExceptionWithConfigData(errorMessage, configData); }); + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) diff --git a/src/Jackett/Indexers/HDSpace.cs b/src/Jackett/Indexers/HDSpace.cs index 0b455b78f21ebd841be8d2a817349fe4f7054349..cfb4f57ee4e56996d8d470dc9ab59605db880d34 100644 --- a/src/Jackett/Indexers/HDSpace.cs +++ b/src/Jackett/Indexers/HDSpace.cs @@ -41,7 +41,7 @@ namespace Jackett.Indexers { } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); @@ -63,6 +63,7 @@ namespace Jackett.Indexers var errorMessage = string.Format(errorStr, attempts); throw new ExceptionWithConfigData(errorMessage, configData); }); + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) diff --git a/src/Jackett/Indexers/HDTorrents.cs b/src/Jackett/Indexers/HDTorrents.cs index c4fa18507bfd65e919e79ae5c74ac3eb0a74c791..294eafda4ccf81a931d3af8dd124b7b9e336d85a 100644 --- a/src/Jackett/Indexers/HDTorrents.cs +++ b/src/Jackett/Indexers/HDTorrents.cs @@ -15,12 +15,13 @@ using System.Text; using System.Threading.Tasks; using System.Web; using Jackett.Models.IndexerConfig; +using System.Collections.Specialized; namespace Jackett.Indexers { public class HDTorrents : BaseIndexer, IIndexer { - private string SearchUrl { get { return SiteLink + "torrents.php?search={0}&active=1&options=0&category%5B%5D=59&category%5B%5D=60&category%5B%5D=30&category%5B%5D=38&page=0"; } } + private string SearchUrl { get { return SiteLink + "torrents.php?"; } } private string LoginUrl { get { return SiteLink + "login.php"; } } private const int MAXPAGES = 3; @@ -34,16 +35,37 @@ namespace Jackett.Indexers : base(name: "HD-Torrents", description: "HD-Torrents is a private torrent website with HD torrents and strict rules on their content.", link: "http://hdts.ru/",// Of the accessible domains the .ru seems the most reliable. https://hdts.ru | https://hd-torrents.org | https://hd-torrents.net | https://hd-torrents.me - caps: TorznabUtil.CreateDefaultTorznabTVCaps(), manager: i, client: w, logger: l, p: ps, configData: new ConfigurationDataBasicLogin()) { + TorznabCaps.Categories.Clear(); + + AddCategoryMapping("1", TorznabCatType.MoviesHD);// Movie/Blu-Ray + AddCategoryMapping("2", TorznabCatType.MoviesHD);// Movie/Remux + AddCategoryMapping("5", TorznabCatType.MoviesHD);//Movie/1080p/i + AddCategoryMapping("3", TorznabCatType.MoviesHD);//Movie/720p + AddCategoryMapping("63", TorznabCatType.Audio);//Movie/Audio Track + + AddCategoryMapping("59", TorznabCatType.TVHD);//TV Show/Blu-ray + AddCategoryMapping("60", TorznabCatType.TVHD);//TV Show/Remux + AddCategoryMapping("30", TorznabCatType.TVHD);//TV Show/1080p/i + AddCategoryMapping("38", TorznabCatType.TVHD);//TV Show/720p + + AddCategoryMapping("44", TorznabCatType.Audio);//Music/Album + AddCategoryMapping("61", TorznabCatType.AudioVideo);//Music/Blu-Ray + AddCategoryMapping("62", TorznabCatType.AudioVideo);//Music/Remux + AddCategoryMapping("57", TorznabCatType.AudioVideo);//Music/1080p/i + AddCategoryMapping("45", TorznabCatType.AudioVideo);//Music/720p + + AddCategoryMapping("58", TorznabCatType.XXX);//XXX/Blu-ray + AddCategoryMapping("48", TorznabCatType.XXX);//XXX/1080p/i + AddCategoryMapping("47", TorznabCatType.XXX);//XXX/720p } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty); @@ -60,13 +82,37 @@ namespace Jackett.Indexers var errorMessage = "Couldn't login"; throw new ExceptionWithConfigData(errorMessage, configData); }); + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { var releases = new List<ReleaseInfo>(); var searchurls = new List<string>(); - var searchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(query.GetQueryString())); + var searchUrl = SearchUrl;// string.Format(SearchUrl, HttpUtility.UrlEncode())); + var queryCollection = new NameValueCollection(); + var searchString = query.GetQueryString(); + + + foreach (var cat in MapTorznabCapsToTrackers(query)) + { + searchUrl += "category%5B%5D=" + cat + "&"; + } + + + if (!string.IsNullOrWhiteSpace(searchString)) + { + queryCollection.Add("search", searchString); + } + + + + queryCollection.Add("active", "1"); + queryCollection.Add("options", "0"); + + searchUrl += queryCollection.GetQueryString(); + + var results = await RequestStringWithCookiesAndRetry(searchUrl); try { @@ -117,14 +163,17 @@ namespace Jackett.Indexers string fullSize = qRow.Find("td.mainblockcontent").Get(6).InnerText; release.Size = ReleaseInfo.GetBytes(fullSize); - release.Guid = new Uri(SiteLink + "/" + qRow.Find("td.mainblockcontent b a").Attr("href")); - release.Link = new Uri(SiteLink + "/" + qRow.Find("td.mainblockcontent").Get(3).FirstChild.GetAttribute("href")); - release.Comments = new Uri(SiteLink + "/" + qRow.Find("td.mainblockcontent b a").Attr("href") + "#comments"); + release.Guid = new Uri(SiteLink + qRow.Find("td.mainblockcontent b a").Attr("href")); + release.Link = new Uri(SiteLink + qRow.Find("td.mainblockcontent").Get(3).FirstChild.GetAttribute("href")); + release.Comments = new Uri(SiteLink + qRow.Find("td.mainblockcontent b a").Attr("href") + "#comments"); string[] dateSplit = qRow.Find("td.mainblockcontent").Get(5).InnerHTML.Split(','); string dateString = dateSplit[1].Substring(0, dateSplit[1].IndexOf('>')); release.PublishDate = DateTime.Parse(dateString, CultureInfo.InvariantCulture); + string category = qRow.Find("td:eq(0) a").Attr("href").Replace("torrents.php?category=", ""); + release.Category = MapTrackerCatToNewznab(category); + releases.Add(release); } } diff --git a/src/Jackett/Indexers/IIndexer.cs b/src/Jackett/Indexers/IIndexer.cs index e3dd527dd6d90f8518bc4f219011dce0551f7aea..c7358891091b3ebd3900ca0dea551b703ca836f7 100644 --- a/src/Jackett/Indexers/IIndexer.cs +++ b/src/Jackett/Indexers/IIndexer.cs @@ -27,7 +27,7 @@ namespace Jackett.Indexers Task<ConfigurationData> GetConfigurationForSetup(); // Called when web API wants to apply setup configuration via web API, usually this is where login and storing cookie happens - Task ApplyConfiguration(JToken configJson); + Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson); // Called on startup when initializing indexers from saved configuration void LoadFromSavedConfiguration(JToken jsonConfig); diff --git a/src/Jackett/Indexers/IPTorrents.cs b/src/Jackett/Indexers/IPTorrents.cs index ebbef2c7da26268d45967884daf1df6157c6c0cd..7055761682b06c9f73310224d69b70dc65465df3 100644 --- a/src/Jackett/Indexers/IPTorrents.cs +++ b/src/Jackett/Indexers/IPTorrents.cs @@ -83,7 +83,7 @@ namespace Jackett.Indexers AddCategoryMapping(94, TorznabCatType.BooksComics); } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { @@ -109,6 +109,7 @@ namespace Jackett.Indexers var errorMessage = messageEl.Text().Trim(); throw new ExceptionWithConfigData(errorMessage, configData); }); + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) diff --git a/src/Jackett/Indexers/ImmortalSeed.cs b/src/Jackett/Indexers/ImmortalSeed.cs index e246b03b8e0fe6d4f97168b36488ddcf0a7de0de..0bb675bf4bc09f87e3faa63bc2eb59e88a6d0fb6 100644 --- a/src/Jackett/Indexers/ImmortalSeed.cs +++ b/src/Jackett/Indexers/ImmortalSeed.cs @@ -67,7 +67,7 @@ namespace Jackett.Indexers } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { @@ -93,6 +93,8 @@ namespace Jackett.Indexers var errorMessage = "Incorrect username or password! " + tries + " tries remaining."; throw new ExceptionWithConfigData(errorMessage, configData); }); + + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) diff --git a/src/Jackett/Indexers/MoreThanTV.cs b/src/Jackett/Indexers/MoreThanTV.cs index 8a61115160aa8940c28bd25156443d05d48148f2..e8a809273a3e71581a6930db48ccb19f29136615 100644 --- a/src/Jackett/Indexers/MoreThanTV.cs +++ b/src/Jackett/Indexers/MoreThanTV.cs @@ -45,7 +45,7 @@ namespace Jackett.Indexers { } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { @@ -63,6 +63,8 @@ namespace Jackett.Indexers var errorMessage = dom["#loginform"].Text().Trim().Replace("\n\t", " "); throw new ExceptionWithConfigData(errorMessage, configData); }); + + return IndexerConfigurationStatus.RequiresTesting; } private void FillReleaseInfoFromJson(ReleaseInfo release, JObject r) diff --git a/src/Jackett/Indexers/Pretome.cs b/src/Jackett/Indexers/Pretome.cs index b24cfc60da504dd03e011d6f1c61bc2a97ea19d7..9cd8c3c1551c65ff242f8fa0adfcf2fc64307e79 100644 --- a/src/Jackett/Indexers/Pretome.cs +++ b/src/Jackett/Indexers/Pretome.cs @@ -12,6 +12,7 @@ using Jackett.Utils; using CsQuery; using System.Web; using Jackett.Models.IndexerConfig; +using System.Collections.Specialized; namespace Jackett.Indexers { @@ -19,7 +20,9 @@ namespace Jackett.Indexers { private string LoginUrl { get { return SiteLink + "takelogin.php"; } } private string LoginReferer { get { return SiteLink + "index.php?cat=1"; } } - private string SearchUrl { get { return SiteLink + "browse.php?tags=&st=1&tf=all&cat%5B%5D=7&search={0}"; } } + private string SearchUrl { get { return SiteLink + "browse.php"; } } + + private List<CategoryMapping> resultMapping = new List<CategoryMapping>(); new ConfigurationDataPinNumber configData { @@ -38,9 +41,135 @@ namespace Jackett.Indexers p: ps, configData: new ConfigurationDataPinNumber()) { + + AddCategoryMapping("cat[]=22&tags=Windows", TorznabCatType.PC0day); + AddCategoryMapping("cat[]=22&tags=MAC", TorznabCatType.PCMac); + AddCategoryMapping("cat[]=22&tags=Linux", TorznabCatType.PC); + + AddCategoryMapping("cat[]=27", TorznabCatType.BooksEbook); + + AddCategoryMapping("cat[]=4&tags=PC", TorznabCatType.PCGames); + AddCategoryMapping("cat[]=4&tags=RIP", TorznabCatType.PCGames); + AddCategoryMapping("cat[]=4&tags=ISO", TorznabCatType.PCGames); + AddCategoryMapping("cat[]=4&tags=XBOX360", TorznabCatType.ConsoleXbox360); + AddCategoryMapping("cat[]=4&tags=PS3", TorznabCatType.ConsolePS3); + AddCategoryMapping("cat[]=4&tags=Wii", TorznabCatType.ConsoleWii); + AddCategoryMapping("cat[]=4&tags=PSP", TorznabCatType.ConsolePSP); + AddCategoryMapping("cat[]=4&tags=NSD", TorznabCatType.ConsoleNDS); + AddCategoryMapping("cat[]=4&tags=XBox", TorznabCatType.ConsoleXbox); + AddCategoryMapping("cat[]=4&tags=PS2", TorznabCatType.ConsoleOther); + + AddCategoryMapping("cat[]=31&tags=Ebook", TorznabCatType.BooksEbook); + AddCategoryMapping("cat[]=31&tags=RARFiX", TorznabCatType.Other); + + AddCategoryMapping("cat[]=19&tags=x264", TorznabCatType.Movies); + AddCategoryMapping("cat[]=19&tags=720p", TorznabCatType.MoviesHD); + AddCategoryMapping("cat[]=19&tags=XviD", TorznabCatType.MoviesSD); + AddCategoryMapping("cat[]=19&tags=BluRay", TorznabCatType.MoviesHD); + AddCategoryMapping("cat[]=19&tags=DVDRiP", TorznabCatType.MoviesSD); + AddCategoryMapping("cat[]=19&tags=1080p", TorznabCatType.MoviesHD); + AddCategoryMapping("cat[]=19&tags=DVD", TorznabCatType.MoviesSD); + AddCategoryMapping("cat[]=19&tags=DVDR", TorznabCatType.MoviesSD); + AddCategoryMapping("cat[]=19&tags=WMV", TorznabCatType.Movies); + AddCategoryMapping("cat[]=19&tags=CAM", TorznabCatType.Movies); + + AddCategoryMapping("cat[]=6&tags=MP3", TorznabCatType.AudioMP3); + AddCategoryMapping("cat[]=6&tags=V2", TorznabCatType.AudioMP3); + AddCategoryMapping("cat[]=6&tags=FLAC", TorznabCatType.AudioLossless); + AddCategoryMapping("cat[]=6&tags=320kbps", TorznabCatType.AudioMP3); + + AddCategoryMapping("cat[]=7&tags=x264", TorznabCatType.TVHD); + AddCategoryMapping("cat[]=7&tags=720p", TorznabCatType.TVHD); + AddCategoryMapping("cat[]=7&tags=HDTV", TorznabCatType.TVHD); + AddCategoryMapping("cat[]=7&tags=XviD", TorznabCatType.TVSD); + AddCategoryMapping("cat[]=7&BluRay", TorznabCatType.TVHD); + AddCategoryMapping("cat[]=7&tags=DVDRip", TorznabCatType.TVSD); + AddCategoryMapping("cat[]=7&tags=DVD", TorznabCatType.TVSD); + AddCategoryMapping("cat[]=7&tags=Documentary", TorznabCatType.TVDocumentary); + AddCategoryMapping("cat[]=7&tags=PDTV", TorznabCatType.TVSD); + AddCategoryMapping("cat[]=7&tags=HD-DVD", TorznabCatType.TVSD); + + + AddCategoryMapping("cat[]=51&tags=XviD", TorznabCatType.XXXXviD); + AddCategoryMapping("cat[]=51&tags=DVDRiP", TorznabCatType.XXXDVD); + + // Unfortunately they are tags not categories so return the results + // as the parent category so do not get results removed with the filtering. + + AddResultCategoryMapping("cat[]=22&tags=Windows", TorznabCatType.PC); + AddResultCategoryMapping("cat[]=22&tags=MAC", TorznabCatType.PC); + AddResultCategoryMapping("cat[]=22&tags=Linux", TorznabCatType.PC); + AddResultCategoryMapping("cat[]=22&tags=All", TorznabCatType.PC); + AddResultCategoryMapping("cat[]=22", TorznabCatType.PC); + + AddResultCategoryMapping("cat[]=27&tags=All", TorznabCatType.Books); + AddResultCategoryMapping("cat[]=27", TorznabCatType.Books); + + AddResultCategoryMapping("cat[]=4&tags=PC", TorznabCatType.PC); + AddResultCategoryMapping("cat[]=4&tags=RIP", TorznabCatType.PC); + AddResultCategoryMapping("cat[]=4&tags=ISO", TorznabCatType.PC); + AddResultCategoryMapping("cat[]=4&tags=XBOX360", TorznabCatType.Console); + AddResultCategoryMapping("cat[]=4&tags=PS3", TorznabCatType.Console); + AddResultCategoryMapping("cat[]=4&tags=Wii", TorznabCatType.Console); + AddResultCategoryMapping("cat[]=4&tags=PSP", TorznabCatType.Console); + AddResultCategoryMapping("cat[]=4&tags=NSD", TorznabCatType.Console); + AddResultCategoryMapping("cat[]=4&tags=XBox", TorznabCatType.Console); + AddResultCategoryMapping("cat[]=4&tags=PS2", TorznabCatType.Console); + AddResultCategoryMapping("cat[]=4&tags=All", TorznabCatType.Console); + AddResultCategoryMapping("cat[]=4", TorznabCatType.Console); + + AddResultCategoryMapping("cat[]=31&tags=Ebook", TorznabCatType.Books); + AddResultCategoryMapping("cat[]=31&tags=RARFiX", TorznabCatType.Other); + AddResultCategoryMapping("cat[]=31&tags=All", TorznabCatType.Other); + AddResultCategoryMapping("cat[]=31", TorznabCatType.Other); + + AddResultCategoryMapping("cat[]=19&tags=x264", TorznabCatType.Movies); + AddResultCategoryMapping("cat[]=19&tags=720p", TorznabCatType.Movies); + AddResultCategoryMapping("cat[]=19&tags=XviD", TorznabCatType.Movies); + AddResultCategoryMapping("cat[]=19&tags=BluRay", TorznabCatType.Movies); + AddResultCategoryMapping("cat[]=19&tags=DVDRiP", TorznabCatType.Movies); + AddResultCategoryMapping("cat[]=19&tags=1080p", TorznabCatType.Movies); + AddResultCategoryMapping("cat[]=19&tags=DVD", TorznabCatType.Movies); + AddResultCategoryMapping("cat[]=19&tags=DVDR", TorznabCatType.Movies); + AddResultCategoryMapping("cat[]=19&tags=WMV", TorznabCatType.Movies); + AddResultCategoryMapping("cat[]=19&tags=CAM", TorznabCatType.Movies); + AddResultCategoryMapping("cat[]=19&tags=All", TorznabCatType.Movies); + AddResultCategoryMapping("cat[]=19", TorznabCatType.Movies); + + AddResultCategoryMapping("cat[]=6&tags=MP3", TorznabCatType.Audio); + AddResultCategoryMapping("cat[]=6&tags=V2", TorznabCatType.Audio); + AddResultCategoryMapping("cat[]=6&tags=FLAC", TorznabCatType.Audio); + AddResultCategoryMapping("cat[]=6&tags=320kbps", TorznabCatType.Audio); + AddResultCategoryMapping("cat[]=6&tags=All", TorznabCatType.Audio); + AddResultCategoryMapping("cat[]=6", TorznabCatType.Audio); + + AddResultCategoryMapping("cat[]=7&tags=x264", TorznabCatType.TV); + AddResultCategoryMapping("cat[]=7&tags=720p", TorznabCatType.TV); + AddResultCategoryMapping("cat[]=7&tags=HDTV", TorznabCatType.TV); + AddResultCategoryMapping("cat[]=7&tags=XviD", TorznabCatType.TV); + AddResultCategoryMapping("cat[]=7&BluRay", TorznabCatType.TV); + AddResultCategoryMapping("cat[]=7&tags=DVDRip", TorznabCatType.TV); + AddResultCategoryMapping("cat[]=7&tags=DVD", TorznabCatType.TV); + AddResultCategoryMapping("cat[]=7&tags=Documentary", TorznabCatType.TV); + AddResultCategoryMapping("cat[]=7&tags=PDTV", TorznabCatType.TV); + AddResultCategoryMapping("cat[]=7&tags=HD-DVD", TorznabCatType.TV); + AddResultCategoryMapping("cat[]=7&tags=All", TorznabCatType.TV); + AddResultCategoryMapping("cat[]=7", TorznabCatType.TV); + + AddResultCategoryMapping("cat[]=51&tags=XviD", TorznabCatType.XXX); + AddResultCategoryMapping("cat[]=51&tags=DVDRiP", TorznabCatType.XXX); + AddResultCategoryMapping("cat[]=51&tags=All", TorznabCatType.XXX); + AddResultCategoryMapping("cat[]=51", TorznabCatType.XXX); + } + + protected void AddResultCategoryMapping(string trackerCategory, TorznabCategory newznabCategory) + { + resultMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory.ID)); + if (!TorznabCaps.Categories.Contains(newznabCategory)) + TorznabCaps.Categories.Add(newznabCategory); } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); @@ -69,13 +198,67 @@ namespace Jackett.Indexers CookieHeader = string.Empty; throw new ExceptionWithConfigData("Failed", configData); }); + + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { var releases = new List<ReleaseInfo>(); - var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(query.GetQueryString())); - var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl); + var queryUrl = SearchUrl; + var queryCollection = new NameValueCollection(); + var cats = MapTorznabCapsToTrackers(query); + var tags = string.Empty; + var catGroups = new List<string>(); + foreach (var cat in cats) + { + //"cat[]=7&tags=x264" + var cSplit = cat.Split('&'); + if (cSplit.Length > 0) + { + var gsplit = cSplit[0].Split('='); + if (gsplit.Length > 1) + { + catGroups.Add(gsplit[1]); + } + } + + if (cSplit.Length > 1) + { + var gsplit = cSplit[1].Split('='); + if (gsplit.Length > 1) + { + if (tags != string.Empty) + tags += ","; + tags += gsplit[1]; + } + } + } + + if (catGroups.Distinct().Count() == 1) + { + queryCollection.Add("cat[]", catGroups.First()); + } + + if (!string.IsNullOrWhiteSpace(query.GetQueryString())) + { + queryCollection.Add("st", "1"); + queryCollection.Add("search", query.GetQueryString()); + } + + // Do not include too many tags as it'll mess with their servers. + if (tags.Split(',').Length < 7) + { + queryCollection.Add("tags", tags); + queryCollection.Add("tf", "any"); + } + + if (queryCollection.Count > 0) + { + queryUrl += "?" + queryCollection.GetQueryString(); + } + + var response = await RequestStringWithCookiesAndRetry(queryUrl); try { @@ -111,6 +294,9 @@ namespace Jackett.Indexers release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(9).InnerText); release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(10).InnerText) + release.Seeders; + var cat = row.ChildElements.ElementAt(0).ChildElements.ElementAt(0).GetAttribute("href").Replace("browse.php?", string.Empty); + release.Category = MapTrackerResultCatToNewznab(cat); + releases.Add(release); } } @@ -120,5 +306,19 @@ namespace Jackett.Indexers } return releases; } + + protected int MapTrackerResultCatToNewznab(string input) + { + if (null != input) + { + input = input.ToLowerInvariant(); + var mapping = resultMapping.Where(m => m.TrackerCategory.ToLowerInvariant() == input.ToLowerInvariant()).FirstOrDefault(); + if (mapping != null) + { + return mapping.NewzNabCategory; + } + } + return 0; + } } } diff --git a/src/Jackett/Indexers/PrivateHD.cs b/src/Jackett/Indexers/PrivateHD.cs index 99009c507d94225780eb6c2be66a91ffad8a457d..6bb387ca12084fd5edac188864be7dca66b44200 100644 --- a/src/Jackett/Indexers/PrivateHD.cs +++ b/src/Jackett/Indexers/PrivateHD.cs @@ -18,116 +18,18 @@ using Jackett.Models.IndexerConfig; namespace Jackett.Indexers { - public class PrivateHD : BaseIndexer, IIndexer + public class PrivateHD : AvistazTracker, IIndexer { - private string LoginUrl { get { return SiteLink + "auth/login"; } } - private string SearchUrl { get { return SiteLink + "torrents?in=1&type={0}&search={1}"; } } - - new ConfigurationDataBasicLogin configData - { - get { return (ConfigurationDataBasicLogin)base.configData; } - set { base.configData = value; } - } - - public PrivateHD(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps) + public PrivateHD(IIndexerManagerService indexerManager, IWebClient webClient, Logger logger, IProtectionService protectionService) : base(name: "PrivateHD", - description: "BitTorrent site for High Quality, High Definition (HD) movies and TV Shows", + desc: "BitTorrent site for High Quality, High Definition (HD) movies and TV Shows", link: "https://privatehd.to/", - caps: TorznabUtil.CreateDefaultTorznabTVCaps(), - manager: i, - client: wc, - logger: l, - p: ps, - configData: new ConfigurationDataBasicLogin()) - { - AddCategoryMapping(1, TorznabCatType.Movies); - AddCategoryMapping(1, TorznabCatType.MoviesForeign); - AddCategoryMapping(1, TorznabCatType.MoviesHD); - AddCategoryMapping(1, TorznabCatType.MoviesSD); - AddCategoryMapping(2, TorznabCatType.TV); - AddCategoryMapping(3, TorznabCatType.Audio); - } - - public async Task ApplyConfiguration(JToken configJson) - { - configData.LoadValuesFromJson(configJson); - var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty); - var token = new Regex("Avz.CSRF_TOKEN = '(.*?)';").Match(loginPage.Content).Groups[1].ToString(); - var pairs = new Dictionary<string, string> { - { "_token", token }, - { "username_email", configData.Username.Value }, - { "password", configData.Password.Value }, - { "remember", "on" } - }; - - var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, null, LoginUrl); - await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("auth/logout"), () => - { - CQ dom = result.Content; - var messageEl = dom[".form-error"]; - var errorMessage = messageEl.Text().Trim(); - throw new ExceptionWithConfigData(errorMessage, configData); - }); - } - - public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) + indexerManager: indexerManager, + logger: logger, + protectionService: protectionService, + webClient: webClient + ) { - var releases = new List<ReleaseInfo>(); - - var categoryMapping = MapTorznabCapsToTrackers(query).Distinct(); - string category = "0"; // Aka all - if (categoryMapping.Count() == 1) - { - category = categoryMapping.First(); - } - - - var episodeSearchUrl = string.Format(SearchUrl, category, HttpUtility.UrlEncode(query.GetQueryString())); - - var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl); - - try - { - CQ dom = response.Content; - var rows = dom["table > tbody > tr"]; - foreach (var row in rows) - { - CQ qRow = row.Cq(); - var release = new ReleaseInfo(); - - release.MinimumRatio = 1; - release.MinimumSeedTime = 172800; - - var qLink = row.ChildElements.ElementAt(1).FirstElementChild.Cq(); - release.Title = qLink.Text().Trim(); - release.Comments = new Uri(qLink.Attr("href")); - release.Guid = release.Comments; - - var qDownload = row.ChildElements.ElementAt(3).FirstElementChild.Cq(); - release.Link = new Uri(qDownload.Attr("href")); - - var dateStr = row.ChildElements.ElementAt(5).Cq().Text().Trim(); - release.PublishDate = DateTimeUtil.FromTimeAgo(dateStr); - - var sizeStr = row.ChildElements.ElementAt(6).Cq().Text(); - release.Size = ReleaseInfo.GetBytes(sizeStr); - - release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(8).Cq().Text()); - release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(9).Cq().Text()) + release.Seeders; - - var cat = row.Cq().Find("td:eq(0) i").First().Attr("class") - .Replace("gi gi-film", "1") - .Replace("gi gi-tv", "2") - .Replace("gi gi-music", "3"); - release.Category = MapTrackerCatToNewznab(cat); - releases.Add(release); - } - } - catch (Exception ex) - { - OnParseError(response.Content, ex); - } - return releases; } } } diff --git a/src/Jackett/Indexers/RUTor.cs b/src/Jackett/Indexers/RUTor.cs index 128847a52fb0fd5a6997bb606d275abec788af90..91b1364376b737a3fc242f3775fb0fe392be78f6 100644 --- a/src/Jackett/Indexers/RUTor.cs +++ b/src/Jackett/Indexers/RUTor.cs @@ -49,7 +49,7 @@ namespace Jackett.Indexers TorznabCaps.Categories.Add(TorznabCatType.Books); } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var oldConfig = configData; @@ -60,6 +60,8 @@ namespace Jackett.Indexers configData = oldConfig; throw new Exception("Could not find releases from this URL"); }); + + return IndexerConfigurationStatus.RequiresTesting; } diff --git a/src/Jackett/Indexers/Rarbg.cs b/src/Jackett/Indexers/Rarbg.cs new file mode 100644 index 0000000000000000000000000000000000000000..5e35e85bbd07101400ff66fcdbba375d4aa65e54 --- /dev/null +++ b/src/Jackett/Indexers/Rarbg.cs @@ -0,0 +1,187 @@ +using Jackett.Models; +using Jackett.Models.IndexerConfig; +using Jackett.Services; +using Jackett.Utils.Clients; +using Newtonsoft.Json.Linq; +using NLog; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using System.Web; + +namespace Jackett.Indexers +{ + public class Rarbg : BaseIndexer, IIndexer + { + readonly static string defaultSiteLink = "https://torrentapi.org/"; + + private Uri BaseUri + { + get { return new Uri(configData.Url.Value); } + set { configData.Url.Value = value.ToString(); } + } + + private string ApiEndpoint { get { return BaseUri + "pubapi_v2.php"; } } + private string TokenUrl { get { return ApiEndpoint + "?get_token=get_token"; } } + private string SearchUrl { get { return ApiEndpoint + "?app_id=jackett_v{0}&mode={1}&format=json_extended&search_string={2}&token={3}"; } } + + + new ConfigurationDataUrl configData + { + get { return (ConfigurationDataUrl)base.configData; } + set { base.configData = value; } + } + + private DateTime lastTokenFetch; + private string token; + + readonly TimeSpan TOKEN_DURATION = TimeSpan.FromMinutes(10); + + private bool HasValidToken { get { return !string.IsNullOrEmpty(token) && lastTokenFetch > DateTime.Now - TOKEN_DURATION; } } + + Dictionary<string, int> categoryLabels; + + public Rarbg(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps) + : base(name: "RARBG", + description: "RARBG", + link: defaultSiteLink, + caps: new TorznabCapabilities(), + manager: i, + client: wc, + logger: l, + p: ps, + configData: new ConfigurationDataUrl(defaultSiteLink)) + { + categoryLabels = new Dictionary<string, int>(); + + AddCat(4, TorznabCatType.XXX, "XXX (18+)"); + AddCat(14, TorznabCatType.MoviesSD, "Movies/XVID"); + AddCat(48, TorznabCatType.MoviesHD, "Movies/XVID/720"); + AddCat(17, TorznabCatType.MoviesSD, "Movies/x264"); + AddCat(44, TorznabCatType.MoviesHD, "Movies/x264/1080"); + AddCat(45, TorznabCatType.MoviesHD, "Movies/x264/720"); + AddCat(47, TorznabCatType.Movies3D, "Movies/x264/3D"); + AddCat(42, TorznabCatType.MoviesBluRay, "Movies/Full BD"); + AddCat(46, TorznabCatType.MoviesBluRay, "Movies/BD Remux"); + AddCat(18, TorznabCatType.TVSD, "TV Episodes"); + AddCat(41, TorznabCatType.TVHD, "TV HD Episodes"); + AddCat(23, TorznabCatType.AudioMP3, "Music/MP3"); + AddCat(25, TorznabCatType.AudioLossless, "Music/FLAC"); + AddCat(27, TorznabCatType.PCGames, "Games/PC ISO"); + AddCat(28, TorznabCatType.PCGames, "Games/PC RIP"); + AddCat(40, TorznabCatType.ConsolePS3, "Games/PS3"); + AddCat(32, TorznabCatType.ConsoleXbox360, "Games/XBOX-360"); + AddCat(33, TorznabCatType.PCISO, "Software/PC ISO"); + AddCat(35, TorznabCatType.BooksEbook, "e-Books"); + } + + void AddCat(int cat, TorznabCategory catType, string label) + { + AddCategoryMapping(cat, catType); + categoryLabels.Add(label, cat); + } + + async Task CheckToken() + { + if (!HasValidToken) + { + var result = await RequestStringWithCookiesAndRetry(TokenUrl); + var json = JObject.Parse(result.Content); + token = json.Value<string>("token"); + lastTokenFetch = DateTime.Now; + } + } + + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) + { + configData.LoadValuesFromJson(configJson); + var releases = await PerformQuery(new TorznabQuery()); + + await ConfigureIfOK(string.Empty, releases.Count() > 0, () => + { + throw new Exception("Could not find releases from this URL"); + }); + + return IndexerConfigurationStatus.Completed; + } + + public Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) + { + return PerformQuery(query, 0); + } + + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query, int attempts = 0) + { + await CheckToken(); + var releases = new List<ReleaseInfo>(); + var queryStr = HttpUtility.UrlEncode(query.GetQueryString()); + + var mode = string.IsNullOrEmpty(queryStr) ? "list" : "search"; + var episodeSearchUrl = string.Format(SearchUrl, Engine.ConfigService.GetVersion(), mode, queryStr, token); + var cats = string.Join(";", MapTorznabCapsToTrackers(query)); + if (!string.IsNullOrEmpty(cats)) + { + episodeSearchUrl += "&category=" + cats; + } + + var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl, string.Empty); + + try + { + var jsonContent = JObject.Parse(response.Content); + + int errorCode = jsonContent.Value<int>("error_code"); + if (errorCode == 20) // no results found + { + return releases.ToArray(); + } + + if (errorCode > 0) // too many requests per second + { + if (attempts < 3) + { + await Task.Delay(TimeSpan.FromSeconds(2)); + return await PerformQuery(query, ++attempts); + } + else + { + throw new Exception(jsonContent.Value<string>("error")); + } + } + + foreach (var item in jsonContent.Value<JArray>("torrent_results")) + { + var release = new ReleaseInfo(); + release.Title = item.Value<string>("title"); + release.Description = release.Title; + release.Category = MapTrackerCatToNewznab(categoryLabels[item.Value<string>("category")].ToString()); + + release.MagnetUri = new Uri(item.Value<string>("download")); + release.InfoHash = release.MagnetUri.ToString().Split(':')[3].Split('&')[0]; + + release.Comments = new Uri(item.Value<string>("info_page")); + release.Guid = release.Comments; + + // ex: 2015-08-16 21:25:08 +0000 + var dateStr = item.Value<string>("pubdate").Replace(" +0000", ""); + var dateTime = DateTime.ParseExact(dateStr, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); + release.PublishDate = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc).ToLocalTime(); + + release.Seeders = item.Value<int>("seeders"); + release.Peers = item.Value<int>("leechers") + release.Seeders; + release.Size = item.Value<long>("size"); + releases.Add(release); + } + } + catch (Exception ex) + { + OnParseError(response.Content, ex); + } + + return releases.ToArray(); + } + + } +} diff --git a/src/Jackett/Indexers/SceneAccess.cs b/src/Jackett/Indexers/SceneAccess.cs index bdad51cf31b0d9627a6b98f23847e9e147d02da6..2c595e609328b0023989f272f558215ad6e8a64b 100644 --- a/src/Jackett/Indexers/SceneAccess.cs +++ b/src/Jackett/Indexers/SceneAccess.cs @@ -40,7 +40,7 @@ namespace Jackett.Indexers { } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); @@ -60,6 +60,8 @@ namespace Jackett.Indexers var errorMessage = messageEl.Text().Trim(); throw new ExceptionWithConfigData(errorMessage, configData); }); + + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) diff --git a/src/Jackett/Indexers/SceneTime.cs b/src/Jackett/Indexers/SceneTime.cs index d03e186fcf3d2e9a02ca3c6fcd32b54cf99c8c69..e517bd323ab7680b3cbd942995f2cac13a1c159f 100644 --- a/src/Jackett/Indexers/SceneTime.cs +++ b/src/Jackett/Indexers/SceneTime.cs @@ -43,7 +43,7 @@ namespace Jackett.Indexers { } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { @@ -58,6 +58,8 @@ namespace Jackett.Indexers var errorMessage = dom["td.text"].Text().Trim(); throw new ExceptionWithConfigData(errorMessage, configData); }); + + return IndexerConfigurationStatus.RequiresTesting; } private Dictionary<string, string> GetSearchFormData(string searchString) diff --git a/src/Jackett/Indexers/ShowRSS.cs b/src/Jackett/Indexers/ShowRSS.cs index 3d29157ad025bf91ff316abd03cd4bec2cb4a9c3..58d0b52d08e6a49ba9e785deefc95b111e0d7ffc 100644 --- a/src/Jackett/Indexers/ShowRSS.cs +++ b/src/Jackett/Indexers/ShowRSS.cs @@ -49,7 +49,7 @@ namespace Jackett.Indexers { } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var releases = await PerformQuery(new TorznabQuery()); @@ -58,6 +58,8 @@ namespace Jackett.Indexers { throw new Exception("Could not find releases from this URL"); }); + + return IndexerConfigurationStatus.RequiresTesting; } // Override to load legacy config format diff --git a/src/Jackett/Indexers/SpeedCD.cs b/src/Jackett/Indexers/SpeedCD.cs index d6b7f67ca86cd5b9cb00cfcf15172b0cfb7a2cf1..8c7d456f1a90db0f35c3628daeb773b5ee023baa 100644 --- a/src/Jackett/Indexers/SpeedCD.cs +++ b/src/Jackett/Indexers/SpeedCD.cs @@ -46,7 +46,7 @@ namespace Jackett.Indexers { } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { @@ -61,6 +61,8 @@ namespace Jackett.Indexers var errorMessage = dom["h5"].First().Text().Trim(); throw new ExceptionWithConfigData(errorMessage, configData); }); + + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) diff --git a/src/Jackett/Indexers/Strike.cs b/src/Jackett/Indexers/Strike.cs index ff38173514d8ae981e7f676edc0409af57606da4..18986602ce0d43d5f0c5b220a73a42382180409e 100644 --- a/src/Jackett/Indexers/Strike.cs +++ b/src/Jackett/Indexers/Strike.cs @@ -14,6 +14,7 @@ using System.Text; using System.Threading.Tasks; using System.Web; using Jackett.Models.IndexerConfig; +using System.Collections.Specialized; namespace Jackett.Indexers { @@ -27,12 +28,12 @@ namespace Jackett.Indexers set { configData.Url.Value = value.ToString(); } } - private string SearchUrl { get { return BaseUri + "api/v2/torrents/search/?category=TV&phrase={0}"; } } + private string SearchUrl { get { return BaseUri + "api/v2/torrents/search/?phrase={0}"; } } private string DownloadUrl { get { return BaseUri + "torrents/api/download/{0}.torrent"; } } - new ConfigurationDataUrl configData + new ConfigurationDataStrike configData { - get { return (ConfigurationDataUrl)base.configData; } + get { return (ConfigurationDataStrike)base.configData; } set { base.configData = value; } } @@ -41,16 +42,37 @@ namespace Jackett.Indexers : base(name: "Strike", description: "Torrent search engine", link: defaultSiteLink, - caps: TorznabUtil.CreateDefaultTorznabTVCaps(), + caps: new TorznabCapabilities(), manager: i, client: wc, logger: l, p: ps, - configData: new ConfigurationDataUrl(defaultSiteLink)) + configData: new ConfigurationDataStrike(defaultSiteLink)) { + AddCategoryMapping("Anime", TorznabCatType.TVAnime); + AddCategoryMapping("Applications", TorznabCatType.PC); + AddCategoryMapping("Books", TorznabCatType.Books); + AddCategoryMapping("Games", TorznabCatType.PCGames); + AddCategoryMapping("Movies", TorznabCatType.Movies); + AddCategoryMapping("TV", TorznabCatType.TV); + AddCategoryMapping("XXX", TorznabCatType.XXX); + AddCategoryMapping("Music", TorznabCatType.Audio); + + /*AddCategoryMapping("Movies:Highres Movies", TorznabCatType.MoviesHD); + AddCategoryMapping("Movies:3D Movies", TorznabCatType.Movies3D); + AddCategoryMapping("Books:Ebooks", TorznabCatType.BooksEbook); + AddCategoryMapping("Books:Comics", TorznabCatType.BooksComics); + AddCategoryMapping("Books:Audio Books", TorznabCatType.AudioAudiobook); + AddCategoryMapping("Games:XBOX360", TorznabCatType.ConsoleXbox360); + AddCategoryMapping("Games:Wii", TorznabCatType.ConsoleWii); + AddCategoryMapping("Games:PSP", TorznabCatType.ConsolePSP); + AddCategoryMapping("Games:PS3", TorznabCatType.ConsolePS3); + AddCategoryMapping("Games:PC", TorznabCatType.PCGames); + AddCategoryMapping("Games:Android", TorznabCatType.PCPhoneAndroid); + AddCategoryMapping("Music:Mp3", TorznabCatType.AudioMP3);*/ } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var releases = await PerformQuery(new TorznabQuery()); @@ -59,6 +81,8 @@ namespace Jackett.Indexers { throw new Exception("Could not find releases from this URL"); }); + + return IndexerConfigurationStatus.Completed; } // Override to load legacy config format @@ -78,9 +102,18 @@ namespace Jackett.Indexers public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) { List<ReleaseInfo> releases = new List<ReleaseInfo>(); + var queryString = query.GetQueryString(); + var searchTerm = string.IsNullOrEmpty(queryString) ? DateTime.Now.Year.ToString() : queryString; + var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchTerm)); + + var trackerCategories = MapTorznabCapsToTrackers(query, mapChildrenCatsToParent: true); + + // This tracker can only search one cat at a time, otherwise search all and filter results + if (trackerCategories.Count == 1) + { + episodeSearchUrl += "&category=" + trackerCategories[0]; + } - var searchTerm = string.IsNullOrEmpty(query.SanitizedSearchTerm) ? "2015" : query.SanitizedSearchTerm; - var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(query.GetQueryString())); var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl, string.Empty); try { @@ -92,6 +125,12 @@ namespace Jackett.Indexers release.MinimumRatio = 1; release.MinimumSeedTime = 172800; + if (trackerCategories.Count > 0 && !trackerCategories.Contains((string)result["torrent_category"])) + { + continue; + } + release.Category = MapTrackerCatToNewznab((string)result["torrent_category"]); + release.Title = (string)result["torrent_title"]; release.Description = release.Title; release.Seeders = (int)result["seeds"]; diff --git a/src/Jackett/Indexers/T411.cs b/src/Jackett/Indexers/T411.cs index a0fc25573ed98e4368911791236dde663cf1f036..887ad221e17e42696a80e7ec3f00f26899d2dda2 100644 --- a/src/Jackett/Indexers/T411.cs +++ b/src/Jackett/Indexers/T411.cs @@ -81,7 +81,7 @@ namespace Jackett.Indexers return configData.ApiToken.Value; } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); @@ -99,6 +99,8 @@ namespace Jackett.Indexers { throw tokenFetchEx; }); + + return IndexerConfigurationStatus.RequiresTesting; } // Override to load legacy config format diff --git a/src/Jackett/Indexers/TVChaosUK.cs b/src/Jackett/Indexers/TVChaosUK.cs index 3d42a570b05b6abe04a9553ff43f3e1567d748be..0d439592ae55068d5a26f134186b84fde28626a9 100644 --- a/src/Jackett/Indexers/TVChaosUK.cs +++ b/src/Jackett/Indexers/TVChaosUK.cs @@ -103,7 +103,7 @@ namespace Jackett.Indexers AddCategoryMapping("HD Factual/Reality", TorznabCatType.TVHD); } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { @@ -139,6 +139,7 @@ namespace Jackett.Indexers IsConfigured = false; throw e; } + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) diff --git a/src/Jackett/Indexers/ThePirateBay.cs b/src/Jackett/Indexers/ThePirateBay.cs index a4ad92616447bf29b79a558e88d9544f9ab563a6..2135442cde2f72aca54f15fa2bc27f0c75122ee1 100644 --- a/src/Jackett/Indexers/ThePirateBay.cs +++ b/src/Jackett/Indexers/ThePirateBay.cs @@ -30,6 +30,7 @@ namespace Jackett.Indexers } private string SearchUrl { get { return BaseUri + "search/{0}/0/99/208,205"; } } + private string RecentUrl { get { return BaseUri + "recent"; } } new ConfigurationDataUrl configData { @@ -50,7 +51,7 @@ namespace Jackett.Indexers { } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var releases = await PerformQuery(new TorznabQuery()); @@ -59,6 +60,8 @@ namespace Jackett.Indexers { throw new Exception("Could not find releases from this URL"); }); + + return IndexerConfigurationStatus.Completed; } // Override to load legacy config format @@ -79,7 +82,7 @@ namespace Jackett.Indexers { var releases = new List<ReleaseInfo>(); var queryStr = HttpUtility.UrlEncode(query.GetQueryString()); - var episodeSearchUrl = string.Format(SearchUrl, queryStr); + var episodeSearchUrl = string.IsNullOrWhiteSpace(queryStr) ? RecentUrl : string.Format(SearchUrl, queryStr); var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl, string.Empty); try @@ -89,8 +92,10 @@ namespace Jackett.Indexers var rows = dom["#searchResult > tbody > tr"]; foreach (var row in rows) { - var release = new ReleaseInfo(); + if (row.ChildElements.Count() < 2) + continue; + var release = new ReleaseInfo(); CQ qRow = row.Cq(); CQ qLink = qRow.Find(".detName > .detLink").First(); @@ -110,7 +115,7 @@ namespace Jackett.Indexers var timeString = descParts[0].Split(' ')[1]; - if (timeString.Contains("mins ago")) + if (timeString.Contains(" ago")) { release.PublishDate = (DateTime.Now - TimeSpan.FromMinutes(ParseUtil.CoerceInt(timeString.Split(' ')[0]))); } diff --git a/src/Jackett/Indexers/TorrentBytes.cs b/src/Jackett/Indexers/TorrentBytes.cs index 4e161ac4e08d39a399eaee8e77fcff0fd3b0f7ed..7ee38462742fedb669e081b1b2230ae3a37371b3 100644 --- a/src/Jackett/Indexers/TorrentBytes.cs +++ b/src/Jackett/Indexers/TorrentBytes.cs @@ -71,7 +71,7 @@ namespace Jackett.Indexers AddCategoryMapping(24, TorznabCatType.XXXImageset); } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { @@ -91,6 +91,7 @@ namespace Jackett.Indexers var errorMessage = messageEl.Text().Trim(); throw new ExceptionWithConfigData(errorMessage, configData); }); + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) diff --git a/src/Jackett/Indexers/TorrentDay.cs b/src/Jackett/Indexers/TorrentDay.cs index c415117702299c2f3699b1745cd52099e5238a04..c67779d83241052b13b01c05e001afcce483886d 100644 --- a/src/Jackett/Indexers/TorrentDay.cs +++ b/src/Jackett/Indexers/TorrentDay.cs @@ -94,7 +94,7 @@ namespace Jackett.Indexers return result; } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { @@ -140,6 +140,7 @@ namespace Jackett.Indexers throw new ExceptionWithConfigData(errorMessage, configData); }); + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) diff --git a/src/Jackett/Indexers/TorrentLeech.cs b/src/Jackett/Indexers/TorrentLeech.cs index b1edcd967d556e1353bea2ccad0c5b4a3fe10bae..10171ecb39964c7e9d939ac5b49d5ad84a4c7550 100644 --- a/src/Jackett/Indexers/TorrentLeech.cs +++ b/src/Jackett/Indexers/TorrentLeech.cs @@ -79,7 +79,7 @@ namespace Jackett.Indexers AddCategoryMapping(33, TorznabCatType.PC0day); } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var pairs = new Dictionary<string, string> { @@ -97,6 +97,7 @@ namespace Jackett.Indexers var errorMessage = messageEl.Text().Trim(); throw new ExceptionWithConfigData(errorMessage, configData); }); + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) @@ -107,7 +108,7 @@ namespace Jackett.Indexers if (!string.IsNullOrWhiteSpace(searchString)) { - searchUrl += "query/" + HttpUtility.UrlEncode(searchString) + "/"; + searchUrl += "query/" + HttpUtility.UrlEncode(searchString) + "/"; } string.Format(SearchUrl, HttpUtility.UrlEncode(searchString)); @@ -160,7 +161,7 @@ namespace Jackett.Indexers release.Seeders = ParseUtil.CoerceInt(qRow.Find(".seeders").Text()); release.Peers = release.Seeders + ParseUtil.CoerceInt(qRow.Find(".leechers").Text()); - var category = qRow.Find(".category a").Attr("href").Replace("/torrents/browse/index/categories/",string.Empty); + var category = qRow.Find(".category a").Attr("href").Replace("/torrents/browse/index/categories/", string.Empty); release.Category = MapTrackerCatToNewznab(category); releases.Add(release); diff --git a/src/Jackett/Indexers/TorrentShack.cs b/src/Jackett/Indexers/TorrentShack.cs index e79b7cae6674a6a7c6f58fb89bd3ec3b8de833dc..b34daa83986fec8a69f9eebaece1af1b63fbd679 100644 --- a/src/Jackett/Indexers/TorrentShack.cs +++ b/src/Jackett/Indexers/TorrentShack.cs @@ -42,7 +42,7 @@ namespace Jackett.Indexers { } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); @@ -62,6 +62,7 @@ namespace Jackett.Indexers var errorMessage = messageEl.Text().Trim(); throw new ExceptionWithConfigData(errorMessage, configData); }); + return IndexerConfigurationStatus.RequiresTesting; } public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) diff --git a/src/Jackett/Indexers/Torrentz.cs b/src/Jackett/Indexers/Torrentz.cs index e68b5efefb4f3dad886d9943cedb3a067bea2473..ed764072633fa8c53902bbe5833232dee2a93371 100644 --- a/src/Jackett/Indexers/Torrentz.cs +++ b/src/Jackett/Indexers/Torrentz.cs @@ -50,7 +50,7 @@ namespace Jackett.Indexers { } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var releases = await PerformQuery(new TorznabQuery()); @@ -59,6 +59,7 @@ namespace Jackett.Indexers { throw new Exception("Could not find releases from this URL"); }); + return IndexerConfigurationStatus.Completed; } // Override to load legacy config format diff --git a/src/Jackett/Indexers/nCore.cs b/src/Jackett/Indexers/nCore.cs index b86176e30325d4138c8f6de36106d835240e37a7..8f49798961acbbaf22cb850e84f421cfdf9303f9 100644 --- a/src/Jackett/Indexers/nCore.cs +++ b/src/Jackett/Indexers/nCore.cs @@ -43,7 +43,7 @@ namespace Jackett.Indexers { } - public async Task ApplyConfiguration(JToken configJson) + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); @@ -69,6 +69,8 @@ namespace Jackett.Indexers var errorMessage = msgContainer != null ? msgContainer.InnerText : "Error while trying to login."; throw new ExceptionWithConfigData(errorMessage, configData); }); + + return IndexerConfigurationStatus.RequiresTesting; } List<KeyValuePair<string, string>> CreateKeyValueList(params string[][] keyValues) diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj index 3bb42874245caf9b6dbf53f64579832a53250df0..b84a20994282bd0fc50e62d5a3e4754471a83047 100644 --- a/src/Jackett/Jackett.csproj +++ b/src/Jackett/Jackett.csproj @@ -175,6 +175,8 @@ <Compile Include="Controllers\DownloadController.cs" /> <Compile Include="Engine.cs" /> <Compile Include="Indexers\AlphaRatio.cs" /> + <Compile Include="Indexers\EuTorrents.cs" /> + <Compile Include="Indexers\Avistaz.cs" /> <Compile Include="Indexers\BakaBT.cs" /> <Compile Include="Indexers\BaseIndexer.cs" /> <Compile Include="Indexers\BB.cs" /> @@ -189,7 +191,9 @@ <Compile Include="Indexers\IIndexer.cs" /> <Compile Include="Indexers\ImmortalSeed.cs" /> <Compile Include="Indexers\FileList.cs" /> - <Compile Include="Indexers\ManualSearchResult.cs" /> + <Compile Include="Indexers\Abstract\AvistazTracker.cs" /> + <Compile Include="Models\ManualSearchResult.cs" /> + <Compile Include="Indexers\Rarbg.cs" /> <Compile Include="Indexers\TVChaosUK.cs" /> <Compile Include="Indexers\NCore.cs" /> <Compile Include="Indexers\RuTor.cs" /> @@ -214,6 +218,7 @@ <Compile Include="Models\CachedResult.cs" /> <Compile Include="Models\CategoryMapping.cs" /> <Compile Include="Models\AdminSearch.cs" /> + <Compile Include="Models\IndexerConfigurationStatus.cs" /> <Compile Include="Models\IndexerConfig\ConfigurationDataFileList.cs" /> <Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginWithRSS.cs" /> <Compile Include="Models\IndexerConfig\ConfigurationDataRecaptchaLogin.cs" /> @@ -221,6 +226,7 @@ <Compile Include="Models\IndexerConfig\ConfigurationDataNCore.cs" /> <Compile Include="Models\IndexerConfig\ConfigurationDataCaptchaLogin.cs" /> <Compile Include="Models\IndexerConfig\ConfigurationDataAnimeBytes.cs" /> + <Compile Include="Models\IndexerConfig\ConfigurationDataStrike.cs" /> <Compile Include="Models\IndexerConfig\ConfigurationDataUrl.cs" /> <Compile Include="Models\IndexerConfig\ISerializableConfig.cs" /> <Compile Include="Models\IndexerConfig\ConfigurationDataPinNumber.cs" /> @@ -402,6 +408,9 @@ <Content Include="Content\logos\animebytes.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> + <Content Include="Content\logos\avistaz.png"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> <Content Include="Content\logos\bakabt.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> @@ -414,6 +423,9 @@ <Content Include="Content\logos\demonoid.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> + <Content Include="Content\logos\eutorrents.png"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> <Content Include="Content\logos\filelist.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> @@ -438,6 +450,9 @@ <Content Include="Content\logos\privatehd.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> + <Content Include="Content\logos\rarbg.png"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> <Content Include="Content\logos\rutor.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> diff --git a/src/Jackett/Models/CategoryMapping.cs b/src/Jackett/Models/CategoryMapping.cs index 6ab314c4841e84d2dbacc4d926ad0377c8a0d76d..3fd22a3e793612bc7c46b0a5b935655cec8a4c3b 100644 --- a/src/Jackett/Models/CategoryMapping.cs +++ b/src/Jackett/Models/CategoryMapping.cs @@ -10,7 +10,7 @@ namespace Jackett.Models { public CategoryMapping(string trackerCat, int newzCat) { - TrackerCategory = trackerCat.ToLowerInvariant(); + TrackerCategory = trackerCat; NewzNabCategory = newzCat; } diff --git a/src/Jackett/Models/IndexerConfig/ConfigurationDataStrike.cs b/src/Jackett/Models/IndexerConfig/ConfigurationDataStrike.cs new file mode 100644 index 0000000000000000000000000000000000000000..124ed0c2c2cd43cc2e964e8155556dd57d0587e8 --- /dev/null +++ b/src/Jackett/Models/IndexerConfig/ConfigurationDataStrike.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Models.IndexerConfig +{ + public class ConfigurationDataStrike : ConfigurationDataUrl + { + public DisplayItem StrikeWarning { get; private set; } + + public ConfigurationDataStrike(string url) : base(url) + { + StrikeWarning = new DisplayItem("This indexer does not support RSS Sync, only Search") { Name = "Warning" }; + } + } +} diff --git a/src/Jackett/Models/IndexerConfigurationStatus.cs b/src/Jackett/Models/IndexerConfigurationStatus.cs new file mode 100644 index 0000000000000000000000000000000000000000..2c232f7c94eeb113967e31902d3f295281988cd9 --- /dev/null +++ b/src/Jackett/Models/IndexerConfigurationStatus.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Models +{ + public enum IndexerConfigurationStatus + { + Completed, + RequiresTesting, + Failed + } +} diff --git a/src/Jackett/Indexers/ManualSearchResult.cs b/src/Jackett/Models/ManualSearchResult.cs similarity index 91% rename from src/Jackett/Indexers/ManualSearchResult.cs rename to src/Jackett/Models/ManualSearchResult.cs index 0831640bf25f5dd2c2408118ff389feb25e772c1..59e1a35bd1abdf6db14decc346834773b3b20bd0 100644 --- a/src/Jackett/Indexers/ManualSearchResult.cs +++ b/src/Jackett/Models/ManualSearchResult.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Jackett.Indexers +namespace Jackett { public class ManualSearchResult { diff --git a/src/Jackett/Properties/AssemblyInfo.cs b/src/Jackett/Properties/AssemblyInfo.cs index 6a6c92c39187f398ef8594c0f3484ebeb7fdbd51..6e876de18719b8a255a1389051a79b0ee9dc6a1c 100644 --- a/src/Jackett/Properties/AssemblyInfo.cs +++ b/src/Jackett/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // 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.2.0")] -[assembly: AssemblyFileVersion("0.6.2.0")] +[assembly: AssemblyVersion("0.6.3.0")] +[assembly: AssemblyFileVersion("0.6.3.0")] diff --git a/src/Jackett/Utils/BrowserUtil.cs b/src/Jackett/Utils/BrowserUtil.cs index cc23d2baa2f59b375914b00598ef23bced72b445..326a6c7da9564f41d657e8770be6568ddeff4395 100644 --- a/src/Jackett/Utils/BrowserUtil.cs +++ b/src/Jackett/Utils/BrowserUtil.cs @@ -10,7 +10,16 @@ namespace Jackett.Utils { public static string ChromeUserAgent { - get { return "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36"; } + get { + if (System.Environment.OSVersion.Platform == PlatformID.Unix) + { + return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chrome/44.0.2403.155 Safari/537.36"; + } + else + { + return "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.155 Safari/537.36"; + } + } } } } diff --git a/src/Website/App_Data/web.db b/src/Website/App_Data/web.db new file mode 100644 index 0000000000000000000000000000000000000000..ef741b127f41f7ea9a44b4124a4f09ae19b1be2c Binary files /dev/null and b/src/Website/App_Data/web.db differ diff --git a/src/Website/Content/libs/jquery.timeago.js b/src/Website/Content/libs/jquery.timeago.js new file mode 100644 index 0000000000000000000000000000000000000000..15805a61935cd5fdc252015bc9446d95326dbbef --- /dev/null +++ b/src/Website/Content/libs/jquery.timeago.js @@ -0,0 +1,221 @@ +/** + * Timeago is a jQuery plugin that makes it easy to support automatically + * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago"). + * + * @name timeago + * @version 1.4.1 + * @requires jQuery v1.2.3+ + * @author Ryan McGeary + * @license MIT License - http://www.opensource.org/licenses/mit-license.php + * + * For usage and examples, visit: + * http://timeago.yarp.com/ + * + * Copyright (c) 2008-2015, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org) + */ + +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else { + // Browser globals + factory(jQuery); + } +}(function ($) { + $.timeago = function(timestamp) { + if (timestamp instanceof Date) { + return inWords(timestamp); + } else if (typeof timestamp === "string") { + return inWords($.timeago.parse(timestamp)); + } else if (typeof timestamp === "number") { + return inWords(new Date(timestamp)); + } else { + return inWords($.timeago.datetime(timestamp)); + } + }; + var $t = $.timeago; + + $.extend($.timeago, { + settings: { + refreshMillis: 60000, + allowPast: true, + allowFuture: false, + localeTitle: false, + cutoff: 0, + strings: { + prefixAgo: null, + prefixFromNow: null, + suffixAgo: "ago", + suffixFromNow: "from now", + inPast: 'any moment now', + seconds: "less than a minute", + minute: "about a minute", + minutes: "%d minutes", + hour: "about an hour", + hours: "about %d hours", + day: "a day", + days: "%d days", + month: "about a month", + months: "%d months", + year: "about a year", + years: "%d years", + wordSeparator: " ", + numbers: [] + } + }, + + inWords: function(distanceMillis) { + if(!this.settings.allowPast && ! this.settings.allowFuture) { + throw 'timeago allowPast and allowFuture settings can not both be set to false.'; + } + + var $l = this.settings.strings; + var prefix = $l.prefixAgo; + var suffix = $l.suffixAgo; + if (this.settings.allowFuture) { + if (distanceMillis < 0) { + prefix = $l.prefixFromNow; + suffix = $l.suffixFromNow; + } + } + + if(!this.settings.allowPast && distanceMillis >= 0) { + return this.settings.strings.inPast; + } + + var seconds = Math.abs(distanceMillis) / 1000; + var minutes = seconds / 60; + var hours = minutes / 60; + var days = hours / 24; + var years = days / 365; + + function substitute(stringOrFunction, number) { + var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction; + var value = ($l.numbers && $l.numbers[number]) || number; + return string.replace(/%d/i, value); + } + + var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) || + seconds < 90 && substitute($l.minute, 1) || + minutes < 45 && substitute($l.minutes, Math.round(minutes)) || + minutes < 90 && substitute($l.hour, 1) || + hours < 24 && substitute($l.hours, Math.round(hours)) || + hours < 42 && substitute($l.day, 1) || + days < 30 && substitute($l.days, Math.round(days)) || + days < 45 && substitute($l.month, 1) || + days < 365 && substitute($l.months, Math.round(days / 30)) || + years < 1.5 && substitute($l.year, 1) || + substitute($l.years, Math.round(years)); + + var separator = $l.wordSeparator || ""; + if ($l.wordSeparator === undefined) { separator = " "; } + return $.trim([prefix, words, suffix].join(separator)); + }, + + parse: function(iso8601) { + var s = $.trim(iso8601); + s = s.replace(/\.\d+/,""); // remove milliseconds + s = s.replace(/-/,"/").replace(/-/,"/"); + s = s.replace(/T/," ").replace(/Z/," UTC"); + s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400 + s = s.replace(/([\+\-]\d\d)$/," $100"); // +09 -> +0900 + return new Date(s); + }, + datetime: function(elem) { + var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title"); + return $t.parse(iso8601); + }, + isTime: function(elem) { + // jQuery's `is()` doesn't play well with HTML5 in IE + return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time"); + } + }); + + // functions that can be called via $(el).timeago('action') + // init is default when no action is given + // functions are called with context of a single element + var functions = { + init: function(){ + var refresh_el = $.proxy(refresh, this); + refresh_el(); + var $s = $t.settings; + if ($s.refreshMillis > 0) { + this._timeagoInterval = setInterval(refresh_el, $s.refreshMillis); + } + }, + update: function(time){ + var parsedTime = $t.parse(time); + $(this).data('timeago', { datetime: parsedTime }); + if($t.settings.localeTitle) $(this).attr("title", parsedTime.toLocaleString()); + refresh.apply(this); + }, + updateFromDOM: function(){ + $(this).data('timeago', { datetime: $t.parse( $t.isTime(this) ? $(this).attr("datetime") : $(this).attr("title") ) }); + refresh.apply(this); + }, + dispose: function () { + if (this._timeagoInterval) { + window.clearInterval(this._timeagoInterval); + this._timeagoInterval = null; + } + } + }; + + $.fn.timeago = function(action, options) { + var fn = action ? functions[action] : functions.init; + if(!fn){ + throw new Error("Unknown function name '"+ action +"' for timeago"); + } + // each over objects here and call the requested function + this.each(function(){ + fn.call(this, options); + }); + return this; + }; + + function refresh() { + //check if it's still visible + if(!$.contains(document.documentElement,this)){ + //stop if it has been removed + $(this).timeago("dispose"); + return this; + } + + var data = prepareData(this); + var $s = $t.settings; + + if (!isNaN(data.datetime)) { + if ( $s.cutoff == 0 || Math.abs(distance(data.datetime)) < $s.cutoff) { + $(this).text(inWords(data.datetime)); + } + } + return this; + } + + function prepareData(element) { + element = $(element); + if (!element.data("timeago")) { + element.data("timeago", { datetime: $t.datetime(element) }); + var text = $.trim(element.text()); + if ($t.settings.localeTitle) { + element.attr("title", element.data('timeago').datetime.toLocaleString()); + } else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) { + element.attr("title", text); + } + } + return element.data("timeago"); + } + + function inWords(date) { + return $t.inWords(distance(date)); + } + + function distance(date) { + return (new Date().getTime() - date.getTime()); + } + + // fix for IE6 suckage + document.createElement("abbr"); + document.createElement("time"); +})); diff --git a/src/Website/Scripts/jquery.timeago.js b/src/Website/Scripts/jquery.timeago.js new file mode 100644 index 0000000000000000000000000000000000000000..15805a61935cd5fdc252015bc9446d95326dbbef --- /dev/null +++ b/src/Website/Scripts/jquery.timeago.js @@ -0,0 +1,221 @@ +/** + * Timeago is a jQuery plugin that makes it easy to support automatically + * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago"). + * + * @name timeago + * @version 1.4.1 + * @requires jQuery v1.2.3+ + * @author Ryan McGeary + * @license MIT License - http://www.opensource.org/licenses/mit-license.php + * + * For usage and examples, visit: + * http://timeago.yarp.com/ + * + * Copyright (c) 2008-2015, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org) + */ + +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else { + // Browser globals + factory(jQuery); + } +}(function ($) { + $.timeago = function(timestamp) { + if (timestamp instanceof Date) { + return inWords(timestamp); + } else if (typeof timestamp === "string") { + return inWords($.timeago.parse(timestamp)); + } else if (typeof timestamp === "number") { + return inWords(new Date(timestamp)); + } else { + return inWords($.timeago.datetime(timestamp)); + } + }; + var $t = $.timeago; + + $.extend($.timeago, { + settings: { + refreshMillis: 60000, + allowPast: true, + allowFuture: false, + localeTitle: false, + cutoff: 0, + strings: { + prefixAgo: null, + prefixFromNow: null, + suffixAgo: "ago", + suffixFromNow: "from now", + inPast: 'any moment now', + seconds: "less than a minute", + minute: "about a minute", + minutes: "%d minutes", + hour: "about an hour", + hours: "about %d hours", + day: "a day", + days: "%d days", + month: "about a month", + months: "%d months", + year: "about a year", + years: "%d years", + wordSeparator: " ", + numbers: [] + } + }, + + inWords: function(distanceMillis) { + if(!this.settings.allowPast && ! this.settings.allowFuture) { + throw 'timeago allowPast and allowFuture settings can not both be set to false.'; + } + + var $l = this.settings.strings; + var prefix = $l.prefixAgo; + var suffix = $l.suffixAgo; + if (this.settings.allowFuture) { + if (distanceMillis < 0) { + prefix = $l.prefixFromNow; + suffix = $l.suffixFromNow; + } + } + + if(!this.settings.allowPast && distanceMillis >= 0) { + return this.settings.strings.inPast; + } + + var seconds = Math.abs(distanceMillis) / 1000; + var minutes = seconds / 60; + var hours = minutes / 60; + var days = hours / 24; + var years = days / 365; + + function substitute(stringOrFunction, number) { + var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction; + var value = ($l.numbers && $l.numbers[number]) || number; + return string.replace(/%d/i, value); + } + + var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) || + seconds < 90 && substitute($l.minute, 1) || + minutes < 45 && substitute($l.minutes, Math.round(minutes)) || + minutes < 90 && substitute($l.hour, 1) || + hours < 24 && substitute($l.hours, Math.round(hours)) || + hours < 42 && substitute($l.day, 1) || + days < 30 && substitute($l.days, Math.round(days)) || + days < 45 && substitute($l.month, 1) || + days < 365 && substitute($l.months, Math.round(days / 30)) || + years < 1.5 && substitute($l.year, 1) || + substitute($l.years, Math.round(years)); + + var separator = $l.wordSeparator || ""; + if ($l.wordSeparator === undefined) { separator = " "; } + return $.trim([prefix, words, suffix].join(separator)); + }, + + parse: function(iso8601) { + var s = $.trim(iso8601); + s = s.replace(/\.\d+/,""); // remove milliseconds + s = s.replace(/-/,"/").replace(/-/,"/"); + s = s.replace(/T/," ").replace(/Z/," UTC"); + s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400 + s = s.replace(/([\+\-]\d\d)$/," $100"); // +09 -> +0900 + return new Date(s); + }, + datetime: function(elem) { + var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title"); + return $t.parse(iso8601); + }, + isTime: function(elem) { + // jQuery's `is()` doesn't play well with HTML5 in IE + return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time"); + } + }); + + // functions that can be called via $(el).timeago('action') + // init is default when no action is given + // functions are called with context of a single element + var functions = { + init: function(){ + var refresh_el = $.proxy(refresh, this); + refresh_el(); + var $s = $t.settings; + if ($s.refreshMillis > 0) { + this._timeagoInterval = setInterval(refresh_el, $s.refreshMillis); + } + }, + update: function(time){ + var parsedTime = $t.parse(time); + $(this).data('timeago', { datetime: parsedTime }); + if($t.settings.localeTitle) $(this).attr("title", parsedTime.toLocaleString()); + refresh.apply(this); + }, + updateFromDOM: function(){ + $(this).data('timeago', { datetime: $t.parse( $t.isTime(this) ? $(this).attr("datetime") : $(this).attr("title") ) }); + refresh.apply(this); + }, + dispose: function () { + if (this._timeagoInterval) { + window.clearInterval(this._timeagoInterval); + this._timeagoInterval = null; + } + } + }; + + $.fn.timeago = function(action, options) { + var fn = action ? functions[action] : functions.init; + if(!fn){ + throw new Error("Unknown function name '"+ action +"' for timeago"); + } + // each over objects here and call the requested function + this.each(function(){ + fn.call(this, options); + }); + return this; + }; + + function refresh() { + //check if it's still visible + if(!$.contains(document.documentElement,this)){ + //stop if it has been removed + $(this).timeago("dispose"); + return this; + } + + var data = prepareData(this); + var $s = $t.settings; + + if (!isNaN(data.datetime)) { + if ( $s.cutoff == 0 || Math.abs(distance(data.datetime)) < $s.cutoff) { + $(this).text(inWords(data.datetime)); + } + } + return this; + } + + function prepareData(element) { + element = $(element); + if (!element.data("timeago")) { + element.data("timeago", { datetime: $t.datetime(element) }); + var text = $.trim(element.text()); + if ($t.settings.localeTitle) { + element.attr("title", element.data('timeago').datetime.toLocaleString()); + } else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) { + element.attr("title", text); + } + } + return element.data("timeago"); + } + + function inWords(date) { + return $t.inWords(distance(date)); + } + + function distance(date) { + return (new Date().getTime() - date.getTime()); + } + + // fix for IE6 suckage + document.createElement("abbr"); + document.createElement("time"); +})); diff --git a/src/Website/Views/Download/Index.cshtml b/src/Website/Views/Download/Index.cshtml index a3cb09d124e88dbe44f27fd7c5cebbbbd4e711c6..51018c5defb8637feeaf90adcddb36a5474b8d22 100644 --- a/src/Website/Views/Download/Index.cshtml +++ b/src/Website/Views/Download/Index.cshtml @@ -9,6 +9,7 @@ { <hr /> <h4><a href="@release.Url">@release.Title - @release.Version</a></h4> + <h5>Published <abbr class="timeago" title="@release.When.ToString("s")Z"></abbr></h5> var files = Website.Services.FileService.FindFilesForRelease(release.Version); diff --git a/src/Website/Views/Shared/_Layout.cshtml b/src/Website/Views/Shared/_Layout.cshtml index 58497ea4d9277065f6b4bbdcb7566b1ec1416cd1..d7373beccc1ff9957ba4d7f2f91433a7ba2a3ca1 100644 --- a/src/Website/Views/Shared/_Layout.cshtml +++ b/src/Website/Views/Shared/_Layout.cshtml @@ -8,6 +8,7 @@ <script src="~/content/libs/handlebars.min.js"></script> <script src="~/content/libs/moment.min.js"></script> <script src="~/content/libs/handlebarsmoment.js"></script> + <script src="~/Content/libs/jquery.timeago.js"></script> <script src="~/content/bootstrap/bootstrap.min.js"></script> <script src="~/content/libs/bootstrap-notify.js"></script> <link href="~/content/bootstrap/bootstrap.min.css" rel="stylesheet"> @@ -36,5 +37,12 @@ <!--@Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/bootstrap") @RenderSection("scripts", required: false)--> + + <script> + $(document).ready(function () { + $("abbr.timeago").timeago(); + }); + </script> + </body> </html> \ No newline at end of file diff --git a/src/Website/Web.config b/src/Website/Web.config index 449a642363ef719257a0f854e8aefcb998f18788..f150b78f164de45c9715649a242cedd2f76564d2 100644 --- a/src/Website/Web.config +++ b/src/Website/Web.config @@ -31,7 +31,7 @@ <httpRuntime targetFramework="4.5" /> </system.web> <system.webServer> - <modules runAllManagedModulesForAllRequests="true"> + <modules runAllManagedModulesForAllRequests="true"> <remove name="FormsAuthentication" /> </modules> diff --git a/src/Website/Website.csproj b/src/Website/Website.csproj index d0872dadcbb5522831cc789d1b678687a4b5b6da..0a14a684eb79dac88dce204f2e3c8212f6ea6c8d 100644 --- a/src/Website/Website.csproj +++ b/src/Website/Website.csproj @@ -293,6 +293,9 @@ <Content Include="Content\libs\jquery.min.js"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> + <Content Include="Content\libs\jquery.timeago.js"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> <Content Include="Content\libs\moment.min.js"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> @@ -345,6 +348,7 @@ <None Include="Scripts\jquery-2.1.4.intellisense.js" /> <Content Include="Scripts\jquery-2.1.4.js" /> <Content Include="Scripts\jquery-2.1.4.min.js" /> + <Content Include="Scripts\jquery.timeago.js" /> <Content Include="Scripts\modernizr-2.6.2.js" /> <Content Include="Scripts\modernizr-2.8.3.js" /> <Content Include="Scripts\respond.js" /> diff --git a/src/Website/packages.config b/src/Website/packages.config index 4215a3659433ed30594cec19b56f0ca27403ded0..92b6ef17047be4dbe14bae17b2d0de6095f1fca8 100644 --- a/src/Website/packages.config +++ b/src/Website/packages.config @@ -4,6 +4,7 @@ <package id="bootstrap" version="3.3.5" targetFramework="net45" /> <package id="CommonMark.NET" version="0.9.1" targetFramework="net45" /> <package id="jQuery" version="2.1.4" targetFramework="net45" /> + <package id="jQuery.TimeAgo" version="1.1.01" targetFramework="net45" /> <package id="LiteDB" version="1.0.2" targetFramework="net45" /> <package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net45" /> <package id="Microsoft.AspNet.Identity.Owin" version="2.2.1" targetFramework="net45" />