Merge pull request #148 from zone117x/develop

Merge v0.6.3
This commit is contained in:
KZ 2015-08-23 22:21:06 +01:00
commit 7bd3c9a541
59 changed files with 1463 additions and 237 deletions

View File

@ -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
![screenshot](http://i.imgur.com/t1sVva6.png "screenshot")
![screenshot](http://i.imgur.com/t1sVva6.png "screenshot")

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -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()
};

View File

@ -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;
}
}
}

View File

@ -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)

View File

@ -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

View File

@ -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
)
{
}
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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);
}

View File

@ -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(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
);
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);
}
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");
}

View File

@ -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);

View File

@ -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)

View File

@ -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)

View File

@ -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
)
{
}
}
}

View File

@ -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;

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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);
}
public async Task ApplyConfiguration(JToken configJson)
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<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;
}
}
}

View File

@ -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())
indexerManager: indexerManager,
logger: logger,
protectionService: protectionService,
webClient: webClient
)
{
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)
{
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;
}
}
}

View File

@ -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;
}

View File

@ -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();
}
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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"];

View File

@ -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

View File

@ -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)

View File

@ -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])));
}

View File

@ -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)

View File

@ -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)

View File

@ -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);

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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>

View File

@ -10,7 +10,7 @@ namespace Jackett.Models
{
public CategoryMapping(string trackerCat, int newzCat)
{
TrackerCategory = trackerCat.ToLowerInvariant();
TrackerCategory = trackerCat;
NewzNabCategory = newzCat;
}

View File

@ -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" };
}
}
}

View File

@ -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
}
}

View File

@ -5,7 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jackett.Indexers
namespace Jackett
{
public class ManualSearchResult
{

View File

@ -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")]

View File

@ -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";
}
}
}
}
}

BIN
src/Website/App_Data/web.db Normal file

Binary file not shown.

View File

@ -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");
}));

View File

@ -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");
}));

View File

@ -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);

View File

@ -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>

View File

@ -31,7 +31,7 @@
<httpRuntime targetFramework="4.5" />
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<modules runAllManagedModulesForAllRequests="true">
<remove name="FormsAuthentication" />
</modules>

View File

@ -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" />

View File

@ -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" />