mirror of
https://github.com/Jackett/Jackett.git
synced 2025-09-11 22:30:48 +02:00
Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
baf44314e9 | ||
![]() |
29ef28b6d7 | ||
![]() |
86dad52919 | ||
![]() |
6ff05656ef | ||
![]() |
71c195cafb | ||
![]() |
dda0ae2485 | ||
![]() |
69dc63c726 | ||
![]() |
ee65721da1 | ||
![]() |
8ffb91f414 | ||
![]() |
50d931b4fb | ||
![]() |
6f475b18f3 | ||
![]() |
782211d06a | ||
![]() |
4f5d7a3d54 | ||
![]() |
f26f2d6f25 | ||
![]() |
6ccbfd6443 | ||
![]() |
c896ed8238 | ||
![]() |
1879ed89df | ||
![]() |
11f99a44d3 | ||
![]() |
f8fcf2fb79 | ||
![]() |
c36a3f558a | ||
![]() |
07a88919b4 | ||
![]() |
d02cb3fefc | ||
![]() |
f05eca3a9f | ||
![]() |
ccc2441a55 | ||
![]() |
5aaa402287 | ||
![]() |
97849dfcaf | ||
![]() |
f2a899eea3 | ||
![]() |
80686c81ee | ||
![]() |
189483b2b7 | ||
![]() |
e7cc147121 | ||
![]() |
73f044c0f2 |
@@ -35,6 +35,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
|
||||
* HD-Space
|
||||
* HD-Torrents
|
||||
* Hounddawgs
|
||||
* ILoveTorrents
|
||||
* Immortalseed
|
||||
* IPTorrents
|
||||
* MoreThanTV
|
||||
|
BIN
src/Jackett/Content/logos/ilovetorrents.png
Normal file
BIN
src/Jackett/Content/logos/ilovetorrents.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
src/Jackett/Content/logos/transmithenet.png
Normal file
BIN
src/Jackett/Content/logos/transmithenet.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
@@ -436,43 +436,23 @@ namespace Jackett.Indexers
|
||||
}
|
||||
}
|
||||
|
||||
protected void AddCategoryMapping(string trackerCategory, int newznabCategory)
|
||||
protected void AddCategoryMapping(string trackerCategory, TorznabCategory newznabCategory)
|
||||
{
|
||||
categoryMapping.Add(new CategoryMapping(trackerCategory, newznabCategory));
|
||||
categoryMapping.Add(new CategoryMapping(trackerCategory, newznabCategory.ID));
|
||||
if (!TorznabCaps.Categories.Contains(newznabCategory))
|
||||
TorznabCaps.Categories.Add(newznabCategory);
|
||||
}
|
||||
|
||||
protected void AddCategoryMapping(int trackerCategory, TorznabCategory newznabCategory)
|
||||
{
|
||||
categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory.ID));
|
||||
if (!TorznabCaps.Categories.Contains(newznabCategory))
|
||||
TorznabCaps.Categories.Add(newznabCategory);
|
||||
}
|
||||
|
||||
protected void AddCategoryMapping(string trackerCategory, TorznabCategory newznabCategory)
|
||||
{
|
||||
categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory.ID));
|
||||
if (!TorznabCaps.Categories.Contains(newznabCategory))
|
||||
TorznabCaps.Categories.Add(newznabCategory);
|
||||
}
|
||||
|
||||
protected void AddCategoryMapping(int trackerCategory, int newznabCategory)
|
||||
{
|
||||
categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory));
|
||||
AddCategoryMapping(trackerCategory.ToString(), newznabCategory);
|
||||
}
|
||||
|
||||
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));
|
||||
AddCategoryMapping(trackerCat, newznabCategory);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -42,7 +42,7 @@ namespace Jackett.Indexers
|
||||
client: c,
|
||||
logger: l,
|
||||
p: ps,
|
||||
configData: new ConfigurationDataCaptchaLogin())
|
||||
configData: new ConfigurationDataCaptchaLogin("Ensure that you have the 'Force SSL' option set to 'yes' in your profile on the BitMeTv webpage."))
|
||||
{
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,6 @@
|
||||
using CsQuery;
|
||||
using Jackett.Indexers;
|
||||
using Jackett.Models;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using Jackett.Utils.Clients;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
@@ -10,14 +8,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.UI.WebControls;
|
||||
using CsQuery.ExtensionMethods;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
|
||||
@@ -38,7 +31,7 @@ namespace Jackett.Indexers
|
||||
: base(name: "DanishBits",
|
||||
description: "A danish closed torrent tracker",
|
||||
link: "https://danishbits.org/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
client: c,
|
||||
logger: l,
|
||||
@@ -151,6 +144,13 @@ namespace Jackett.Indexers
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
TimeZoneInfo.TransitionTime startTransition = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 2, 0, 0), 3, 5, DayOfWeek.Sunday);
|
||||
TimeZoneInfo.TransitionTime endTransition = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 3, 0, 0), 10, 5, DayOfWeek.Sunday);
|
||||
TimeSpan delta = new TimeSpan(1, 0, 0);
|
||||
TimeZoneInfo.AdjustmentRule adjustment = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(new DateTime(1999, 10, 1), DateTime.MaxValue.Date, delta, startTransition, endTransition);
|
||||
TimeZoneInfo.AdjustmentRule[] adjustments = { adjustment };
|
||||
TimeZoneInfo denmarkTz = TimeZoneInfo.CreateCustomTimeZone("Denmark Time", new TimeSpan(1, 0, 0), "(GMT+01:00) Denmark Time", "Denmark Time", "Denmark DST", adjustments);
|
||||
|
||||
var releasesPerPage = 100;
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
@@ -244,9 +244,8 @@ namespace Jackett.Indexers
|
||||
|
||||
var addedElement = qRow.Find("span.time").FirstElement();
|
||||
var addedStr = addedElement.GetAttribute("title");
|
||||
release.PublishDate = DateTime.ParseExact(addedStr, "MMM dd yyyy, HH:mm",
|
||||
CultureInfo.InvariantCulture);
|
||||
|
||||
release.PublishDate = TimeZoneInfo.ConvertTimeToUtc(DateTime.ParseExact(addedStr, "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture), denmarkTz).ToLocalTime();
|
||||
|
||||
var columns = qRow.Children();
|
||||
var seedersElement = columns.Reverse().Skip(1).First();
|
||||
release.Seeders = int.Parse(seedersElement.InnerText);
|
||||
|
@@ -1,6 +1,4 @@
|
||||
using CsQuery;
|
||||
using Jackett.Indexers;
|
||||
using Jackett.Models;
|
||||
using Jackett.Models;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using Jackett.Utils.Clients;
|
||||
@@ -10,14 +8,10 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.UI.WebControls;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
using AngleSharp;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
@@ -42,7 +36,7 @@ namespace Jackett.Indexers
|
||||
client: c,
|
||||
logger: l,
|
||||
p: ps,
|
||||
configData: new ConfigurationDataBasicLogin())
|
||||
configData: new ConfigurationDataBasicLogin("For best results, change the 'Torrents per page' setting to 100 in your profile on the FreshOn webpage."))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -60,15 +54,32 @@ namespace Jackett.Indexers
|
||||
|
||||
await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("/logout.php"), () =>
|
||||
{
|
||||
CQ dom = response.Content;
|
||||
var messageEl = dom[".error_text"];
|
||||
var errorMessage = messageEl.Text().Trim();
|
||||
var parser = new AngleSharp.Parser.Html.HtmlParser();
|
||||
var document = parser.Parse(response.Content);
|
||||
var messageEl = document.QuerySelector(".error_text");
|
||||
var errorMessage = messageEl.TextContent.Trim();
|
||||
throw new ExceptionWithConfigData(errorMessage, configData);
|
||||
});
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
string Url;
|
||||
if (string.IsNullOrEmpty(query.GetQueryString()))
|
||||
Url = SearchUrl;
|
||||
else
|
||||
{
|
||||
Url = $"{SearchUrl}?search={HttpUtility.UrlEncode(query.GetQueryString())}&cat=0";
|
||||
}
|
||||
|
||||
var response = await RequestStringWithCookiesAndRetry(Url);
|
||||
List<ReleaseInfo> releases = ParseResponse(response.Content);
|
||||
|
||||
return releases;
|
||||
}
|
||||
|
||||
private List<ReleaseInfo> ParseResponse(string htmlResponse)
|
||||
{
|
||||
TimeZoneInfo.TransitionTime startTransition = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 3, 0, 0), 3, 5, DayOfWeek.Sunday);
|
||||
TimeZoneInfo.TransitionTime endTransition = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 4, 0, 0), 10, 5, DayOfWeek.Sunday);
|
||||
@@ -77,53 +88,40 @@ namespace Jackett.Indexers
|
||||
TimeZoneInfo.AdjustmentRule[] adjustments = { adjustment };
|
||||
TimeZoneInfo romaniaTz = TimeZoneInfo.CreateCustomTimeZone("Romania Time", new TimeSpan(2, 0, 0), "(GMT+02:00) Romania Time", "Romania Time", "Romania Daylight Time", adjustments);
|
||||
|
||||
var releases = new List<ReleaseInfo>();
|
||||
string episodeSearchUrl;
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
if (string.IsNullOrEmpty(query.GetQueryString()))
|
||||
episodeSearchUrl = SearchUrl;
|
||||
else
|
||||
{
|
||||
episodeSearchUrl = $"{SearchUrl}?search={HttpUtility.UrlEncode(query.GetQueryString())}&cat=0";
|
||||
}
|
||||
|
||||
var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
|
||||
try
|
||||
{
|
||||
CQ dom = results.Content;
|
||||
var parser = new AngleSharp.Parser.Html.HtmlParser();
|
||||
var document = parser.Parse(htmlResponse);
|
||||
var rows = document.QuerySelectorAll("#highlight > tbody > tr:not(:First-child)");
|
||||
|
||||
var rows = dom["#highlight > tbody > tr"];
|
||||
|
||||
foreach (var row in rows.Skip(1))
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
var qRow = row.Cq();
|
||||
var qLink = qRow.Find("a.torrent_name_link").First();
|
||||
var linkNameElement = row.QuerySelector("a.torrent_name_link");
|
||||
|
||||
release.Title = linkNameElement.GetAttribute("title");
|
||||
release.Description = release.Title;
|
||||
release.Guid = new Uri(SiteLink + linkNameElement.GetAttribute("href"));
|
||||
release.Comments = release.Guid;
|
||||
release.Link = new Uri(SiteLink + row.QuerySelector("td.table_links > a").GetAttribute("href"));
|
||||
release.Category = TvCategoryParser.ParseTvShowQuality(release.Title);
|
||||
release.Seeders = ParseUtil.CoerceInt(row.QuerySelector("td.table_seeders").TextContent.Trim());
|
||||
release.Peers = ParseUtil.CoerceInt(row.QuerySelector("td.table_leechers").TextContent.Trim()) + release.Seeders;
|
||||
release.Size = ReleaseInfo.GetBytes(row.QuerySelector("td.table_size").TextContent);
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
release.Title = qLink.Attr("title");
|
||||
release.Description = release.Title;
|
||||
release.Guid = new Uri(SiteLink + qLink.Attr("href"));
|
||||
release.Comments = release.Guid;
|
||||
release.Link = new Uri(SiteLink + qRow.Find("td.table_links > a").First().Attr("href"));
|
||||
release.Category = TvCategoryParser.ParseTvShowQuality(release.Title);
|
||||
|
||||
release.Seeders = ParseUtil.CoerceInt(qRow.Find("td.table_seeders").Text().Trim());
|
||||
release.Peers = ParseUtil.CoerceInt(qRow.Find("td.table_leechers").Text().Trim()) + release.Seeders;
|
||||
|
||||
var sizeStr = qRow.Find("td.table_size")[0].Cq().Text();
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
|
||||
DateTime pubDateRomania;
|
||||
var dateString = qRow.Find("td.table_added").Text().Trim();
|
||||
var dateString = row.QuerySelector("td.table_added").TextContent.Trim();
|
||||
if (dateString.StartsWith("Today "))
|
||||
{ pubDateRomania = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Unspecified) + TimeSpan.Parse(dateString.Split(' ')[1]); }
|
||||
{ pubDateRomania = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Unspecified) + TimeSpan.Parse(dateString.Split(' ')[1]); }
|
||||
else if (dateString.StartsWith("Yesterday "))
|
||||
{ pubDateRomania = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Unspecified) + TimeSpan.Parse(dateString.Split(' ')[1]) - TimeSpan.FromDays(1); }
|
||||
{ pubDateRomania = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Unspecified) + TimeSpan.Parse(dateString.Split(' ')[1]) - TimeSpan.FromDays(1); }
|
||||
else
|
||||
{ pubDateRomania = DateTime.SpecifyKind(DateTime.ParseExact(dateString, "d-MMM-yyyy HH:mm:ss", CultureInfo.InvariantCulture), DateTimeKind.Unspecified); }
|
||||
{ pubDateRomania = DateTime.SpecifyKind(DateTime.ParseExact(dateString, "d-MMM-yyyy HH:mm:ss", CultureInfo.InvariantCulture), DateTimeKind.Unspecified); }
|
||||
|
||||
DateTime pubDateUtc = TimeZoneInfo.ConvertTimeToUtc(pubDateRomania, romaniaTz);
|
||||
release.PublishDate = pubDateUtc.ToLocalTime();
|
||||
@@ -133,7 +131,7 @@ namespace Jackett.Indexers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnParseError(results.Content, ex);
|
||||
OnParseError(htmlResponse, ex);
|
||||
}
|
||||
|
||||
return releases;
|
||||
|
182
src/Jackett/Indexers/ILoveTorrents.cs
Normal file
182
src/Jackett/Indexers/ILoveTorrents.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CsQuery;
|
||||
using CsQuery.ExtensionMethods.Internal;
|
||||
using Jackett.Models;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using Jackett.Utils.Clients;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public class ILoveTorrents : BaseIndexer, IIndexer
|
||||
{
|
||||
private string BrowseUrl => SiteLink + "browse.php";
|
||||
private string LoginUrl => SiteLink + "takelogin.php";
|
||||
|
||||
new ConfigurationDataBasicLogin configData
|
||||
{
|
||||
get { return (ConfigurationDataBasicLogin)base.configData; }
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public ILoveTorrents(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "ILoveTorrents",
|
||||
description: "ILT",
|
||||
link: "https://www.ilovetorrents.me/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
configData: new ConfigurationDataBasicLogin())
|
||||
{
|
||||
|
||||
AddCategoryMapping(85, TorznabCatType.Movies3D);
|
||||
AddCategoryMapping(23, TorznabCatType.TVAnime);
|
||||
AddCategoryMapping(24, TorznabCatType.BooksEbook);
|
||||
AddCategoryMapping(4, TorznabCatType.PCGames);
|
||||
AddCategoryMapping(38, TorznabCatType.ConsolePS3);
|
||||
AddCategoryMapping(38, TorznabCatType.ConsolePS4);
|
||||
AddCategoryMapping(38, TorznabCatType.ConsolePSP);
|
||||
AddCategoryMapping(43, TorznabCatType.ConsoleWii);
|
||||
AddCategoryMapping(43, TorznabCatType.ConsoleWiiU);
|
||||
AddCategoryMapping(12, TorznabCatType.ConsoleXBOX360DLC);
|
||||
AddCategoryMapping(12, TorznabCatType.ConsoleXbox);
|
||||
AddCategoryMapping(12, TorznabCatType.ConsoleXbox360);
|
||||
AddCategoryMapping(12, TorznabCatType.ConsoleXboxOne);
|
||||
AddCategoryMapping(6, TorznabCatType.Audio);
|
||||
|
||||
AddCategoryMapping(7, TorznabCatType.TV);
|
||||
AddCategoryMapping(40, TorznabCatType.TVSD);
|
||||
AddCategoryMapping(8, TorznabCatType.TVHD);
|
||||
|
||||
AddCategoryMapping(9, TorznabCatType.XXX);
|
||||
AddCategoryMapping(11, TorznabCatType.XXXDVD);
|
||||
AddCategoryMapping(10, TorznabCatType.XXXx264);
|
||||
|
||||
AddCategoryMapping(80, TorznabCatType.MoviesBluRay);
|
||||
AddCategoryMapping(20, TorznabCatType.MoviesDVD);
|
||||
AddCategoryMapping(41, TorznabCatType.MoviesHD);
|
||||
AddCategoryMapping(19, TorznabCatType.Movies);
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
configData.LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", configData.Username.Value },
|
||||
{ "password", configData.Password.Value },
|
||||
{ "returnto", "/" },
|
||||
{ "login", "Log in!" }
|
||||
};
|
||||
|
||||
var loginPage = await RequestStringWithCookies(SiteLink, string.Empty);
|
||||
|
||||
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, SiteLink, SiteLink);
|
||||
await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () =>
|
||||
{
|
||||
CQ dom = result.Content;
|
||||
var messageEl = dom["body > div"].First();
|
||||
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 searchString = query.GetQueryString();
|
||||
var searchUrl = BrowseUrl;
|
||||
var trackerCats = MapTorznabCapsToTrackers(query);
|
||||
var queryCollection = new NameValueCollection();
|
||||
|
||||
// Tracker can only search OR return things in categories
|
||||
if (!string.IsNullOrWhiteSpace(searchString))
|
||||
{
|
||||
queryCollection.Add("search", searchString);
|
||||
queryCollection.Add("cat", "0");
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var cat in MapTorznabCapsToTrackers(query))
|
||||
{
|
||||
queryCollection.Add("c" + cat, "1");
|
||||
}
|
||||
|
||||
queryCollection.Add("incldead", "0");
|
||||
}
|
||||
|
||||
searchUrl += "?" + queryCollection.GetQueryString();
|
||||
|
||||
await ProcessPage(releases, searchUrl);
|
||||
return releases;
|
||||
}
|
||||
|
||||
private async Task ProcessPage(List<ReleaseInfo> releases, string searchUrl)
|
||||
{
|
||||
var response = await RequestStringWithCookiesAndRetry(searchUrl, null, BrowseUrl);
|
||||
var results = response.Content;
|
||||
try
|
||||
{
|
||||
CQ dom = results;
|
||||
|
||||
var rows = dom[".koptekst tr"];
|
||||
foreach (var row in rows.Skip(1))
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
var link = row.Cq().Find("td:eq(1) a:eq(0)").First();
|
||||
release.Guid = new Uri(SiteLink + link.Attr("href"));
|
||||
release.Comments = release.Guid;
|
||||
release.Title = link.Text().Trim();
|
||||
release.Description = release.Title;
|
||||
|
||||
// If we search an get no results, we still get a table just with no info.
|
||||
if (string.IsNullOrWhiteSpace(release.Title))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if the release has been assigned a category
|
||||
if (row.Cq().Find("td:eq(0) a").Length > 0)
|
||||
{
|
||||
var cat = row.Cq().Find("td:eq(0) a").First().Attr("href").Substring(15);
|
||||
release.Category = MapTrackerCatToNewznab(cat);
|
||||
}
|
||||
|
||||
var qLink = row.Cq().Find("td:eq(2) a").First();
|
||||
release.Link = new Uri(SiteLink + qLink.Attr("href"));
|
||||
|
||||
var added = row.Cq().Find("td:eq(7)").First().Text().Trim();
|
||||
var date = added.Substring(0, 10);
|
||||
var time = added.Substring(12, 8);
|
||||
var dateTime = date + time;
|
||||
release.PublishDate = DateTime.ParseExact(dateTime, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToLocalTime();
|
||||
|
||||
var sizeStr = row.Cq().Find("td:eq(8)").First().Text().Trim();
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
|
||||
release.Seeders = ParseUtil.CoerceInt(row.Cq().Find("td:eq(10)").First().Text().Trim());
|
||||
release.Peers = ParseUtil.CoerceInt(row.Cq().Find("td:eq(11)").First().Text().Trim()) + release.Seeders;
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnParseError(results, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -165,7 +165,7 @@ namespace Jackett.Indexers
|
||||
release.PublishDate = DateTimeUtil.FromTimeAgo(dateString);
|
||||
|
||||
var qLink = row.ChildElements.ElementAt(3).Cq().Children("a");
|
||||
release.Link = new Uri(SiteLink + qLink.Attr("href").TrimStart('/'));
|
||||
release.Link = new Uri(SiteLink + HttpUtility.UrlEncode(qLink.Attr("href").TrimStart('/')));
|
||||
|
||||
var sizeStr = row.ChildElements.ElementAt(5).Cq().Text();
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
|
@@ -7,14 +7,10 @@ using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
@@ -34,13 +30,52 @@ namespace Jackett.Indexers
|
||||
: base(name: "SceneTime",
|
||||
description: "Always on time",
|
||||
link: "https://www.scenetime.com/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
configData: new ConfigurationDataBasicLogin())
|
||||
configData: new ConfigurationDataBasicLogin("For best results, change the 'Torrents per page' setting to the maximum in your profile on the SceneTime webpage."))
|
||||
{
|
||||
AddCategoryMapping(1, TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping(3, TorznabCatType.MoviesDVD);
|
||||
AddCategoryMapping(47, TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping(57, TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping(59, TorznabCatType.MoviesHD);
|
||||
AddCategoryMapping(61, TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping(64, TorznabCatType.Movies3D);
|
||||
AddCategoryMapping(80, TorznabCatType.MoviesForeign);
|
||||
AddCategoryMapping(81, TorznabCatType.MoviesBluRay);
|
||||
AddCategoryMapping(82, TorznabCatType.MoviesOther);
|
||||
AddCategoryMapping(102, TorznabCatType.MoviesOther);
|
||||
AddCategoryMapping(103, TorznabCatType.MoviesWEBDL);
|
||||
AddCategoryMapping(105, TorznabCatType.Movies);
|
||||
|
||||
AddCategoryMapping(6, TorznabCatType.PCGames);
|
||||
AddCategoryMapping(48, TorznabCatType.ConsoleXbox);
|
||||
AddCategoryMapping(49, TorznabCatType.ConsolePSP);
|
||||
AddCategoryMapping(50, TorznabCatType.ConsolePS3);
|
||||
AddCategoryMapping(51, TorznabCatType.ConsoleWii);
|
||||
AddCategoryMapping(55, TorznabCatType.ConsoleNDS);
|
||||
AddCategoryMapping(107, TorznabCatType.ConsolePS4);
|
||||
|
||||
AddCategoryMapping(2, TorznabCatType.TVSD);
|
||||
AddCategoryMapping(43, TorznabCatType.TV);
|
||||
AddCategoryMapping(9, TorznabCatType.TVHD);
|
||||
AddCategoryMapping(63, TorznabCatType.TV);
|
||||
AddCategoryMapping(77, TorznabCatType.TVSD);
|
||||
AddCategoryMapping(79, TorznabCatType.TVSport);
|
||||
AddCategoryMapping(100, TorznabCatType.TVFOREIGN);
|
||||
AddCategoryMapping(83, TorznabCatType.TVWEBDL);
|
||||
|
||||
AddCategoryMapping(5, TorznabCatType.PC0day);
|
||||
AddCategoryMapping(7, TorznabCatType.Books);
|
||||
AddCategoryMapping(52, TorznabCatType.PCMac);
|
||||
AddCategoryMapping(65, TorznabCatType.BooksComics);
|
||||
AddCategoryMapping(53, TorznabCatType.PC);
|
||||
|
||||
AddCategoryMapping(4, TorznabCatType.Audio);
|
||||
AddCategoryMapping(11, TorznabCatType.AudioVideo);
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
@@ -62,23 +97,45 @@ namespace Jackett.Indexers
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> GetSearchFormData(string searchString)
|
||||
{
|
||||
return new Dictionary<string, string> {
|
||||
{ "c2", "1" }, { "c43", "1" }, { "c9", "1" }, { "c63", "1" }, { "c77", "1" }, { "c100", "1" }, { "c101", "1" },
|
||||
{ "cata", "yes" }, { "sec", "jax" },
|
||||
{ "search", searchString}
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var results = await PostDataWithCookiesAndRetry(SearchUrl, GetSearchFormData(query.GetQueryString()));
|
||||
Dictionary<string, string> qParams = new Dictionary<string, string>();
|
||||
qParams.Add("cata", "yes");
|
||||
qParams.Add("sec", "jax");
|
||||
|
||||
List<string> catList = MapTorznabCapsToTrackers(query);
|
||||
foreach (string cat in catList)
|
||||
{
|
||||
qParams.Add("c" + cat, "1");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(query.SanitizedSearchTerm))
|
||||
{
|
||||
qParams.Add("search", query.GetQueryString());
|
||||
}
|
||||
|
||||
var results = await PostDataWithCookiesAndRetry(SearchUrl, qParams);
|
||||
List<ReleaseInfo> releases = ParseResponse(results.Content);
|
||||
|
||||
return releases;
|
||||
}
|
||||
|
||||
public List<ReleaseInfo> ParseResponse(string htmlResponse)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
try
|
||||
{
|
||||
CQ dom = results.Content;
|
||||
CQ dom = htmlResponse;
|
||||
|
||||
List<string> headerColumns = dom["table[class*='movehere']"].First().Find("tbody > tr > td[class='cat_Head']").Select(x => x.Cq().Text()).ToList();
|
||||
int categoryIndex = headerColumns.FindIndex(x => x.Equals("Type"));
|
||||
int nameIndex = headerColumns.FindIndex(x => x.Equals("Name"));
|
||||
int sizeIndex = headerColumns.FindIndex(x => x.Equals("Size"));
|
||||
int seedersIndex = headerColumns.FindIndex(x => x.Equals("Seeders"));
|
||||
int leechersIndex = headerColumns.FindIndex(x => x.Equals("Leechers"));
|
||||
|
||||
var rows = dom["tr.browse"];
|
||||
foreach (var row in rows)
|
||||
{
|
||||
@@ -86,7 +143,12 @@ namespace Jackett.Indexers
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
|
||||
var descCol = row.ChildElements.ElementAt(1);
|
||||
var categoryCol = row.ChildElements.ElementAt(categoryIndex);
|
||||
string catLink = categoryCol.Cq().Find("a").Attr("href");
|
||||
string catId = new Regex(@"\?cat=(\d*)").Match(catLink).Groups[1].ToString().Trim();
|
||||
release.Category = MapTrackerCatToNewznab(catId);
|
||||
|
||||
var descCol = row.ChildElements.ElementAt(nameIndex);
|
||||
var qDescCol = descCol.Cq();
|
||||
var qLink = qDescCol.Find("a");
|
||||
release.Title = qLink.Text();
|
||||
@@ -98,19 +160,20 @@ namespace Jackett.Indexers
|
||||
|
||||
release.PublishDate = DateTimeUtil.FromTimeAgo(descCol.ChildNodes.Last().InnerText);
|
||||
|
||||
var sizeStr = row.ChildElements.ElementAt(5).Cq().Text();
|
||||
var sizeStr = row.ChildElements.ElementAt(sizeIndex).Cq().Text();
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
|
||||
release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(6).Cq().Text().Trim());
|
||||
release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(7).Cq().Text().Trim()) + release.Seeders;
|
||||
release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(seedersIndex).Cq().Text().Trim());
|
||||
release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(leechersIndex).Cq().Text().Trim()) + release.Seeders;
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnParseError(results.Content, ex);
|
||||
OnParseError(htmlResponse, ex);
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using CsQuery;
|
||||
using Jackett.Indexers;
|
||||
using Jackett.Models;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
@@ -41,27 +40,35 @@ namespace Jackett.Indexers
|
||||
Separate options with a space if using more than one option.<br>Filter options available:
|
||||
<br><code>QualityEncodeOnly</code><br><code>FreeLeechOnly</code>"))
|
||||
{
|
||||
AddCategoryMapping(7, TorznabCatType.Movies);
|
||||
AddCategoryMapping(7, TorznabCatType.MoviesForeign);
|
||||
AddCategoryMapping(7, TorznabCatType.MoviesOther);
|
||||
AddCategoryMapping(7, TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping(7, TorznabCatType.MoviesHD);
|
||||
AddCategoryMapping(7, TorznabCatType.Movies3D);
|
||||
AddCategoryMapping(7, TorznabCatType.MoviesBluRay);
|
||||
AddCategoryMapping(7, TorznabCatType.MoviesDVD);
|
||||
AddCategoryMapping(7, TorznabCatType.MoviesWEBDL);
|
||||
AddCategoryMapping(1, TorznabCatType.Movies);
|
||||
AddCategoryMapping(1, TorznabCatType.MoviesForeign);
|
||||
AddCategoryMapping(1, TorznabCatType.MoviesOther);
|
||||
AddCategoryMapping(1, TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping(1, TorznabCatType.MoviesHD);
|
||||
AddCategoryMapping(1, TorznabCatType.Movies3D);
|
||||
AddCategoryMapping(1, TorznabCatType.MoviesBluRay);
|
||||
AddCategoryMapping(1, TorznabCatType.MoviesDVD);
|
||||
AddCategoryMapping(1, TorznabCatType.MoviesWEBDL);
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
configData.LoadValuesFromJson(configJson);
|
||||
|
||||
await DoLogin();
|
||||
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
private async Task DoLogin()
|
||||
{
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", configData.Username.Value },
|
||||
{ "password", configData.Password.Value },
|
||||
{ "keeplogged", "1" },
|
||||
{ "login", "Log In!" }
|
||||
};
|
||||
|
||||
|
||||
var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, indexUrl, SiteLink);
|
||||
|
||||
await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("/logout.php"), () =>
|
||||
@@ -70,11 +77,24 @@ namespace Jackett.Indexers
|
||||
string errorMessage = "Unable to login to TehConnection";
|
||||
throw new ExceptionWithConfigData(errorMessage, configData);
|
||||
});
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var loggedInCheck = await RequestStringWithCookies(SearchUrl);
|
||||
if (!loggedInCheck.Content.Contains("/logout.php"))
|
||||
{
|
||||
//Cookie appears to expire after a period of time or logging in to the site via browser
|
||||
DateTime lastLoggedInCheck;
|
||||
DateTime.TryParse(configData.LastLoggedInCheck.Value, out lastLoggedInCheck);
|
||||
if (lastLoggedInCheck < DateTime.Now.AddMinutes(-15))
|
||||
{
|
||||
await DoLogin();
|
||||
configData.LastLoggedInCheck.Value = DateTime.Now.ToString("o");
|
||||
SaveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
var releases = new List<ReleaseInfo>();
|
||||
bool configFreeLeechOnly = configData.FilterString.Value.ToLowerInvariant().Contains("freeleechonly");
|
||||
bool configQualityEncodeOnly = configData.FilterString.Value.ToLowerInvariant().Contains("qualityencodeonly");
|
||||
|
133
src/Jackett/Indexers/TransmitheNet.cs
Normal file
133
src/Jackett/Indexers/TransmitheNet.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
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.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
using AngleSharp.Parser.Html;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class TransmitheNet : BaseIndexer, IIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
private string SearchUrl { get { return SiteLink + "torrents.php"; } }
|
||||
|
||||
new ConfigurationDataBasicLogin configData
|
||||
{
|
||||
get { return (ConfigurationDataBasicLogin)base.configData; }
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public TransmitheNet(IIndexerManagerService i, Logger l, IWebClient c, IProtectionService ps)
|
||||
: base(name: "TransmitTheNet",
|
||||
description: " At Transmithe.net we will change the way you think about TV",
|
||||
link: "https://transmithe.net/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
client: c,
|
||||
logger: l,
|
||||
p: ps,
|
||||
configData: new ConfigurationDataBasicLogin("For best results, change the 'Torrents per page' setting to 100 in your profile on the TTN webpage."))
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
configData.LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", configData.Username.Value },
|
||||
{ "password", configData.Password.Value },
|
||||
{ "keeplogged", "on" },
|
||||
{ "login", "Login" }
|
||||
};
|
||||
|
||||
CookieHeader = string.Empty;
|
||||
var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, CookieHeader, true, null, LoginUrl);
|
||||
|
||||
await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("logout.php"), () =>
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var document = parser.Parse(response.Content);
|
||||
var messageEl = document.QuerySelector("form > span[class='warning']");
|
||||
var errorMessage = messageEl.TextContent.Trim();
|
||||
throw new ExceptionWithConfigData(errorMessage, configData);
|
||||
});
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
string Url;
|
||||
if (string.IsNullOrEmpty(query.GetQueryString()))
|
||||
Url = SearchUrl;
|
||||
else
|
||||
{
|
||||
Url = $"{SearchUrl}?searchtext={HttpUtility.UrlEncode(query.GetQueryString())}";
|
||||
}
|
||||
|
||||
var response = await RequestStringWithCookiesAndRetry(Url);
|
||||
List<ReleaseInfo> releases = ParseResponse(response.Content);
|
||||
|
||||
return releases;
|
||||
}
|
||||
|
||||
public List<ReleaseInfo> ParseResponse(string htmlResponse)
|
||||
{
|
||||
List<ReleaseInfo> releases = new List<ReleaseInfo>();
|
||||
|
||||
try
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var document = parser.Parse(htmlResponse);
|
||||
var rows = document.QuerySelectorAll(".torrent_table > tbody > tr:not(:First-child)");
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
string title = row.QuerySelector("a[data-src]").GetAttribute("data-src");
|
||||
if (string.IsNullOrEmpty(title) || title == "0")
|
||||
{
|
||||
title = row.QuerySelector("a[data-src]").TextContent;
|
||||
title = Regex.Replace(title, @"[\[\]\/]", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
title = title.Remove(title.LastIndexOf("."));
|
||||
}
|
||||
|
||||
release.Title = title;
|
||||
release.Description = release.Title;
|
||||
release.Guid = new Uri(SiteLink + row.QuerySelector("a[data-src]").GetAttribute("href"));
|
||||
release.Comments = release.Guid;
|
||||
release.Link = new Uri(SiteLink + row.QuerySelector("a[href*='action=download']").GetAttribute("href"));
|
||||
release.Category = TvCategoryParser.ParseTvShowQuality(release.Title);
|
||||
|
||||
var timeAnchor = row.QuerySelector("span[class='time']");
|
||||
release.PublishDate = DateTime.ParseExact(timeAnchor.GetAttribute("title"), "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal);
|
||||
release.Seeders = ParseUtil.CoerceInt(timeAnchor.ParentElement.NextElementSibling.NextElementSibling.TextContent.Trim());
|
||||
release.Peers = ParseUtil.CoerceInt(timeAnchor.ParentElement.NextElementSibling.NextElementSibling.NextElementSibling.TextContent.Trim()) + release.Seeders;
|
||||
release.Size = ReleaseInfo.GetBytes(timeAnchor.ParentElement.PreviousElementSibling.TextContent);
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnParseError(htmlResponse, ex);
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
}
|
||||
}
|
@@ -58,8 +58,8 @@
|
||||
</StartupObject>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="AngleSharp, Version=0.9.4.42449, Culture=neutral, PublicKeyToken=e83494dcdc6d31ea, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\AngleSharp.0.9.4\lib\net45\AngleSharp.dll</HintPath>
|
||||
<Reference Include="AngleSharp, Version=0.9.5.41771, Culture=neutral, PublicKeyToken=e83494dcdc6d31ea, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\AngleSharp.0.9.5\lib\net45\AngleSharp.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Autofac, Version=3.5.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
|
||||
@@ -201,6 +201,7 @@
|
||||
<Compile Include="Indexers\DanishBits.cs" />
|
||||
<Compile Include="Indexers\Abnormal.cs" />
|
||||
<Compile Include="Indexers\GFTracker.cs" />
|
||||
<Compile Include="Indexers\ILoveTorrents.cs" />
|
||||
<Compile Include="Indexers\RevolutionTT.cs" />
|
||||
<Compile Include="Indexers\TehConnection.cs" />
|
||||
<Compile Include="Indexers\Hounddawgs.cs" />
|
||||
@@ -213,6 +214,7 @@
|
||||
<Compile Include="Indexers\FileList.cs" />
|
||||
<Compile Include="Indexers\Abstract\AvistazTracker.cs" />
|
||||
<Compile Include="Indexers\FrenchADN.cs" />
|
||||
<Compile Include="Indexers\TransmitheNet.cs" />
|
||||
<Compile Include="Indexers\WiHD.cs" />
|
||||
<Compile Include="Indexers\XSpeeds.cs" />
|
||||
<Compile Include="Models\GitHub\Asset.cs" />
|
||||
@@ -318,6 +320,7 @@
|
||||
<Compile Include="Startup.cs" />
|
||||
<Compile Include="Models\TorznabQuery.cs" />
|
||||
<Compile Include="CurlHelper.cs" />
|
||||
<Compile Include="Utils\StringCipher.cs" />
|
||||
<Compile Include="Utils\StringUtil.cs" />
|
||||
<Compile Include="Utils\TorznabCapsUtil.cs" />
|
||||
<Compile Include="Utils\Clients\UnixSafeCurlWebClient.cs" />
|
||||
@@ -478,6 +481,9 @@
|
||||
<Content Include="Content\logos\hdtorrents.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Content\logos\ilovetorrents.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Content\logos\immortalseed.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -571,6 +577,9 @@
|
||||
<Content Include="Content\logos\torrentleech.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Content\logos\transmithenet.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Content\logos\tvchaosuk.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
@@ -11,6 +11,7 @@ namespace Jackett.Models.IndexerConfig
|
||||
{
|
||||
public StringItem Username { get; private set; }
|
||||
public StringItem Password { get; private set; }
|
||||
public HiddenItem LastLoggedInCheck { get; private set; }
|
||||
public DisplayItem FilterExample { get; private set; }
|
||||
public StringItem FilterString { get; private set; }
|
||||
|
||||
@@ -18,6 +19,7 @@ namespace Jackett.Models.IndexerConfig
|
||||
{
|
||||
Username = new StringItem { Name = "Username" };
|
||||
Password = new StringItem { Name = "Password" };
|
||||
LastLoggedInCheck = new HiddenItem { Name = "LastLoggedInCheck" };
|
||||
FilterExample = new DisplayItem(FilterInstructions)
|
||||
{
|
||||
Name = ""
|
||||
|
@@ -18,13 +18,17 @@ namespace Jackett.Models.IndexerConfig
|
||||
|
||||
public HiddenItem CaptchaCookie { get; private set; }
|
||||
|
||||
public ConfigurationDataCaptchaLogin()
|
||||
public DisplayItem Instructions { get; private set; }
|
||||
|
||||
/// <param name="instructionMessageOptional">Enter any instructions the user will need to setup the tracker</param>
|
||||
public ConfigurationDataCaptchaLogin(string instructionMessageOptional = null)
|
||||
{
|
||||
Username = new StringItem { Name = "Username" };
|
||||
Password = new StringItem { Name = "Password" };
|
||||
CaptchaImage = new ImageItem { Name = "Captcha Image" };
|
||||
CaptchaText = new StringItem { Name = "Captcha Text" };
|
||||
CaptchaCookie = new HiddenItem("") { Name = "Captcha Cookie" };
|
||||
Instructions = new DisplayItem(instructionMessageOptional) { Name = "" };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Jackett.Utils;
|
||||
|
||||
namespace Jackett.Services
|
||||
{
|
||||
@@ -18,6 +19,7 @@ namespace Jackett.Services
|
||||
public class ProtectionService : IProtectionService
|
||||
{
|
||||
DataProtectionScope PROTECTION_SCOPE = DataProtectionScope.LocalMachine;
|
||||
private const string JACKETT_KEY = "JACKETT_KEY";
|
||||
const string APPLICATION_KEY = "Dvz66r3n8vhTGip2/quiw5ISyM37f7L2iOdupzdKmzkvXGhAgQiWK+6F+4qpxjPVNks1qO7LdWuVqRlzgLzeW8mChC6JnBMUS1Fin4N2nS9lh4XPuCZ1che75xO92Nk2vyXUo9KSFG1hvEszAuLfG2Mcg1r0sVyVXd2gQDU/TbY=";
|
||||
|
||||
IServerService serverService;
|
||||
@@ -34,6 +36,34 @@ namespace Jackett.Services
|
||||
}
|
||||
|
||||
public string Protect(string plainText)
|
||||
{
|
||||
var jackettKey = Environment.GetEnvironmentVariable(JACKETT_KEY);
|
||||
|
||||
if (jackettKey == null)
|
||||
{
|
||||
return ProtectDefaultMethod(plainText);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ProtectUsingKey(plainText, jackettKey);
|
||||
}
|
||||
}
|
||||
|
||||
public string UnProtect(string plainText)
|
||||
{
|
||||
var jackettKey = Environment.GetEnvironmentVariable(JACKETT_KEY);
|
||||
|
||||
if (jackettKey == null)
|
||||
{
|
||||
return UnProtectDefaultMethod(plainText);
|
||||
}
|
||||
else
|
||||
{
|
||||
return UnProtectUsingKey(plainText, jackettKey);
|
||||
}
|
||||
}
|
||||
|
||||
private string ProtectDefaultMethod(string plainText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(plainText))
|
||||
return string.Empty;
|
||||
@@ -72,7 +102,7 @@ namespace Jackett.Services
|
||||
return Convert.ToBase64String(protectedBytes);
|
||||
}
|
||||
|
||||
public string UnProtect(string plainText)
|
||||
private string UnProtectDefaultMethod(string plainText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(plainText))
|
||||
return string.Empty;
|
||||
@@ -111,6 +141,16 @@ namespace Jackett.Services
|
||||
return Encoding.UTF8.GetString(unprotectedBytes);
|
||||
}
|
||||
|
||||
private string ProtectUsingKey(string plainText, string key)
|
||||
{
|
||||
return StringCipher.Encrypt(plainText, key);
|
||||
}
|
||||
|
||||
private string UnProtectUsingKey(string plainText, string key)
|
||||
{
|
||||
return StringCipher.Decrypt(plainText, key);
|
||||
}
|
||||
|
||||
public void Protect<T>(T obj)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
|
106
src/Jackett/Utils/StringCipher.cs
Normal file
106
src/Jackett/Utils/StringCipher.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett.Utils
|
||||
{
|
||||
public static class StringCipher
|
||||
{
|
||||
// This constant is used to determine the keysize of the encryption algorithm in bits.
|
||||
// We divide this by 8 within the code below to get the equivalent number of bytes.
|
||||
private const int Keysize = 256;
|
||||
|
||||
// This constant determines the number of iterations for the password bytes generation function.
|
||||
private const int DerivationIterations = 1000;
|
||||
|
||||
public static string Encrypt(string plainText, string passPhrase)
|
||||
{
|
||||
// Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
|
||||
// so that the same Salt and IV values can be used when decrypting.
|
||||
var saltStringBytes = Generate256BitsOfRandomEntropy();
|
||||
var ivStringBytes = Generate256BitsOfRandomEntropy();
|
||||
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
|
||||
{
|
||||
var keyBytes = password.GetBytes(Keysize / 8);
|
||||
using (var symmetricKey = new RijndaelManaged())
|
||||
{
|
||||
symmetricKey.BlockSize = 256;
|
||||
symmetricKey.Mode = CipherMode.CBC;
|
||||
symmetricKey.Padding = PaddingMode.PKCS7;
|
||||
using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
|
||||
{
|
||||
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
|
||||
cryptoStream.FlushFinalBlock();
|
||||
// Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
|
||||
var cipherTextBytes = saltStringBytes;
|
||||
cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
|
||||
cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
|
||||
memoryStream.Close();
|
||||
cryptoStream.Close();
|
||||
return Convert.ToBase64String(cipherTextBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string Decrypt(string cipherText, string passPhrase)
|
||||
{
|
||||
// Get the complete stream of bytes that represent:
|
||||
// [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
|
||||
var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
|
||||
// Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
|
||||
var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
|
||||
// Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
|
||||
var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
|
||||
// Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
|
||||
var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();
|
||||
|
||||
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
|
||||
{
|
||||
var keyBytes = password.GetBytes(Keysize / 8);
|
||||
using (var symmetricKey = new RijndaelManaged())
|
||||
{
|
||||
symmetricKey.BlockSize = 256;
|
||||
symmetricKey.Mode = CipherMode.CBC;
|
||||
symmetricKey.Padding = PaddingMode.PKCS7;
|
||||
using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
|
||||
{
|
||||
using (var memoryStream = new MemoryStream(cipherTextBytes))
|
||||
{
|
||||
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
|
||||
{
|
||||
var plainTextBytes = new byte[cipherTextBytes.Length];
|
||||
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
|
||||
memoryStream.Close();
|
||||
cryptoStream.Close();
|
||||
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] Generate256BitsOfRandomEntropy()
|
||||
{
|
||||
var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits.
|
||||
using (var rngCsp = new RNGCryptoServiceProvider())
|
||||
{
|
||||
// Fill the array with cryptographically secure random bytes.
|
||||
rngCsp.GetBytes(randomBytes);
|
||||
}
|
||||
return randomBytes;
|
||||
}
|
||||
}
|
||||
}
|
@@ -74,7 +74,7 @@ namespace Jackett.Utils
|
||||
// Filter out releases that do have a valid imdb ID, that is not equal to the one we're searching for.
|
||||
return
|
||||
results.Where(
|
||||
result => !result.Imdb.HasValue || result.Imdb.Value == 0 || ("tt" + result.Imdb.Value).Equals(imdb));
|
||||
result => !result.Imdb.HasValue || result.Imdb.Value == 0 || ("tt" + result.Imdb.Value.ToString("D7")).Equals(imdb));
|
||||
}
|
||||
|
||||
private static string CleanTitle(string title)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="AngleSharp" version="0.9.4" targetFramework="net45" />
|
||||
<package id="AngleSharp" version="0.9.5" targetFramework="net45" />
|
||||
<package id="Autofac" version="3.5.2" targetFramework="net45" />
|
||||
<package id="Autofac.Owin" version="3.1.0" targetFramework="net45" />
|
||||
<package id="Autofac.WebApi" version="3.1.0" targetFramework="net45" />
|
||||
|
Reference in New Issue
Block a user