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 ;
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 ;
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
{
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" ;
private string TorrentsUrl = > SiteLink + "torrents.php" ;
public bool AllowRaws = > configData . IncludeRaw . Value ;
public bool PadEpisode = > configData . PadEpisode ! = null & & configData . PadEpisode . Value ;
public bool AddSynonyms = > configData . AddSynonyms . Value ;
public bool FilterSeasonEpisode = > configData . FilterSeasonEpisode . Value ;
2017-04-15 08:45:10 +00:00
2017-10-29 06:21:18 +00:00
private new ConfigurationDataAnimeBytes configData
2017-04-15 08:45:10 +00:00
{
2020-02-25 16:08:03 +00:00
get = > ( ConfigurationDataAnimeBytes ) base . configData ;
set = > base . configData = value ;
2017-04-15 08:45:10 +00:00
}
2017-11-05 09:42:03 +00:00
public AnimeBytes ( IIndexerConfigurationService configService , Utils . Clients . WebClient client , Logger l , IProtectionService ps )
2017-04-15 08:45:10 +00:00
: base ( name : "AnimeBytes" ,
link : "https://animebytes.tv/" ,
description : "Powered by Tentacles" ,
2017-07-10 20:58:44 +00:00
configService : configService ,
2017-04-15 08:45:10 +00:00
client : client ,
caps : new TorznabCapabilities ( TorznabCatType . TVAnime ,
TorznabCatType . Movies ,
TorznabCatType . BooksComics ,
TorznabCatType . ConsolePSP ,
TorznabCatType . ConsoleOther ,
TorznabCatType . PCGames ,
TorznabCatType . AudioMP3 ,
TorznabCatType . AudioLossless ,
TorznabCatType . AudioOther ) ,
logger : l ,
p : ps ,
2018-05-12 01:59:15 +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" ) ;
AddCategoryMapping ( "gamec[game]" , TorznabCatType . PCGames , "Game" ) ;
AddCategoryMapping ( "gamec[visual_novel]" , TorznabCatType . PCGames , "Visual Novel" ) ;
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
}
2020-02-25 16:08:03 +00:00
// Prevent filtering
protected override IEnumerable < ReleaseInfo > FilterResults ( TorznabQuery query , IEnumerable < ReleaseInfo > input ) = >
2017-04-15 08:45:10 +00:00
2020-02-25 16:08:03 +00:00
input ;
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 ) ;
2018-05-08 05:25:04 +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 . ToString ( ) ) ;
2017-10-18 08:46:38 +00:00
2018-04-27 14:32:26 +00:00
var results = await PerformQuery ( new TorznabQuery ( ) ) ;
if ( results . Count ( ) = = 0 )
2017-04-15 08:45:10 +00:00
{
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
{
// The result list
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 ) )
{
2018-04-26 17:30:03 +00:00
foreach ( var result in await GetResults ( query , "music" , query . SanitizedSearchTerm ) )
2017-04-15 08:45:10 +00:00
{
releases . Add ( result ) ;
}
}
2018-04-26 17:30:03 +00:00
foreach ( var result in await GetResults ( query , "anime" , StripEpisodeNumber ( query . SanitizedSearchTerm ) ) )
2017-04-15 08:45:10 +00:00
{
releases . Add ( result ) ;
}
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
{
// The result list
var releases = new List < ReleaseInfo > ( ) ;
2018-04-26 17:30:03 +00:00
var queryCollection = new NameValueCollection ( ) ;
var queryCats = MapTorznabCapsToTrackers ( query ) ;
2019-08-19 03:01:07 +00:00
if ( queryCats . Count > 0 )
2017-04-15 08:45:10 +00:00
{
2019-08-19 03:01:07 +00:00
foreach ( var cat in queryCats )
{
queryCollection . Add ( cat , "1" ) ;
}
2017-04-15 08:45:10 +00:00
}
2018-05-06 00:36:53 +00:00
queryCollection . Add ( "username" , configData . Username . Value ) ;
2018-04-26 17:30:03 +00:00
queryCollection . Add ( "torrent_pass" , configData . Passkey . Value ) ;
queryCollection . Add ( "type" , searchType ) ;
queryCollection . Add ( "searchstr" , searchTerm ) ;
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
var response = await RequestStringWithCookiesAndRetry ( queryUrl ) ;
2018-04-26 17:30:03 +00:00
if ( ! response . Content . StartsWith ( "{" ) ) // not JSON => error
throw new ExceptionWithConfigData ( "unexcepted response (not JSON)" , configData ) ;
dynamic json = JsonConvert . DeserializeObject < dynamic > ( response . Content ) ;
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
2018-04-27 14:52:21 +00:00
var Matches = ( long ) json [ "Matches" ] ;
2018-04-26 17:30:03 +00:00
2020-02-09 02:35:16 +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
2018-04-27 14:52:21 +00:00
foreach ( JObject group in groups )
{
var synonyms = new List < string > ( ) ;
var groupID = ( long ) group [ "ID" ] ;
var Image = ( string ) group [ "Image" ] ;
var ImageUrl = ( string . IsNullOrWhiteSpace ( Image ) ? null : new Uri ( Image ) ) ;
var Year = ( int ) group [ "Year" ] ;
var GroupName = ( string ) group [ "GroupName" ] ;
var SeriesName = ( string ) group [ "SeriesName" ] ;
var mainTitle = WebUtility . HtmlDecode ( ( string ) group [ "FullName" ] ) ;
if ( SeriesName ! = null )
mainTitle = SeriesName ;
synonyms . Add ( mainTitle ) ;
2020-02-09 02:35:16 +00:00
if ( AddSynonyms )
{
2020-02-01 18:31:04 +00:00
foreach ( string synonym in group [ "Synonymns" ] )
synonyms . Add ( synonym ) ;
2017-04-15 08:45:10 +00:00
}
2018-04-27 14:52:21 +00:00
List < int > Category = null ;
var category = ( string ) group [ "CategoryName" ] ;
2018-04-26 17:30:03 +00:00
2018-04-27 14:52:21 +00:00
var Description = ( string ) group [ "Description" ] ;
2018-04-26 17:30:03 +00:00
2018-04-27 14:52:21 +00:00
foreach ( JObject 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 ;
var EditionTitle = ( string ) torrent [ "EditionData" ] [ "EditionTitle" ] ;
if ( ! string . IsNullOrWhiteSpace ( EditionTitle ) )
releaseInfo = WebUtility . HtmlDecode ( EditionTitle ) ;
2020-02-10 22:16:19 +00:00
var SeasonRegEx = new Regex ( @"Season (\d+)" , RegexOptions . Compiled ) ;
2018-04-27 14:52:21 +00:00
var SeasonRegExMatch = SeasonRegEx . Match ( releaseInfo ) ;
if ( SeasonRegExMatch . Success )
season = ParseUtil . CoerceInt ( SeasonRegExMatch . Groups [ 1 ] . Value ) ;
2020-02-10 22:16:19 +00:00
var EpisodeRegEx = new Regex ( @"Episode (\d+)" , RegexOptions . Compiled ) ;
2018-04-27 14:52:21 +00:00
var EpisodeRegExMatch = EpisodeRegEx . Match ( releaseInfo ) ;
if ( EpisodeRegExMatch . Success )
episode = EpisodeRegExMatch . Groups [ 1 ] . Value ;
releaseInfo = releaseInfo . Replace ( "Episode " , "" ) ;
releaseInfo = releaseInfo . Replace ( "Season " , "S" ) ;
releaseInfo = releaseInfo . Trim ( ) ;
2020-02-10 22:16:19 +00:00
if ( PadEpisode & & int . TryParse ( releaseInfo , out var test ) & & 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 ;
}
var torrentID = ( long ) torrent [ "ID" ] ;
var Property = ( string ) torrent [ "Property" ] ;
Property = 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 PublushDate = DateTime . SpecifyKind ( UploadTime , DateTimeKind . Utc ) . ToLocalTime ( ) ;
var CommentsLink = TorrentsUrl + "?id=" + groupID . ToString ( ) + "&torrentid=" + torrentID . ToString ( ) ;
var CommentsLinkUri = new Uri ( CommentsLink ) ;
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" ] ;
if ( RawDownMultiplier = = null )
RawDownMultiplier = 0 ;
var RawUpMultiplier = ( int? ) torrent [ "RawUpMultiplier" ] ;
if ( RawUpMultiplier = = null )
RawDownMultiplier = 0 ;
if ( searchType = = "anime" )
{
2019-08-19 03:01:07 +00:00
if ( GroupName = = "TV Series" | | GroupName = = "OVA" )
2018-04-27 14:52:21 +00:00
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
2019-06-20 19:45:22 +00:00
if ( GroupName = = "Movie" | | GroupName = = "Live Action Movie" )
2018-04-27 14:52:21 +00:00
Category = new List < int > { TorznabCatType . Movies . ID } ;
2017-04-15 08:45:10 +00:00
2018-04-27 14:52:21 +00:00
if ( category = = "Manga" | | category = = "Oneshot" | | category = = "Anthology" | | category = = "Manhwa" | | category = = "Manhua" | | category = = "Light Novel" )
Category = new List < int > { TorznabCatType . BooksComics . ID } ;
2017-04-15 08:45:10 +00:00
2018-04-27 14:52:21 +00:00
if ( category = = "Novel" | | category = = "Artbook" )
Category = new List < int > { TorznabCatType . BooksComics . ID } ;
2017-04-15 08:45:10 +00:00
2018-04-27 14:52:21 +00:00
if ( category = = "Game" | | category = = "Visual Novel" )
{
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-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
{
2018-04-27 14:52:21 +00:00
if ( category = = "Single" | | category = = "EP" | | category = = "Album" | | category = = "Compilation" | | category = = "Soundtrack" | | category = = "Remix CD" | | category = = "PV" | | category = = "Live Album" | | category = = "Image CD" | | category = = "Drama CD" | | category = = "Vocal CD" )
{
if ( Property . Contains ( " Lossless " ) )
Category = new List < int > { TorznabCatType . AudioLossless . ID } ;
else if ( Property . Contains ( "MP3" ) )
Category = new List < int > { TorznabCatType . AudioMP3 . ID } ;
else
Category = new List < int > { TorznabCatType . AudioOther . ID } ;
}
2018-04-26 17:30:03 +00:00
}
2017-04-15 08:45:10 +00:00
2018-04-27 14:52:21 +00:00
// We dont 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
2018-04-27 14:52:21 +00:00
var releasegroup = releaseTags . LastOrDefault ( ) ;
if ( releasegroup ! = null & & releasegroup . Contains ( "(" ) & & releasegroup . Contains ( ")" ) )
{
// Skip raws if set
if ( releasegroup . ToLowerInvariant ( ) . StartsWith ( "raw" ) & & ! AllowRaws )
{
continue ;
}
2017-04-15 08:45:10 +00:00
2018-04-27 14:52:21 +00:00
var start = releasegroup . IndexOf ( "(" ) ;
releasegroup = "[" + releasegroup . Substring ( start + 1 , ( releasegroup . IndexOf ( ")" ) - 1 ) - start ) + "] " ;
}
else
{
releasegroup = string . Empty ;
}
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 + "]" ) ;
2018-04-27 14:52:21 +00:00
var MinimumSeedTime = 259200 ;
// Additional 5 hours per GB
MinimumSeedTime + = ( int ) ( ( Size / 1000000000 ) * 18000 ) ;
foreach ( var title in synonyms )
2018-04-26 17:30:03 +00:00
{
2018-04-27 14:52:21 +00:00
string releaseTitle = null ;
if ( GroupName = = "Movie" )
{
releaseTitle = string . Format ( "{0} {1} {2}{3}" , title , Year , releasegroup , infoString ) ;
}
else
{
releaseTitle = string . Format ( "{0}{1} {2} {3}" , releasegroup , title , releaseInfo , infoString ) ;
}
2018-04-26 17:30:03 +00:00
2020-02-25 16:08:03 +00:00
var release = new ReleaseInfo
{
MinimumRatio = 1 ,
MinimumSeedTime = MinimumSeedTime ,
Title = releaseTitle ,
Comments = CommentsLinkUri ,
Guid = new Uri ( CommentsLinkUri + "&nh=" + StringUtil . Hash ( title ) ) , // Sonarr should dedupe on this url - allow a url per name.
Link = LinkUri ,
BannerUrl = ImageUrl ,
PublishDate = PublushDate ,
Category = Category ,
Description = Description ,
Size = Size ,
Seeders = Seeders ,
Peers = Peers ,
Grabs = Snatched ,
Files = FileCount ,
DownloadVolumeFactor = RawDownMultiplier ,
UploadVolumeFactor = RawUpMultiplier
} ;
2018-04-27 14:52:21 +00:00
releases . Add ( release ) ;
}
2017-04-15 08:45:10 +00:00
}
}
}
}
catch ( Exception ex )
{
OnParseError ( response . Content , ex ) ;
}
// Add to the cache
lock ( cache )
{
cache . Add ( new CachedQueryResult ( queryUrl , releases ) ) ;
}
return releases . Select ( s = > ( ReleaseInfo ) s . Clone ( ) ) ;
}
}
}