mirror of
https://github.com/Jackett/Jackett
synced 2025-01-04 22:41:49 +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
|
* Anime Tosho
|
||||||
* AniRena
|
* AniRena
|
||||||
* AniSource
|
* AniSource
|
||||||
|
* ApacheTorrent
|
||||||
* AudioBook Bay (ABB)
|
* AudioBook Bay (ABB)
|
||||||
* Badass Torrents
|
* Badass Torrents
|
||||||
* Bangumi Moe
|
* Bangumi Moe
|
||||||
* BigFANGroup
|
* BigFANGroup
|
||||||
* BitRu
|
* BitRu
|
||||||
* BitSearch
|
* BitSearch
|
||||||
|
* BluDV
|
||||||
* BlueRoms
|
* BlueRoms
|
||||||
* BT.etree
|
* BT.etree
|
||||||
* BTdirectory (BT目录)
|
* BTdirectory (BT目录)
|
||||||
|
@ -62,6 +64,7 @@ A third-party Golang SDK for Jackett is available from [webtor-io/go-jackett](ht
|
||||||
* EXT Torrents
|
* EXT Torrents
|
||||||
* ExtraTorrent.st
|
* ExtraTorrent.st
|
||||||
* EZTV
|
* EZTV
|
||||||
|
* FilmesHdTorrent
|
||||||
* Frozen Layer
|
* Frozen Layer
|
||||||
* GamesTorrents
|
* GamesTorrents
|
||||||
* GkTorrent
|
* GkTorrent
|
||||||
|
@ -76,6 +79,7 @@ A third-party Golang SDK for Jackett is available from [webtor-io/go-jackett](ht
|
||||||
* kickasstorrents.to
|
* kickasstorrents.to
|
||||||
* kickasstorrents.ws
|
* kickasstorrents.ws
|
||||||
* Knaben
|
* Knaben
|
||||||
|
* LAPUMiA
|
||||||
* LePorno.info
|
* LePorno.info
|
||||||
* Libronube
|
* Libronube
|
||||||
* LimeTorrents
|
* LimeTorrents
|
||||||
|
@ -106,6 +110,7 @@ A third-party Golang SDK for Jackett is available from [webtor-io/go-jackett](ht
|
||||||
* Postman
|
* Postman
|
||||||
* ProPorn
|
* ProPorn
|
||||||
* Rapidzona
|
* Rapidzona
|
||||||
|
* RedeTorrent
|
||||||
* RinTorNeT
|
* RinTorNeT
|
||||||
* RuTor
|
* RuTor
|
||||||
* RuTracker.RU
|
* RuTracker.RU
|
||||||
|
@ -130,6 +135,7 @@ A third-party Golang SDK for Jackett is available from [webtor-io/go-jackett](ht
|
||||||
* Torrent9
|
* Torrent9
|
||||||
* Torrent9-tel
|
* Torrent9-tel
|
||||||
* TorrentFunk
|
* TorrentFunk
|
||||||
|
* TorrentDosFilmes
|
||||||
* TorrentDownload
|
* TorrentDownload
|
||||||
* TorrentKitty
|
* TorrentKitty
|
||||||
* TorrentProject2
|
* 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