2020-02-09 02:35:16 +00:00
using System ;
2015-04-19 02:05:38 +00:00
using System.Collections.Generic ;
2016-09-03 02:04:08 +00:00
using System.Globalization ;
2015-04-19 02:05:38 +00:00
using System.Linq ;
2017-11-05 09:42:03 +00:00
using System.Net ;
2016-12-06 13:56:47 +00:00
using System.Text ;
2015-11-18 11:55:00 +00:00
using System.Text.RegularExpressions ;
2015-04-19 02:05:38 +00:00
using System.Threading.Tasks ;
2016-09-03 02:04:08 +00:00
using AngleSharp.Dom ;
2019-01-20 00:09:27 +00:00
using AngleSharp.Html.Parser ;
2016-09-03 02:04:08 +00:00
using CsQuery ;
2018-03-10 08:05:56 +00:00
using Jackett.Common.Models ;
using Jackett.Common.Models.IndexerConfig ;
using Jackett.Common.Services.Interfaces ;
using Jackett.Common.Utils ;
2017-10-29 06:21:18 +00:00
using Newtonsoft.Json.Linq ;
using NLog ;
2017-04-15 08:45:10 +00:00
2018-03-10 08:05:56 +00:00
namespace Jackett.Common.Indexers
2015-04-19 02:05:38 +00:00
{
2017-07-10 20:58:44 +00:00
public class MoreThanTV : BaseWebIndexer
2015-04-19 02:05:38 +00:00
{
2016-09-03 02:04:08 +00:00
private string LoginUrl = > SiteLink + "login.php" ;
private string SearchUrl = > SiteLink + "ajax.php?action=browse&searchstr=" ;
private string DownloadUrl = > SiteLink + "torrents.php?action=download&id=" ;
private string GuidUrl = > SiteLink + "torrents.php?torrentid=" ;
2015-04-19 02:05:38 +00:00
2017-10-29 06:21:18 +00:00
private ConfigurationDataBasicLogin ConfigData = > ( ConfigurationDataBasicLogin ) configData ;
2015-08-03 21:38:45 +00:00
2017-11-05 09:42:03 +00:00
public MoreThanTV ( IIndexerConfigurationService configService , Utils . Clients . WebClient c , Logger l , IProtectionService ps )
2015-07-19 23:05:30 +00:00
: base ( name : "MoreThanTV" ,
description : "ROMANIAN Private Torrent Tracker for TV / MOVIES, and the internal tracker for the release group DRACULA." ,
2015-07-27 00:03:51 +00:00
link : "https://www.morethan.tv/" ,
2015-08-13 22:41:21 +00:00
caps : new TorznabCapabilities ( TorznabCatType . TV ,
TorznabCatType . Movies ) ,
2017-07-10 20:58:44 +00:00
configService : configService ,
2015-07-27 00:03:51 +00:00
client : c ,
2015-08-03 21:38:45 +00:00
logger : l ,
2015-08-07 19:09:13 +00:00
p : ps ,
2015-08-03 21:38:45 +00:00
configData : new ConfigurationDataBasicLogin ( ) )
2015-04-19 02:05:38 +00:00
{
2017-11-05 09:42:03 +00:00
Encoding = Encoding . UTF8 ;
2016-12-09 17:20:58 +00:00
Language = "en-us" ;
2017-01-27 15:57:32 +00:00
Type = "private" ;
2015-04-19 02:05:38 +00:00
}
2017-06-28 05:31:38 +00:00
public override async Task < IndexerConfigurationStatus > ApplyConfiguration ( JToken configJson )
2015-04-19 02:05:38 +00:00
{
2017-01-02 20:39:28 +00:00
LoadValuesFromJson ( configJson ) ;
2015-04-23 06:44:21 +00:00
var pairs = new Dictionary < string , string > {
2016-09-03 02:04:08 +00:00
{ "username" , ConfigData . Username . Value } ,
{ "password" , ConfigData . Password . Value } ,
2015-08-03 21:38:45 +00:00
{ "login" , "Log in" } ,
{ "keeplogged" , "1" }
} ;
2015-04-19 02:05:38 +00:00
2015-10-12 18:58:40 +00:00
var preRequest = await RequestStringWithCookiesAndRetry ( LoginUrl , string . Empty ) ;
var result = await RequestLoginAndFollowRedirect ( LoginUrl , pairs , preRequest . Cookies , true , SearchUrl , SiteLink ) ;
2016-09-03 02:04:08 +00:00
2015-10-12 18:58:40 +00:00
await ConfigureIfOK ( result . Cookies , result . Content ! = null & & result . Content . Contains ( "status\":\"success\"" ) , ( ) = >
2015-04-19 02:05:38 +00:00
{
2018-03-14 14:16:20 +00:00
if ( result . Content . Contains ( "Your IP address has been banned." ) )
throw new ExceptionWithConfigData ( "Your IP address has been banned." , ConfigData ) ;
2015-07-27 00:03:51 +00:00
CQ dom = result . Content ;
2015-04-19 02:05:38 +00:00
dom [ "#loginform > table" ] . Remove ( ) ;
var errorMessage = dom [ "#loginform" ] . Text ( ) . Trim ( ) . Replace ( "\n\t" , " " ) ;
2016-09-03 02:04:08 +00:00
throw new ExceptionWithConfigData ( errorMessage , ConfigData ) ;
2015-07-27 00:03:51 +00:00
} ) ;
2015-08-22 20:57:13 +00:00
return IndexerConfigurationStatus . RequiresTesting ;
2015-04-19 02:05:38 +00:00
}
2017-07-03 05:15:47 +00:00
protected override async Task < IEnumerable < ReleaseInfo > > PerformQuery ( TorznabQuery query )
2015-04-19 02:05:38 +00:00
{
2017-02-21 12:07:54 +00:00
var isTv = TorznabCatType . QueryContainsParentCategory ( query . Categories , new List < int > { TorznabCatType . TV . ID } ) ;
2016-09-03 02:04:08 +00:00
var releases = new List < ReleaseInfo > ( ) ;
var searchQuery = query . GetQueryString ( ) ;
2017-10-03 11:03:25 +00:00
searchQuery = searchQuery . Replace ( "Marvels" , "Marvel" ) ; // strip 's for better results
2017-04-30 07:38:10 +00:00
var searchQuerySingleEpisodes = Regex . Replace ( searchQuery , @"(S\d{2})$" , "$1*" ) ; // If we're just seaching for a season (no episode) append an * to include all episodes of that season.
2016-09-03 02:04:08 +00:00
2017-04-30 07:38:10 +00:00
await GetReleases ( releases , query , searchQuerySingleEpisodes ) ;
2016-09-03 02:04:08 +00:00
2017-10-03 11:03:25 +00:00
// Always search for torrent groups (complete seasons) too
var seasonMatch = new Regex ( @".*\s[Ss]{1}\d{2}([Ee]{1}\d{2,3})?$" ) . Match ( searchQuery ) ;
2017-07-08 13:23:36 +00:00
if ( seasonMatch . Success )
2015-08-13 22:41:21 +00:00
{
2017-10-03 11:03:25 +00:00
var newSearchQuery = Regex . Replace ( searchQuery , @"[Ss]{1}\d{2}([Ee]{1}\d{2,3})?" , $"Season {query.Season}" ) ;
2016-09-03 02:04:08 +00:00
2017-07-08 13:23:36 +00:00
await GetReleases ( releases , query , newSearchQuery ) ;
2015-08-13 22:41:21 +00:00
}
2016-09-03 02:04:08 +00:00
return releases ;
2015-04-19 02:05:38 +00:00
}
2016-09-03 02:04:08 +00:00
private string GetTorrentSearchUrl ( int [ ] categories , string searchQuery )
2015-04-19 02:05:38 +00:00
{
2016-09-03 02:04:08 +00:00
var extra = "" ;
2015-07-29 17:47:51 +00:00
2016-09-03 02:04:08 +00:00
if ( Array . IndexOf ( categories , TorznabCatType . Movies . ID ) > - 1 )
extra + = "&filter_cat%5B1%5D=1" ;
if ( Array . IndexOf ( categories , TorznabCatType . TV . ID ) > - 1 )
extra + = "&filter_cat%5B2%5D=1" ;
2015-11-18 11:55:00 +00:00
2017-11-05 09:42:03 +00:00
return SiteLink + $"torrents.php?searchstr={WebUtility.UrlEncode(searchQuery)}&tags_type=1&order_by=time&order_way=desc&group_results=1{extra}&action=basic&searchsubmit=1" ;
2016-09-03 02:04:08 +00:00
}
private async Task GetReleases ( ICollection < ReleaseInfo > releases , TorznabQuery query , string searchQuery )
{
var searchUrl = GetTorrentSearchUrl ( query . Categories , searchQuery ) ;
var response = await RequestStringWithCookiesAndRetry ( searchUrl ) ;
2017-04-15 08:45:10 +00:00
if ( response . IsRedirect )
{
// re login
await ApplyConfiguration ( null ) ;
response = await RequestStringWithCookiesAndRetry ( searchUrl ) ;
2017-03-03 17:52:19 +00:00
}
2015-04-19 02:05:38 +00:00
2015-07-18 20:35:02 +00:00
try
{
2016-09-03 02:04:08 +00:00
var parser = new HtmlParser ( ) ;
2019-01-20 00:09:27 +00:00
var document = parser . ParseDocument ( response . Content ) ;
2016-09-03 02:04:08 +00:00
var groups = document . QuerySelectorAll ( ".torrent_table > tbody > tr.group" ) ;
var torrents = document . QuerySelectorAll ( ".torrent_table > tbody > tr.torrent" ) ;
// Loop through all torrent (season) groups
foreach ( var group in groups )
2015-04-23 06:44:21 +00:00
{
2016-09-03 02:04:08 +00:00
var showName = group . QuerySelector ( ".tp-showname a" ) . InnerHtml . Replace ( "(" , "" ) . Replace ( ")" , "" ) . Replace ( ' ' , '.' ) ;
var season = group . QuerySelector ( ".big_info a" ) . InnerHtml ;
2017-10-17 17:10:53 +00:00
var seasonNumber = SeasonToNumber ( season ) ;
if ( seasonNumber ! = null & & query . Season > 0 & & seasonNumber ! = query . Season ) // filter unwanted seasons
continue ;
var seasonTag = SeasonNumberToShortSeason ( seasonNumber ) ? ? season ;
2016-09-03 02:04:08 +00:00
// Loop through all group items
var previousElement = group ;
var qualityEdition = string . Empty ;
while ( true )
2015-04-19 02:05:38 +00:00
{
2016-09-03 02:04:08 +00:00
var groupItem = previousElement . NextElementSibling ;
2015-05-04 04:12:14 +00:00
2020-02-09 02:35:16 +00:00
if ( groupItem = = null )
break ;
2015-05-04 04:12:14 +00:00
2016-09-03 02:04:08 +00:00
if ( ! groupItem . ClassList [ 0 ] . Equals ( "group_torrent" ) | |
2020-02-09 02:35:16 +00:00
! groupItem . ClassList [ 1 ] . StartsWith ( "groupid_" ) )
break ;
2015-11-18 11:55:00 +00:00
2016-09-03 02:04:08 +00:00
// Found a new edition
if ( groupItem . ClassList [ 2 ] . Equals ( "edition" ) )
2015-04-19 02:05:38 +00:00
{
2016-09-03 02:04:08 +00:00
qualityEdition = groupItem . QuerySelector ( ".edition_info strong" ) . TextContent . Split ( '/' ) [ 1 ] . Trim ( ) ;
2015-04-19 02:05:38 +00:00
}
2016-09-03 02:04:08 +00:00
else if ( groupItem . ClassList [ 2 ] . StartsWith ( "edition_" ) )
{
2020-02-09 02:35:16 +00:00
if ( qualityEdition . Equals ( string . Empty ) )
break ;
2016-09-03 02:04:08 +00:00
// Parse required data
var downloadAnchor = groupItem . QuerySelectorAll ( "td a" ) . Last ( ) ;
var qualityData = downloadAnchor . InnerHtml . Split ( '/' ) ;
2019-05-26 07:28:52 +00:00
switch ( qualityData . Length )
{
case 0 :
Array . Resize ( ref qualityData , 2 ) ;
qualityData [ 0 ] = " " ;
qualityData [ 1 ] = " " ;
break ;
case 1 :
Array . Resize ( ref qualityData , 2 ) ;
qualityData [ 1 ] = " " ;
break ;
default :
break ;
}
2019-06-13 21:21:03 +00:00
// Replace 4K quality tag with 2160p, so Sonarr etc. can properly parse it
qualityData [ 1 ] = qualityData [ 1 ] . Replace ( "4K" , "2160p" ) ;
2016-09-03 02:04:08 +00:00
// Build title
var title = string . Join ( "." , new List < string >
{
showName ,
2017-10-17 17:10:53 +00:00
seasonTag ,
2016-09-03 02:04:08 +00:00
qualityData [ 1 ] . Trim ( ) ,
qualityEdition , // Audio quality should be after this one. Unobtainable at the moment.
$"{qualityData[0].Trim()}-MTV"
2017-04-15 08:45:10 +00:00
} ) ;
2016-09-03 02:04:08 +00:00
releases . Add ( GetReleaseInfo ( groupItem , downloadAnchor , title , TorznabCatType . TV . ID ) ) ;
}
else
{
break ;
}
previousElement = groupItem ;
}
}
// Loop through all torrents
foreach ( var torrent in torrents )
{
// Parse required data
var downloadAnchor = torrent . QuerySelector ( ".big_info > .group_info > a" ) ;
var title = downloadAnchor . TextContent ;
int category ;
var categories = torrent . QuerySelector ( ".cats_col div" ) . ClassList ;
if ( categories . Contains ( "cats_tv" ) )
{
category = TorznabCatType . TV . ID ;
}
else if ( categories . Contains ( "cats_movies" ) )
{
category = TorznabCatType . Movies . ID ;
2015-05-04 04:12:14 +00:00
}
2017-04-17 04:22:00 +00:00
else if ( categories . Contains ( "cats_other" ) )
{
category = TorznabCatType . Other . ID ;
}
2015-07-18 20:35:02 +00:00
else
{
2016-09-03 02:04:08 +00:00
throw new Exception ( "Couldn't find category." ) ;
2015-07-18 20:35:02 +00:00
}
2016-09-03 02:04:08 +00:00
releases . Add ( GetReleaseInfo ( torrent , downloadAnchor , title , category ) ) ;
2015-05-04 04:12:14 +00:00
}
2015-07-18 20:35:02 +00:00
}
catch ( Exception ex )
{
2015-07-23 20:36:23 +00:00
OnParseError ( response . Content , ex ) ;
2015-04-19 02:05:38 +00:00
}
2016-09-03 02:04:08 +00:00
}
2015-04-19 02:05:38 +00:00
2016-09-03 02:04:08 +00:00
private ReleaseInfo GetReleaseInfo ( IElement row , IElement downloadAnchor , string title , int category )
{
// Parse required data
var downloadAnchorHref = downloadAnchor . Attributes [ "href" ] . Value ;
var torrentId = downloadAnchorHref . Substring ( downloadAnchorHref . LastIndexOf ( '=' ) + 1 ) ;
2016-11-01 07:55:49 +00:00
var qFiles = row . QuerySelector ( "td:nth-last-child(6)" ) ;
var files = ParseUtil . CoerceLong ( qFiles . TextContent ) ;
2016-11-21 08:46:53 +00:00
var publishDate = DateTime . ParseExact ( row . QuerySelector ( ".time.tooltip" ) . Attributes [ "title" ] . Value , "MMM dd yyyy, HH:mm" , CultureInfo . InvariantCulture , DateTimeStyles . AssumeUniversal ) . ToLocalTime ( ) ;
2016-09-03 02:04:08 +00:00
var torrentData = row . QuerySelectorAll ( ".number_column" ) ; // Size (xx.xx GB[ (Max)]) Snatches (xx) Seeders (xx) Leechers (xx)
if ( torrentData . Length ! = 4 )
throw new Exception ( $"We expected 4 torrent datas, instead we have {torrentData.Length}." ) ;
if ( torrentId . Contains ( '#' ) )
torrentId = torrentId . Split ( '#' ) [ 0 ] ;
2016-12-08 06:31:31 +00:00
var size = ReleaseInfo . GetBytes ( torrentData [ 0 ] . TextContent ) ;
2017-08-05 01:20:50 +00:00
var grabs = int . Parse ( torrentData [ 1 ] . TextContent , NumberStyles . AllowThousands , CultureInfo . InvariantCulture ) ;
var seeders = int . Parse ( torrentData [ 2 ] . TextContent , NumberStyles . AllowThousands , CultureInfo . InvariantCulture ) ;
var leechers = int . Parse ( torrentData [ 3 ] . TextContent , NumberStyles . AllowThousands , CultureInfo . InvariantCulture ) ;
2016-09-03 02:04:08 +00:00
var guid = new Uri ( GuidUrl + torrentId ) ;
// Build releaseinfo
return new ReleaseInfo
{
Title = title ,
2017-02-21 12:07:54 +00:00
Category = new List < int > { category } , // Who seasons movies right
2016-09-03 02:04:08 +00:00
Link = new Uri ( DownloadUrl + torrentId ) ,
PublishDate = publishDate ,
Seeders = seeders ,
2017-08-05 01:20:50 +00:00
Peers = seeders + leechers ,
2016-10-27 07:35:31 +00:00
Files = files ,
2016-09-03 02:04:08 +00:00
Size = size ,
2016-10-27 07:35:31 +00:00
Grabs = grabs ,
2016-09-03 02:04:08 +00:00
Guid = guid ,
2016-10-27 07:35:31 +00:00
Comments = guid ,
DownloadVolumeFactor = 0 , // ratioless tracker
UploadVolumeFactor = 1
2016-09-03 02:04:08 +00:00
} ;
}
2017-10-17 17:10:53 +00:00
// Changes "Season 1" to "1"
private static int? SeasonToNumber ( string season )
2016-09-03 02:04:08 +00:00
{
var seasonMatch = new Regex ( @"Season (?<seasonNumber>\d{1,2})" ) . Match ( season ) ;
if ( seasonMatch . Success )
{
2017-10-17 17:10:53 +00:00
return int . Parse ( seasonMatch . Groups [ "seasonNumber" ] . Value ) ;
2016-09-03 02:04:08 +00:00
}
2017-10-17 17:10:53 +00:00
return null ;
}
// Changes "1" to "S01"
private static string SeasonNumberToShortSeason ( int? season )
{
if ( season = = null )
return null ;
return $"S{season:00}" ;
2016-09-03 02:04:08 +00:00
}
2015-04-19 02:05:38 +00:00
}
}