@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.Collections.Specialized ;
using System.Collections.Specialized ;
using System.Globalization ;
using System.Globalization ;
using System.Linq ;
using System.Linq ;
using System.Net ;
using System.Net.Http ;
using System.Net.Http ;
using System.Text ;
using System.Text ;
using System.Text.RegularExpressions ;
using System.Text.RegularExpressions ;
using System.Threading.Tasks ;
using System.Threading.Tasks ;
using AngleSharp.Dom ;
using AngleSharp.Html.Parser ;
using AngleSharp.Html.Parser ;
using NLog ;
using NLog ;
using NzbDrone.Common.Extensions ;
using NzbDrone.Common.Extensions ;
@ -21,352 +21,339 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser ;
using NzbDrone.Core.Parser ;
using NzbDrone.Core.Parser.Model ;
using NzbDrone.Core.Parser.Model ;
namespace NzbDrone.Core.Indexers.Definitions
namespace NzbDrone.Core.Indexers.Definitions ;
public class Libble : TorrentIndexerBase < LibbleSettings >
{
{
internal class Libble : TorrentIndexerBase < LibbleSettings >
public override string Name = > "Libble" ;
public override string [ ] IndexerUrls = > new [ ] { "https://libble.me/" } ;
public override string Description = > "Libble is a Private Torrent Tracker for MUSIC" ;
private string LoginUrl = > Settings . BaseUrl + "login.php" ;
public override string Language = > "en-US" ;
public override Encoding Encoding = > Encoding . UTF8 ;
public override DownloadProtocol Protocol = > DownloadProtocol . Torrent ;
public override IndexerPrivacy Privacy = > IndexerPrivacy . Private ;
public override int PageSize = > 50 ;
public override IndexerCapabilities Capabilities = > SetCapabilities ( ) ;
public Libble ( IIndexerHttpClient httpClient , IEventAggregator eventAggregator , IIndexerStatusService indexerStatusService , IConfigService configService , Logger logger )
: base ( httpClient , eventAggregator , indexerStatusService , configService , logger )
{
{
public override string Name = > "Libble" ;
}
public override string [ ] IndexerUrls = > new string [ ] { "https://libble.me/" } ;
public override string Description = > "Libble is a Private Torrent Tracker for MUSIC" ;
private string LoginUrl = > Settings . BaseUrl + "login.php" ;
public override string Language = > "en-US" ;
public override Encoding Encoding = > Encoding . UTF8 ;
public override DownloadProtocol Protocol = > DownloadProtocol . Torrent ;
public override IndexerPrivacy Privacy = > IndexerPrivacy . Private ;
public override int PageSize = > 50 ;
public override IndexerCapabilities Capabilities = > SetCapabilities ( ) ;
public Libble ( IIndexerHttpClient httpClient , IEventAggregator eventAggregator , IIndexerStatusService indexerStatusService , IConfigService configService , Logger logger )
: base ( httpClient , eventAggregator , indexerStatusService , configService , logger )
{
}
public override IIndexerRequestGenerator GetRequestGenerator ( )
public override IIndexerRequestGenerator GetRequestGenerator ( )
{
{
return new LibbleRequestGenerator ( ) { Settings = Settings , Capabilities = Capabilities } ;
return new LibbleRequestGenerator ( Settings , Capabilities ) ;
}
}
public override IParseIndexerResponse GetParser ( )
public override IParseIndexerResponse GetParser ( )
{
{
return new LibbleParser ( Setting s, Capabilities . Categorie s) ;
return new LibbleParser ( Settings ) ;
}
}
protected override async Task DoLogin ( )
protected override async Task DoLogin ( )
{
var requestBuilder = new HttpRequestBuilder ( LoginUrl )
{
{
var requestBuilder = new HttpRequestBuilder ( LoginUrl )
AllowAutoRedirect = true ,
{
Method = HttpMethod . Post
Method = HttpMethod . Post ,
} ;
AllowAutoRedirect = true
requestBuilder . PostProcess + = r = > r . RequestTimeout = TimeSpan . FromSeconds ( 15 ) ;
} ;
var cookies = Cookies ;
Cookies = null ;
var authLoginRequest = requestBuilder
. AddFormParameter ( "username" , Settings . Username )
. AddFormParameter ( "password" , Settings . Password )
. AddFormParameter ( "code" , Settings . TwoFactorAuthCode )
. AddFormParameter ( "keeplogged" , "1" )
. AddFormParameter ( "login" , "Login" )
. SetHeader ( "Content-Type" , "application/x-www-form-urlencoded" )
. SetHeader ( "Referer" , LoginUrl )
. Build ( ) ;
var response = await ExecuteAuth ( authLoginRequest ) ;
if ( CheckIfLoginNeeded ( response ) )
{
var parser = new HtmlParser ( ) ;
var dom = parser . ParseDocument ( response . Content ) ;
var errorMessage = dom . QuerySelector ( "#loginform > .warning" ) ? . TextContent . Trim ( ) ;
throw new IndexerAuthException ( errorMessage ? ? "Unknown error message, please report." ) ;
}
requestBuilder . PostProcess + = r = > r . RequestTimeout = TimeSpan . FromSeconds ( 15 ) ;
cookies = response . GetCookies ( ) ;
UpdateCookies ( cookies , DateTime . Now + TimeSpan . FromDays ( 30 ) ) ;
var cookies = Cookies ;
_logger . Debug ( "Authentication succeeded." ) ;
}
Cookies = null ;
protected override bool CheckIfLoginNeeded ( HttpResponse httpResponse )
var authLoginRequest = requestBuilder
{
. AddFormParameter ( "username" , Settings . Username )
return ! httpResponse . Content . Contains ( "logout.php" ) ;
. AddFormParameter ( "password" , Settings . Password )
}
. AddFormParameter ( "code" , Settings . TwoFactorAuthCode )
. AddFormParameter ( "keeplogged" , "1" )
. AddFormParameter ( "login" , "Login" )
. SetHeader ( "Content-Type" , "multipart/form-data" )
. Build ( ) ;
var headers = new NameValueCollection
private IndexerCapabilities SetCapabilities ( )
{
var caps = new IndexerCapabilities
{
MusicSearchParams = new List < MusicSearchParam >
{
{
{ "Referer" , LoginUrl }
MusicSearchParam . Q , MusicSearchParam . Artist , MusicSearchParam . Album , MusicSearchParam . Label , MusicSearchParam . Year , MusicSearchParam . Genre
} ;
}
} ;
authLoginRequest . Headers . Add ( headers ) ;
caps . Categories . AddCategoryMapping ( 1 , NewznabStandardCategory . Audio , "Music" ) ;
caps . Categories . AddCategoryMapping ( 2 , NewznabStandardCategory . Audio , "Libble Mixtapes" ) ;
caps . Categories . AddCategoryMapping ( 7 , NewznabStandardCategory . AudioVideo , "Music Videos" ) ;
var response = await ExecuteAuth ( authLoginRequest ) ;
return caps ;
}
}
if ( CheckIfLoginNeeded ( response ) )
public class LibbleRequestGenerator : IIndexerRequestGenerator
{
{
var parser = new HtmlParser ( ) ;
private readonly LibbleSettings _settings ;
var dom = parser . ParseDocument ( response . Content ) ;
private readonly IndexerCapabilities _capabilities ;
var errorMessage = dom . QuerySelector ( "#loginform > .warning" ) ? . TextContent . Trim ( ) ;
throw new IndexerAuthException ( $"Libble authentication failed. Error: \" { errorMessage } \ "" ) ;
public LibbleRequestGenerator ( LibbleSettings settings , IndexerCapabilities capabilities )
}
{
_settings = settings ;
_capabilities = capabilities ;
}
cookies = response . GetCookies ( ) ;
public IndexerPageableRequestChain GetSearchRequests ( MusicSearchCriteria searchCriteria )
UpdateCookies ( cookies , DateTime . Now + TimeSpan . FromDays ( 30 ) ) ;
{
var pageableRequests = new IndexerPageableRequestChain ( ) ;
var parameters = new NameValueCollection ( ) ;
_logger . Debug ( "Libble authentication succeeded." ) ;
if ( searchCriteria . Artist . IsNotNullOrWhiteSpace ( ) )
{
parameters . Set ( "artistname" , searchCriteria . Artist ) ;
}
}
protected override bool CheckIfLoginNeeded ( HttpResponse httpResponse )
if ( searchCriteria . Album . IsNotNullOrWhiteSpace ( ) )
{
{
return ! httpResponse . Content . Contains ( "logout.php" ) ;
parameters . Set ( "groupname" , searchCriteria . Album ) ;
}
}
private IndexerCapabilities SetCapabilities ( )
if ( searchCriteria . Label . IsNotNullOrWhiteSpace ( ) )
{
{
var caps = new IndexerCapabilities
parameters . Set ( "recordlabel" , searchCriteria . Label ) ;
{
MusicSearchParams = new List < MusicSearchParam >
{
MusicSearchParam . Q , MusicSearchParam . Artist , MusicSearchParam . Album , MusicSearchParam . Label , MusicSearchParam . Year , MusicSearchParam . Genre
}
} ;
caps . Categories . AddCategoryMapping ( 1 , NewznabStandardCategory . Audio ) ;
caps . Categories . AddCategoryMapping ( 2 , NewznabStandardCategory . Audio ) ;
caps . Categories . AddCategoryMapping ( 7 , NewznabStandardCategory . AudioVideo ) ;
return caps ;
}
}
}
public class LibbleRequestGenerator : IIndexerRequestGenerator
if ( searchCriteria . Year . HasValue )
{
public LibbleSettings Settings { get ; set ; }
public IndexerCapabilities Capabilities { get ; set ; }
public LibbleRequestGenerator ( )
{
{
parameters . Set ( "year" , searchCriteria . Year . ToString ( ) ) ;
}
}
private IEnumerable < IndexerRequest > GetPagedRequests ( SearchCriteriaBase searchCriteria , NameValueCollection parameters )
if ( searchCriteria . Genre . IsNotNullOrWhiteSpace ( ) )
{
{
var term = searchCriteria . SanitizedSearchTerm . Trim ( ) ;
parameters . Set ( "taglist" , searchCriteria . Genre ) ;
parameters . Set ( "tags_type" , "0" ) ;
parameters . Add ( "order_by" , "time" ) ;
}
parameters . Add ( "order_way" , "desc" ) ;
parameters . Add ( "searchstr" , term ) ;
var queryCats = Capabilities . Categories . MapTorznabCapsToTrackers ( searchCriteria . Categories ) ;
if ( queryCats . Count > 0 )
{
foreach ( var cat in queryCats )
{
parameters . Add ( $"filter_cat[{cat}]" , "1" ) ;
}
}
if ( searchCriteria . Offset . HasValue & & searchCriteria . Limit . HasValue & & searchCriteria . Offset > 0 & & searchCriteria . Limit > 0 )
pageableRequests . Add ( GetPagedRequests ( searchCriteria , parameters ) ) ;
{
var page = ( int ) ( searchCriteria . Offset / searchCriteria . Limit ) + 1 ;
parameters . Add ( "page" , page . ToString ( ) ) ;
}
var searchUrl = string . Format ( "{0}/torrents.php?{1}" , Settings . BaseUrl . TrimEnd ( '/' ) , parameters . GetQueryString ( ) ) ;
return pageableRequests ;
}
var request = new IndexerRequest ( searchUrl , HttpAccept . Html ) ;
public IndexerPageableRequestChain GetSearchRequests ( BasicSearchCriteria searchCriteria )
{
var pageableRequests = new IndexerPageableRequestChain ( ) ;
var parameters = new NameValueCollection ( ) ;
yield return request ;
pageableRequests . Add ( GetPagedRequests ( searchCriteria , parameters ) ) ;
}
public IndexerPageableRequestChain GetSearchRequests ( MusicSearchCriteria searchCriteria )
return pageableRequests ;
{
}
var pageableRequests = new IndexerPageableRequestChain ( ) ;
var parameters = new NameValueCollection ( ) ;
if ( searchCriteria . Artist . IsNotNullOrWhiteSpace ( ) )
{
parameters . Add ( "artistname" , searchCriteria . Artist ) ;
}
if ( searchCriteria . Album . IsNotNullOrWhiteSpace ( ) )
public IndexerPageableRequestChain GetSearchRequests ( MovieSearchCriteria searchCriteria )
{
{
parameters . Add ( "groupname" , searchCriteria . Album ) ;
return new IndexerPageableRequestChain ( ) ;
}
}
if ( searchCriteria . Label . IsNotNullOrWhiteSpace ( ) )
public IndexerPageableRequestChain GetSearchRequests ( TvSearchCriteria searchCriteria )
{
{
parameters . Add ( "recordlabel" , searchCriteria . Label ) ;
return new IndexerPageableRequestChain ( ) ;
}
}
if ( searchCriteria . Year . HasValue )
public IndexerPageableRequestChain GetSearchRequests ( BookSearchCriteria searchCriteria )
{
{
parameters . Add ( "year" , searchCriteria . Year . ToString ( ) ) ;
return new IndexerPageableRequestChain ( ) ;
}
}
if ( searchCriteria . Genre . IsNotNullOrWhiteSpace ( ) )
private IEnumerable < IndexerRequest > GetPagedRequests ( SearchCriteriaBase searchCriteria , NameValueCollection parameters )
{
{
parameters . Add ( "taglist" , searchCriteria . Genre ) ;
var term = searchCriteria . SanitizedSearchTerm . Trim ( ) ;
}
pageableRequests . Add ( GetPagedRequests ( searchCriteria , parameters ) ) ;
parameters . Set ( "order_by" , "time" ) ;
parameters . Set ( "order_way" , "desc" ) ;
parameters . Set ( "searchstr" , term ) ;
return pageableRequests ;
var queryCats = _capabilities . Categories . MapTorznabCapsToTrackers ( searchCriteria . Categories ) ;
if ( queryCats . Any ( ) )
{
queryCats . ForEach ( cat = > parameters . Set ( $"filter_cat[{cat}]" , "1" ) ) ;
}
}
public IndexerPageableRequestChain GetSearchRequests ( BasicSearchCriteria searchCriteria )
if ( searchCriteria . Offset . HasValue & & searchCriteria . Limit . HasValue & & searchCriteria . Offset > 0 & & searchCriteria . Limit > 0 )
{
{
var pageableRequests = new IndexerPageableRequestChain ( ) ;
var page = ( int ) ( searchCriteria . Offset / searchCriteria . Limit ) + 1 ;
var parameters = new NameValueCollection ( ) ;
parameters . Set ( "page" , page . ToString ( ) ) ;
}
pageableRequests . Add ( GetPagedRequests ( searchCriteria , parameters ) ) ;
var searchUrl = $"{_settings.BaseUrl.TrimEnd('/')}/torrents.php?{parameters.GetQueryString()}" ;
return pageableRequests ;
var request = new IndexerRequest ( searchUrl , HttpAccept . Html ) ;
}
public IndexerPageableRequestChain GetSearchRequests ( MovieSearchCriteria searchCriteria )
yield return request ;
{
}
return new IndexerPageableRequestChain ( ) ;
}
public IndexerPageableRequestChain GetSearchRequests ( TvSearchCriteria searchCriteria )
public Func < IDictionary < string , string > > GetCookies { get ; set ; }
{
public Action < IDictionary < string , string > , DateTime ? > CookiesUpdater { get ; set ; }
return new IndexerPageableRequestChain ( ) ;
}
}
public IndexerPageableRequestChain GetSearchRequests ( BookSearchCriteria searchCriteria )
public class LibbleParser : IParseIndexerResponse
{
{
return new IndexerPageableRequestChain ( ) ;
private readonly LibbleSettings _settings ;
}
private static Regex ReleaseYearRegex = > new (@"\[(\d{4 })\]$", RegexOptions . Compiled ) ;
public Func < IDictionary < string , string > > GetCookies { get ; set ; }
public LibbleParser ( LibbleSettings settings )
public Action < IDictionary < string , string > , DateTime ? > CookiesUpdater { get ; set ; }
{
_settings = settings ;
}
}
public class LibbleParser : IParseIndexerResponse
public IList < ReleaseInfo > ParseResponse ( IndexerResponse indexerResponse )
{
{
private readonly LibbleSettings _settings ;
var releaseInfos = new List < ReleaseInfo > ( ) ;
private readonly IndexerCapabilitiesCategories _categories ;
public LibbleParser ( LibbleSettings settings , IndexerCapabilitiesCategories categories )
var parser = new HtmlParser ( ) ;
{
var doc = parser . ParseDocument ( indexerResponse . Content ) ;
_settings = settings ;
_categories = categories ;
}
public IList < ReleaseInfo > ParseResponse ( IndexerResponse indexerResponse )
var groups = doc . QuerySelectorAll ( "table#torrent_table > tbody > tr.group:has(strong > a[href*=\"torrents.php?id=\"])" ) ;
foreach ( var group in groups )
{
{
var torrentInfos = new List < ReleaseInfo > ( ) ;
var albumLinkNode = group . QuerySelector ( "strong > a[href*=\"torrents.php?id=\"]" ) ;
var groupId = ParseUtil . GetArgumentFromQueryString ( albumLinkNode . GetAttribute ( "href" ) , "id" ) ;
var parser = new HtmlParser ( ) ;
var artistsNodes = group . QuerySelectorAll ( "strong > a[href*=\"artist.php?id=\"]" ) ;
var doc = parser . ParseDocument ( indexerResponse . Content ) ;
var rows = doc . QuerySelectorAll ( "table#torrent_table > tbody > tr.group:has(strong > a[href*=\"torrents.php?id=\"])" ) ;
var releaseYearRegex = new Regex ( @"\[(\d{4})\]$" ) ;
foreach ( var row in rows )
var releaseArtist = "Various Artists" ;
if ( artistsNodes . Any ( ) )
{
{
var albumLinkNode = row . QuerySelector ( "strong > a[href*=\"torrents.php?id=\"]" ) ;
releaseArtist = artistsNodes . Select ( artist = > artist . TextContent . Trim ( ) ) . ToList ( ) . Join ( ", " ) ;
var groupId = ParseUtil . GetArgumentFromQueryString ( albumLinkNode . GetAttribute ( "href" ) , "id" ) ;
}
var artistsNodes = row . QuerySelectorAll ( "strong > a[href*=\"artist.php?id=\"]" ) ;
var releaseArtist = "Various Artists" ;
var releaseAlbumName = group . QuerySelector ( "strong > a[href*=\"torrents.php?id=\"]" ) ? . TextContent . Trim ( ) ;
if ( artistsNodes . Count ( ) > 0 )
{
releaseArtist = artistsNodes . Select ( artist = > artist . TextContent . Trim ( ) ) . ToList ( ) . Join ( ", " ) ;
}
var releaseAlbumName = row . QuerySelector ( "strong > a[href*=\"torrents.php?id=\"]" ) ? . TextContent . Trim ( ) ;
var title = group . QuerySelector ( "td:nth-child(4) > strong" ) ? . TextContent . Trim ( ) ;
var releaseAlbumYear = ReleaseYearRegex . Match ( title ) ;
var title = row . QuerySelector ( "td:nth-child(4) > strong ") ? . TextContent . Trim ( ) ;
var releaseDescription = group . QuerySelector ( "div.tags ") ? . TextContent . Trim ( ) ;
var releaseAlbumYear = releaseYearRegex . Match ( title ) ;
var releaseThumbnailUrl = group . QuerySelector ( ".thumbnail" ) ? . GetAttribute ( "title" ) ? . Trim ( ) ;
var releaseDescription = row . QuerySelector ( "div.tags" ) ? . TextContent . Trim ( ) ;
var releaseGenres = new List < string > ( ) ;
var releaseThumbnailUrl = row . QuerySelector ( ".thumbnail" ) ? . GetAttribute ( "title" ) . Trim ( ) ;
if ( ! string . IsNullOrEmpty ( releaseDescription ) )
{
releaseGenres = releaseDescription . Split ( ',' , StringSplitOptions . TrimEntries | StringSplitOptions . RemoveEmptyEntries ) . ToList ( ) ;
}
var releaseGenres = new List < string > ( ) ;
var rows = doc . QuerySelectorAll ( $"table#torrent_table > tbody > tr.group_torrent.groupid_{groupId}:has(a[href*=\" torrents . php ? id = \ "])" ) ;
if ( ! string . IsNullOrEmpty ( releaseDescription ) )
foreach ( var row in rows )
{
{
releaseGenres = releaseGenres . Union ( releaseDescription . Split ( ',' ) . Select ( tag = > tag . Trim ( ) ) . ToList ( ) ) . ToList ( ) ;
var detailsNode = row . QuerySelector ( "a[href^=\"torrents.php?id=\"]" ) ;
}
var cat = row . QuerySelector ( "td.cats_col div.cat_icon" ) ? . GetAttribute ( "class" ) . Trim ( ) ;
var infoUrl = _settings . BaseUrl + detailsNode . GetAttribute ( "href" ) . Trim ( ) ;
var downloadLink = _settings . BaseUrl + row . QuerySelector ( "a[href^=\"torrents.php?action=download&id=\"]" ) . GetAttribute ( "href" ) . Trim ( ) ;
var matchCategory = Regex . Match ( cat , @"\bcats_(.*?)\b" ) ;
var releaseTags = detailsNode . FirstChild ? . TextContent . Trim ( ' ' , '/' ) ;
if ( matchCategory . Success )
var seeders = ParseUtil . CoerceInt ( row . QuerySelector ( "td:nth-child(6)" ) . TextContent ) ;
{
cat = matchCategory . Groups [ 1 ] . Value . Trim ( ) ;
}
var category = new List < IndexerCategory >
var release = new TorrentInfo
{
{
cat switch
Guid = infoUrl ,
{
InfoUrl = infoUrl ,
"music" = > NewznabStandardCategory . Audio ,
DownloadUrl = downloadLink ,
"libblemixtapes" = > NewznabStandardCategory . Audio ,
Title = $"{releaseArtist} - {releaseAlbumName} {releaseAlbumYear} {releaseTags}" . Trim ( ' ' , '-' ) ,
"musicvideos" = > NewznabStandardCategory . AudioVideo ,
Categories = ParseCategories ( group ) ,
_ = > NewznabStandardCategory . Other ,
Description = releaseDescription ,
}
Size = ParseUtil . GetBytes ( row . QuerySelector ( "td:nth-child(4)" ) . TextContent . Trim ( ) ) ,
Files = ParseUtil . CoerceInt ( row . QuerySelector ( "td:nth-child(2)" ) . TextContent ) ,
Grabs = ParseUtil . CoerceInt ( row . QuerySelector ( "td:nth-child(5)" ) . TextContent ) ,
Seeders = seeders ,
Peers = seeders + ParseUtil . CoerceInt ( row . QuerySelector ( "td:nth-child(7)" ) . TextContent ) ,
DownloadVolumeFactor = 1 ,
UploadVolumeFactor = 1 ,
MinimumRatio = 1 ,
MinimumSeedTime = 259200 , // 72 hours,
Genres = releaseGenres ,
PosterUrl = releaseThumbnailUrl ,
} ;
} ;
var releaseRows = doc . QuerySelectorAll ( string . Format ( "table#torrent_table > tbody > tr.group_torrent.groupid_{0}:has(a[href*=\"torrents.php?id=\"])" , groupId ) ) ;
try
{
var dateAdded = row . QuerySelector ( "td:nth-child(3) > span[title]" ) . GetAttribute ( "title" ) . Trim ( ) ;
release . PublishDate = DateTime . ParseExact ( dateAdded , "MMM dd yyyy, HH:mm" , CultureInfo . InvariantCulture , DateTimeStyles . AssumeUniversal ) ;
}
catch ( Exception )
{
release . PublishDate = DateTimeUtil . FromTimeAgo ( row . QuerySelector ( "td:nth-child(3)" ) ? . TextContent . Trim ( ) ) ;
}
foreach ( var releaseRow in releaseRows )
switch ( row . QuerySelector ( "a[href^=\"torrents.php?id=\"] strong" ) ? . TextContent . ToLower ( ) . Trim ( ' ' , '!' ) )
{
{
var release = new TorrentInfo ( ) ;
case "neutral" :
release . DownloadVolumeFactor = 0 ;
var detailsNode = releaseRow . QuerySelector ( "a[href^=\"torrents.php?id=\"]" ) ;
release . UploadVolumeFactor = 0 ;
var downloadLink = _settings . BaseUrl + releaseRow . QuerySelector ( "a[href^=\"torrents.php?action=download&id=\"]" ) . GetAttribute ( "href" ) . Trim ( ) ;
break ;
case "freeleech" :
var releaseTags = detailsNode . FirstChild . TextContent . Trim ( ' ' , '/' ) ;
release . DownloadVolumeFactor = 0 ;
release . UploadVolumeFactor = 1 ;
release . Title = string . Format ( "{0} - {1} {2} {3}" , releaseArtist , releaseAlbumName , releaseAlbumYear , releaseTags ) . Trim ( ) ;
break ;
release . Categories = category ;
release . Description = releaseDescription ;
release . Genres = releaseGenres ;
release . PosterUrl = releaseThumbnailUrl ;
release . InfoUrl = _settings . BaseUrl + detailsNode . GetAttribute ( "href" ) . Trim ( ) ;
release . DownloadUrl = downloadLink ;
release . Guid = release . InfoUrl ;
release . Size = ParseUtil . GetBytes ( releaseRow . QuerySelector ( "td:nth-child(4)" ) . TextContent . Trim ( ) ) ;
release . Files = ParseUtil . CoerceInt ( releaseRow . QuerySelector ( "td:nth-child(2)" ) . TextContent ) ;
release . Grabs = ParseUtil . CoerceInt ( releaseRow . QuerySelector ( "td:nth-child(5)" ) . TextContent ) ;
release . Seeders = ParseUtil . CoerceInt ( releaseRow . QuerySelector ( "td:nth-child(6)" ) . TextContent ) ;
release . Peers = release . Seeders + ParseUtil . CoerceInt ( releaseRow . QuerySelector ( "td:nth-child(7)" ) . TextContent ) ;
release . MinimumRatio = 1 ;
release . MinimumSeedTime = 259200 ; // 72 hours
try
{
release . PublishDate = DateTime . ParseExact (
releaseRow . QuerySelector ( "td:nth-child(3) > span[title]" ) . GetAttribute ( "title" ) . Trim ( ) ,
"MMM dd yyyy, HH:mm" ,
CultureInfo . InvariantCulture ,
DateTimeStyles . AssumeUniversal ) ;
}
catch ( Exception )
{
}
switch ( releaseRow . QuerySelector ( "a[href^=\"torrents.php?id=\"] strong" ) ? . TextContent . Trim ( ) )
{
case "Neutral!" :
release . DownloadVolumeFactor = 0 ;
release . UploadVolumeFactor = 0 ;
break ;
case "Freeleech!" :
release . DownloadVolumeFactor = 0 ;
release . UploadVolumeFactor = 1 ;
break ;
default :
release . DownloadVolumeFactor = 1 ;
release . UploadVolumeFactor = 1 ;
break ;
}
torrentInfos . Add ( release ) ;
}
}
}
return torrentInfos . ToArray ( ) ;
releaseInfos . Add ( release ) ;
}
}
}
public Action < IDictionary < string , string > , DateTime ? > CookiesUpdater { get ; set ; }
return releaseInfos . ToArray ( ) ;
}
}
p ublic class LibbleSettings : UserPassTorrentBaseSettings
private IList < IndexerCategory > ParseCategories ( IElement group )
{
{
public LibbleSettings ( )
var cat = group . QuerySelector ( "td.cats_col div.cat_icon" ) ? . GetAttribute ( "class" ) ? . Trim ( ) ;
var matchCategory = Regex . Match ( cat , @"\bcats_(.*?)\b" ) ;
if ( matchCategory . Success )
{
{
TwoFactorAuthCode = "" ;
cat = matchCategory . Groups [ 1 ] . Value . Trim ( ) ;
}
}
[FieldDefinition(4, Label = "2FA code", Type = FieldType.Textbox, HelpText = "Only fill in the <b>2FA code</b> box if you have enabled <b>2FA</b> on the Libble Web Site. Otherwise just leave it empty.")]
return new List < IndexerCategory >
public string TwoFactorAuthCode { get ; set ; }
{
cat switch
{
"music" = > NewznabStandardCategory . Audio ,
"libblemixtapes" = > NewznabStandardCategory . Audio ,
"musicvideos" = > NewznabStandardCategory . AudioVideo ,
_ = > NewznabStandardCategory . Other ,
}
} ;
}
public Action < IDictionary < string , string > , DateTime ? > CookiesUpdater { get ; set ; }
}
public class LibbleSettings : UserPassTorrentBaseSettings
{
public LibbleSettings ( )
{
TwoFactorAuthCode = "" ;
}
}
[FieldDefinition(4, Label = "2FA code", Type = FieldType.Textbox, HelpText = "Only fill in the <b>2FA code</b> box if you have enabled <b>2FA</b> on the Libble Web Site. Otherwise just leave it empty.")]
public string TwoFactorAuthCode { get ; set ; }
}
}