2020-02-09 02:35:16 +00:00
using System ;
2017-04-15 08:45:10 +00:00
using System.Collections.Generic ;
2018-04-26 17:30:03 +00:00
using System.Collections.Specialized ;
2020-05-03 23:35:52 +00:00
using System.Diagnostics.CodeAnalysis ;
2018-04-26 17:30:03 +00:00
using System.Globalization ;
2017-04-15 08:45:10 +00:00
using System.Linq ;
using System.Net ;
using System.Text ;
using System.Text.RegularExpressions ;
using System.Threading.Tasks ;
2018-03-10 08:05:56 +00:00
using Jackett.Common.Models ;
using Jackett.Common.Models.IndexerConfig.Bespoke ;
using Jackett.Common.Services.Interfaces ;
using Jackett.Common.Utils ;
2018-04-26 17:30:03 +00:00
using Newtonsoft.Json ;
2017-10-29 06:21:18 +00:00
using Newtonsoft.Json.Linq ;
using NLog ;
2020-11-03 21:58:36 +00:00
using WebClient = Jackett . Common . Utils . Clients . WebClient ;
2017-04-15 08:45:10 +00:00
2018-03-10 08:05:56 +00:00
namespace Jackett.Common.Indexers
2017-04-15 08:45:10 +00:00
{
2020-05-03 23:35:52 +00:00
[ExcludeFromCodeCoverage]
2017-07-10 20:58:44 +00:00
public class AnimeBytes : BaseCachingWebIndexer
2017-04-15 08:45:10 +00:00
{
2020-02-25 16:08:03 +00:00
private string ScrapeUrl = > SiteLink + "scrape.php" ;
2020-11-03 21:58:36 +00:00
private bool AllowRaws = > ConfigData . IncludeRaw . Value ;
private bool PadEpisode = > ConfigData . PadEpisode ! = null & & ConfigData . PadEpisode . Value ;
private bool AddJapaneseTitle = > ConfigData . AddJapaneseTitle . Value ;
private bool AddRomajiTitle = > ConfigData . AddRomajiTitle . Value ;
private bool AddAlternativeTitles = > ConfigData . AddAlternativeTitles . Value ;
private bool FilterSeasonEpisode = > ConfigData . FilterSeasonEpisode . Value ;
private ConfigurationDataAnimeBytes ConfigData = > ( ConfigurationDataAnimeBytes ) configData ;
2017-04-15 08:45:10 +00:00
2020-12-11 22:14:21 +00:00
public AnimeBytes ( IIndexerConfigurationService configService , WebClient client , Logger l ,
IProtectionService ps , ICacheService cs )
2020-05-11 19:59:28 +00:00
: base ( id : "animebytes" ,
name : "AnimeBytes" ,
description : "Powered by Tentacles" ,
link : "https://animebytes.tv/" ,
configService : configService ,
client : client ,
2021-05-16 18:13:54 +00:00
caps : new TorznabCapabilities
{
2020-10-18 20:47:36 +00:00
TvSearchParams = new List < TvSearchParam >
{
TvSearchParam . Q , TvSearchParam . Season , TvSearchParam . Ep
} ,
MovieSearchParams = new List < MovieSearchParam >
{
MovieSearchParam . Q
2020-10-31 01:00:36 +00:00
} ,
MusicSearchParams = new List < MusicSearchParam >
{
MusicSearchParam . Q
} ,
BookSearchParams = new List < BookSearchParam >
{
BookSearchParam . Q
2020-10-18 20:47:36 +00:00
}
2020-10-18 17:26:22 +00:00
} ,
2020-05-11 19:59:28 +00:00
logger : l ,
p : ps ,
2020-12-11 22:14:21 +00:00
cacheService : cs ,
2020-05-11 19:59:28 +00:00
configData : new ConfigurationDataAnimeBytes ( "Note: Go to AnimeBytes site and open your account settings. Go to 'Account' tab, move cursor over black part near 'Passkey' and copy its value. Your username is case sensitive." ) )
2017-04-15 08:45:10 +00:00
{
2017-11-05 09:42:03 +00:00
Encoding = Encoding . UTF8 ;
2017-04-15 08:45:10 +00:00
Language = "en-us" ;
Type = "private" ;
2017-08-11 14:53:49 +00:00
webclient . EmulateBrowser = false ; // Animebytes doesn't like fake user agents (issue #1535)
2019-08-19 03:01:07 +00:00
AddCategoryMapping ( "anime[tv_series]" , TorznabCatType . TVAnime , "TV Series" ) ;
AddCategoryMapping ( "anime[tv_special]" , TorznabCatType . TVAnime , "TV Special" ) ;
AddCategoryMapping ( "anime[ova]" , TorznabCatType . TVAnime , "OVA" ) ;
AddCategoryMapping ( "anime[ona]" , TorznabCatType . TVAnime , "ONA" ) ;
AddCategoryMapping ( "anime[dvd_special]" , TorznabCatType . TVAnime , "DVD Special" ) ;
AddCategoryMapping ( "anime[bd_special]" , TorznabCatType . TVAnime , "BD Special" ) ;
AddCategoryMapping ( "anime[movie]" , TorznabCatType . Movies , "Movie" ) ;
2020-10-30 21:02:38 +00:00
AddCategoryMapping ( "audio" , TorznabCatType . Audio , "Music" ) ;
2019-08-19 03:01:07 +00:00
AddCategoryMapping ( "gamec[game]" , TorznabCatType . PCGames , "Game" ) ;
2020-11-06 03:09:57 +00:00
AddCategoryMapping ( "gamec[visual_novel]" , TorznabCatType . PCGames , "Game Visual Novel" ) ;
2019-08-19 03:01:07 +00:00
AddCategoryMapping ( "printedtype[manga]" , TorznabCatType . BooksComics , "Manga" ) ;
AddCategoryMapping ( "printedtype[oneshot]" , TorznabCatType . BooksComics , "Oneshot" ) ;
AddCategoryMapping ( "printedtype[anthology]" , TorznabCatType . BooksComics , "Anthology" ) ;
AddCategoryMapping ( "printedtype[manhwa]" , TorznabCatType . BooksComics , "Manhwa" ) ;
AddCategoryMapping ( "printedtype[light_novel]" , TorznabCatType . BooksComics , "Light Novel" ) ;
AddCategoryMapping ( "printedtype[artbook]" , TorznabCatType . BooksComics , "Artbook" ) ;
2017-04-15 08:45:10 +00:00
}
2017-11-06 13:29:46 +00:00
public override async Task < IndexerConfigurationStatus > ApplyConfiguration ( JToken configJson )
{
LoadValuesFromJson ( configJson ) ;
2020-11-03 21:58:36 +00:00
if ( ConfigData . Passkey . Value . Length ! = 32 & & ConfigData . Passkey . Value . Length ! = 48 )
throw new Exception ( "invalid passkey configured: expected length: 32 or 48, got " + ConfigData . Passkey . Value . Length ) ;
2017-10-18 08:46:38 +00:00
2018-04-27 14:32:26 +00:00
var results = await PerformQuery ( new TorznabQuery ( ) ) ;
2020-11-03 21:58:36 +00:00
if ( ! results . Any ( ) )
2018-04-27 14:32:26 +00:00
throw new Exception ( "no results found, please report this bug" ) ;
IsConfigured = true ;
SaveConfig ( ) ;
return IndexerConfigurationStatus . Completed ;
2017-04-15 08:45:10 +00:00
}
private string StripEpisodeNumber ( string term )
{
// Tracer does not support searching with episode number so strip it if we have one
term = Regex . Replace ( term , @"\W(\dx)?\d?\d$" , string . Empty ) ;
term = Regex . Replace ( term , @"\W(S\d\d?E)?\d?\d$" , string . Empty ) ;
2019-08-19 03:01:07 +00:00
term = Regex . Replace ( term , @"\W\d+$" , string . Empty ) ;
2017-04-15 08:45:10 +00:00
return term ;
}
2017-07-03 05:15:47 +00:00
protected override async Task < IEnumerable < ReleaseInfo > > PerformQuery ( TorznabQuery query )
2017-04-15 08:45:10 +00:00
{
var releases = new List < ReleaseInfo > ( ) ;
2017-07-10 20:58:44 +00:00
2017-04-15 08:45:10 +00:00
if ( ContainsMusicCategories ( query . Categories ) )
2020-11-03 21:58:36 +00:00
releases . AddRange ( await GetResults ( query , "music" , query . SanitizedSearchTerm ) ) ;
2017-04-15 08:45:10 +00:00
2020-11-03 21:58:36 +00:00
releases . AddRange (
await GetResults ( query , "anime" , StripEpisodeNumber ( query . SanitizedSearchTerm ) )
) ;
2017-04-15 08:45:10 +00:00
return releases . ToArray ( ) ;
}
private bool ContainsMusicCategories ( int [ ] categories )
{
var music = new [ ]
{
TorznabCatType . Audio . ID ,
TorznabCatType . AudioMP3 . ID ,
TorznabCatType . AudioLossless . ID ,
TorznabCatType . AudioOther . ID ,
TorznabCatType . AudioForeign . ID
} ;
return categories . Length = = 0 | | music . Any ( categories . Contains ) ;
}
2018-04-26 17:30:03 +00:00
private async Task < IEnumerable < ReleaseInfo > > GetResults ( TorznabQuery query , string searchType , string searchTerm )
2017-04-15 08:45:10 +00:00
{
var releases = new List < ReleaseInfo > ( ) ;
2020-11-03 21:58:36 +00:00
var queryCollection = new NameValueCollection
{
{ "username" , ConfigData . Username . Value } ,
{ "torrent_pass" , ConfigData . Passkey . Value } ,
{ "type" , searchType } ,
{ "searchstr" , searchTerm }
} ;
2018-04-26 17:30:03 +00:00
var queryCats = MapTorznabCapsToTrackers ( query ) ;
2019-08-19 03:01:07 +00:00
if ( queryCats . Count > 0 )
foreach ( var cat in queryCats )
queryCollection . Add ( cat , "1" ) ;
2017-04-15 08:45:10 +00:00
2018-04-26 17:30:03 +00:00
var queryUrl = ScrapeUrl + "?" + queryCollection . GetQueryString ( ) ;
2020-02-09 02:35:16 +00:00
2017-04-15 08:45:10 +00:00
// Check cache first so we don't query the server for each episode when searching for each episode in a series.
lock ( cache )
{
// Remove old cache items
CleanCache ( ) ;
var cachedResult = cache . Where ( i = > i . Query = = queryUrl ) . FirstOrDefault ( ) ;
if ( cachedResult ! = null )
return cachedResult . Results . Select ( s = > ( ReleaseInfo ) s . Clone ( ) ) . ToArray ( ) ;
}
2017-07-10 20:58:44 +00:00
2017-04-15 08:45:10 +00:00
// Get the content from the tracker
2020-06-11 15:09:27 +00:00
var response = await RequestWithCookiesAndRetryAsync ( queryUrl ) ;
2020-06-09 17:36:57 +00:00
if ( ! response . ContentString . StartsWith ( "{" ) ) // not JSON => error
2020-11-03 21:58:36 +00:00
throw new ExceptionWithConfigData ( "Unexpected response (not JSON)" , ConfigData ) ;
2020-10-19 21:19:10 +00:00
var json = JsonConvert . DeserializeObject < dynamic > ( response . ContentString ) ;
2017-04-15 08:45:10 +00:00
// Parse
try
{
2018-04-26 17:30:03 +00:00
if ( json [ "error" ] ! = null )
throw new Exception ( json [ "error" ] . ToString ( ) ) ;
2017-04-15 08:45:10 +00:00
2020-11-03 21:58:36 +00:00
var matches = ( long ) json [ "Matches" ] ;
2018-04-26 17:30:03 +00:00
2020-11-03 21:58:36 +00:00
if ( matches > 0 )
2017-04-15 08:45:10 +00:00
{
2018-04-27 14:52:21 +00:00
var groups = ( JArray ) json . Groups ;
2017-04-15 08:45:10 +00:00
2020-11-03 21:58:36 +00:00
foreach ( var group in groups )
2018-04-27 14:52:21 +00:00
{
var synonyms = new List < string > ( ) ;
2020-11-07 23:43:33 +00:00
var posterStr = ( string ) group [ "Image" ] ;
var poster = ( string . IsNullOrWhiteSpace ( posterStr ) ? null : new Uri ( posterStr ) ) ;
2020-11-03 21:58:36 +00:00
var year = ( int ) group [ "Year" ] ;
var groupName = ( string ) group [ "GroupName" ] ;
var seriesName = ( string ) group [ "SeriesName" ] ;
2018-04-27 14:52:21 +00:00
var mainTitle = WebUtility . HtmlDecode ( ( string ) group [ "FullName" ] ) ;
2020-11-03 21:58:36 +00:00
if ( seriesName ! = null )
mainTitle = seriesName ;
2018-04-27 14:52:21 +00:00
synonyms . Add ( mainTitle ) ;
2020-09-13 17:00:09 +00:00
if ( group [ "Synonymns" ] . HasValues )
2020-02-09 02:35:16 +00:00
{
2020-09-13 17:00:09 +00:00
if ( group [ "Synonymns" ] is JArray )
{
2020-10-19 21:19:10 +00:00
var allSyonyms = group [ "Synonymns" ] . ToObject < List < string > > ( ) ;
2020-09-13 17:00:09 +00:00
if ( AddJapaneseTitle & & allSyonyms . Count > = 1 )
synonyms . Add ( allSyonyms [ 0 ] ) ;
if ( AddRomajiTitle & & allSyonyms . Count > = 2 )
synonyms . Add ( allSyonyms [ 1 ] ) ;
if ( AddAlternativeTitles & & allSyonyms . Count > = 3 )
synonyms . AddRange ( allSyonyms [ 2 ] . Split ( ',' ) . Select ( t = > t . Trim ( ) ) ) ;
2021-05-16 18:13:54 +00:00
}
else
2020-09-13 17:00:09 +00:00
{
2020-10-19 21:19:10 +00:00
var allSynonyms = group [ "Synonymns" ] . ToObject < Dictionary < int , string > > ( ) ;
2020-09-13 17:00:09 +00:00
if ( AddJapaneseTitle & & allSynonyms . ContainsKey ( 0 ) )
synonyms . Add ( allSynonyms [ 0 ] ) ;
if ( AddRomajiTitle & & allSynonyms . ContainsKey ( 1 ) )
synonyms . Add ( allSynonyms [ 1 ] ) ;
2021-05-16 18:13:54 +00:00
if ( AddAlternativeTitles & & allSynonyms . ContainsKey ( 2 ) )
{
2020-09-13 17:00:09 +00:00
synonyms . AddRange ( allSynonyms [ 2 ] . Split ( ',' ) . Select ( t = > t . Trim ( ) ) ) ;
}
}
2017-04-15 08:45:10 +00:00
}
2020-11-03 21:58:36 +00:00
List < int > category = null ;
var categoryName = ( string ) group [ "CategoryName" ] ;
2018-04-26 17:30:03 +00:00
2020-11-03 21:58:36 +00:00
var description = ( string ) group [ "Description" ] ;
2018-04-26 17:30:03 +00:00
2020-11-03 21:58:36 +00:00
foreach ( var torrent in group [ "Torrents" ] )
2017-04-15 08:45:10 +00:00
{
2018-04-27 14:52:21 +00:00
var releaseInfo = "S01" ;
string episode = null ;
int? season = null ;
2020-11-03 21:58:36 +00:00
var editionTitle = ( string ) torrent [ "EditionData" ] [ "EditionTitle" ] ;
if ( ! string . IsNullOrWhiteSpace ( editionTitle ) )
releaseInfo = WebUtility . HtmlDecode ( editionTitle ) ;
2018-04-27 14:52:21 +00:00
2020-11-03 21:58:36 +00:00
var seasonRegEx = new Regex ( @"Season (\d+)" , RegexOptions . Compiled ) ;
var seasonRegExMatch = seasonRegEx . Match ( releaseInfo ) ;
if ( seasonRegExMatch . Success )
season = ParseUtil . CoerceInt ( seasonRegExMatch . Groups [ 1 ] . Value ) ;
2018-04-27 14:52:21 +00:00
2020-11-03 21:58:36 +00:00
var episodeRegEx = new Regex ( @"Episode (\d+)" , RegexOptions . Compiled ) ;
var episodeRegExMatch = episodeRegEx . Match ( releaseInfo ) ;
if ( episodeRegExMatch . Success )
episode = episodeRegExMatch . Groups [ 1 ] . Value ;
2018-04-27 14:52:21 +00:00
releaseInfo = releaseInfo . Replace ( "Episode " , "" ) ;
releaseInfo = releaseInfo . Replace ( "Season " , "S" ) ;
releaseInfo = releaseInfo . Trim ( ) ;
2020-11-03 21:58:36 +00:00
if ( PadEpisode & & int . TryParse ( releaseInfo , out _ ) & & releaseInfo . Length = = 1 )
2018-04-27 14:52:21 +00:00
{
2019-07-30 19:36:26 +00:00
releaseInfo = "0" + releaseInfo ;
2018-04-27 14:52:21 +00:00
}
2017-04-15 08:45:10 +00:00
2018-04-27 14:52:21 +00:00
if ( FilterSeasonEpisode )
{
if ( query . Season ! = 0 & & season ! = null & & season ! = query . Season ) // skip if season doesn't match
continue ;
if ( query . Episode ! = null & & episode ! = null & & episode ! = query . Episode ) // skip if episode doesn't match
continue ;
}
2020-11-03 21:58:36 +00:00
var torrentId = ( long ) torrent [ "ID" ] ;
var property = ( ( string ) torrent [ "Property" ] ) . Replace ( " | Freeleech" , "" ) ;
var link = ( string ) torrent [ "Link" ] ;
var linkUri = new Uri ( link ) ;
var uploadTimeString = ( string ) torrent [ "UploadTime" ] ;
var uploadTime = DateTime . ParseExact ( uploadTimeString , "yyyy-MM-dd HH:mm:ss" , CultureInfo . InvariantCulture ) ;
var publishDate = DateTime . SpecifyKind ( uploadTime , DateTimeKind . Utc ) . ToLocalTime ( ) ;
2020-11-08 02:11:27 +00:00
var details = new Uri ( SiteLink + "torrent/" + torrentId + "/group" ) ;
2020-11-03 21:58:36 +00:00
var size = ( long ) torrent [ "Size" ] ;
var snatched = ( long ) torrent [ "Snatched" ] ;
var seeders = ( int ) torrent [ "Seeders" ] ;
var leechers = ( int ) torrent [ "Leechers" ] ;
var fileCount = ( long ) torrent [ "FileCount" ] ;
var peers = seeders + leechers ;
var rawDownMultiplier = ( int? ) torrent [ "RawDownMultiplier" ] ? ? 0 ;
var rawUpMultiplier = ( int? ) torrent [ "RawUpMultiplier" ] ? ? 0 ;
2018-04-27 14:52:21 +00:00
if ( searchType = = "anime" )
{
2020-11-03 21:58:36 +00:00
if ( groupName = = "TV Series" | | groupName = = "OVA" )
category = new List < int > { TorznabCatType . TVAnime . ID } ;
2017-04-15 08:45:10 +00:00
2018-04-27 14:52:21 +00:00
// Ignore these categories as they'll cause hell with the matcher
// TV Special, OVA, ONA, DVD Special, BD Special
2017-04-15 08:45:10 +00:00
2020-11-03 21:58:36 +00:00
if ( groupName = = "Movie" | | groupName = = "Live Action Movie" )
category = new List < int > { TorznabCatType . Movies . ID } ;
2017-04-15 08:45:10 +00:00
2020-11-03 21:58:36 +00:00
if ( categoryName = = "Manga" | | categoryName = = "Oneshot" | | categoryName = = "Anthology" | | categoryName = = "Manhwa" | | categoryName = = "Manhua" | | categoryName = = "Light Novel" )
category = new List < int > { TorznabCatType . BooksComics . ID } ;
2017-04-15 08:45:10 +00:00
2020-11-03 21:58:36 +00:00
if ( categoryName = = "Novel" | | categoryName = = "Artbook" )
category = new List < int > { TorznabCatType . BooksComics . ID } ;
2017-04-15 08:45:10 +00:00
2020-11-03 21:58:36 +00:00
if ( categoryName = = "Game" | | categoryName = = "Visual Novel" )
2018-04-27 14:52:21 +00:00
{
2020-11-03 21:58:36 +00:00
if ( property . Contains ( " PSP " ) )
category = new List < int > { TorznabCatType . ConsolePSP . ID } ;
if ( property . Contains ( "PSX" ) )
category = new List < int > { TorznabCatType . ConsoleOther . ID } ;
if ( property . Contains ( " NES " ) )
category = new List < int > { TorznabCatType . ConsoleOther . ID } ;
if ( property . Contains ( " PC " ) )
category = new List < int > { TorznabCatType . PCGames . ID } ;
2018-04-27 14:52:21 +00:00
}
2018-04-26 17:30:03 +00:00
}
2018-04-27 14:52:21 +00:00
else if ( searchType = = "music" )
2018-04-26 17:30:03 +00:00
{
2020-11-03 21:58:36 +00:00
if ( categoryName = = "Single" | | categoryName = = "EP" | | categoryName = = "Album" | | categoryName = = "Compilation" | | categoryName = = "Soundtrack" | | categoryName = = "Remix CD" | | categoryName = = "PV" | | categoryName = = "Live Album" | | categoryName = = "Image CD" | | categoryName = = "Drama CD" | | categoryName = = "Vocal CD" )
2018-04-27 14:52:21 +00:00
{
2020-11-03 21:58:36 +00:00
if ( property . Contains ( " Lossless " ) )
category = new List < int > { TorznabCatType . AudioLossless . ID } ;
else if ( property . Contains ( "MP3" ) )
category = new List < int > { TorznabCatType . AudioMP3 . ID } ;
2018-04-27 14:52:21 +00:00
else
2020-11-03 21:58:36 +00:00
category = new List < int > { TorznabCatType . AudioOther . ID } ;
2018-04-27 14:52:21 +00:00
}
2018-04-26 17:30:03 +00:00
}
2017-04-15 08:45:10 +00:00
2020-11-03 21:58:36 +00:00
// We don't actually have a release name >.> so try to create one
var releaseTags = property . Split ( "|" . ToCharArray ( ) , StringSplitOptions . RemoveEmptyEntries ) . ToList ( ) ;
2020-02-10 22:16:19 +00:00
for ( var i = releaseTags . Count - 1 ; i > = 0 ; i - - )
2018-04-26 17:30:03 +00:00
{
2018-04-27 14:52:21 +00:00
releaseTags [ i ] = releaseTags [ i ] . Trim ( ) ;
if ( string . IsNullOrWhiteSpace ( releaseTags [ i ] ) )
releaseTags . RemoveAt ( i ) ;
2018-04-26 17:30:03 +00:00
}
2017-04-15 08:45:10 +00:00
2020-11-03 21:58:36 +00:00
var releaseGroup = releaseTags . LastOrDefault ( ) ;
if ( releaseGroup ! = null & & releaseGroup . Contains ( "(" ) & & releaseGroup . Contains ( ")" ) )
2018-04-27 14:52:21 +00:00
{
// Skip raws if set
2020-11-03 21:58:36 +00:00
if ( releaseGroup . ToLowerInvariant ( ) . StartsWith ( "raw" ) & & ! AllowRaws )
2018-04-27 14:52:21 +00:00
{
continue ;
}
2017-04-15 08:45:10 +00:00
2020-11-03 21:58:36 +00:00
var start = releaseGroup . IndexOf ( "(" , StringComparison . Ordinal ) ;
releaseGroup = "[" + releaseGroup . Substring ( start + 1 , ( releaseGroup . IndexOf ( ")" , StringComparison . Ordinal ) - 1 ) - start ) + "] " ;
2018-04-27 14:52:21 +00:00
}
else
{
2020-11-03 21:58:36 +00:00
releaseGroup = string . Empty ;
2018-04-27 14:52:21 +00:00
}
2020-02-06 02:27:26 +00:00
if ( ! AllowRaws & & releaseTags . Contains ( "raw" , StringComparer . InvariantCultureIgnoreCase ) )
continue ;
2017-04-15 08:45:10 +00:00
2020-02-09 02:35:16 +00:00
var infoString = releaseTags . Aggregate ( "" , ( prev , cur ) = > prev + "[" + cur + "]" ) ;
2020-11-03 21:58:36 +00:00
var minimumSeedTime = 259200 ;
2018-04-27 14:52:21 +00:00
// Additional 5 hours per GB
2020-11-03 21:58:36 +00:00
minimumSeedTime + = ( int ) ( ( size / 1000000000 ) * 18000 ) ;
2018-04-27 14:52:21 +00:00
foreach ( var title in synonyms )
2018-04-26 17:30:03 +00:00
{
2020-11-03 21:58:36 +00:00
var releaseTitle = groupName = = "Movie" ?
$"{title} {year} {releaseGroup}{infoString}" :
$"{releaseGroup}{title} {releaseInfo} {infoString}" ;
2018-04-26 17:30:03 +00:00
2020-11-08 02:11:27 +00:00
var guid = new Uri ( details + "&nh=" + StringUtil . Hash ( title ) ) ;
2020-02-25 16:08:03 +00:00
var release = new ReleaseInfo
{
MinimumRatio = 1 ,
2020-11-03 21:58:36 +00:00
MinimumSeedTime = minimumSeedTime ,
2020-02-25 16:08:03 +00:00
Title = releaseTitle ,
2020-11-08 02:11:27 +00:00
Details = details ,
2020-11-03 21:58:36 +00:00
Guid = guid ,
Link = linkUri ,
2020-11-07 23:43:33 +00:00
Poster = poster ,
2020-11-03 21:58:36 +00:00
PublishDate = publishDate ,
Category = category ,
Description = description ,
Size = size ,
Seeders = seeders ,
Peers = peers ,
Grabs = snatched ,
Files = fileCount ,
DownloadVolumeFactor = rawDownMultiplier ,
UploadVolumeFactor = rawUpMultiplier
2020-02-25 16:08:03 +00:00
} ;
2018-04-27 14:52:21 +00:00
releases . Add ( release ) ;
}
2017-04-15 08:45:10 +00:00
}
}
}
}
catch ( Exception ex )
{
2020-06-09 17:36:57 +00:00
OnParseError ( response . ContentString , ex ) ;
2017-04-15 08:45:10 +00:00
}
// Add to the cache
lock ( cache )
{
cache . Add ( new CachedQueryResult ( queryUrl , releases ) ) ;
}
return releases . Select ( s = > ( ReleaseInfo ) s . Clone ( ) ) ;
}
}
}