Compare commits

..

16 Commits

Author SHA1 Message Date
chibidev
c2f11306b1 Fixing SupportsCategories (or at least making it better)
I cannot wrap my head around why it has started to fail now as it was
faulty from the beginning... Anyway, SupportsCategories didn't really
take sub categories into account, which is bad.

- Potentially fixes #1654 and #1656
2017-08-14 10:38:51 +02:00
JigSaw
07a0c2c828 fix(indexers): fixed wihd, added ssl support and misc things (#1666)
* docs(config): add warning on wihd config to use classic view only

* refactor(wihd): refactor indexer, optimized dev mode, added freeleech and SSL

* fix(indexers): removed T411 orginal tracker

closed tracker by gov

* refactor(clean): removed old orphan config files

* docs(readme): updated for wihd
2017-08-14 02:10:50 +02:00
JigSaw
28eaa637df fix(indexers): Abn Indexer, Added 4K to Xthor (#1665)
* feat(xthor): added 4K category

* fix(abn): optimized abnormal indexer, fixed scraping for pending and dev mode
2017-08-14 00:15:11 +02:00
JigSaw
8707e6b2e9 fix(indexers): fixed and optimized xthor indexer
* feat(utils): added sha1 hash function and refactored md5 hash function

* fix(indexers): now use cross plateform path building for dev mode

* fix(indexers): fix output log of xthor for dev mode

* feat(release): added ToString method

* refactor(dev): optimized dev mode

* style(clean): cleanup code

* feat(indexer): added tmdb info to releases

* fix(cat): enabled categories on xthor
2017-08-13 20:49:03 +02:00
chibidev
7a8d83b693 Allow anonymous access to results - probably fixes #1654 2017-08-13 02:23:39 +02:00
chibidev
403c0ef7e4 Correct API path in JS client 2017-08-12 20:12:30 +02:00
chibidev
3374c14311 Fix download handling in AggregateIndexer 2017-08-12 11:12:02 +02:00
chibidev
9eade51d89 Increase Sonarr compatbility 2017-08-12 10:46:12 +02:00
David Torosyan
bc9e4a30cb Fix thepiratebay.xml for General Hospital (#1647)
* Fix thepiratebay.xml for General Hospital

* Add comment
2017-08-12 16:19:41 +10:00
flightlevel
0103c48c97 Fix settings update from UI (#1655)
https://github.com/Jackett/Jackett/issues/1654
2017-08-12 11:12:56 +10:00
chibidev
29ecf8a584 "Fix" ShowRSS category handling 2017-08-12 01:59:50 +02:00
chibidev
eec07bc5b8 Fix manual search - append apikey 2017-08-12 01:04:30 +02:00
chibidev
bf23878477 Manual search URL fix - hash will reset now 2017-08-12 01:04:07 +02:00
chibidev
043085e8f3 Fix torznab capability API 2017-08-12 00:30:43 +02:00
chibidev
39e612d60c Move debug data to appropriate log level 2017-08-11 23:55:55 +02:00
chibidev
6f8b1b749d Fix default torznab query 2017-08-11 23:55:40 +02:00
24 changed files with 359 additions and 661 deletions

View File

@@ -227,7 +227,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
* Ultimate Gamer Club
* ULTRAHDCLUB
* Waffles
* World-In-HD [![(invite needed)][inviteneeded]](#)
* World-In-HD
* WorldOfP2P
* x264
* XSpeeds

View File

@@ -33,9 +33,12 @@ $(document).ready(function () {
return opts.inverse(this);
});
var index = window.location.pathname.indexOf("/UI");
var pathPrefix = window.location.pathname.substr(0, index);
api.root = pathPrefix + api.root;
bindUIButtons();
loadJackettSettings();
openSearchIfNecessary();
});
function openSearchIfNecessary() {
@@ -71,6 +74,8 @@ function loadJackettSettings() {
basePath = '';
}
api.key = data.api_key;
$("#jackett-savedir").val(data.blackholedir);
$("#jackett-allowext").attr('checked', data.external);
$("#jackett-allowupdate").attr('checked', data.updatedisabled);
@@ -135,6 +140,7 @@ function reloadIndexers() {
}
displayConfiguredIndexersList(configuredIndexers);
$('#indexers div.dataTables_filter input').focusWithoutScrolling();
openSearchIfNecessary();
}).fail(function () {
doNotify("Error loading indexers, request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
});
@@ -720,6 +726,7 @@ function showSearch(selectedIndexer, query) {
releaseDialog.on('hidden.bs.modal', function (e) {
$('#indexers div.dataTables_filter input').focusWithoutScrolling();
window.location.hash = '';
}) ;
var setCategories = function (tracker, items) {
@@ -1087,7 +1094,7 @@ function bindUIButtons() {
basepathoverride: jackett_basepathoverride,
omdbkey: jackett_omdb_key
};
api.updateServerConfig(function (data) {
api.updateServerConfig(jsonObject, function (data) {
if (data !== undefined && data.result == "error") {
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
return;

View File

@@ -1,6 +1,7 @@
var api = {
version: "2.0",
root: "/api",
key: "",
getApiPath: function(category, action) {
var path = this.root + "/v" + this.version + "/" + category;
@@ -47,7 +48,7 @@ var api = {
},
resultsForIndexer: function(indexerId, query, callback) {
return $.get(this.getApiPath("indexers", indexerId + "/results"), query, callback);
return $.get(this.getApiPath("indexers", indexerId + "/results?apikey=" + this.key), query, callback);
},
getServerCache: function(callback) {

View File

@@ -132,7 +132,7 @@ namespace Jackett.Controllers.V20
TorznabQuery CurrentQuery { get; set; }
}
[JackettAuthorized]
[AllowAnonymous]
[JackettAPINoCache]
[RoutePrefix("api/v2.0/indexers")]
[RequiresApiKey]

View File

@@ -73,7 +73,12 @@
settings: []
search:
path: "{{if .Query.Keywords}}/search/{{ .Query.Keywords}}/0/99/{{range .Categories }}{{.}}{{end}}{{else}}/recent{{end}}"
path: "{{if .Query.Keywords}}/search/{{ .Keywords}}/0/99/{{range .Categories }}{{.}}{{end}}{{else}}/recent{{end}}"
keywordsfilters:
# currently, the only uploader for General Hospital puts a space between season and episode
# this filter searches both formats, so "General Hospital S01E02" becomes "General Hospital S01E02 | (S01 E02)"
- name: re_replace
args: ["General Hospital S(\\d{2,3})E(\\d{2,3})", "$0 | \\(S$1 E$2\\)"]
rows:
selector: "#searchResult tbody tr:has(td.vertTh)"
fields:

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
@@ -32,7 +33,7 @@ namespace Jackett.Indexers
private bool Latency { get { return ConfigData.Latency.Value; } }
private bool DevMode { get { return ConfigData.DevMode.Value; } }
private bool CacheMode { get { return ConfigData.HardDriveCache.Value; } }
private string directory { get { return System.IO.Path.GetTempPath() + "Jackett\\" + MethodBase.GetCurrentMethod().DeclaringType.Name + "\\"; } }
private static string Directory => Path.Combine(Path.GetTempPath(), Assembly.GetExecutingAssembly().GetName().Name.ToLower(), MethodBase.GetCurrentMethod().DeclaringType?.Name.ToLower());
private Dictionary<string, string> emulatedBrowserHeaders = new Dictionary<string, string>();
private CQ fDom = null;
@@ -217,8 +218,7 @@ namespace Jackett.Indexers
var request = buildQuery(searchTerm, query, searchUrl);
// Getting results & Store content
WebClientStringResult results = await queryExec(request);
fDom = results.Content;
fDom = await queryExec(request);
try
{
@@ -274,10 +274,7 @@ namespace Jackett.Indexers
var pageRequest = buildQuery(searchTerm, query, searchUrl, i);
// Getting results & Store content
WebClientStringResult pageResults = await queryExec(pageRequest);
// Assign response
fDom = pageResults.Content;
fDom = await queryExec(pageRequest);
// Process page results
var additionalPageRows = findTorrentRows();
@@ -286,20 +283,6 @@ namespace Jackett.Indexers
torrentRowList.AddRange(additionalPageRows.Select(fRow => fRow.Cq()));
}
}
else
{
// No search term, maybe testing... so registring autkey and torrentpass for future uses
string infosData = firstPageRows.First().Find("td:eq(3) > a").Attr("href");
IList<string> infosList = infosData.Split('&').Select(s => s.Trim()).Where(s => s != String.Empty).ToList();
IList<string> infosTracker = infosList.Select(s => s.Split(new[] { '=' }, 2)[1].Trim()).ToList();
output("\nStoring Authkey for future uses...");
ConfigData.AuthKey.Value = infosTracker[2];
output("\nStoring TorrentPass for future uses...");
ConfigData.TorrentPass.Value = infosTracker[3];
}
// Loop on results
foreach (CQ tRow in torrentRowList)
@@ -311,12 +294,13 @@ namespace Jackett.Indexers
output("ID: " + id);
// Release Name
string name = tRow.Find("td:eq(1) > a").Text().ToString();
string name = tRow.Find("td:eq(1) > a").Text();
output("Release: " + name);
// Category
string categoryID = tRow.Find("td:eq(0) > a").Attr("href").Replace("torrents.php?cat[]=", String.Empty);
output("Category: " + MapTrackerCatToNewznab(categoryID) + " (" + categoryID + ")");
var newznab = MapTrackerCatToNewznab(categoryID);
output("Category: " + MapTrackerCatToNewznab(categoryID).First().ToString() + " (" + categoryID + ")");
// Seeders
int seeders = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(5)").Text(), @"\d+").Value);
@@ -349,30 +333,46 @@ namespace Jackett.Indexers
output("Comments Link: " + commentsLink.AbsoluteUri);
// Torrent Download URL
Uri downloadLink = new Uri(TorrentDownloadUrl.Replace("{id}", id.ToString()).Replace("{auth_key}", ConfigData.AuthKey.Value).Replace("{torrent_pass}", ConfigData.TorrentPass.Value));
output("Download Link: " + downloadLink.AbsoluteUri);
Uri downloadLink = null;
string link = tRow.Find("td:eq(3) > a").Attr("href");
if (!String.IsNullOrEmpty(link))
{
// Download link available
downloadLink = new Uri(SiteLink + link);
output("Download Link: " + downloadLink.AbsoluteUri);
}
else
{
// No download link available -- Must be on pending ( can't be downloaded now...)
output("Download Link: Not available, torrent pending ? Skipping ...");
continue;
}
// Freeleech
int downloadVolumeFactor = 1;
if (tRow.Find("img[alt=\"Freeleech\"]").Length >= 1)
{
downloadVolumeFactor = 0;
output("FreeLeech =)");
}
// Building release infos
var release = new ReleaseInfo();
release.Category = MapTrackerCatToNewznab(categoryID.ToString());
release.Title = name;
release.Seeders = seeders;
release.Peers = seeders + leechers;
release.MinimumRatio = 1;
release.MinimumSeedTime = 172800;
release.PublishDate = date;
release.Size = size;
release.Guid = detailsLink;
release.Comments = commentsLink;
release.Link = downloadLink;
// freeleech
if (tRow.Find("img[alt=\"Freeleech\"]").Length >= 1)
release.DownloadVolumeFactor = 0;
else
release.DownloadVolumeFactor = 1;
release.UploadVolumeFactor = 1;
var release = new ReleaseInfo()
{
Category = MapTrackerCatToNewznab(categoryID.ToString()),
Title = name,
Seeders = seeders,
Peers = seeders + leechers,
MinimumRatio = 1,
MinimumSeedTime = 172800,
PublishDate = date,
Size = size,
Guid = detailsLink,
Comments = commentsLink,
Link = downloadLink,
UploadVolumeFactor = 1,
DownloadVolumeFactor = downloadVolumeFactor
};
releases.Add(release);
}
@@ -450,9 +450,9 @@ namespace Jackett.Indexers
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> queryExec(string request)
private async Task<String> queryExec(string request)
{
WebClientStringResult results = null;
String results = null;
// Switch in we are in DEV mode with Hard Drive Cache or not
if (DevMode && CacheMode)
@@ -473,25 +473,40 @@ namespace Jackett.Indexers
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> queryCache(string request)
private async Task<String> queryCache(string request)
{
WebClientStringResult results = null;
String results;
// Create Directory if not exist
System.IO.Directory.CreateDirectory(directory);
System.IO.Directory.CreateDirectory(Directory);
// Clean Storage Provider Directory from outdated cached queries
cleanCacheStorage();
// File Name
string fileName = StringUtil.HashSHA1(request) + ".json";
// Create fingerprint for request
string file = directory + request.GetHashCode() + ".json";
string file = Path.Combine(Directory, fileName);
// Checking modes states
if (System.IO.File.Exists(file))
if (File.Exists(file))
{
// File exist... loading it right now !
output("Loading results from hard drive cache ..." + request.GetHashCode() + ".json");
results = JsonConvert.DeserializeObject<WebClientStringResult>(System.IO.File.ReadAllText(file));
output("Loading results from hard drive cache ..." + fileName);
try
{
using (StreamReader fileReader = File.OpenText(file))
{
JsonSerializer serializer = new JsonSerializer();
results = (String)serializer.Deserialize(fileReader, typeof(String));
}
}
catch (Exception e)
{
output("Error loading cached results ! " + e.Message, "error");
results = null;
}
}
else
{
@@ -499,8 +514,12 @@ namespace Jackett.Indexers
results = await queryTracker(request);
// Cached file didn't exist for our query, writing it right now !
output("Writing results to hard drive cache ..." + request.GetHashCode() + ".json");
System.IO.File.WriteAllText(file, JsonConvert.SerializeObject(results));
output("Writing results to hard drive cache ..." + fileName);
using (StreamWriter fileWriter = File.CreateText(file))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(fileWriter, results);
}
}
return results;
}
@@ -510,7 +529,7 @@ namespace Jackett.Indexers
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> queryTracker(string request)
private async Task<String> queryTracker(string request)
{
WebClientStringResult results = null;
@@ -522,14 +541,14 @@ namespace Jackett.Indexers
results = await RequestStringWithCookiesAndRetry(request, null, null, emulatedBrowserHeaders);
// Return results from tracker
return results;
return results.Content;
}
/// <summary>
/// Clean Hard Drive Cache Storage
/// </summary>
/// <param name="force">Force Provider Folder deletion</param>
private void cleanCacheStorage(Boolean force = false)
private void cleanCacheStorage(bool force = false)
{
// Check cleaning method
if (force)
@@ -538,10 +557,10 @@ namespace Jackett.Indexers
output("\nDeleting Provider Storage folder and all files recursively ...");
// Check if directory exist
if (System.IO.Directory.Exists(directory))
if (System.IO.Directory.Exists(Directory))
{
// Delete storage directory of provider
System.IO.Directory.Delete(directory, true);
System.IO.Directory.Delete(Directory, true);
output("-> Storage folder deleted successfully.");
}
else
@@ -552,11 +571,11 @@ namespace Jackett.Indexers
}
else
{
int i = 0;
var i = 0;
// Check if there is file older than ... and delete them
output("\nCleaning Provider Storage folder... in progress.");
System.IO.Directory.GetFiles(directory)
.Select(f => new System.IO.FileInfo(f))
System.IO.Directory.GetFiles(Directory)
.Select(f => new FileInfo(f))
.Where(f => f.LastAccessTime < DateTime.Now.AddMilliseconds(-Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value)))
.ToList()
.ForEach(f =>

View File

@@ -189,6 +189,9 @@ namespace Jackett.Indexers
{
if (query == null)
return false;
if (query.QueryType == "caps")
return true;
var caps = TorznabCaps;
if (query.HasSpecifiedCategories)

View File

@@ -836,12 +836,12 @@ namespace Jackett.Indexers
case "hexdump":
// this is mainly for debugging invisible special char related issues
var HexData = string.Join("", Data.Select(c => c + "(" + ((int)c).ToString("X2") + ")"));
logger.Info(string.Format("CardigannIndexer ({0}): strdump: {1}", ID, HexData));
logger.Debug(string.Format("CardigannIndexer ({0}): strdump: {1}", ID, HexData));
break;
case "strdump":
// for debugging
var DebugData = Data.Replace("\r", "\\r").Replace("\n", "\\n").Replace("\xA0", "\\xA0");
logger.Info(string.Format("CardigannIndexer ({0}): strdump: {1}", ID, DebugData));
logger.Debug(string.Format("CardigannIndexer ({0}): strdump: {1}", ID, DebugData));
break;
default:
break;
@@ -1304,7 +1304,7 @@ namespace Jackett.Indexers
break;
case "strdump":
// for debugging
logger.Info(string.Format("CardigannIndexer ({0}): row strdump: {1}", ID, Row.ToHtmlPretty()));
logger.Debug(string.Format("CardigannIndexer ({0}): row strdump: {1}", ID, Row.ToHtmlPretty()));
break;
default:
logger.Error(string.Format("CardigannIndexer ({0}): Unsupported rows filter: {1}", ID, Filter.Name));

View File

@@ -27,9 +27,19 @@ namespace Jackett.Indexers.Meta
return Task.FromResult(IndexerConfigurationStatus.Completed);
}
protected override IEnumerable<ReleaseInfo> FilterResults(TorznabQuery query, IEnumerable<ReleaseInfo> results)
public override async Task<IEnumerable<ReleaseInfo>> ResultsForQuery(TorznabQuery query)
{
return results;
if (!CanHandleQuery(query))
return new ReleaseInfo[0];
var results = await PerformQuery(query);
var correctedResults = results.Select(r =>
{
if (r.PublishDate > DateTime.Now)
r.PublishDate = DateTime.Now;
return r;
});
return correctedResults;
}
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)

View File

@@ -86,8 +86,11 @@ namespace Jackett.Indexers
continue;
release.Comments = new Uri(node.SelectSingleNode("link").InnerText);
int category = 0;
int.TryParse(node.SelectSingleNode("title").InnerText, out category);
// Try to guess the category... I'm not proud of myself...
int category = 5030;
if (serie_title.Contains("720p"))
category = 5040;
release.Category = new List<int> { category };
var test = node.SelectSingleNode("enclosure");
release.Guid = new Uri(test.Attributes["url"].Value);

View File

@@ -1,278 +0,0 @@
using Jackett.Models;
using Jackett.Services;
using Jackett.Utils;
using Jackett.Utils.Clients;
using Newtonsoft.Json.Linq;
using NLog;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Jackett.Models.IndexerConfig;
namespace Jackett.Indexers
{
public class T411 : BaseWebIndexer
{
const string ApiUrl = "https://api.t411.al";
const string AuthUrl = ApiUrl + "/auth";
const string SearchUrl = ApiUrl + "/torrents/search/";
const string TermsUrl = ApiUrl + "/terms/tree";
const string DownloadUrl = ApiUrl + "/torrents/download/";
private string CommentsUrl { get { return SiteLink + "torrents/"; } }
new ConfigurationDataLoginTokin configData
{
get { return (ConfigurationDataLoginTokin)base.configData; }
set { base.configData = value; }
}
private Dictionary<int, List<int>> _mediaCategoryMapping = new Dictionary<int, List<int>>();
public T411(IIndexerConfigurationService configService, IWebClient wc, Logger l, IProtectionService ps)
: base(name: "T411",
description: "French Torrent Tracker",
link: "https://t411.al/",
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
configService: configService,
client: wc,
logger: l,
p: ps,
configData: new ConfigurationDataLoginTokin())
{
Encoding = Encoding.UTF8;
Type = "semi-private";
Language = "fr-fr";
// 210, FilmVidéo
AddCategoryMapping(210, 402, TorznabCatType.Movies, "Vidéoclips");
AddCategoryMapping(210, 433, TorznabCatType.TV, "Série TV");
AddCategoryMapping(210, 455, TorznabCatType.TVAnime, "Animation");
AddCategoryMapping(210, 631, TorznabCatType.Movies, "Film");
AddCategoryMapping(210, 633, TorznabCatType.Movies, "Concert");
AddCategoryMapping(210, 634, TorznabCatType.TVDocumentary, "Documentaire");
AddCategoryMapping(210, 635, TorznabCatType.TV, "Spectacle");
AddCategoryMapping(210, 636, TorznabCatType.TVSport, "Sport");
AddCategoryMapping(210, 637, TorznabCatType.TVAnime, "Animation Série");
AddCategoryMapping(210, 639, TorznabCatType.TV, "Emission TV");
// 233, Application
AddCategoryMapping(233, 234, TorznabCatType.PC, "Linux");
AddCategoryMapping(233, 235, TorznabCatType.PCMac, "MacOS");
AddCategoryMapping(233, 236, TorznabCatType.PC, "Windows");
AddCategoryMapping(233, 625, TorznabCatType.PCPhoneOther, "Smartphone");
AddCategoryMapping(233, 627, TorznabCatType.PCPhoneOther, "Tablette");
AddCategoryMapping(233, 629, TorznabCatType.PC, "Autre");
AddCategoryMapping(233, 638, TorznabCatType.PC, "Formation");
// 395, Audio
AddCategoryMapping(395, 400, TorznabCatType.Audio, "Karaoke");
AddCategoryMapping(395, 403, TorznabCatType.Audio, "Samples");
AddCategoryMapping(395, 623, TorznabCatType.Audio, "Musique");
AddCategoryMapping(395, 642, TorznabCatType.Audio, "Podcast Radio");
// 404, eBook
AddCategoryMapping(404, 405, TorznabCatType.Books, "Audio");
AddCategoryMapping(404, 406, TorznabCatType.Books, "Bds");
AddCategoryMapping(404, 407, TorznabCatType.Books, "Comics");
AddCategoryMapping(404, 408, TorznabCatType.Books, "Livres");
AddCategoryMapping(404, 409, TorznabCatType.Books, "Mangas");
AddCategoryMapping(404, 410, TorznabCatType.Books, "Presse");
// 456, xXx
AddCategoryMapping(456, 461, TorznabCatType.XXX, "eBooks");
AddCategoryMapping(456, 462, TorznabCatType.XXX, "Jeux vidéo");
AddCategoryMapping(456, 632, TorznabCatType.XXX, "Video");
AddCategoryMapping(456, 641, TorznabCatType.XXX, "Animation");
// 624, Jeu vidéo
AddCategoryMapping(624, 239, TorznabCatType.PCGames, "Linux");
AddCategoryMapping(624, 245, TorznabCatType.PCMac, "MacOS");
AddCategoryMapping(624, 246, TorznabCatType.PCGames, "Windows");
AddCategoryMapping(624, 307, TorznabCatType.ConsoleNDS, "Nintendo");
AddCategoryMapping(624, 308, TorznabCatType.ConsolePS4, "Sony");
AddCategoryMapping(624, 309, TorznabCatType.ConsoleXbox, "Microsoft");
AddCategoryMapping(624, 626, TorznabCatType.PCPhoneOther, "Smartphone");
AddCategoryMapping(624, 628, TorznabCatType.PCPhoneOther, "Tablette");
AddCategoryMapping(624, 630, TorznabCatType.ConsoleOther, "Autre");
}
private void AddCategoryMapping(int trackerMediaCategory, int trackerCategory, TorznabCategory newznabCategory, string trackerCategoryDesc = null)
{
AddCategoryMapping(trackerCategory, newznabCategory, trackerCategoryDesc);
if (!_mediaCategoryMapping.ContainsKey(trackerMediaCategory))
_mediaCategoryMapping.Add(trackerMediaCategory, new List<int>());
_mediaCategoryMapping[trackerMediaCategory].Add(trackerCategory);
}
private KeyValuePair<int, List<int>> GetCategoryFromSubCat(int subCategory)
{
try
{
return _mediaCategoryMapping.First(pair => pair.Value.Contains(subCategory));
}
catch (Exception)
{
return new KeyValuePair<int, List<int>>(0, new List<int>() { 0 }); //If the provided category does not exist, we return 0 (ALL)
}
}
async Task<string> GetAuthToken(bool forceFetch = false)
{
if (!forceFetch && configData.LastTokenFetchDateTime > DateTime.Now - TimeSpan.FromHours(48))
{
return configData.ApiToken.Value;
}
var pairs = new Dictionary<string, string> {
{ "username", configData.Username.Value },
{ "password", configData.Password.Value }
};
var response = await PostDataWithCookies(AuthUrl, pairs);
var responseContent = response.Content;
var jsonResponse = JObject.Parse(responseContent);
if (jsonResponse["error"] != null)
{
throw new ApplicationException((string)jsonResponse["error"]);
}
configData.ApiToken.Value = (string)jsonResponse["token"];
configData.LastTokenFetchDateTime = DateTime.Now;
return configData.ApiToken.Value;
}
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
configData.LoadValuesFromJson(configJson);
Exception tokenFetchEx = null;
try
{
await GetAuthToken(true);
}
catch (Exception ex)
{
tokenFetchEx = new ExceptionWithConfigData(ex.Message, configData);
}
await ConfigureIfOK(string.Empty, tokenFetchEx == null, () =>
{
throw tokenFetchEx;
});
return IndexerConfigurationStatus.RequiresTesting;
}
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var releases = new List<ReleaseInfo>();
// API doesn't support getting the latest torrents, searching for the empty string will cause an error and all torrents returned
var searchUrl = SearchUrl + HttpUtility.UrlEncode(query.SanitizedSearchTerm).Replace("+", "%20");
searchUrl += "?offset=0&limit=200&cat=" + GetCategoryFromSubCat(query.Categories.FirstOrDefault()).Key;
// handle special term search for tvsearch
var queryStringOverride = query.SanitizedSearchTerm;
switch (query.QueryType)
{
case "tvsearch":
// T411 make the difference beetween Animation Movies and TV Animation Series, while Torznab does not.
// So here we take LastOrDefault from the category ids, so if the query specify an animation tv serie, we select the correct id.
searchUrl += "&subcat=" + GetCategoryFromSubCat(query.Categories.FirstOrDefault()).Value.LastOrDefault();
if (query.Season >= 1 && query.Season <= 30)
{
var seasonTermValue = 967 + query.Season;
searchUrl += "&term[45][]=" + seasonTermValue;
queryStringOverride += " " + query.Season;
}
if (query.Episode != null)
{
int episodeInt;
int episodeCategoryOffset = 936;
ParseUtil.TryCoerceInt(query.Episode, out episodeInt);
if (episodeInt >= 1 && episodeInt <= 8)
episodeCategoryOffset = 936;
else if (episodeInt >= 9 && episodeInt <= 30)
episodeCategoryOffset = 937;
else if (episodeInt >= 31)
episodeCategoryOffset = 1057;
searchUrl += "&term[46][]=" + (episodeCategoryOffset + episodeInt);
queryStringOverride += " " + query.Episode;
}
break;
case "movie":
// T411 make the difference beetween Animation Movies and TV Animation Series, while Torznab does not.
// So here we take FirstOrDefault from the category ids, so if the query specify an animation movie, we select the correct id.
searchUrl += "&subcat=" + GetCategoryFromSubCat(query.Categories.FirstOrDefault()).Value.FirstOrDefault();
break;
}
var headers = new Dictionary<string, string>();
headers.Add("Authorization", await GetAuthToken());
var response = await RequestStringWithCookies(searchUrl, null, null, headers);
var results = response.Content;
var jsonStart = results.IndexOf('{');
var jsonLength = results.Length - jsonStart;
var jsonResult = JObject.Parse(results.Substring(jsonStart));
try
{
var items = (JArray)jsonResult["torrents"];
foreach (var item in items)
{
if (item.GetType() == typeof(JValue))
{
logger.Debug(string.Format("{0}: skipping torrent ID {1} (pending release without details)", ID, item.ToString()));
continue;
}
var release = new ReleaseInfo();
release.MinimumRatio = 1;
release.MinimumSeedTime = 172800;
release.DownloadVolumeFactor = 0;
release.DownloadVolumeFactor = 1;
var torrentId = (string)item["id"];
release.Link = new Uri(DownloadUrl + torrentId);
release.Title = (string)item["name"];
if ((query.ImdbID == null || !TorznabCaps.SupportsImdbSearch) && !query.MatchQueryStringAND(release.Title, null, queryStringOverride))
continue;
if ((string)item["isVerified"] == "1")
release.Description = "Verified";
release.Comments = new Uri(CommentsUrl + (string)item["rewritename"]);
release.Guid = release.Comments;
var dateUtc = DateTime.ParseExact((string)item["added"], "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
release.PublishDate = DateTime.SpecifyKind(dateUtc, DateTimeKind.Utc).ToLocalTime();
release.Seeders = ParseUtil.CoerceInt((string)item["seeders"]);
release.Peers = ParseUtil.CoerceInt((string)item["leechers"]) + release.Seeders;
release.Size = ParseUtil.CoerceLong((string)item["size"]);
release.Category = MapTrackerCatToNewznab((string)item["category"]);
release.Grabs = ParseUtil.CoerceLong((string)item["times_completed"]);
releases.Add(release);
}
}
catch (Exception ex)
{
OnParseError(results, ex);
}
return releases;
}
public override async Task<byte[]> Download(Uri link)
{
var headers = new Dictionary<string, string>();
headers.Add("Authorization", await GetAuthToken());
var response = await RequestBytesWithCookies(link.AbsoluteUri, null, RequestType.GET, null, null, headers);
return response.Content;
}
}
}

View File

@@ -15,6 +15,7 @@ using Jackett.Utils.Clients;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using System.IO;
namespace Jackett.Indexers
{
@@ -29,7 +30,7 @@ namespace Jackett.Indexers
private bool Latency { get { return ConfigData.Latency.Value; } }
private bool DevMode { get { return ConfigData.DevMode.Value; } }
private bool CacheMode { get { return ConfigData.HardDriveCache.Value; } }
private string directory { get { return System.IO.Path.GetTempPath() + "Jackett\\" + MethodBase.GetCurrentMethod().DeclaringType.Name + "\\"; } }
private static string Directory => Path.Combine(Path.GetTempPath(), Assembly.GetExecutingAssembly().GetName().Name.ToLower(), MethodBase.GetCurrentMethod().DeclaringType?.Name.ToLower());
private Dictionary<string, string> emulatedBrowserHeaders = new Dictionary<string, string>();
private CQ fDom = null;
@@ -44,13 +45,13 @@ namespace Jackett.Indexers
: base(
name: "WiHD",
description: "Your World in High Definition",
link: "http://world-in-hd.net/",
link: "https://world-in-hd.net/",
caps: new TorznabCapabilities(),
configService: configService,
client: w,
logger: l,
p: ps,
downloadBase: "http://world-in-hd.net/torrents/download/",
downloadBase: "https://world-in-hd.net/torrents/download/",
configData: new ConfigurationDataWiHD())
{
Encoding = Encoding.UTF8;
@@ -215,8 +216,7 @@ namespace Jackett.Indexers
var request = buildQuery(searchTerm, query, searchUrl);
// Getting results & Store content
WebClientStringResult results = await queryExec(request);
fDom = results.Content;
fDom = await queryExec(request);
try
{
@@ -271,10 +271,7 @@ namespace Jackett.Indexers
var pageRequest = buildQuery(searchTerm, query, searchUrl, i);
// Getting results & Store content
WebClientStringResult pageResults = await queryExec(pageRequest);
// Assign response
fDom = pageResults.Content;
fDom = await queryExec(pageRequest);
// Process page results
var additionalPageRows = findTorrentRows();
@@ -296,7 +293,7 @@ namespace Jackett.Indexers
// Category
string categoryID = tRow.Find(".category > img").Attr("src").Split('/').Last().ToString();
string categoryName = tRow.Find(".category > img").Attr("title").ToString();
output("Category: " + MapTrackerCatToNewznab(mediaToCategory(categoryID, categoryName)) + " (" + categoryName + ")");
output("Category: " + MapTrackerCatToNewznab(mediaToCategory(categoryID, categoryName)).First().ToString() + " (" + categoryName + ")");
// Uploader
string uploader = tRow.Find(".uploader > span > a").Attr("title").ToString();
@@ -345,19 +342,31 @@ namespace Jackett.Indexers
Uri downloadLink = new Uri(SiteLink + download);
output("Download Link: " + downloadLink.AbsoluteUri);
// Freeleech
int downloadVolumeFactor = 1;
if (tRow.Find(".fl-item").Length >= 1)
{
downloadVolumeFactor = 0;
output("FreeLeech =)");
}
// Building release infos
var release = new ReleaseInfo();
release.Category = MapTrackerCatToNewznab(mediaToCategory(categoryID, categoryName));
release.Title = name;
release.Seeders = seeders;
release.Peers = seeders + leechers;
release.MinimumRatio = 1;
release.MinimumSeedTime = 345600;
release.PublishDate = clock;
release.Size = size;
release.Guid = detailsLink;
release.Comments = commentsLink;
release.Link = downloadLink;
var release = new ReleaseInfo()
{
Category = MapTrackerCatToNewznab(mediaToCategory(categoryID, categoryName)),
Title = name,
Seeders = seeders,
Peers = seeders + leechers,
MinimumRatio = 1,
MinimumSeedTime = 345600,
PublishDate = clock,
Size = size,
Guid = detailsLink,
Comments = commentsLink,
Link = downloadLink,
UploadVolumeFactor = 1,
DownloadVolumeFactor = downloadVolumeFactor
};
releases.Add(release);
}
@@ -448,9 +457,9 @@ namespace Jackett.Indexers
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> queryExec(string request)
private async Task<String> queryExec(string request)
{
WebClientStringResult results = null;
String results = null;
// Switch in we are in DEV mode with Hard Drive Cache or not
if (DevMode && CacheMode)
@@ -471,28 +480,40 @@ namespace Jackett.Indexers
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> queryCache(string request)
private async Task<String> queryCache(string request)
{
WebClientStringResult results = null;
String results;
// Create Directory if not exist
System.IO.Directory.CreateDirectory(directory);
System.IO.Directory.CreateDirectory(Directory);
// Clean Storage Provider Directory from outdated cached queries
cleanCacheStorage();
// Remove timestamp from query
string file = string.Join("&", Regex.Split(request, "&").Where(s => !Regex.IsMatch(s, @"_=\d")).ToList());
// File Name
string fileName = StringUtil.HashSHA1(request) + ".json";
// Create fingerprint for request
file = directory + file.GetHashCode() + ".json";
string file = Path.Combine(Directory, fileName);
// Checking modes states
if (System.IO.File.Exists(file))
if (File.Exists(file))
{
// File exist... loading it right now !
output("Loading results from hard drive cache ..." + request.GetHashCode() + ".json");
results = JsonConvert.DeserializeObject<WebClientStringResult>(System.IO.File.ReadAllText(file));
output("Loading results from hard drive cache ..." + fileName);
try
{
using (StreamReader fileReader = File.OpenText(file))
{
JsonSerializer serializer = new JsonSerializer();
results = (String)serializer.Deserialize(fileReader, typeof(String));
}
}
catch (Exception e)
{
output("Error loading cached results ! " + e.Message, "error");
results = null;
}
}
else
{
@@ -500,8 +521,12 @@ namespace Jackett.Indexers
results = await queryTracker(request);
// Cached file didn't exist for our query, writing it right now !
output("Writing results to hard drive cache ..." + request.GetHashCode() + ".json");
System.IO.File.WriteAllText(file, JsonConvert.SerializeObject(results));
output("Writing results to hard drive cache ..." + fileName);
using (StreamWriter fileWriter = File.CreateText(file))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(fileWriter, results);
}
}
return results;
}
@@ -511,7 +536,7 @@ namespace Jackett.Indexers
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> queryTracker(string request)
private async Task<String> queryTracker(string request)
{
WebClientStringResult results = null;
@@ -523,14 +548,14 @@ namespace Jackett.Indexers
results = await RequestStringWithCookiesAndRetry(request, null, null, emulatedBrowserHeaders);
// Return results from tracker
return results;
return results.Content;
}
/// <summary>
/// Clean Hard Drive Cache Storage
/// </summary>
/// <param name="force">Force Provider Folder deletion</param>
private void cleanCacheStorage(Boolean force = false)
private void cleanCacheStorage(bool force = false)
{
// Check cleaning method
if (force)
@@ -539,10 +564,10 @@ namespace Jackett.Indexers
output("\nDeleting Provider Storage folder and all files recursively ...");
// Check if directory exist
if (System.IO.Directory.Exists(directory))
if (System.IO.Directory.Exists(Directory))
{
// Delete storage directory of provider
System.IO.Directory.Delete(directory, true);
System.IO.Directory.Delete(Directory, true);
output("-> Storage folder deleted successfully.");
}
else
@@ -553,11 +578,11 @@ namespace Jackett.Indexers
}
else
{
int i = 0;
var i = 0;
// Check if there is file older than ... and delete them
output("\nCleaning Provider Storage folder... in progress.");
System.IO.Directory.GetFiles(directory)
.Select(f => new System.IO.FileInfo(f))
System.IO.Directory.GetFiles(Directory)
.Select(f => new FileInfo(f))
.Where(f => f.LastAccessTime < DateTime.Now.AddMilliseconds(-Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value)))
.ToList()
.ForEach(f =>

View File

@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Jackett.Models;
@@ -27,7 +28,7 @@ namespace Jackett.Indexers
private string TorrentDescriptionUrl => SiteLink + "details.php?id={id}";
private bool DevMode => ConfigData.DevMode.Value;
private bool CacheMode => ConfigData.HardDriveCache.Value;
private static string Directory => System.IO.Path.GetTempPath() + "Jackett\\" + MethodBase.GetCurrentMethod().DeclaringType?.Name + "\\";
private static string Directory => Path.Combine(Path.GetTempPath(), Assembly.GetExecutingAssembly().GetName().Name.ToLower(), MethodBase.GetCurrentMethod().DeclaringType?.Name.ToLower());
public Dictionary<string, string> EmulatedBrowserHeaders { get; } = new Dictionary<string, string>();
private ConfigurationDataXthor ConfigData => (ConfigurationDataXthor)configData;
@@ -59,6 +60,7 @@ namespace Jackett.Indexers
AddCategoryMapping(4, TorznabCatType.MoviesHD); // HD 1080P X264
AddCategoryMapping(100, TorznabCatType.MoviesHD); // HD 1080P X265
AddCategoryMapping(94, TorznabCatType.MoviesHD); // WEBDL
AddCategoryMapping(107, TorznabCatType.MoviesHD); // 4K
AddCategoryMapping(1, TorznabCatType.MoviesBluRay); // FULL BLURAY
AddCategoryMapping(2, TorznabCatType.MoviesBluRay); // BLURAY REMUX
AddCategoryMapping(3, TorznabCatType.MoviesBluRay); // FULL BLURAY 3D
@@ -130,7 +132,7 @@ namespace Jackett.Indexers
// Setting our data for a better emulated browser (maximum security)
// TODO: Encoded Content not supported by Jackett at this time
// emulatedBrowserHeaders.Add("Accept-Encoding", "gzip, deflate");
// EmulatedBrowserHeaders.Add("Accept-Encoding", "gzip, deflate");
// Clean headers
EmulatedBrowserHeaders.Clear();
@@ -183,7 +185,7 @@ namespace Jackett.Indexers
try
{
// Deserialize our Json Response
var xthorResponse = JsonConvert.DeserializeObject<XthorResponse>(results.Content);
var xthorResponse = JsonConvert.DeserializeObject<XthorResponse>(results);
// Check Tracker's State
CheckApiState(xthorResponse.error);
@@ -192,24 +194,35 @@ namespace Jackett.Indexers
if (xthorResponse.torrents != null)
{
// Adding each torrent row to releases
releases.AddRange(xthorResponse.torrents.Select(torrent => new ReleaseInfo
releases.AddRange(xthorResponse.torrents.Select(torrent =>
{
// Mapping data
Category = MapTrackerCatToNewznab(torrent.category.ToString()),
Title = torrent.name,
Seeders = torrent.seeders,
Peers = torrent.seeders + torrent.leechers,
MinimumRatio = 1,
MinimumSeedTime = 345600,
PublishDate = DateTimeUtil.UnixTimestampToDateTime(torrent.added),
Size = torrent.size,
Grabs = torrent.times_completed,
Files = torrent.numfiles,
UploadVolumeFactor = 1,
DownloadVolumeFactor = (torrent.freeleech == 1 ? 0 : 1),
Guid = new Uri(TorrentDescriptionUrl.Replace("{id}", torrent.id.ToString())),
Comments = new Uri(TorrentCommentUrl.Replace("{id}", torrent.id.ToString())),
Link = new Uri(torrent.download_link)
var release = new ReleaseInfo
{
// Mapping data
Category = MapTrackerCatToNewznab(torrent.category.ToString()),
Title = torrent.name,
Seeders = torrent.seeders,
Peers = torrent.seeders + torrent.leechers,
MinimumRatio = 1,
MinimumSeedTime = 345600,
PublishDate = DateTimeUtil.UnixTimestampToDateTime(torrent.added),
Size = torrent.size,
Grabs = torrent.times_completed,
Files = torrent.numfiles,
UploadVolumeFactor = 1,
DownloadVolumeFactor = (torrent.freeleech == 1 ? 0 : 1),
Guid = new Uri(TorrentDescriptionUrl.Replace("{id}", torrent.id.ToString())),
Comments = new Uri(TorrentCommentUrl.Replace("{id}", torrent.id.ToString())),
Link = new Uri(torrent.download_link),
TMDb = torrent.tmdb_id
};
if (DevMode)
{
Output(release.ToString());
}
return release;
}));
}
}
@@ -273,6 +286,12 @@ namespace Jackett.Indexers
public int numfiles { get; set; }
public string release_group { get; set; }
public string download_link { get; set; }
public int tmdb_id { get; set; }
public override string ToString()
{
return string.Format("[XthorTorrent: id={0}, category={1}, seeders={2}, leechers={3}, name={4}, times_completed={5}, size={6}, added={7}, freeleech={8}, numfiles={9}, release_group={10}, download_link={11}, tmdb_id={12}]", id, category, seeders, leechers, name, times_completed, size, added, freeleech, numfiles, release_group, download_link, tmdb_id);
}
}
/// <summary>
@@ -308,7 +327,7 @@ namespace Jackett.Indexers
if (categoriesList.Count > 0)
{
// ignore categories for now, something changed or is buggy, needs investigation
//parameters.Add("category", string.Join("+", categoriesList));
parameters.Add("category", string.Join("+", categoriesList));
}
// If Only Freeleech Enabled
@@ -331,9 +350,9 @@ namespace Jackett.Indexers
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> QueryExec(string request)
private async Task<String> QueryExec(string request)
{
WebClientStringResult results;
String results;
// Switch in we are in DEV mode with Hard Drive Cache or not
if (DevMode && CacheMode)
@@ -354,9 +373,9 @@ namespace Jackett.Indexers
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> QueryCache(string request)
private async Task<String> QueryCache(string request)
{
WebClientStringResult results;
String results;
// Create Directory if not exist
System.IO.Directory.CreateDirectory(Directory);
@@ -364,15 +383,30 @@ namespace Jackett.Indexers
// Clean Storage Provider Directory from outdated cached queries
CleanCacheStorage();
// File Name
string fileName = StringUtil.HashSHA1(request) + ".json";
// Create fingerprint for request
string file = Directory + request.GetHashCode() + ".json";
string file = Path.Combine(Directory, fileName);
// Checking modes states
if (System.IO.File.Exists(file))
if (File.Exists(file))
{
// File exist... loading it right now !
Output("Loading results from hard drive cache ..." + request.GetHashCode() + ".json");
results = JsonConvert.DeserializeObject<WebClientStringResult>(System.IO.File.ReadAllText(file));
Output("Loading results from hard drive cache ..." + fileName);
try
{
using (StreamReader fileReader = File.OpenText(file))
{
JsonSerializer serializer = new JsonSerializer();
results = (String)serializer.Deserialize(fileReader, typeof(String));
}
}
catch (Exception e)
{
Output("Error loading cached results ! " + e.Message, "error");
results = null;
}
}
else
{
@@ -380,8 +414,12 @@ namespace Jackett.Indexers
results = await QueryTracker(request);
// Cached file didn't exist for our query, writing it right now !
Output("Writing results to hard drive cache ..." + request.GetHashCode() + ".json");
System.IO.File.WriteAllText(file, JsonConvert.SerializeObject(results));
Output("Writing results to hard drive cache ..." + fileName);
using (StreamWriter fileWriter = File.CreateText(file))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(fileWriter, results);
}
}
return results;
}
@@ -391,7 +429,7 @@ namespace Jackett.Indexers
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebClientStringResult> QueryTracker(string request)
private async Task<String> QueryTracker(string request)
{
// Cache mode not enabled or cached file didn't exist for our query
Output("\nQuerying tracker for results....");
@@ -409,7 +447,7 @@ namespace Jackett.Indexers
var results = await webclient.GetString(myIndexRequest);
// Return results from tracker
return results;
return results.Content;
}
/// <summary>
@@ -479,7 +517,7 @@ namespace Jackett.Indexers
// Check if there is file older than ... and delete them
Output("\nCleaning Provider Storage folder... in progress.");
System.IO.Directory.GetFiles(Directory)
.Select(f => new System.IO.FileInfo(f))
.Select(f => new FileInfo(f))
.Where(f => f.LastAccessTime < DateTime.Now.AddMilliseconds(-Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value)))
.ToList()
.ForEach(f =>

View File

@@ -193,7 +193,6 @@
<Compile Include="Indexers\AnimeTorrents.cs" />
<Compile Include="Indexers\7tor.cs" />
<Compile Include="Indexers\EliteTracker.cs" />
<Compile Include="Indexers\T411.cs" />
<Compile Include="Indexers\Torrentech.cs" />
<Compile Include="Indexers\notwhatcd.cs" />
<Compile Include="Indexers\Redacted.cs" />
@@ -254,8 +253,6 @@
<Compile Include="Models\GitHub\Release.cs" />
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataNorbits.cs" />
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataXthor.cs" />
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataPhxBit.cs" />
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataBlueTigers.cs" />
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataAbnormal.cs" />
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataWiHD.cs" />
<Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginWithFilterAndPasskey.cs" />
@@ -288,7 +285,6 @@
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataNCore.cs" />
<Compile Include="Models\IndexerConfig\ConfigurationDataCaptchaLogin.cs" />
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataAnimeBytes.cs" />
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataStrike.cs" />
<Compile Include="Models\IndexerConfig\ConfigurationDataUrl.cs" />
<Compile Include="Models\IndexerConfig\ISerializableConfig.cs" />
<Compile Include="Models\IndexerConfig\ConfigurationDataPinNumber.cs" />
@@ -324,7 +320,6 @@
<Compile Include="Models\IndexerConfig\ConfigurationData.cs" />
<Compile Include="Models\IndexerConfig\ConfigurationDataBasicLogin.cs" />
<Compile Include="Models\IndexerConfig\ConfigurationDataCookie.cs" />
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataRuTor.cs" />
<Compile Include="CookieContainerExtensions.cs" />
<Compile Include="Utils\Clients\WebRequest.cs" />
<Compile Include="Utils\DataUrl.cs" />

View File

@@ -21,11 +21,13 @@ namespace Jackett.Models.DTO
{
var query = new TorznabQuery()
{
QueryType = request.t,
QueryType = "search",
SearchTerm = request.q,
ImdbID = request.imdbid,
Episode = request.ep,
};
if (request.t != null)
query.QueryType = request.t;
if (!request.extended.IsNullOrEmptyOrWhitespace())
query.Extended = ParseUtil.CoerceInt(request.extended);
if (!request.limit.IsNullOrEmptyOrWhitespace())

View File

@@ -1,63 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Jackett.Models.IndexerConfig.Bespoke
{
public class ConfigurationDataBlueTigers : ConfigurationData
{
public StringItem Username { get; private set; }
public StringItem Password { get; private set; }
public DisplayItem Instructions { get; set; }
public BoolItem French { get; set; }
public BoolItem English { get; set; }
public BoolItem Spanish { get; set; }
public ConfigurationDataBlueTigers(string displayInstructions)
{
Username = new StringItem { Name = "Username", Value = "" };
Password = new StringItem { Name = "Password", Value = "" };
Instructions = new DisplayItem(displayInstructions) { Name = "" };
French = new BoolItem { Name = "French", Value = true };
English = new BoolItem { Name = "English", Value = true };
Spanish = new BoolItem { Name = "Spanish", Value = true };
}
public ConfigurationDataBlueTigers(JToken json)
{
ConfigurationDataNCore configData = new ConfigurationDataNCore();
dynamic configArray = JsonConvert.DeserializeObject(json.ToString());
foreach (var config in configArray)
{
string propertyName = UppercaseFirst((string)config.id);
switch (propertyName)
{
case "Username":
Username = new StringItem { Name = propertyName, Value = config.value };
break;
case "Password":
Password = new StringItem { Name = propertyName, Value = config.value };
break;
case "French":
French = new BoolItem { Name = propertyName, Value = config.value };
break;
case "English":
English = new BoolItem { Name = propertyName, Value = config.value };
break;
case "Spanish":
Spanish = new BoolItem { Name = propertyName, Value = config.value };
break;
default:
break;
}
}
}
static string UppercaseFirst(string s)
{
if (string.IsNullOrEmpty(s))
return string.Empty;
return char.ToUpper(s[0]) + s.Substring(1);
}
}
}

View File

@@ -1,55 +0,0 @@
namespace Jackett.Models.IndexerConfig.Bespoke
{
class ConfigurationDataPhxBit : ConfigurationData
{
public HiddenItem PassKey { get; set; }
public DisplayItem CredentialsWarning { get; private set; }
public StringItem Username { get; private set; }
public StringItem Password { get; private set; }
public DisplayItem PagesWarning { get; private set; }
public StringItem Pages { get; private set; }
public DisplayItem SecurityWarning { get; private set; }
public BoolItem Latency { get; private set; }
public BoolItem Browser { get; private set; }
public DisplayItem LatencyWarning { get; private set; }
public StringItem LatencyStart { get; private set; }
public StringItem LatencyEnd { get; private set; }
public DisplayItem HeadersWarning { get; private set; }
public StringItem HeaderAccept { get; private set; }
public StringItem HeaderAcceptLang { get; private set; }
public BoolItem HeaderDNT { get; private set; }
public BoolItem HeaderUpgradeInsecure { get; private set; }
public StringItem HeaderUserAgent { get; private set; }
public DisplayItem DevWarning { get; private set; }
public BoolItem DevMode { get; private set; }
public BoolItem HardDriveCache { get; private set; }
public StringItem HardDriveCacheKeepTime { get; private set; }
public ConfigurationDataPhxBit()
: base()
{
PassKey = new HiddenItem { Name = "PassKey", Value = "" };
CredentialsWarning = new DisplayItem("<b>Credentials Configuration</b> (<i>Private Tracker</i>),<br /><br /> <ul><li><b>Username</b> is your account name on this tracker.</li><li><b>Password</b> is your password associated to your account name.</li></ul>") { Name = "Credentials" };
Username = new StringItem { Name = "Username (Required)", Value = "" };
Password = new StringItem { Name = "Password (Required)", Value = "" };
PagesWarning = new DisplayItem("<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Max Pages to Process</b> let you specify how many page (max) Jackett can process when doing a search. Setting a value <b>higher than 4 is dangerous</b> for you account ! (<b>Result of too many requests to tracker...that <u>will be suspect</u></b>).</li></ul>") { Name = "Preferences" };
Pages = new StringItem { Name = "Max Pages to Process (Required)", Value = "4" };
SecurityWarning = new DisplayItem("<b>Security Configuration</b> (<i>Read this area carefully !</i>),<br /><br /> <ul><li><b>Latency Simulation</b> will simulate human browsing with Jacket by pausing Jacket for an random time between each request, to fake a real content browsing.</li><li><b>Browser Simulation</b> will simulate a real human browser by injecting additionals headers when doing requests to tracker.</li></ul>") { Name = "Security" };
Latency = new BoolItem() { Name = "Latency Simulation (Optional)", Value = false };
Browser = new BoolItem() { Name = "Browser Simulation (Optional)", Value = true };
LatencyWarning = new DisplayItem("<b>Latency Configuration</b> (<i>Required if latency simulation enabled</i>),<br /><br/> <ul><li>By filling this range, <b>Jackett will make a random timed pause</b> <u>between requests</u> to tracker <u>to simulate a real browser</u>.</li><li>MilliSeconds <b>only</b></li></ul>") { Name = "Simulate Latency" };
LatencyStart = new StringItem { Name = "Minimum Latency (ms)", Value = "1589" };
LatencyEnd = new StringItem { Name = "Maximum Latency (ms)", Value = "3674" };
HeadersWarning = new DisplayItem("<b>Browser Headers Configuration</b> (<i>Required if browser simulation enabled</i>),<br /><br /> <ul><li>By filling these fields, <b>Jackett will inject headers</b> with your values <u>to simulate a real browser</u>.</li><li>You can get <b>your browser values</b> here: <a href='https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending' target='blank'>www.whatismybrowser.com</a></li></ul><br /><i><b>Note that</b> some headers are not necessary because they are injected automatically by this provider such as Accept_Encoding, Connection, Host or X-Requested-With</i>") { Name = "Injecting headers" };
HeaderAccept = new StringItem { Name = "Accept", Value = "" };
HeaderAcceptLang = new StringItem { Name = "Accept-Language", Value = "" };
HeaderDNT = new BoolItem { Name = "DNT", Value = false };
HeaderUpgradeInsecure = new BoolItem { Name = "Upgrade-Insecure-Requests", Value = false };
HeaderUserAgent = new StringItem { Name = "User-Agent", Value = "" };
DevWarning = new DisplayItem("<b>Development Facility</b> (<i>For Developers ONLY</i>),<br /><br /> <ul><li>By enabling development mode, <b>Jackett will bypass his cache</b> and will <u>output debug messages to console</u> instead of his log file.</li><li>By enabling Hard Drive Cache, <b>This provider</b> will <u>save each query answers from tracker</u> in temp directory, in fact this reduce drastically HTTP requests when building a provider at parsing step for example. So, <b> Jackett will search for a cached query answer on hard drive before executing query on tracker side !</b> <i>DEV MODE must be enabled to use it !</li></ul>") { Name = "Development" };
DevMode = new BoolItem { Name = "Enable DEV MODE (Developers ONLY)", Value = false };
HardDriveCache = new BoolItem { Name = "Enable HARD DRIVE CACHE (Developers ONLY)", Value = false };
HardDriveCacheKeepTime = new StringItem { Name = "Keep Cached files for (ms)", Value = "300000" };
}
}
}

View File

@@ -1,27 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jackett.Models.IndexerConfig.Bespoke
{
public class ConfigurationDataRuTor : ConfigurationData
{
[JsonProperty]
public StringItem Url { get; private set; }
[JsonProperty]
public BoolItem StripRussian { get; private set; }
public ConfigurationDataRuTor()
{
}
public ConfigurationDataRuTor(string defaultUrl)
{
Url = new StringItem { Name = "Url", Value = defaultUrl };
StripRussian = new BoolItem() { Name = "StripRusNamePrefix", Value = true };
}
}
}

View File

@@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jackett.Models.IndexerConfig.Bespoke
{
public class ConfigurationDataStrike : ConfigurationDataUrl
{
public DisplayItem StrikeWarning { get; private set; }
public ConfigurationDataStrike(string url) : base(url)
{
StrikeWarning = new DisplayItem("This indexer does not support RSS Sync, only Search") { Name = "Warning" };
}
}
}

View File

@@ -30,10 +30,10 @@
public ConfigurationDataWiHD()
: base()
{
CredentialsWarning = new DisplayItem("<b>Credentials Configuration</b> (<i>Private Tracker</i>),<br /><br /> <ul><li><b>Username</b> is your account name on this tracker.</li><li><b>Password</b> is your password associated to your account name.</li></ul>") { Name = "Credentials" };
CredentialsWarning = new DisplayItem("<b> YOU MUST USE CLASSIC VIEW ON WIHD !</b><br /> In order to use this provider with Jackett, take a look in your settings on the tracker before proceeding...<br /><br /><b>Credentials Configuration</b> (<i>Private Tracker</i>),<br /><br /> <ul><li><b>Username</b> is your account name on this tracker.</li><li><b>Password</b> is your password associated to your account name.</li></ul>") { Name = "Credentials" };
Username = new StringItem { Name = "Username (Required)", Value = "" };
Password = new StringItem { Name = "Password (Required)", Value = "" };
PagesWarning = new DisplayItem("<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Max Pages to Process</b> let you specify how many page (max) Jackett can process when doing a search. Setting a value <b>higher than 4 is dangerous</b> for you account ! (<b>Result of too many requests to tracker...that <u>will be suspect</u></b>).</li><li><b>Exclusive Only</b> let you search <u>only</u> for torrents which are marked Exclusive.</li><li><b>Freeleech Only</b> let you search <u>only</u> for torrents which are marked Freeleech.</li><li><b>Reseed Only</b> let you search <u>only</u> for torrents which need to be seeded.</li></ul>") { Name = "Preferences" };
PagesWarning = new DisplayItem("<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Max Pages to Process</b> let you specify how many page (max) Jackett can process when doing a search. Setting a value <b>higher than 4 is dangerous</b> for you account ! (<b>Result of too many requests to tracker...that <u>will be suspect</u></b>).</li><li><b>Exclusive Only</b> let you search <u>only</u> for torrents which are marked Exclusive.</li><li><b>Freeleech Only</b> let you search <u>only</u> for torrents which are marked Freeleech.</li><li><b>Reseed Only</b> let you search <u>only</u> for torrents which need to be seeded.</li></ul>") { Name = "Preferences" };
Pages = new StringItem { Name = "Max Pages to Process (Required)", Value = "4" };
Exclusive = new BoolItem() { Name = "Exclusive Only (Optional)", Value = false };
Freeleech = new BoolItem() { Name = "Freeleech Only (Optional)", Value = false };

View File

@@ -114,5 +114,10 @@ namespace Jackett.Models
{
return (long)(kb * 1024f);
}
public override string ToString()
{
return string.Format("[ReleaseInfo: Title={0}, Guid={1}, Link={2}, Comments={3}, PublishDate={4}, Category={5}, Size={6}, Files={7}, Grabs={8}, Description={9}, RageID={10}, TVDBId={11}, Imdb={12}, TMDb={13}, Seeders={14}, Peers={15}, BannerUrl={16}, InfoHash={17}, MagnetUri={18}, MinimumRatio={19}, MinimumSeedTime={20}, DownloadVolumeFactor={21}, UploadVolumeFactor={22}, Gain={23}]", Title, Guid, Link, Comments, PublishDate, Category, Size, Files, Grabs, Description, RageID, TVDBId, Imdb, TMDb, Seeders, Peers, BannerUrl, InfoHash, MagnetUri, MinimumRatio, MinimumSeedTime, DownloadVolumeFactor, UploadVolumeFactor, Gain);
}
}
}

View File

@@ -66,15 +66,18 @@ namespace Jackett.Models
}
}
public bool SupportsCategories (int[] categories)
public bool SupportsCategories(int[] categories)
{
return Categories.Count(i => categories.Any(c => c == i.ID)) > 0;
var subCategories = Categories.SelectMany(c => c.SubCategories);
var allCategories = Categories.Concat(subCategories);
var supportsCategory = allCategories.Any(i => categories.Any(c => c == i.ID));
return supportsCategory;
}
public JArray CapsToJson()
{
var jArray = new JArray();
foreach (var cat in Categories.GroupBy(p => p.ID).Select(g => g.First()).OrderBy(c=> c.ID < 100000 ? "z"+c.ID.ToString() : c.Name))
foreach (var cat in Categories.GroupBy(p => p.ID).Select(g => g.First()).OrderBy(c => c.ID < 100000 ? "z" + c.ID.ToString() : c.Name))
{
jArray.Add(cat.ToJson());
}
@@ -124,7 +127,7 @@ namespace Jackett.Models
lhs.MovieSearchAvailable = lhs.MovieSearchAvailable || rhs.MovieSearchAvailable;
lhs.SupportsTVRageSearch = lhs.SupportsTVRageSearch || rhs.SupportsTVRageSearch;
lhs.SupportsImdbSearch = lhs.SupportsImdbSearch || rhs.SupportsImdbSearch;
lhs.Categories.AddRange (rhs.Categories.Except (lhs.Categories));
lhs.Categories.AddRange(rhs.Categories.Except(lhs.Categories));
return lhs;
}

View File

@@ -54,6 +54,39 @@ namespace Jackett
}
}
static class JackettRouteExtensions
{
public static void ConfigureLegacyRoutes(this HttpRouteCollection routeCollection)
{
// Sonarr appends /api by default to all Torznab indexers, so we need that "ignored"
// parameter to catch these as well.
// Legacy fallback for Torznab results
routeCollection.MapHttpRoute(
name: "LegacyTorznab",
routeTemplate: "torznab/{indexerId}/{ignored}",
defaults: new
{
controller = "Results",
action = "Torznab",
ignored = RouteParameter.Optional,
}
);
// Legacy fallback for Potato results
routeCollection.MapHttpRoute(
name: "LegacyPotato",
routeTemplate: "potato/{indexerId}/{ignored}",
defaults: new
{
controller = "Results",
action = "Potato",
ignored = RouteParameter.Optional,
}
);
}
}
public class Startup
{
public static bool TracingEnabled
@@ -151,13 +184,17 @@ namespace Jackett
config.DependencyResolver = Engine.DependencyResolver();
config.MapHttpAttributeRoutes();
// Sonarr appends /api by default to all Torznab indexers, so we need that "ignored"
// parameter to catch these as well.
// (I'd rather not duplicate the whole route.)
config.Routes.MapHttpRoute(
name: "IndexerResultsAPI",
routeTemplate: "api/v2.0/indexers/{indexerId}/results/{action}",
routeTemplate: "api/v2.0/indexers/{indexerId}/results/{action}/{ignored}",
defaults: new
{
controller = "Results",
action = "Results"
action = "Results",
ignored = RouteParameter.Optional,
}
);
@@ -180,48 +217,6 @@ namespace Jackett
}
);
// Legacy fallback for Torznab results
config.Routes.MapHttpRoute(
name: "LegacyTorznab",
routeTemplate: "torznab/{indexerId}",
defaults: new
{
controller = "Results",
action = "Torznab"
}
);
config.Routes.MapHttpRoute(
name: "LegacyTorznabApi",
routeTemplate: "torznab/{indexerId}/api",
defaults: new
{
controller = "Results",
action = "Torznab"
}
);
// Legacy fallback for Potato results
config.Routes.MapHttpRoute(
name: "LegacyPotato",
routeTemplate: "potato/{indexerId}",
defaults: new
{
controller = "Results",
action = "Potato"
}
);
config.Routes.MapHttpRoute(
name: "LegacyPotatoApi",
routeTemplate: "potato/{indexerId}/api",
defaults: new
{
controller = "Results",
action = "Potato"
}
);
config.Routes.MapHttpRoute(
name: "WebUI",
routeTemplate: "UI/{action}",
@@ -240,6 +235,8 @@ namespace Jackett
defaults: new { controller = "Blackhole", action = "Blackhole" }
);
config.Routes.ConfigureLegacyRoutes();
appBuilder.UseWebApi(config);

View File

@@ -55,20 +55,46 @@ namespace Jackett.Utils
return new FormUrlEncodedContent(dict).ReadAsStringAsync().Result;
}
public static string Hash(string input)
/// <summary>
/// Convert an array of bytes to a string of hex digits
/// </summary>
/// <param name="bytes">array of bytes</param>
/// <returns>String of hex digits</returns>
public static string HexStringFromBytes(byte[] bytes)
{
var sb = new StringBuilder();
foreach (byte b in bytes)
{
var hex = b.ToString("x2");
sb.Append(hex);
}
return sb.ToString();
}
/// <summary>
/// Compute hash for string encoded as UTF8
/// </summary>
/// <param name="s">String to be hashed</param>
/// <returns>40-character hex string</returns>
public static string HashSHA1(string s)
{
var sha1 = SHA1.Create();
byte[] bytes = Encoding.UTF8.GetBytes(s);
byte[] hashBytes = sha1.ComputeHash(bytes);
return HexStringFromBytes(hashBytes);
}
public static string Hash(string s)
{
// Use input string to calculate MD5 hash
MD5 md5 = System.Security.Cryptography.MD5.Create();
byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(s);
byte[] hashBytes = md5.ComputeHash(inputBytes);
// Convert the byte array to hexadecimal string
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hashBytes.Length; i++)
{
sb.Append(hashBytes[i].ToString("X2"));
}
return sb.ToString();
return HexStringFromBytes(hashBytes);
}
public static string GetExceptionDetails(this Exception exception)