2020-04-07 19:58:36 +00:00
using System ;
using System.Collections.Generic ;
2020-05-03 23:35:52 +00:00
using System.Diagnostics.CodeAnalysis ;
2020-04-07 19:58:36 +00:00
using System.Linq ;
using System.Text ;
using System.Text.RegularExpressions ;
using System.Threading.Tasks ;
using AngleSharp.Html.Parser ;
2023-09-04 00:46:11 +00:00
using Jackett.Common.Extensions ;
2020-04-07 19:58:36 +00:00
using Jackett.Common.Helpers ;
using Jackett.Common.Models ;
using Jackett.Common.Models.IndexerConfig ;
using Jackett.Common.Services.Interfaces ;
using Jackett.Common.Utils.Clients ;
using Newtonsoft.Json.Linq ;
using NLog ;
2024-03-11 16:57:02 +00:00
using static Jackett . Common . Models . IndexerConfig . ConfigurationData ;
2020-04-07 19:58:36 +00:00
using WebClient = Jackett . Common . Utils . Clients . WebClient ;
namespace Jackett.Common.Indexers
{
2020-05-03 23:35:52 +00:00
[ExcludeFromCodeCoverage]
2023-03-09 14:06:12 +00:00
public class Cinecalidad : IndexerBase
2020-04-07 19:58:36 +00:00
{
2023-03-09 14:06:12 +00:00
public override string Id = > "cinecalidad" ;
public override string Name = > "Cinecalidad" ;
2023-09-04 05:16:16 +00:00
public override string Description = > "Películas Full UHD/HD en Latino Dual." ;
2023-12-11 04:26:00 +00:00
public override string SiteLink { get ; protected set ; } = "https://cinecalidad.fi/" ;
2023-03-09 14:06:12 +00:00
public override string [ ] LegacySiteLinks = > new [ ]
{
2023-09-30 17:39:25 +00:00
"https://wwv.cinecalidad.foo/" ,
2023-10-06 03:10:29 +00:00
"https://wv.cinecalidad.foo/" ,
2023-10-10 18:02:09 +00:00
"https://vwv.cinecalidad.foo/" ,
2023-10-12 17:19:15 +00:00
"https://wzw.cinecalidad.foo/" ,
2023-10-29 16:56:11 +00:00
"https://v2.cinecalidad.foo/" ,
2023-11-14 18:25:59 +00:00
"https://www.cinecalidad.so/" ,
"https://wvw.cinecalidad.so/" ,
2023-11-19 04:39:03 +00:00
"https://vww.cinecalidad.so/" ,
2023-11-21 00:33:37 +00:00
"https://wwv.cinecalidad.so/" ,
2023-11-22 15:37:16 +00:00
"https://vvv.cinecalidad.so/" ,
2023-11-25 16:57:25 +00:00
"https://ww.cinecalidad.so/" ,
2023-11-29 17:35:04 +00:00
"https://w.cinecalidad.so/" ,
2023-12-02 04:09:02 +00:00
"https://wv.cinecalidad.so/" ,
2023-12-08 17:18:37 +00:00
"https://vvvv.cinecalidad.so/" ,
2023-12-11 04:26:00 +00:00
"https://wvvv.cinecalidad.so/" ,
2020-12-15 19:16:09 +00:00
} ;
2023-03-09 14:06:12 +00:00
public override string Language = > "es-419" ;
public override string Type = > "public" ;
2023-03-10 11:20:29 +00:00
public override TorznabCapabilities TorznabCaps = > SetCapabilities ( ) ;
2023-03-09 14:06:12 +00:00
private const int MaxLatestPageLimit = 3 ; // 12 items per page * 3 pages = 36
private const int MaxSearchPageLimit = 6 ;
2020-12-15 19:16:09 +00:00
2020-12-11 22:14:21 +00:00
public Cinecalidad ( IIndexerConfigurationService configService , WebClient wc , Logger l , IProtectionService ps ,
2023-03-09 14:06:12 +00:00
ICacheService cs )
2023-03-10 11:20:29 +00:00
: base ( configService : configService ,
2020-04-07 19:58:36 +00:00
client : wc ,
logger : l ,
p : ps ,
2020-12-11 22:14:21 +00:00
cacheService : cs ,
2020-04-07 19:58:36 +00:00
configData : new ConfigurationData ( ) )
{
2024-03-11 16:57:02 +00:00
configData . AddDynamic ( "flaresolverr" , new 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." ) ) ;
2023-03-10 11:20:29 +00:00
}
private TorznabCapabilities SetCapabilities ( )
{
var caps = new TorznabCapabilities
{
MovieSearchParams = new List < MovieSearchParam >
{
MovieSearchParam . Q
}
} ;
caps . Categories . AddCategoryMapping ( 1 , TorznabCatType . MoviesHD ) ;
2023-09-04 00:46:11 +00:00
caps . Categories . AddCategoryMapping ( 2 , TorznabCatType . MoviesUHD ) ;
2023-03-10 11:20:29 +00:00
return caps ;
2020-04-07 19:58:36 +00:00
}
public override async Task < IndexerConfigurationStatus > ApplyConfiguration ( JToken configJson )
{
LoadValuesFromJson ( configJson ) ;
2023-09-04 00:46:11 +00:00
2020-04-07 19:58:36 +00:00
var releases = await PerformQuery ( new TorznabQuery ( ) ) ;
await ConfigureIfOK ( string . Empty , releases . Any ( ) , ( ) = >
throw new Exception ( "Could not find release from this URL." ) ) ;
return IndexerConfigurationStatus . Completed ;
}
protected override async Task < IEnumerable < ReleaseInfo > > PerformQuery ( TorznabQuery query )
{
var releases = new List < ReleaseInfo > ( ) ;
var templateUrl = SiteLink ;
2021-10-09 18:36:22 +00:00
templateUrl + = "{0}?s=" ; // placeholder for page
2020-04-07 19:58:36 +00:00
2022-03-08 20:13:47 +00:00
var maxPages = MaxLatestPageLimit ; // we scrape only 3 pages for recent torrents
2023-09-04 00:46:11 +00:00
2022-03-08 20:13:47 +00:00
var recent = ! string . IsNullOrWhiteSpace ( query . GetQueryString ( ) ) ;
if ( recent )
2020-04-07 19:58:36 +00:00
{
2021-10-09 18:36:22 +00:00
templateUrl + = WebUtilityHelpers . UrlEncode ( query . GetQueryString ( ) , Encoding . UTF8 ) ;
2020-04-07 19:58:36 +00:00
maxPages = MaxSearchPageLimit ;
}
var lastPublishDate = DateTime . Now ;
for ( var page = 1 ; page < = maxPages ; page + + )
{
var pageParam = page > 1 ? $"page/{page}/" : "" ;
var searchUrl = string . Format ( templateUrl , pageParam ) ;
2020-06-11 15:09:27 +00:00
var response = await RequestWithCookiesAndRetryAsync ( searchUrl ) ;
2020-04-07 19:58:36 +00:00
var pageReleases = ParseReleases ( response , query ) ;
// publish date is not available in the torrent list, but we add a relative date so we can sort
2020-10-02 12:29:14 +00:00
foreach ( var release in pageReleases )
2020-04-07 19:58:36 +00:00
{
release . PublishDate = lastPublishDate ;
lastPublishDate = lastPublishDate . AddMinutes ( - 1 ) ;
}
releases . AddRange ( pageReleases ) ;
2022-03-08 20:13:47 +00:00
if ( pageReleases . Count < 1 & & recent )
2023-09-04 00:46:11 +00:00
{
// this is the last page
break ;
}
2020-04-07 19:58:36 +00:00
}
return releases ;
}
public override async Task < byte [ ] > Download ( Uri link )
{
2023-09-04 00:46:11 +00:00
var parser = new HtmlParser ( ) ;
2020-09-21 16:39:47 +00:00
var results = await RequestWithCookiesAsync ( link . ToString ( ) ) ;
2020-04-07 19:58:36 +00:00
try
{
2023-10-05 01:06:23 +00:00
using var dom = await parser . ParseDocumentAsync ( results . ContentString ) ;
2023-09-04 00:46:11 +00:00
var downloadLink = link . Query . Contains ( "type=4k" )
? dom . QuerySelector ( "ul.links a:contains('Bittorrent 4K')" )
: dom . QuerySelector ( "ul.links a:contains('Torrent')" ) ;
var protectedLink = downloadLink ? . GetAttribute ( "data-url" ) ;
if ( protectedLink . IsNullOrWhiteSpace ( ) )
{
throw new Exception ( $"Invalid download link for {link}" ) ;
}
2022-01-16 10:30:26 +00:00
protectedLink = Base64Decode ( protectedLink ) ;
2022-09-07 08:41:57 +00:00
// turn
// link=https://cinecalidad.dev/pelicula/la-chica-salvaje/
2022-11-12 02:12:22 +00:00
// and
2022-09-07 08:41:57 +00:00
// protectedlink=https://cinecalidad.dev/links/MS8xMDA5NTIvMQ==
// into
2022-11-12 02:12:22 +00:00
// https://cinecalidad.dev/pelicula/la-chica-salvaje/?link=MS8xMDA5NTIvMQ==
2022-09-07 08:41:57 +00:00
var protectedLinkSplit = protectedLink . Split ( '/' ) ;
var key = protectedLinkSplit . Last ( ) ;
2023-09-04 00:46:11 +00:00
protectedLink = link . AddQueryParameter ( "link" , key ) . ToString ( ) ;
2020-12-22 17:21:00 +00:00
protectedLink = GetAbsoluteUrl ( protectedLink ) ;
2020-04-07 19:58:36 +00:00
2020-11-28 20:40:10 +00:00
results = await RequestWithCookiesAsync ( protectedLink ) ;
2023-10-05 01:06:23 +00:00
using var document = parser . ParseDocument ( results . ContentString ) ;
var magnetUrl = document . QuerySelector ( "a[href^=magnet]" ) . GetAttribute ( "href" ) ;
2020-04-07 19:58:36 +00:00
return await base . Download ( new Uri ( magnetUrl ) ) ;
}
catch ( Exception ex )
{
2020-06-09 17:36:57 +00:00
OnParseError ( results . ContentString , ex ) ;
2020-04-07 19:58:36 +00:00
}
return null ;
}
2020-06-10 21:22:29 +00:00
private List < ReleaseInfo > ParseReleases ( WebResult response , TorznabQuery query )
2020-04-07 19:58:36 +00:00
{
var releases = new List < ReleaseInfo > ( ) ;
try
{
var parser = new HtmlParser ( ) ;
2023-10-05 01:06:23 +00:00
using var dom = parser . ParseDocument ( response . ContentString ) ;
2020-04-07 19:58:36 +00:00
2023-09-04 00:46:11 +00:00
var rows = dom . QuerySelectorAll ( "article:has(a.absolute):has(img.rounded)" ) ;
2020-04-07 19:58:36 +00:00
foreach ( var row in rows )
{
2022-01-16 10:30:26 +00:00
if ( row . QuerySelector ( "div.selt" ) ! = null )
2023-09-04 00:46:11 +00:00
{
// we only support movies
continue ;
}
2022-01-16 10:30:26 +00:00
var qLink = row . QuerySelector ( "a.absolute" ) ;
2022-03-08 20:13:47 +00:00
var qImg = row . QuerySelector ( "img.rounded" ) ;
2022-01-16 10:30:26 +00:00
if ( qLink = = null | | qImg = = null )
2023-09-04 00:46:11 +00:00
{
// skip results without image
continue ;
}
2022-01-16 10:30:26 +00:00
var title = qLink . TextContent . Trim ( ) ;
2020-04-07 19:58:36 +00:00
if ( ! CheckTitleMatchWords ( query . GetQueryString ( ) , title ) )
2023-09-04 00:46:11 +00:00
{
// skip if it doesn't contain all words
continue ;
}
var poster = new Uri ( GetAbsoluteUrl ( qImg . GetAttribute ( "data-src" ) ? ? qImg . GetAttribute ( "src" ) ) ) ;
2022-01-16 10:30:26 +00:00
var link = new Uri ( qLink . GetAttribute ( "href" ) ) ;
2020-04-07 19:58:36 +00:00
2023-09-04 00:46:11 +00:00
releases . Add ( new ReleaseInfo
2020-04-07 19:58:36 +00:00
{
Guid = link ,
2023-09-04 00:46:11 +00:00
Details = link ,
Link = link ,
Title = $"{title} MULTi/LATiN SPANiSH 1080p BDRip x264" ,
2020-10-02 12:29:14 +00:00
Category = new List < int > { TorznabCatType . MoviesHD . ID } ,
2020-11-07 23:43:33 +00:00
Poster = poster ,
2020-04-07 19:58:36 +00:00
Size = 2147483648 , // 2 GB
Files = 1 ,
Seeders = 1 ,
Peers = 2 ,
DownloadVolumeFactor = 0 ,
UploadVolumeFactor = 1
2023-09-04 00:46:11 +00:00
} ) ;
2020-04-07 19:58:36 +00:00
2023-09-04 00:46:11 +00:00
if ( row . QuerySelector ( "a[aria-label=\"4K\"]" ) ! = null )
{
var link4K = link . AddQueryParameter ( "type" , "4k" ) ;
releases . Add ( new ReleaseInfo
{
Guid = link4K ,
Details = link ,
Link = link4K ,
2023-09-04 05:16:16 +00:00
Title = $"{title} MULTi/LATiN SPANiSH 2160p BDRip x265" ,
2023-09-04 00:46:11 +00:00
Category = new List < int > { TorznabCatType . MoviesUHD . ID } ,
Poster = poster ,
2023-09-04 05:16:16 +00:00
Size = 10737418240 , // 10 GB
2023-09-04 00:46:11 +00:00
Files = 1 ,
Seeders = 1 ,
Peers = 2 ,
DownloadVolumeFactor = 0 ,
UploadVolumeFactor = 1
} ) ;
}
2020-04-07 19:58:36 +00:00
}
}
catch ( Exception ex )
{
2020-06-09 17:36:57 +00:00
OnParseError ( response . ContentString , ex ) ;
2020-04-07 19:58:36 +00:00
}
return releases ;
}
// TODO: merge this method with query.MatchQueryStringAND
private static bool CheckTitleMatchWords ( string queryStr , string title )
{
// this code split the words, remove words with 2 letters or less, remove accents and lowercase
var queryMatches = Regex . Matches ( queryStr , @"\b[\w']*\b" ) ;
var queryWords = from m in queryMatches . Cast < Match > ( )
where ! string . IsNullOrEmpty ( m . Value ) & & m . Value . Length > 2
select Encoding . UTF8 . GetString ( Encoding . GetEncoding ( "ISO-8859-8" ) . GetBytes ( m . Value . ToLower ( ) ) ) ;
var titleMatches = Regex . Matches ( title , @"\b[\w']*\b" ) ;
var titleWords = from m in titleMatches . Cast < Match > ( )
where ! string . IsNullOrEmpty ( m . Value ) & & m . Value . Length > 2
select Encoding . UTF8 . GetString ( Encoding . GetEncoding ( "ISO-8859-8" ) . GetBytes ( m . Value . ToLower ( ) ) ) ;
titleWords = titleWords . ToArray ( ) ;
return queryWords . All ( word = > titleWords . Contains ( word ) ) ;
}
2020-12-22 17:21:00 +00:00
private string GetAbsoluteUrl ( string url )
{
url = url . Trim ( ) ;
2023-09-04 00:46:11 +00:00
2020-12-22 17:21:00 +00:00
if ( ! url . StartsWith ( "http" ) )
2023-09-04 00:46:11 +00:00
{
2020-12-22 17:21:00 +00:00
return SiteLink + url . TrimStart ( '/' ) ;
2023-09-04 00:46:11 +00:00
}
2020-12-22 17:21:00 +00:00
return url ;
}
2022-01-16 10:30:26 +00:00
private string Base64Decode ( string base64EncodedData )
{
var base64EncodedBytes = Convert . FromBase64String ( base64EncodedData ) ;
return Encoding . UTF8 . GetString ( base64EncodedBytes ) ;
}
2020-04-07 19:58:36 +00:00
}
}