mirror of
https://github.com/Jackett/Jackett
synced 2024-12-30 11:47:28 +00:00
Expand Brazilian Portuguese Torrent Support with New Indexers (#15713)
Indexers: ApacheTorrent, RedeTorrent, TorrentDosFilmes, FilmesHdTorrent, LAPUMiA, BluDV and TorrentsMegaFilmes. Mostly usable with Radarr, not so much with Sonarr.
This commit is contained in:
parent
8779d57169
commit
14860ff396
9 changed files with 1331 additions and 0 deletions
|
@ -36,12 +36,14 @@ A third-party Golang SDK for Jackett is available from [webtor-io/go-jackett](ht
|
|||
* Anime Tosho
|
||||
* AniRena
|
||||
* AniSource
|
||||
* ApacheTorrent
|
||||
* AudioBook Bay (ABB)
|
||||
* Badass Torrents
|
||||
* Bangumi Moe
|
||||
* BigFANGroup
|
||||
* BitRu
|
||||
* BitSearch
|
||||
* BluDV
|
||||
* BlueRoms
|
||||
* BT.etree
|
||||
* BTdirectory (BT目录)
|
||||
|
@ -62,6 +64,7 @@ A third-party Golang SDK for Jackett is available from [webtor-io/go-jackett](ht
|
|||
* EXT Torrents
|
||||
* ExtraTorrent.st
|
||||
* EZTV
|
||||
* FilmesHdTorrent
|
||||
* Frozen Layer
|
||||
* GamesTorrents
|
||||
* GkTorrent
|
||||
|
@ -76,6 +79,7 @@ A third-party Golang SDK for Jackett is available from [webtor-io/go-jackett](ht
|
|||
* kickasstorrents.to
|
||||
* kickasstorrents.ws
|
||||
* Knaben
|
||||
* LAPUMiA
|
||||
* LePorno.info
|
||||
* Libronube
|
||||
* LimeTorrents
|
||||
|
@ -106,6 +110,7 @@ A third-party Golang SDK for Jackett is available from [webtor-io/go-jackett](ht
|
|||
* Postman
|
||||
* ProPorn
|
||||
* Rapidzona
|
||||
* RedeTorrent
|
||||
* RinTorNeT
|
||||
* RuTor
|
||||
* RuTracker.RU
|
||||
|
@ -130,6 +135,7 @@ A third-party Golang SDK for Jackett is available from [webtor-io/go-jackett](ht
|
|||
* Torrent9
|
||||
* Torrent9-tel
|
||||
* TorrentFunk
|
||||
* TorrentDosFilmes
|
||||
* TorrentDownload
|
||||
* TorrentKitty
|
||||
* TorrentProject2
|
||||
|
|
|
@ -0,0 +1,396 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using AngleSharp.Dom;
|
||||
using Jackett.Common.Models;
|
||||
using Jackett.Common.Models.IndexerConfig;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
using Jackett.Common.Utils;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using static System.Linq.Enumerable;
|
||||
using WebClient = Jackett.Common.Utils.Clients.WebClient;
|
||||
|
||||
namespace Jackett.Common.Indexers.Definitions.Abstract
|
||||
{
|
||||
public abstract class PublicBrazilianIndexerBase : IndexerBase
|
||||
{
|
||||
public PublicBrazilianIndexerBase(IIndexerConfigurationService configService, WebClient wc, Logger l,
|
||||
IProtectionService ps, ICacheService cs) : base(
|
||||
configService: configService, client: wc, logger: l, p: ps, cacheService: cs,
|
||||
configData: new ConfigurationData())
|
||||
{
|
||||
webclient.requestDelay = .5;
|
||||
}
|
||||
|
||||
public override string Description =>
|
||||
$"{Name} is a Public Torrent Tracker for Movies and TV Shows dubbed in Brazilian Portuguese";
|
||||
|
||||
public override string Language => "pt-BR";
|
||||
public override string Type => "public";
|
||||
public override TorznabCapabilities TorznabCaps => SetCapabilities();
|
||||
|
||||
private TorznabCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new TorznabCapabilities
|
||||
{
|
||||
MovieSearchParams = new List<MovieSearchParam> { MovieSearchParam.Q },
|
||||
TvSearchParams = new List<TvSearchParam> { TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep }
|
||||
};
|
||||
caps.Categories.AddCategoryMapping("filmes", TorznabCatType.Movies);
|
||||
caps.Categories.AddCategoryMapping("series", TorznabCatType.TV);
|
||||
return caps;
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator() => new SimpleRequestGenerator(SiteLink);
|
||||
|
||||
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
LoadValuesFromJson(configJson);
|
||||
await ConfigureIfOK(string.Empty, true, () => throw new Exception("Could not find releases from this URL"));
|
||||
return IndexerConfigurationStatus.Completed;
|
||||
}
|
||||
public class FileInfo
|
||||
{
|
||||
public string[] Genres { get; set; }
|
||||
public string[] Audio { get; set; }
|
||||
public string Subtitle { get; set; }
|
||||
public string Format { get; set; }
|
||||
public string Quality { get; set; }
|
||||
public string Size { get; set; }
|
||||
public string ReleaseYear { get; set; }
|
||||
public string Duration { get; set; }
|
||||
public string AudioQuality { get; set; }
|
||||
public string VideoQuality { get; set; }
|
||||
public string TitleTranslated { get; set; }
|
||||
public string TitleOriginal { get; set; }
|
||||
public string IMDb { get; set; }
|
||||
|
||||
public static FileInfo FromDictionary(Dictionary<string, string> dict)
|
||||
{
|
||||
return new FileInfo
|
||||
{
|
||||
Genres = dict.TryGetValue("Gênero", out var genres) ? genres?.Split(',').Select(g => g.Trim()).ToArray() : null,
|
||||
Audio = dict.TryGetValue("Áudio", out var audio) ? audio?.Split(',').Select(a => a.Trim()).ToArray() : (
|
||||
dict.TryGetValue("Idioma", out var lang) ? new[] { lang } : null),
|
||||
Subtitle = dict.TryGetValue("Legenda", out var subtitle) ? subtitle : null,
|
||||
Format = dict.TryGetValue("Formato", out var format) ? format : null,
|
||||
Quality = dict.TryGetValue("Qualidade", out var quality) ? quality : null,
|
||||
Size = dict.TryGetValue("Tamanho", out var size) ? size : null,
|
||||
ReleaseYear = dict.TryGetValue("Ano de Lançamento", out var releaseYear) ? releaseYear : (dict.TryGetValue("Lançamento", out var year) ? year : null),
|
||||
Duration = dict.TryGetValue("Duração", out var duration) ? duration : null,
|
||||
AudioQuality = dict.TryGetValue("Qualidade de Áudio", out var audioQuality) ? audioQuality : null,
|
||||
VideoQuality = dict.TryGetValue("Qualidade de Vídeo", out var videoQuality) ? videoQuality : null,
|
||||
TitleTranslated = dict.TryGetValue("Título Traduzido", out var titleTr) ? titleTr : null,
|
||||
TitleOriginal = dict.TryGetValue("Título Original", out var titleOr) ? titleOr : (dict.TryGetValue("Título", out var title) ? title : null),
|
||||
IMDb = dict.TryGetValue("IMDb", out var imdb) ? imdb : null
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SimpleRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
private readonly string _siteLink;
|
||||
private string SearchQueryParamsKey { get; }
|
||||
|
||||
public SimpleRequestGenerator(string siteLink, string searchQueryParamsKey = "?s=")
|
||||
{
|
||||
_siteLink = siteLink;
|
||||
SearchQueryParamsKey = searchQueryParamsKey;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TorznabQuery query)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
var searchUrl = $"{_siteLink}{SearchQueryParamsKey}";
|
||||
if (!string.IsNullOrWhiteSpace(query.SearchTerm))
|
||||
{
|
||||
searchUrl += WebUtility.UrlEncode(query.SearchTerm);
|
||||
if (query.Season is { } value)
|
||||
{
|
||||
searchUrl += WebUtility.UrlEncode($" {value}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
searchUrl = _siteLink;
|
||||
}
|
||||
|
||||
pageableRequests.Add(new[] { new IndexerRequest(searchUrl) });
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RowParsingExtensions
|
||||
{
|
||||
public static Uri ExtractMagnet(this IElement downloadButton)
|
||||
{
|
||||
var magnetLink = downloadButton.GetAttribute("href");
|
||||
var magnet = string.IsNullOrEmpty(magnetLink) ? null : new Uri(magnetLink);
|
||||
return magnet;
|
||||
}
|
||||
|
||||
public static List<string> ExtractGenres(this IElement row)
|
||||
{
|
||||
var genres = new List<string>();
|
||||
row.ExtractFromRow(
|
||||
"span:contains(\"Gênero:\")", genreText =>
|
||||
{
|
||||
ExtractPattern(
|
||||
genreText, @"Gênero:\s*(.+)", genre => ExtractMultiValuesFromField(values: out genres, field: genre));
|
||||
});
|
||||
return genres;
|
||||
}
|
||||
|
||||
public static List<int> ExtractCategory(this IElement row, string title = null)
|
||||
{
|
||||
var releaseCategory = new List<int>();
|
||||
var category = TorznabCatType.Movies;
|
||||
row.ExtractFromRow(
|
||||
"div.title > a", categoryText =>
|
||||
{
|
||||
category = ExtractCategory(categoryText);
|
||||
});
|
||||
if (!category.Equals(TorznabCatType.TV) && !string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
category = ExtractCategory(title);
|
||||
}
|
||||
releaseCategory.Add(category.ID);
|
||||
return releaseCategory;
|
||||
}
|
||||
|
||||
private static TorznabCategory ExtractCategory(string text)
|
||||
{
|
||||
var hasSeasonInfo = text.IndexOf("temporada", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
text.IndexOf("season", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
Regex.IsMatch(text, @"\bS\d{1,2}(?:E\d{1,2})?\b", RegexOptions.IgnoreCase);
|
||||
var category = hasSeasonInfo ? TorznabCatType.TV : TorznabCatType.Movies;
|
||||
return category;
|
||||
}
|
||||
|
||||
public static DateTime ExtractReleaseDate(this IElement row)
|
||||
{
|
||||
var result = DateTime.Today;
|
||||
row.ExtractFromRow(
|
||||
"span:contains(\"Lançamento:\")", releaseDateText =>
|
||||
{
|
||||
ExtractPattern(
|
||||
releaseDateText, @"Lançamento:\s*(.+)", releaseDate =>
|
||||
{
|
||||
DateTime.TryParseExact(
|
||||
releaseDate, "yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out result);
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<string> ExtractSubtitles(this IElement row)
|
||||
{
|
||||
var subtitles = new List<string>();
|
||||
row.ExtractFromRow(
|
||||
"span:contains(\"Legenda:\")", subtitleText =>
|
||||
{
|
||||
ExtractPattern(
|
||||
subtitleText, @"Legenda:\s*(.+)", subtitle => ExtractMultiValuesFromField(values: out subtitles, field: subtitle));
|
||||
});
|
||||
return subtitles;
|
||||
}
|
||||
|
||||
public static long ExtractSize(this IElement row)
|
||||
{
|
||||
long result = 0;
|
||||
row.ExtractFromRow(
|
||||
"span:contains(\"Tamanho:\")", sizeText =>
|
||||
{
|
||||
ExtractPattern(
|
||||
sizeText, @"Tamanho:\s*(.+)", size =>
|
||||
{
|
||||
result = GetBytes(size);
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public static long GetBytes(string text)
|
||||
{
|
||||
if (Regex.Matches(text, @"\b[GTKP]?B\b", RegexOptions.IgnoreCase).Count > 1)
|
||||
{
|
||||
var match = Regex.Match(text, @"[GTKP]?B([.,| \d]+[GTKP]?B)", RegexOptions.RightToLeft);
|
||||
if (match.Success)
|
||||
{
|
||||
text = match.Groups[1].Value;
|
||||
}
|
||||
}
|
||||
|
||||
return ParseUtil.GetBytes(text);
|
||||
}
|
||||
|
||||
public static List<string> ExtractLanguages(this IElement row)
|
||||
{
|
||||
var languages = new List<string>();
|
||||
row.ExtractFromRow(
|
||||
"span:contains(\"Áudio:\")", audioText =>
|
||||
{
|
||||
ExtractPattern(
|
||||
audioText, @"Áudio:\s*(.+)", language => ExtractMultiValuesFromField(values: out languages, field: language));
|
||||
});
|
||||
if (languages.Count == 0)
|
||||
{
|
||||
row.ExtractFromRow(
|
||||
"span:contains(\"Idioma:\")", languageText =>
|
||||
{
|
||||
ExtractPattern(
|
||||
languageText, @"Idioma:\s*(.+)", language => ExtractMultiValuesFromField(values: out languages, field: language));
|
||||
});
|
||||
}
|
||||
return languages;
|
||||
}
|
||||
private static void ExtractMultiValuesFromField(out List<string> values, in string field)
|
||||
{
|
||||
if (field.Contains("|"))
|
||||
{
|
||||
values = field.Split('|').Select(token => token.Trim()).ToList();
|
||||
}
|
||||
else if (field.Contains(","))
|
||||
{
|
||||
values = field.Split(',').Select(token => token.Trim()).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
values = new List<string> { field };
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExtractFromRow(this IElement row, string selector, Action<string> extraction)
|
||||
{
|
||||
var element = row.QuerySelector(selector);
|
||||
if (element != null)
|
||||
{
|
||||
extraction(element.TextContent);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExtractPattern(string text, string pattern, Action<string> extraction)
|
||||
{
|
||||
var match = Regex.Match(text, pattern);
|
||||
if (match.Success)
|
||||
{
|
||||
extraction(match.Groups[1].Value.Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
public abstract class PublicBrazilianParser : IParseIndexerResponse
|
||||
{
|
||||
public abstract IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse);
|
||||
|
||||
|
||||
|
||||
public string ExtractTitleOrDefault(IElement downloadButton, string defaultTitle)
|
||||
{
|
||||
var magnetTitle = "";
|
||||
RowParsingExtensions.ExtractPattern(downloadButton?.GetAttribute("href"),
|
||||
@"&dn=(.+?)&|&dn=(.+?)$",
|
||||
mt => magnetTitle = HttpUtility.UrlDecode(mt));
|
||||
if (!string.IsNullOrWhiteSpace(magnetTitle))
|
||||
return FormatTitle(CleanTitle(magnetTitle), ExtractResolution(magnetTitle));
|
||||
var description = GetTitleElementOrNull(downloadButton);
|
||||
var resolution = description?.TextContent switch
|
||||
{
|
||||
string text when !string.IsNullOrWhiteSpace(text) => ExtractResolution(text),
|
||||
_ => ExtractResolution(defaultTitle)
|
||||
};
|
||||
var title = (defaultTitle, description?.TextContent) switch
|
||||
{
|
||||
(string defTitle, _) when !string.IsNullOrWhiteSpace(defTitle) => CleanTitle(defTitle),
|
||||
(_, string text) when !string.IsNullOrWhiteSpace(text) => CleanTitle(text),
|
||||
_ => defaultTitle
|
||||
};
|
||||
return FormatTitle(title, resolution);
|
||||
}
|
||||
|
||||
private string ExtractResolution(string text)
|
||||
{
|
||||
var resolution = "";
|
||||
RowParsingExtensions.ExtractPattern(text, @"\b(\d{3,4}p)\b", res => resolution = res);
|
||||
return resolution;
|
||||
}
|
||||
|
||||
private string FormatTitle(string title, string resolution = null)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(resolution)
|
||||
? $"{title}"
|
||||
: $"{title} {resolution}";
|
||||
}
|
||||
|
||||
public long ExtractSizeByResolution(string title)
|
||||
{
|
||||
var resolution = "Other";
|
||||
RowParsingExtensions.ExtractPattern(
|
||||
title, @"\b(\d{3,4}p)\b", res =>
|
||||
{
|
||||
resolution = res;
|
||||
});
|
||||
|
||||
var size = resolution switch
|
||||
{
|
||||
"720p" => "1GB",
|
||||
"1080p" => "2.5GB",
|
||||
"2160p" => "5GB",
|
||||
_ => "512MB"
|
||||
};
|
||||
|
||||
return RowParsingExtensions.GetBytes(size);
|
||||
}
|
||||
|
||||
protected static string CleanTitle(string title)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
return null;
|
||||
|
||||
// Remove size info in parentheses
|
||||
title = Regex.Replace(title, @"\(\d+(?:\.\d+)?\s*(?:GB|MB)\)", "", RegexOptions.IgnoreCase);
|
||||
|
||||
// Remove quality info
|
||||
title = Regex.Replace(title, @"\b(?:720p|1080p|2160p|4K)\b", "", RegexOptions.IgnoreCase);
|
||||
|
||||
// Remove source info
|
||||
title = Regex.Replace(title, @"\b(?:WEB-DL|BRRip|HDRip|WEBRip|BluRay|Torrent|Download)\b", "", RegexOptions.IgnoreCase);
|
||||
|
||||
// Remove language info
|
||||
title = Regex.Replace(title, @"\b(?:Legendado|Leg|Dublado|Dub|[AÁ]udio)\b", "", RegexOptions.IgnoreCase);
|
||||
|
||||
// Clean up torrent group names
|
||||
title = Regex.Replace(title, @"HIDRATORRENTS\.ORG|\[?Erai-raws\]?|\[?Anime Time\]?|COMANDO4K\.COM|COMANDO\.TO|VEMTORRENT\.COM|VACATORRENT\.COM", "", RegexOptions.IgnoreCase);
|
||||
|
||||
// Remove brackets/parentheses content
|
||||
title = Regex.Replace(title, @"\[(?:.*?)\]|\((?:.*?)\)", "", RegexOptions.IgnoreCase);
|
||||
|
||||
// Remove dangling punctuation and separators
|
||||
title = Regex.Replace(title, @"[\\/,|~_-]+\s*|\s*[\\/,|~_-]+", " ", RegexOptions.IgnoreCase);
|
||||
|
||||
// Clean up multiple spaces
|
||||
title = Regex.Replace(title, @"\s+", " ");
|
||||
|
||||
// Remove file extension from the beginning of title
|
||||
title = Regex.Replace(title, @"MKV|MP4", "", RegexOptions.IgnoreCase);
|
||||
|
||||
// Remove dots between words but keep dots in version numbers
|
||||
title = Regex.Replace(title, @"(?<!\d)\.(?!\d)", " ", RegexOptions.IgnoreCase);
|
||||
|
||||
// Remove any remaining punctuation at start/end
|
||||
title = title.Trim(' ', '.', ',', '-', '_', '~', '/', '\\', '|');
|
||||
return title;
|
||||
}
|
||||
|
||||
protected abstract INode GetTitleElementOrNull(IElement downloadButton);
|
||||
|
||||
protected static bool NotSpanTag(INode description) =>
|
||||
(description.NodeType != NodeType.Element || ((Element)description).TagName != "SPAN");
|
||||
}
|
||||
}
|
166
src/Jackett.Common/Indexers/Definitions/ApacheTorrent.cs
Normal file
166
src/Jackett.Common/Indexers/Definitions/ApacheTorrent.cs
Normal file
|
@ -0,0 +1,166 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp.Html.Parser;
|
||||
using Jackett.Common.Extensions;
|
||||
using Jackett.Common.Indexers.Definitions.Abstract;
|
||||
using Jackett.Common.Models;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
using Jackett.Common.Utils;
|
||||
using Jackett.Common.Utils.Clients;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Common.Indexers.Definitions
|
||||
{
|
||||
public class ApacheTorrent : PublicBrazilianIndexerBase
|
||||
{
|
||||
public override string Id => "apachetorrent";
|
||||
|
||||
public override string Name => "ApacheTorrent";
|
||||
|
||||
public override string SiteLink { get; protected set; } = "https://apachetorrent.com/";
|
||||
|
||||
public ApacheTorrent(IIndexerConfigurationService configService, WebClient wc, Logger l, IProtectionService ps,
|
||||
ICacheService cs) : base(configService, wc, l, ps, cs)
|
||||
{
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser() =>
|
||||
new ApacheTorrentParser(webclient);
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator() => new SimpleRequestGenerator(SiteLink, searchQueryParamsKey: "index.php?s=");
|
||||
}
|
||||
|
||||
public class ApacheTorrentParser : PublicBrazilianParser
|
||||
{
|
||||
private readonly WebClient _webclient;
|
||||
public string Tracker { get; }
|
||||
|
||||
public ApacheTorrentParser(WebClient webclient)
|
||||
{
|
||||
_webclient = webclient;
|
||||
Tracker = "ApacheTorrent";
|
||||
}
|
||||
|
||||
private Dictionary<string, string> ExtractFileInfo(IDocument detailsDom)
|
||||
{
|
||||
var fileInfo = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
var infoSection = detailsDom.QuerySelector("#informacoes p");
|
||||
if (infoSection == null)
|
||||
return fileInfo;
|
||||
|
||||
var lines = infoSection.InnerHtml.Split(new[] { "<br>" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.Contains("<strong>") && line.Contains(":"))
|
||||
{
|
||||
var parts = line.Split(new[] { ':' }, 2);
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
var key = parts[0].Replace("<strong>", "").Replace("</strong>", "").Trim();
|
||||
var value = parts[1]
|
||||
.Replace("<strong>", "")
|
||||
.Replace("</strong>", "")
|
||||
.Replace("<span style=\"12px arial,verdana,tahoma;\">", "")
|
||||
.Replace("</span>", "")
|
||||
.Replace("<span class=\"entry-date\">", "")
|
||||
.Trim();
|
||||
value = value switch
|
||||
{
|
||||
var v when v.Contains("Dual Áudio") => v.Replace("Dual Áudio", "Dual"),
|
||||
var v when v.Contains("Dual Audio") => v.Replace("Dual Audio", "Dual"),
|
||||
var v when v.Contains("Full HD") => v.Replace("Full HD", "1080p"),
|
||||
var v when v.Contains("4K") => v.Replace("4K", "2160p"),
|
||||
var v when v.Contains("SD") => v.Replace("SD", "480p"),
|
||||
var v when v.Contains("WEB") => v.Replace("WEB", "WEB-DL"),
|
||||
_ => value
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
|
||||
{
|
||||
fileInfo[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
public override IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
var rows = dom.QuerySelectorAll("div.capaname");
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var detailAnchor = row.QuerySelector("a[href^=\"https://\"]");
|
||||
if (detailAnchor == null)
|
||||
continue;
|
||||
|
||||
var detailUrl = new Uri(detailAnchor.GetAttribute("href") ?? string.Empty);
|
||||
var title = detailAnchor.GetAttribute("title")?.Trim() ?? string.Empty;
|
||||
|
||||
var releaseCommonInfo = new ReleaseInfo
|
||||
{
|
||||
Title = CleanTitle(title),
|
||||
Details = detailUrl,
|
||||
Guid = detailUrl,
|
||||
Seeders = 1
|
||||
};
|
||||
|
||||
var detailsPage = _webclient.GetResultAsync(new WebRequest(detailUrl.ToString())).Result;
|
||||
var detailsDom = parser.ParseDocument(detailsPage.ContentString);
|
||||
|
||||
var fileInfoDict = ExtractFileInfo(detailsDom);
|
||||
var fileInfo = PublicBrazilianIndexerBase.FileInfo.FromDictionary(fileInfoDict);
|
||||
releaseCommonInfo.PublishDate = fileInfo.ReleaseYear != null ? DateTime.ParseExact(fileInfo.ReleaseYear, "yyyy", null) : DateTime.Today;
|
||||
|
||||
var magnetLinks = detailsDom.QuerySelectorAll("a.btn[href^=\"magnet:?xt\"]");
|
||||
foreach (var magnetLink in magnetLinks)
|
||||
{
|
||||
var magnet = magnetLink.GetAttribute("href");
|
||||
var release = releaseCommonInfo.Clone() as ReleaseInfo;
|
||||
release.Link = release.MagnetUri = new Uri(magnet ?? "");
|
||||
release.DownloadVolumeFactor = 0;
|
||||
release.UploadVolumeFactor = 1;
|
||||
|
||||
// Extract resolution from file info
|
||||
var resolution = fileInfo.Quality ?? fileInfo.VideoQuality ?? string.Empty;
|
||||
|
||||
// Format the title
|
||||
release.Title = $"{release.Title} {resolution}".Trim();
|
||||
release.Title = ExtractTitleOrDefault(magnetLink, release.Title);
|
||||
release.Category = magnetLink.ExtractCategory(release.Title);
|
||||
|
||||
// Additional metadata
|
||||
release.Languages = fileInfo.Audio?.ToList() ?? release.Languages;
|
||||
release.Genres = fileInfo.Genres?.ToList() ?? release.Genres;
|
||||
release.Subs = string.IsNullOrEmpty(fileInfo.Subtitle) ? release.Subs : new[] { fileInfo.Subtitle };
|
||||
var size = RowParsingExtensions.GetBytes(fileInfo.Size ?? string.Empty);
|
||||
release.Size = size > 0 ? size : ExtractSizeByResolution(release.Title);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(release.Title))
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
|
||||
protected override INode GetTitleElementOrNull(IElement downloadButton)
|
||||
{
|
||||
var description = downloadButton.PreviousSibling;
|
||||
while (description != null && description.NodeType != NodeType.Text)
|
||||
{
|
||||
description = description.PreviousSibling;
|
||||
}
|
||||
|
||||
return description;
|
||||
}
|
||||
}
|
||||
}
|
107
src/Jackett.Common/Indexers/Definitions/BluDV.cs
Normal file
107
src/Jackett.Common/Indexers/Definitions/BluDV.cs
Normal file
|
@ -0,0 +1,107 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp.Html.Parser;
|
||||
using Jackett.Common.Extensions;
|
||||
using Jackett.Common.Indexers.Definitions.Abstract;
|
||||
using Jackett.Common.Models;
|
||||
using Jackett.Common.Models.IndexerConfig;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
using NLog;
|
||||
using WebClient = Jackett.Common.Utils.Clients.WebClient;
|
||||
using WebRequest = Jackett.Common.Utils.Clients.WebRequest;
|
||||
|
||||
namespace Jackett.Common.Indexers.Definitions
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class BluDV : PublicBrazilianIndexerBase
|
||||
{
|
||||
public override string Id => "bludv";
|
||||
public override string Name => "BluDV";
|
||||
public override string SiteLink { get; protected set; } = "https://bludv.xyz/";
|
||||
|
||||
public BluDV(IIndexerConfigurationService configService, WebClient wc, Logger l, IProtectionService ps, ICacheService cs)
|
||||
: base(configService, wc, l, ps, cs)
|
||||
{
|
||||
configData.AddDynamic(
|
||||
"flaresolverr",
|
||||
new ConfigurationData.DisplayInfoConfigurationItem("FlareSolverr",
|
||||
"This site may use Cloudflare DDoS Protection, therefore Jackett requires <a href=\"https://github.com/Jackett/Jackett#configuring-flaresolverr\" target=\"_blank\">FlareSolverr</a> to access it."));
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser() => new BluDVParser(webclient);
|
||||
}
|
||||
|
||||
public class BluDVParser : PublicBrazilianParser
|
||||
{
|
||||
private readonly WebClient _webclient;
|
||||
|
||||
public BluDVParser(WebClient webclient)
|
||||
{
|
||||
_webclient = webclient;
|
||||
}
|
||||
|
||||
public override IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
var rows = dom.QuerySelectorAll("div.post");
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
// Get the details page to extract the magnet link
|
||||
var detailsParser = new HtmlParser();
|
||||
var detailAnchor = row.QuerySelector("a.more-link");
|
||||
var detailUrl = new Uri(detailAnchor?.GetAttribute("href") ?? string.Empty);
|
||||
var title = row.QuerySelector("div.title > a")?.TextContent.Trim();
|
||||
var releaseCommonInfo = new ReleaseInfo
|
||||
{
|
||||
Title = CleanTitle(title),
|
||||
Genres = row.ExtractGenres(),
|
||||
Subs = row.ExtractSubtitles(),
|
||||
Size = row.ExtractSize(),
|
||||
Languages = row.ExtractLanguages(),
|
||||
Details = detailUrl,
|
||||
Guid = detailUrl,
|
||||
PublishDate = row.ExtractReleaseDate(),
|
||||
Seeders = 1
|
||||
};
|
||||
var detailsPage = _webclient.GetResultAsync(new WebRequest(detailUrl.ToString())).Result;
|
||||
var detailsDom = detailsParser.ParseDocument(detailsPage.ContentString);
|
||||
foreach (var downloadButton in detailsDom.QuerySelectorAll("a.customButton[href^=\"magnet:\"]"))
|
||||
{
|
||||
var magnet = downloadButton.ExtractMagnet();
|
||||
var release = releaseCommonInfo.Clone() as ReleaseInfo;
|
||||
release.Title = ExtractTitleOrDefault(downloadButton, release.Title);
|
||||
release.Category = downloadButton.ExtractCategory(release.Title);
|
||||
release.Size = release.Size > 0 ? release.Size : ExtractSizeByResolution(release.Title);
|
||||
release.Languages = row.ExtractLanguages();
|
||||
release.Link = release.Guid = release.MagnetUri = magnet;
|
||||
release.DownloadVolumeFactor = 0; // Free
|
||||
release.UploadVolumeFactor = 1;
|
||||
|
||||
if (release.Title.IsNotNullOrWhiteSpace())
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
|
||||
protected override INode GetTitleElementOrNull(IElement downloadButton)
|
||||
{
|
||||
var description = downloadButton.PreviousSibling;
|
||||
while (description != null && NotSpanTag(description))
|
||||
{
|
||||
description = description.PreviousSibling;
|
||||
}
|
||||
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
140
src/Jackett.Common/Indexers/Definitions/FilmesHdTorrent.cs
Normal file
140
src/Jackett.Common/Indexers/Definitions/FilmesHdTorrent.cs
Normal file
|
@ -0,0 +1,140 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp.Html.Parser;
|
||||
using Jackett.Common.Extensions;
|
||||
using Jackett.Common.Indexers.Definitions.Abstract;
|
||||
using Jackett.Common.Models;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
using Jackett.Common.Utils;
|
||||
using Jackett.Common.Utils.Clients;
|
||||
using NLog;
|
||||
using WebClient = Jackett.Common.Utils.Clients.WebClient;
|
||||
|
||||
namespace Jackett.Common.Indexers.Definitions
|
||||
{
|
||||
public class FilmesHdTorrent : PublicBrazilianIndexerBase
|
||||
{
|
||||
public override string Id => "filmeshdtorrent";
|
||||
public override string Name => "Filmes HD Torrent";
|
||||
public override string SiteLink { get; protected set; } = "https://www.filmeshdtorrent.vip/";
|
||||
|
||||
public override string[] AlternativeSiteLinks { get; protected set; } = {
|
||||
"https://www.filmeshdtorrent.vip/",
|
||||
"https://torrentalerta.net/",
|
||||
};
|
||||
|
||||
public override string[] LegacySiteLinks { get; protected set; } = {
|
||||
"https://baixarfilmestorrents.net/",
|
||||
"https://comandofilmes.life/"
|
||||
};
|
||||
|
||||
public FilmesHdTorrent(IIndexerConfigurationService configService, WebClient wc, Logger l, IProtectionService ps, ICacheService cs) : base(configService, wc, l, ps, cs)
|
||||
{
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser() => new FilmesHdTorrentParser(webclient);
|
||||
}
|
||||
public class FilmesHdTorrentParser : PublicBrazilianParser
|
||||
{
|
||||
private readonly WebClient _webclient;
|
||||
|
||||
public FilmesHdTorrentParser(WebClient webclient)
|
||||
{
|
||||
_webclient = webclient;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> ExtractFileInfo(IDocument detailsDom)
|
||||
{
|
||||
var fileInfo = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
var content = detailsDom.QuerySelector("div.content");
|
||||
if (content == null)
|
||||
return fileInfo;
|
||||
|
||||
var lines = content.InnerHtml.Split(new[] { "<br>" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.Contains("<strong>") && line.Contains("</strong>") && line.Contains(":"))
|
||||
{
|
||||
var cleanLine = Regex.Replace(line, @"<[^>]+>", ""); // Remove HTML tags
|
||||
var parts = cleanLine.Split(new[] { ':' }, 2);
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
var key = parts[0].Trim();
|
||||
var value = parts[1].Trim();
|
||||
if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
|
||||
{
|
||||
fileInfo[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
public override IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
var rows = dom.QuerySelectorAll("div.item");
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var detailsParser = new HtmlParser();
|
||||
var detailAnchor = row.QuerySelector("a[title]");
|
||||
var detailUrl = new Uri(detailAnchor?.GetAttribute("href") ?? string.Empty);
|
||||
var releaseCommonInfo = new ReleaseInfo
|
||||
{
|
||||
Title = CleanTitle(row.QuerySelector("div.titulo span")?.TextContent.Trim() ?? detailAnchor?.GetAttribute("title")?.Trim() ?? string.Empty),
|
||||
Details = detailUrl,
|
||||
Guid = detailUrl,
|
||||
PublishDate = row.ExtractReleaseDate(),
|
||||
Seeders = 1
|
||||
};
|
||||
|
||||
var detailsPage = _webclient.GetResultAsync(new WebRequest(detailUrl.ToString())).Result;
|
||||
var detailsDom = detailsParser.ParseDocument(detailsPage.ContentString);
|
||||
|
||||
var fileInfoDict = ExtractFileInfo(detailsDom);
|
||||
var fileInfo = PublicBrazilianIndexerBase.FileInfo.FromDictionary(fileInfoDict);
|
||||
var querySelectorAll = detailsDom.QuerySelectorAll("a[href^=\"magnet:?xt\"]");
|
||||
foreach (var downloadButton in querySelectorAll)
|
||||
{
|
||||
var magnet = downloadButton.ExtractMagnet();
|
||||
var release = releaseCommonInfo.Clone() as ReleaseInfo;
|
||||
release.Title = ExtractTitleOrDefault(downloadButton, release.Title);
|
||||
release.Category = downloadButton.ExtractCategory(release.Title);
|
||||
release.Languages = fileInfo.Audio?.ToList() ?? release.Languages;
|
||||
release.Genres = fileInfo.Genres?.ToList() ?? release.Genres;
|
||||
release.Subs = string.IsNullOrEmpty(fileInfo.Subtitle) ? release.Subs : new[] { fileInfo.Subtitle };
|
||||
var size = RowParsingExtensions.GetBytes(fileInfo.Size ?? string.Empty);
|
||||
release.Size = size > 0 ? size : ExtractSizeByResolution(release.Title);
|
||||
release.Link = release.Guid = release.MagnetUri = magnet;
|
||||
release.DownloadVolumeFactor = 0;
|
||||
release.UploadVolumeFactor = 1;
|
||||
|
||||
if (release.Title.IsNotNullOrWhiteSpace())
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
|
||||
protected override INode GetTitleElementOrNull(IElement downloadButton)
|
||||
{
|
||||
var description = downloadButton.PreviousSibling;
|
||||
while (description != null && NotSpanTag(description))
|
||||
{
|
||||
description = description.PreviousSibling;
|
||||
}
|
||||
|
||||
return description;
|
||||
}
|
||||
}
|
||||
}
|
128
src/Jackett.Common/Indexers/Definitions/LAPUMiA.cs
Normal file
128
src/Jackett.Common/Indexers/Definitions/LAPUMiA.cs
Normal file
|
@ -0,0 +1,128 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp.Html.Parser;
|
||||
using Jackett.Common.Extensions;
|
||||
using Jackett.Common.Indexers.Definitions.Abstract;
|
||||
using Jackett.Common.Models;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
using Jackett.Common.Utils;
|
||||
using NLog;
|
||||
using static System.Linq.Enumerable;
|
||||
using WebClient = Jackett.Common.Utils.Clients.WebClient;
|
||||
using WebRequest = Jackett.Common.Utils.Clients.WebRequest;
|
||||
|
||||
namespace Jackett.Common.Indexers.Definitions
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class LAPUMiA : PublicBrazilianIndexerBase
|
||||
{
|
||||
public override string Id => "lapumia";
|
||||
public override string Name => "LAPUMiA";
|
||||
public override string SiteLink { get; protected set; } = "https://lapumia.net/";
|
||||
|
||||
public LAPUMiA(IIndexerConfigurationService configService, WebClient wc, Logger l, IProtectionService ps,
|
||||
ICacheService cs) : base(configService: configService, wc, l, ps, cs)
|
||||
{
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser() => new LAPUMiAParser(webclient);
|
||||
}
|
||||
|
||||
public class LAPUMiAParser : PublicBrazilianParser
|
||||
{
|
||||
private WebClient _webclient;
|
||||
|
||||
public LAPUMiAParser(WebClient webclient)
|
||||
{
|
||||
_webclient = webclient;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> ExtractFileInfo(IDocument detailsDom)
|
||||
{
|
||||
var fileInfo = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
var infoItems = detailsDom.QuerySelectorAll("div.info li");
|
||||
foreach (var item in infoItems)
|
||||
{
|
||||
var text = item.TextContent.Trim();
|
||||
var parts = text.Split(
|
||||
new[]
|
||||
{
|
||||
':'
|
||||
}, 2);
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
var key = parts[0].Trim();
|
||||
var value = parts[1].Trim();
|
||||
fileInfo[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
public override IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
var rows = dom.QuerySelectorAll("div.item");
|
||||
foreach (var row in rows)
|
||||
{
|
||||
// Get the details page to extract the magnet link
|
||||
var detailsParser = new HtmlParser();
|
||||
var detailAnchor = row.QuerySelector("a[title]");
|
||||
var detailUrl = new Uri(detailAnchor?.GetAttribute("href"));
|
||||
var title = detailAnchor.GetAttribute("title");
|
||||
var releaseCommonInfo = new ReleaseInfo
|
||||
{
|
||||
Title = CleanTitle(title),
|
||||
Details = detailUrl,
|
||||
Guid = detailUrl,
|
||||
PublishDate = row.ExtractReleaseDate(),
|
||||
Seeders = 1
|
||||
};
|
||||
var detailsPage = _webclient.GetResultAsync(new WebRequest(detailUrl.ToString())).Result;
|
||||
var detailsDom = detailsParser.ParseDocument(detailsPage.ContentString);
|
||||
foreach (var downloadButton in detailsDom.QuerySelectorAll("ul.buttons a[href^=\"magnet:?xt\"]"))
|
||||
{
|
||||
var release = releaseCommonInfo.Clone() as ReleaseInfo;
|
||||
release.Title = ExtractTitleOrDefault(downloadButton, release.Title);
|
||||
release.Category = downloadButton.ExtractCategory(release.Title);
|
||||
var fileInfoDict = ExtractFileInfo(detailsDom);
|
||||
var fileInfo = PublicBrazilianIndexerBase.FileInfo.FromDictionary(fileInfoDict);
|
||||
release.Languages = fileInfo.Audio?.ToList() ?? release.Languages;
|
||||
release.Genres = fileInfo.Genres?.ToList() ?? release.Genres;
|
||||
release.Subs = string.IsNullOrEmpty(fileInfo.Subtitle)
|
||||
? release.Subs
|
||||
: new[]
|
||||
{
|
||||
fileInfo.Subtitle
|
||||
};
|
||||
var size = RowParsingExtensions.GetBytes(fileInfo.Size ?? string.Empty);
|
||||
release.Size = size > 0 ? size : ExtractSizeByResolution(release.Title);
|
||||
var magnet = downloadButton.ExtractMagnet();
|
||||
release.Link = release.Guid = release.MagnetUri = magnet;
|
||||
release.DownloadVolumeFactor = 0; // Free
|
||||
release.UploadVolumeFactor = 1;
|
||||
if (release.Title.IsNotNullOrWhiteSpace())
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
|
||||
protected override INode GetTitleElementOrNull(IElement downloadButton)
|
||||
{
|
||||
var description = downloadButton.PreviousSibling;
|
||||
while (description != null && description.NodeType != NodeType.Text)
|
||||
{
|
||||
description = description.PreviousSibling;
|
||||
}
|
||||
|
||||
return description;
|
||||
}
|
||||
}
|
||||
}
|
167
src/Jackett.Common/Indexers/Definitions/RedeTorrent.cs
Normal file
167
src/Jackett.Common/Indexers/Definitions/RedeTorrent.cs
Normal file
|
@ -0,0 +1,167 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp.Html.Parser;
|
||||
using Jackett.Common.Extensions;
|
||||
using Jackett.Common.Indexers.Definitions.Abstract;
|
||||
using Jackett.Common.Models;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
using Jackett.Common.Utils;
|
||||
using Jackett.Common.Utils.Clients;
|
||||
using NLog;
|
||||
using WebClient = Jackett.Common.Utils.Clients.WebClient;
|
||||
|
||||
namespace Jackett.Common.Indexers.Definitions
|
||||
{
|
||||
public class RedeTorrent : PublicBrazilianIndexerBase
|
||||
{
|
||||
public override string Id => "redetorrent";
|
||||
|
||||
public override string Name => "RedeTorrent";
|
||||
|
||||
public override string SiteLink { get; protected set; } = "https://redetorrent.com/";
|
||||
|
||||
public RedeTorrent(IIndexerConfigurationService configService, WebClient wc, Logger l, IProtectionService ps, ICacheService cs)
|
||||
: base(configService, wc, l, ps, cs)
|
||||
{
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser() => new RedeTorrentParser(webclient);
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator() => new SimpleRequestGenerator(SiteLink, searchQueryParamsKey: "index.php?s=");
|
||||
}
|
||||
|
||||
public class RedeTorrentParser : PublicBrazilianParser
|
||||
{
|
||||
private readonly WebClient _webclient;
|
||||
protected string Tracker;
|
||||
|
||||
public RedeTorrentParser(WebClient webclient)
|
||||
{
|
||||
_webclient = webclient;
|
||||
Tracker = "RedeTorrent";
|
||||
}
|
||||
|
||||
private Dictionary<string, string> ExtractFileInfo(IDocument detailsDom)
|
||||
{
|
||||
var fileInfo = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
var infoSection = detailsDom.QuerySelector("#informacoes p");
|
||||
if (infoSection == null)
|
||||
return fileInfo;
|
||||
|
||||
var lines = infoSection.InnerHtml.Split(new[] { "<br>" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.Contains("<strong>") && line.Contains(":"))
|
||||
{
|
||||
var parts = line.Split(new[] { ':' }, 2);
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
var key = parts[0].Replace("<strong>", "").Replace("</strong>", "").Trim();
|
||||
var value = parts[1]
|
||||
.Replace("<strong>", "")
|
||||
.Replace("</strong>", "")
|
||||
.Replace("<span style=\"12px arial,verdana,tahoma;\">", "")
|
||||
.Replace("</span>", "")
|
||||
.Replace("<span class=\"entry-date\">", "")
|
||||
.Trim();
|
||||
value = value switch
|
||||
{
|
||||
var v when v.Contains("Dual Áudio") => v.Replace("Dual Áudio", "Dual"),
|
||||
var v when v.Contains("Dual Audio") => v.Replace("Dual Audio", "Dual"),
|
||||
var v when v.Contains("Full HD") => v.Replace("Full HD", "1080p"),
|
||||
var v when v.Contains("4K") => v.Replace("4K", "2160p"),
|
||||
var v when v.Contains("SD") => v.Replace("SD", "480p"),
|
||||
var v when v.Contains("WEB") => v.Replace("WEB", "WEB-DL"),
|
||||
_ => value
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
|
||||
{
|
||||
fileInfo[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
public override IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
var rows = dom.QuerySelectorAll("div.capa_lista");
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var detailAnchor = row.QuerySelector("a[href^=\"https://\"]");
|
||||
if (detailAnchor == null)
|
||||
continue;
|
||||
|
||||
var detailUrl = new Uri(detailAnchor.GetAttribute("href") ?? string.Empty);
|
||||
var titleElement = row.QuerySelector("h2[itemprop='headline']");
|
||||
var title = titleElement?.TextContent.Trim() ?? detailAnchor.GetAttribute("title")?.Trim() ?? string.Empty;
|
||||
|
||||
var releaseCommonInfo = new ReleaseInfo
|
||||
{
|
||||
Title = CleanTitle(title),
|
||||
Details = detailUrl,
|
||||
Guid = detailUrl,
|
||||
Seeders = 1
|
||||
};
|
||||
|
||||
var detailsPage = _webclient.GetResultAsync(new WebRequest(detailUrl.ToString())).Result;
|
||||
var detailsDom = parser.ParseDocument(detailsPage.ContentString);
|
||||
|
||||
var fileInfoDict = ExtractFileInfo(detailsDom);
|
||||
var fileInfo = PublicBrazilianIndexerBase.FileInfo.FromDictionary(fileInfoDict);
|
||||
releaseCommonInfo.PublishDate = fileInfo.ReleaseYear != null ? DateTime.ParseExact(fileInfo.ReleaseYear, "yyyy", null) : DateTime.Today;
|
||||
|
||||
var magnetLinks = detailsDom.QuerySelectorAll("a.btn[href^=\"magnet:?xt\"]");
|
||||
foreach (var magnetLink in magnetLinks)
|
||||
{
|
||||
var magnet = magnetLink.GetAttribute("href");
|
||||
var release = releaseCommonInfo.Clone() as ReleaseInfo;
|
||||
release.Guid = release.Link = release.MagnetUri = new Uri(magnet ?? string.Empty);
|
||||
release.DownloadVolumeFactor = 0;
|
||||
release.UploadVolumeFactor = 1;
|
||||
|
||||
// Extract resolution from file info
|
||||
var resolution = fileInfo.Quality ?? fileInfo.VideoQuality ?? string.Empty;
|
||||
|
||||
// Format the title
|
||||
release.Title = $"{release.Title} {resolution}".Trim();
|
||||
release.Title = ExtractTitleOrDefault(magnetLink, release.Title);
|
||||
release.Category = magnetLink.ExtractCategory(release.Title);
|
||||
|
||||
// Additional metadata
|
||||
release.Languages = fileInfo.Audio?.ToList() ?? release.Languages;
|
||||
release.Genres = fileInfo.Genres?.ToList() ?? release.Genres;
|
||||
release.Subs = string.IsNullOrEmpty(fileInfo.Subtitle) ? release.Subs : new[] { fileInfo.Subtitle };
|
||||
var size = RowParsingExtensions.GetBytes(fileInfo.Size ?? string.Empty);
|
||||
release.Size = size > 0 ? size : ExtractSizeByResolution(release.Title);
|
||||
|
||||
if (release.Title.IsNotNullOrWhiteSpace())
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
|
||||
protected override INode GetTitleElementOrNull(IElement downloadButton)
|
||||
{
|
||||
var description = downloadButton.PreviousSibling;
|
||||
while (description != null && description.NodeType != NodeType.Text)
|
||||
{
|
||||
description = description.PreviousSibling;
|
||||
}
|
||||
|
||||
return description;
|
||||
}
|
||||
}
|
||||
}
|
134
src/Jackett.Common/Indexers/Definitions/TorrentDosFilmes.cs
Normal file
134
src/Jackett.Common/Indexers/Definitions/TorrentDosFilmes.cs
Normal file
|
@ -0,0 +1,134 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp.Html.Parser;
|
||||
using Jackett.Common.Extensions;
|
||||
using Jackett.Common.Indexers.Definitions.Abstract;
|
||||
using Jackett.Common.Models;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
using Jackett.Common.Utils;
|
||||
using NLog;
|
||||
using WebClient = Jackett.Common.Utils.Clients.WebClient;
|
||||
using WebRequest = Jackett.Common.Utils.Clients.WebRequest;
|
||||
|
||||
namespace Jackett.Common.Indexers.Definitions
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class TorrentDosFilmes : PublicBrazilianIndexerBase
|
||||
{
|
||||
public override string Id => "torrentdosfilmes";
|
||||
public override string Name => "TorrentDosFilmes";
|
||||
public override string SiteLink { get; protected set; } = "https://torrentsdosfilmes.to/";
|
||||
|
||||
public override string[] AlternativeSiteLinks { get; protected set; } = {
|
||||
"https://torrentsdosfilmes.to/",
|
||||
"https://ComandoFilmes.xyz/"
|
||||
};
|
||||
|
||||
public override string[] LegacySiteLinks { get; protected set; } = {
|
||||
"https://torrentdosfilmes.site/"
|
||||
};
|
||||
|
||||
public TorrentDosFilmes(IIndexerConfigurationService configService, WebClient wc, Logger l, IProtectionService ps,
|
||||
ICacheService cs) : base(configService, wc, l, ps, cs)
|
||||
{
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser() => new TorrentDosFilmesParser(webclient);
|
||||
}
|
||||
|
||||
public class TorrentDosFilmesParser : PublicBrazilianParser
|
||||
{
|
||||
private readonly WebClient _webclient;
|
||||
|
||||
public TorrentDosFilmesParser(WebClient webclient)
|
||||
{
|
||||
_webclient = webclient;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> ExtractFileInfo(IDocument detailsDom)
|
||||
{
|
||||
var fileInfo = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
var infoSpans = detailsDom.QuerySelectorAll("span[style*='color: black']");
|
||||
|
||||
foreach (var span in infoSpans)
|
||||
{
|
||||
var text = span.TextContent.Trim();
|
||||
var parts = text.Split(new[] { ':' }, 2);
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
var key = parts[0].Replace("<em>", "").Replace("</em>", "").Replace("<strong>", "").Replace("</strong>", "").Trim();
|
||||
var value = parts[1].Trim();
|
||||
fileInfo[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
public override IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
var rows = dom.QuerySelectorAll("div.post");
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var detailsParser = new HtmlParser();
|
||||
var detailAnchor = row.QuerySelector("div.title a[title]");
|
||||
var detailUrl = new Uri(detailAnchor?.GetAttribute("href") ?? string.Empty);
|
||||
var title = detailAnchor?.TextContent.Trim() ?? string.Empty;
|
||||
var releaseCommonInfo = new ReleaseInfo
|
||||
{
|
||||
Title = CleanTitle(title),
|
||||
Details = detailUrl,
|
||||
Guid = detailUrl,
|
||||
PublishDate = row.ExtractReleaseDate(),
|
||||
Seeders = 1
|
||||
};
|
||||
|
||||
var detailsPage = _webclient.GetResultAsync(new WebRequest(detailUrl.ToString())).Result;
|
||||
var detailsDom = detailsParser.ParseDocument(detailsPage.ContentString);
|
||||
|
||||
var fileInfoDict = ExtractFileInfo(detailsDom);
|
||||
var fileInfo = PublicBrazilianIndexerBase.FileInfo.FromDictionary(fileInfoDict);
|
||||
|
||||
foreach (var downloadButton in detailsDom.QuerySelectorAll("a.customButton[href^=\"magnet:\"]"))
|
||||
{
|
||||
var magnet = downloadButton.ExtractMagnet();
|
||||
var release = releaseCommonInfo.Clone() as ReleaseInfo;
|
||||
release.Title = ExtractTitleOrDefault(downloadButton, release.Title);
|
||||
release.Category = downloadButton.ExtractCategory(release.Title);
|
||||
release.Languages = fileInfo.Audio?.ToList() ?? release.Languages;
|
||||
release.Genres = fileInfo.Genres?.ToList() ?? release.Genres;
|
||||
release.Subs = string.IsNullOrEmpty(fileInfo.Subtitle) ? release.Subs : new[] { fileInfo.Subtitle };
|
||||
var size = RowParsingExtensions.GetBytes(fileInfo.Size ?? string.Empty);
|
||||
release.Size = size > 0 ? size : ExtractSizeByResolution(release.Title);
|
||||
release.Link = release.Guid = release.MagnetUri = magnet;
|
||||
release.DownloadVolumeFactor = 0;
|
||||
release.UploadVolumeFactor = 1;
|
||||
|
||||
if (release.Title.IsNotNullOrWhiteSpace())
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
|
||||
protected override INode GetTitleElementOrNull(IElement downloadButton)
|
||||
{
|
||||
var description = downloadButton.PreviousSibling;
|
||||
while (description != null && NotSpanTag(description))
|
||||
{
|
||||
description = description.PreviousSibling;
|
||||
}
|
||||
|
||||
return description;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp.Html.Parser;
|
||||
using Jackett.Common.Extensions;
|
||||
using Jackett.Common.Indexers.Definitions.Abstract;
|
||||
using Jackett.Common.Models;
|
||||
using Jackett.Common.Services.Interfaces;
|
||||
using Jackett.Common.Utils.Clients;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Common.Indexers.Definitions
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class TorrentsMegaFilmes : PublicBrazilianIndexerBase
|
||||
{
|
||||
public override string Id => "torrentsmegafilmes";
|
||||
public override string Name => "Torrents Mega Filmes";
|
||||
public override string SiteLink { get; protected set; } = "https://torrentsmegafilmes.top/";
|
||||
|
||||
public TorrentsMegaFilmes(IIndexerConfigurationService configService, WebClient wc, Logger l, IProtectionService ps, ICacheService cs) : base(configService, wc, l, ps, cs)
|
||||
{
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser() => new TorrentsMegaFilmesParser(webclient);
|
||||
}
|
||||
|
||||
public class TorrentsMegaFilmesParser : PublicBrazilianParser
|
||||
{
|
||||
private readonly WebClient _webclient;
|
||||
public TorrentsMegaFilmesParser(WebClient webclient)
|
||||
{
|
||||
_webclient = webclient;
|
||||
}
|
||||
|
||||
public override IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
var detailAnchors = dom.QuerySelectorAll("div.title > a");
|
||||
foreach (var detailAnchor in detailAnchors)
|
||||
{
|
||||
var detailUrl = new Uri(detailAnchor?.GetAttribute("href") ?? string.Empty);
|
||||
var title = detailAnchor?.TextContent.Trim();
|
||||
var detailsPage = _webclient.GetResultAsync(new WebRequest(detailUrl.ToString())).Result;
|
||||
var detailsDom = parser.ParseDocument(detailsPage.ContentString);
|
||||
var detailsInfo = detailsDom.QuerySelector("div.info");
|
||||
var releaseCommonInfo = new ReleaseInfo
|
||||
{
|
||||
Title = CleanTitle(title),
|
||||
Genres = detailsInfo.ExtractGenres(),
|
||||
Subs = detailsInfo.ExtractSubtitles(),
|
||||
Size = detailsInfo.ExtractSize(),
|
||||
Languages = detailsInfo.ExtractLanguages(),
|
||||
Details = detailUrl,
|
||||
Guid = detailUrl,
|
||||
PublishDate = detailsInfo.ExtractReleaseDate(),
|
||||
Seeders = 1
|
||||
};
|
||||
foreach (var downloadButton in detailsDom.QuerySelectorAll("ul.buttons a[href]"))
|
||||
{
|
||||
var magnet = downloadButton.ExtractMagnet();
|
||||
var release = releaseCommonInfo.Clone() as ReleaseInfo;
|
||||
release.Link = release.Guid = release.MagnetUri = magnet;
|
||||
release.Title = ExtractTitleOrDefault(downloadButton, release.Title + " " + downloadButton.TextContent);
|
||||
release.Category = downloadButton.ExtractCategory(release.Title);
|
||||
release.DownloadVolumeFactor = 0; // Free
|
||||
release.UploadVolumeFactor = 1;
|
||||
|
||||
if (release.Title.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
}
|
||||
return releases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return null to concatenate titles rather than ranking, i.e., button only contains resolution, season, and episode.
|
||||
*/
|
||||
protected override INode GetTitleElementOrNull(IElement downloadButton) => null;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue