mirror of https://github.com/Jackett/Jackett
commit
7bd3c9a541
19
README.md
19
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
|
||||
|
||||
![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 |
|
@ -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()
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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])));
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Jackett.Models
|
|||
{
|
||||
public CategoryMapping(string trackerCat, int newzCat)
|
||||
{
|
||||
TrackerCategory = trackerCat.ToLowerInvariant();
|
||||
TrackerCategory = trackerCat;
|
||||
NewzNabCategory = newzCat;
|
||||
}
|
||||
|
||||
|
|
|
@ -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" };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
namespace Jackett
|
||||
{
|
||||
public class ManualSearchResult
|
||||
{
|
|
@ -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")]
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -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");
|
||||
}));
|
|
@ -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");
|
||||
}));
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -31,7 +31,7 @@
|
|||
<httpRuntime targetFramework="4.5" />
|
||||
</system.web>
|
||||
<system.webServer>
|
||||
<modules runAllManagedModulesForAllRequests="true">
|
||||
<modules runAllManagedModulesForAllRequests="true">
|
||||
<remove name="FormsAuthentication" />
|
||||
</modules>
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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" />
|
||||
|
|
Loading…
Reference in New Issue